Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / external_protocol / external_protocol_handler.cc
blobcf8c99c209482ab7bb07fb08d6323b92b9cb2316
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/external_protocol/external_protocol_handler.h"
7 #include <set>
9 #include "base/bind.h"
10 #include "base/logging.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/prefs/pref_registry_simple.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "base/strings/string_util.h"
16 #include "base/threading/thread.h"
17 #include "build/build_config.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/platform_util.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/tab_contents/tab_util.h"
22 #include "chrome/common/pref_names.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/web_contents.h"
25 #include "net/base/escape.h"
26 #include "url/gurl.h"
28 using content::BrowserThread;
30 // Whether we accept requests for launching external protocols. This is set to
31 // false every time an external protocol is requested, and set back to true on
32 // each user gesture. This variable should only be accessed from the UI thread.
33 static bool g_accept_requests = true;
35 namespace {
37 // Functions enabling unit testing. Using a NULL delegate will use the default
38 // behavior; if a delegate is provided it will be used instead.
39 ShellIntegration::DefaultProtocolClientWorker* CreateShellWorker(
40 ShellIntegration::DefaultWebClientObserver* observer,
41 const std::string& protocol,
42 ExternalProtocolHandler::Delegate* delegate) {
43 if (!delegate)
44 return new ShellIntegration::DefaultProtocolClientWorker(observer,
45 protocol);
47 return delegate->CreateShellWorker(observer, protocol);
50 ExternalProtocolHandler::BlockState GetBlockStateWithDelegate(
51 const std::string& scheme,
52 ExternalProtocolHandler::Delegate* delegate) {
53 if (!delegate)
54 return ExternalProtocolHandler::GetBlockState(scheme);
56 return delegate->GetBlockState(scheme);
59 void RunExternalProtocolDialogWithDelegate(
60 const GURL& url,
61 int render_process_host_id,
62 int routing_id,
63 ui::PageTransition page_transition,
64 bool has_user_gesture,
65 ExternalProtocolHandler::Delegate* delegate) {
66 if (!delegate) {
67 ExternalProtocolHandler::RunExternalProtocolDialog(
68 url, render_process_host_id, routing_id, page_transition,
69 has_user_gesture);
70 } else {
71 delegate->RunExternalProtocolDialog(
72 url, render_process_host_id, routing_id, page_transition,
73 has_user_gesture);
77 void LaunchUrlWithoutSecurityCheckWithDelegate(
78 const GURL& url,
79 int render_process_host_id,
80 int tab_contents_id,
81 ExternalProtocolHandler::Delegate* delegate) {
82 if (!delegate) {
83 ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
84 url, render_process_host_id, tab_contents_id);
85 } else {
86 delegate->LaunchUrlWithoutSecurityCheck(url);
90 // When we are about to launch a URL with the default OS level application,
91 // we check if that external application will be us. If it is we just ignore
92 // the request.
93 class ExternalDefaultProtocolObserver
94 : public ShellIntegration::DefaultWebClientObserver {
95 public:
96 ExternalDefaultProtocolObserver(const GURL& escaped_url,
97 int render_process_host_id,
98 int tab_contents_id,
99 bool prompt_user,
100 ui::PageTransition page_transition,
101 bool has_user_gesture,
102 ExternalProtocolHandler::Delegate* delegate)
103 : delegate_(delegate),
104 escaped_url_(escaped_url),
105 render_process_host_id_(render_process_host_id),
106 tab_contents_id_(tab_contents_id),
107 prompt_user_(prompt_user),
108 page_transition_(page_transition),
109 has_user_gesture_(has_user_gesture) {}
111 void SetDefaultWebClientUIState(
112 ShellIntegration::DefaultWebClientUIState state) override {
113 DCHECK(base::MessageLoopForUI::IsCurrent());
115 // If we are still working out if we're the default, or we've found
116 // out we definately are the default, we end here.
117 if (state == ShellIntegration::STATE_PROCESSING) {
118 return;
121 if (delegate_)
122 delegate_->FinishedProcessingCheck();
124 if (state == ShellIntegration::STATE_IS_DEFAULT) {
125 if (delegate_)
126 delegate_->BlockRequest();
127 return;
130 // If we get here, either we are not the default or we cannot work out
131 // what the default is, so we proceed.
132 if (prompt_user_) {
133 // Ask the user if they want to allow the protocol. This will call
134 // LaunchUrlWithoutSecurityCheck if the user decides to accept the
135 // protocol.
136 RunExternalProtocolDialogWithDelegate(
137 escaped_url_, render_process_host_id_, tab_contents_id_,
138 page_transition_, has_user_gesture_, delegate_);
139 return;
142 LaunchUrlWithoutSecurityCheckWithDelegate(
143 escaped_url_, render_process_host_id_, tab_contents_id_, delegate_);
146 bool IsOwnedByWorker() override { return true; }
148 private:
149 ExternalProtocolHandler::Delegate* delegate_;
150 const GURL escaped_url_;
151 const int render_process_host_id_;
152 const int tab_contents_id_;
153 const bool prompt_user_;
154 const ui::PageTransition page_transition_;
155 const bool has_user_gesture_;
158 } // namespace
160 // static
161 void ExternalProtocolHandler::PrepopulateDictionary(
162 base::DictionaryValue* win_pref) {
163 static bool is_warm = false;
164 if (is_warm)
165 return;
166 is_warm = true;
168 static const char* const denied_schemes[] = {
169 "afp",
170 "data",
171 "disk",
172 "disks",
173 // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
174 // execute the file specified! Hopefully we won't see any "file" schemes
175 // because we think of file:// URLs as handled URLs, but better to be safe
176 // than to let an attacker format the user's hard drive.
177 "file",
178 "hcp",
179 "javascript",
180 "ms-help",
181 "nntp",
182 "shell",
183 "vbscript",
184 // view-source is a special case in chrome. When it comes through an
185 // iframe or a redirect, it looks like an external protocol, but we don't
186 // want to shellexecute it.
187 "view-source",
188 "vnd.ms.radio",
191 static const char* const allowed_schemes[] = {
192 "mailto",
193 "news",
194 "snews",
195 #if defined(OS_WIN)
196 "ms-windows-store",
197 #endif
200 bool should_block;
201 for (size_t i = 0; i < arraysize(denied_schemes); ++i) {
202 if (!win_pref->GetBoolean(denied_schemes[i], &should_block)) {
203 win_pref->SetBoolean(denied_schemes[i], true);
207 for (size_t i = 0; i < arraysize(allowed_schemes); ++i) {
208 if (!win_pref->GetBoolean(allowed_schemes[i], &should_block)) {
209 win_pref->SetBoolean(allowed_schemes[i], false);
214 // static
215 ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
216 const std::string& scheme) {
217 // If we are being carpet bombed, block the request.
218 if (!g_accept_requests)
219 return BLOCK;
221 if (scheme.length() == 1) {
222 // We have a URL that looks something like:
223 // C:/WINDOWS/system32/notepad.exe
224 // ShellExecuting this URL will cause the specified program to be executed.
225 return BLOCK;
228 // Check the stored prefs.
229 // TODO(pkasting): This kind of thing should go in the preferences on the
230 // profile, not in the local state. http://crbug.com/457254
231 PrefService* pref = g_browser_process->local_state();
232 if (pref) { // May be NULL during testing.
233 DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
235 // Warm up the dictionary if needed.
236 PrepopulateDictionary(update_excluded_schemas.Get());
238 bool should_block;
239 if (update_excluded_schemas->GetBoolean(scheme, &should_block))
240 return should_block ? BLOCK : DONT_BLOCK;
243 return UNKNOWN;
246 // static
247 void ExternalProtocolHandler::SetBlockState(const std::string& scheme,
248 BlockState state) {
249 // Set in the stored prefs.
250 // TODO(pkasting): This kind of thing should go in the preferences on the
251 // profile, not in the local state. http://crbug.com/457254
252 PrefService* pref = g_browser_process->local_state();
253 if (pref) { // May be NULL during testing.
254 DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
256 if (state == UNKNOWN) {
257 update_excluded_schemas->Remove(scheme, NULL);
258 } else {
259 update_excluded_schemas->SetBoolean(scheme, (state == BLOCK));
264 // static
265 void ExternalProtocolHandler::LaunchUrlWithDelegate(
266 const GURL& url,
267 int render_process_host_id,
268 int tab_contents_id,
269 ui::PageTransition page_transition,
270 bool has_user_gesture,
271 Delegate* delegate) {
272 DCHECK(base::MessageLoopForUI::IsCurrent());
274 // Escape the input scheme to be sure that the command does not
275 // have parameters unexpected by the external program.
276 std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec());
277 GURL escaped_url(escaped_url_string);
278 BlockState block_state =
279 GetBlockStateWithDelegate(escaped_url.scheme(), delegate);
280 if (block_state == BLOCK) {
281 if (delegate)
282 delegate->BlockRequest();
283 return;
286 g_accept_requests = false;
288 // The worker creates tasks with references to itself and puts them into
289 // message loops. When no tasks are left it will delete the observer and
290 // eventually be deleted itself.
291 ShellIntegration::DefaultWebClientObserver* observer =
292 new ExternalDefaultProtocolObserver(url,
293 render_process_host_id,
294 tab_contents_id,
295 block_state == UNKNOWN,
296 page_transition,
297 has_user_gesture,
298 delegate);
299 scoped_refptr<ShellIntegration::DefaultProtocolClientWorker> worker =
300 CreateShellWorker(observer, escaped_url.scheme(), delegate);
302 // Start the check process running. This will send tasks to the FILE thread
303 // and when the answer is known will send the result back to the observer on
304 // the UI thread.
305 worker->StartCheckIsDefault();
308 // static
309 void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
310 const GURL& url,
311 int render_process_host_id,
312 int tab_contents_id) {
313 content::WebContents* web_contents = tab_util::GetWebContentsByID(
314 render_process_host_id, tab_contents_id);
315 if (!web_contents)
316 return;
318 platform_util::OpenExternal(
319 Profile::FromBrowserContext(web_contents->GetBrowserContext()), url);
322 // static
323 void ExternalProtocolHandler::RegisterPrefs(PrefRegistrySimple* registry) {
324 registry->RegisterDictionaryPref(prefs::kExcludedSchemes);
327 // static
328 void ExternalProtocolHandler::PermitLaunchUrl() {
329 DCHECK(base::MessageLoopForUI::IsCurrent());
330 g_accept_requests = true;