1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/external_protocol_handler.h"
6
7 #include <set>
8
9 #include "base/logging.h"
10 #include "base/message_loop.h"
11 #include "base/string_util.h"
12 #include "base/threading/thread.h"
13 #include "build/build_config.h"
14 #include "chrome/browser/browser_process_impl.h"
15 #include "chrome/browser/platform_util.h"
16 #include "chrome/browser/prefs/pref_service.h"
17 #include "chrome/browser/prefs/scoped_user_pref_update.h"
18 #include "chrome/common/pref_names.h"
19 #include "googleurl/src/gurl.h"
20 #include "net/base/escape.h"
21
22 // Whether we accept requests for launching external protocols. This is set to
23 // false every time an external protocol is requested, and set back to true on
24 // each user gesture. This variable should only be accessed from the UI thread.
25 static bool g_accept_requests = true;
26
27 // static
PrepopulateDictionary(DictionaryValue * win_pref)28 void ExternalProtocolHandler::PrepopulateDictionary(DictionaryValue* win_pref) {
29 static bool is_warm = false;
30 if (is_warm)
31 return;
32 is_warm = true;
33
34 static const char* const denied_schemes[] = {
35 "afp",
36 "data",
37 "disk",
38 "disks",
39 // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
40 // execute the file specified! Hopefully we won't see any "file" schemes
41 // because we think of file:// URLs as handled URLs, but better to be safe
42 // than to let an attacker format the user's hard drive.
43 "file",
44 "hcp",
45 "javascript",
46 "ms-help",
47 "nntp",
48 "shell",
49 "vbscript",
50 // view-source is a special case in chrome. When it comes through an
51 // iframe or a redirect, it looks like an external protocol, but we don't
52 // want to shellexecute it.
53 "view-source",
54 "vnd.ms.radio",
55 };
56
57 static const char* const allowed_schemes[] = {
58 "mailto",
59 "news",
60 "snews",
61 };
62
63 bool should_block;
64 for (size_t i = 0; i < arraysize(denied_schemes); ++i) {
65 if (!win_pref->GetBoolean(denied_schemes[i], &should_block)) {
66 win_pref->SetBoolean(denied_schemes[i], true);
67 }
68 }
69
70 for (size_t i = 0; i < arraysize(allowed_schemes); ++i) {
71 if (!win_pref->GetBoolean(allowed_schemes[i], &should_block)) {
72 win_pref->SetBoolean(allowed_schemes[i], false);
73 }
74 }
75 }
76
77 // static
GetBlockState(const std::string & scheme)78 ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
79 const std::string& scheme) {
80 // If we are being carpet bombed, block the request.
81 if (!g_accept_requests)
82 return BLOCK;
83
84 if (scheme.length() == 1) {
85 // We have a URL that looks something like:
86 // C:/WINDOWS/system32/notepad.exe
87 // ShellExecuting this URL will cause the specified program to be executed.
88 return BLOCK;
89 }
90
91 // Check the stored prefs.
92 // TODO(pkasting): http://b/1119651 This kind of thing should go in the
93 // preferences on the profile, not in the local state.
94 PrefService* pref = g_browser_process->local_state();
95 if (pref) { // May be NULL during testing.
96 DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
97
98 // Warm up the dictionary if needed.
99 PrepopulateDictionary(update_excluded_schemas.Get());
100
101 bool should_block;
102 if (update_excluded_schemas->GetBoolean(scheme, &should_block))
103 return should_block ? BLOCK : DONT_BLOCK;
104 }
105
106 return UNKNOWN;
107 }
108
109 // static
SetBlockState(const std::string & scheme,BlockState state)110 void ExternalProtocolHandler::SetBlockState(const std::string& scheme,
111 BlockState state) {
112 // Set in the stored prefs.
113 // TODO(pkasting): http://b/1119651 This kind of thing should go in the
114 // preferences on the profile, not in the local state.
115 PrefService* pref = g_browser_process->local_state();
116 if (pref) { // May be NULL during testing.
117 DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
118
119 if (state == UNKNOWN) {
120 update_excluded_schemas->Remove(scheme, NULL);
121 } else {
122 update_excluded_schemas->SetBoolean(scheme,
123 state == BLOCK ? true : false);
124 }
125 }
126 }
127
128 // static
LaunchUrl(const GURL & url,int render_process_host_id,int tab_contents_id)129 void ExternalProtocolHandler::LaunchUrl(const GURL& url,
130 int render_process_host_id,
131 int tab_contents_id) {
132 DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type());
133
134 // Escape the input scheme to be sure that the command does not
135 // have parameters unexpected by the external program.
136 std::string escaped_url_string = EscapeExternalHandlerValue(url.spec());
137 GURL escaped_url(escaped_url_string);
138 BlockState block_state = GetBlockState(escaped_url.scheme());
139 if (block_state == BLOCK)
140 return;
141
142 g_accept_requests = false;
143
144 if (block_state == UNKNOWN) {
145 // Ask the user if they want to allow the protocol. This will call
146 // LaunchUrlWithoutSecurityCheck if the user decides to accept the protocol.
147 RunExternalProtocolDialog(escaped_url,
148 render_process_host_id,
149 tab_contents_id);
150 return;
151 }
152
153 LaunchUrlWithoutSecurityCheck(escaped_url);
154 }
155
156 // static
LaunchUrlWithoutSecurityCheck(const GURL & url)157 void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(const GURL& url) {
158 #if defined(OS_MACOSX)
159 // This must run on the UI thread on OS X.
160 platform_util::OpenExternal(url);
161 #else
162 // Otherwise put this work on the file thread. On Windows ShellExecute may
163 // block for a significant amount of time, and it shouldn't hurt on Linux.
164 MessageLoop* loop = g_browser_process->file_thread()->message_loop();
165 if (loop == NULL) {
166 return;
167 }
168
169 loop->PostTask(FROM_HERE,
170 NewRunnableFunction(&platform_util::OpenExternal, url));
171 #endif
172 }
173
174 // static
RegisterPrefs(PrefService * prefs)175 void ExternalProtocolHandler::RegisterPrefs(PrefService* prefs) {
176 prefs->RegisterDictionaryPref(prefs::kExcludedSchemes);
177 }
178
179 // static
PermitLaunchUrl()180 void ExternalProtocolHandler::PermitLaunchUrl() {
181 DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type());
182 g_accept_requests = true;
183 }
184