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