• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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