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.
25static bool g_accept_requests = true;
26
27// static
28void 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
78ExternalProtocolHandler::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
110void 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
129void 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
157void 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
175void ExternalProtocolHandler::RegisterPrefs(PrefService* prefs) {
176  prefs->RegisterDictionaryPref(prefs::kExcludedSchemes);
177}
178
179// static
180void ExternalProtocolHandler::PermitLaunchUrl() {
181  DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type());
182  g_accept_requests = true;
183}
184