Add GCMChannelStatusSyncer to schedule requests and enable/disable GCM
[chromium-blink-merge.git] / chrome / browser / external_protocol / external_protocol_handler.cc
blobf6f3c0b1d998e743769e645c55ccfd52d999570a
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 ExternalProtocolHandler::Delegate* delegate) {
64 if (!delegate) {
65 ExternalProtocolHandler::RunExternalProtocolDialog(url,
66 render_process_host_id,
67 routing_id);
68 } else {
69 delegate->RunExternalProtocolDialog(url, render_process_host_id,
70 routing_id);
74 void LaunchUrlWithoutSecurityCheckWithDelegate(
75 const GURL& url,
76 int render_process_host_id,
77 int tab_contents_id,
78 ExternalProtocolHandler::Delegate* delegate) {
79 if (!delegate) {
80 ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
81 url, render_process_host_id, tab_contents_id);
82 } else {
83 delegate->LaunchUrlWithoutSecurityCheck(url);
87 // When we are about to launch a URL with the default OS level application,
88 // we check if that external application will be us. If it is we just ignore
89 // the request.
90 class ExternalDefaultProtocolObserver
91 : public ShellIntegration::DefaultWebClientObserver {
92 public:
93 ExternalDefaultProtocolObserver(const GURL& escaped_url,
94 int render_process_host_id,
95 int tab_contents_id,
96 bool prompt_user,
97 ExternalProtocolHandler::Delegate* delegate)
98 : delegate_(delegate),
99 escaped_url_(escaped_url),
100 render_process_host_id_(render_process_host_id),
101 tab_contents_id_(tab_contents_id),
102 prompt_user_(prompt_user) {}
104 virtual void SetDefaultWebClientUIState(
105 ShellIntegration::DefaultWebClientUIState state) OVERRIDE {
106 DCHECK(base::MessageLoopForUI::IsCurrent());
108 // If we are still working out if we're the default, or we've found
109 // out we definately are the default, we end here.
110 if (state == ShellIntegration::STATE_PROCESSING) {
111 return;
114 if (delegate_)
115 delegate_->FinishedProcessingCheck();
117 if (state == ShellIntegration::STATE_IS_DEFAULT) {
118 if (delegate_)
119 delegate_->BlockRequest();
120 return;
123 // If we get here, either we are not the default or we cannot work out
124 // what the default is, so we proceed.
125 if (prompt_user_) {
126 // Ask the user if they want to allow the protocol. This will call
127 // LaunchUrlWithoutSecurityCheck if the user decides to accept the
128 // protocol.
129 RunExternalProtocolDialogWithDelegate(escaped_url_,
130 render_process_host_id_, tab_contents_id_, delegate_);
131 return;
134 LaunchUrlWithoutSecurityCheckWithDelegate(
135 escaped_url_, render_process_host_id_, tab_contents_id_, delegate_);
138 virtual bool IsOwnedByWorker() OVERRIDE { return true; }
140 private:
141 ExternalProtocolHandler::Delegate* delegate_;
142 GURL escaped_url_;
143 int render_process_host_id_;
144 int tab_contents_id_;
145 bool prompt_user_;
148 } // namespace
150 // static
151 void ExternalProtocolHandler::PrepopulateDictionary(
152 base::DictionaryValue* win_pref) {
153 static bool is_warm = false;
154 if (is_warm)
155 return;
156 is_warm = true;
158 static const char* const denied_schemes[] = {
159 "afp",
160 "data",
161 "disk",
162 "disks",
163 // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
164 // execute the file specified! Hopefully we won't see any "file" schemes
165 // because we think of file:// URLs as handled URLs, but better to be safe
166 // than to let an attacker format the user's hard drive.
167 "file",
168 "hcp",
169 "javascript",
170 "ms-help",
171 "nntp",
172 "shell",
173 "vbscript",
174 // view-source is a special case in chrome. When it comes through an
175 // iframe or a redirect, it looks like an external protocol, but we don't
176 // want to shellexecute it.
177 "view-source",
178 "vnd.ms.radio",
181 static const char* const allowed_schemes[] = {
182 "mailto",
183 "news",
184 "snews",
185 #if defined(OS_WIN)
186 "ms-windows-store",
187 #endif
190 bool should_block;
191 for (size_t i = 0; i < arraysize(denied_schemes); ++i) {
192 if (!win_pref->GetBoolean(denied_schemes[i], &should_block)) {
193 win_pref->SetBoolean(denied_schemes[i], true);
197 for (size_t i = 0; i < arraysize(allowed_schemes); ++i) {
198 if (!win_pref->GetBoolean(allowed_schemes[i], &should_block)) {
199 win_pref->SetBoolean(allowed_schemes[i], false);
204 // static
205 ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
206 const std::string& scheme) {
207 // If we are being carpet bombed, block the request.
208 if (!g_accept_requests)
209 return BLOCK;
211 if (scheme.length() == 1) {
212 // We have a URL that looks something like:
213 // C:/WINDOWS/system32/notepad.exe
214 // ShellExecuting this URL will cause the specified program to be executed.
215 return BLOCK;
218 // Check the stored prefs.
219 // TODO(pkasting): http://b/1119651 This kind of thing should go in the
220 // preferences on the profile, not in the local state.
221 PrefService* pref = g_browser_process->local_state();
222 if (pref) { // May be NULL during testing.
223 DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
225 // Warm up the dictionary if needed.
226 PrepopulateDictionary(update_excluded_schemas.Get());
228 bool should_block;
229 if (update_excluded_schemas->GetBoolean(scheme, &should_block))
230 return should_block ? BLOCK : DONT_BLOCK;
233 return UNKNOWN;
236 // static
237 void ExternalProtocolHandler::SetBlockState(const std::string& scheme,
238 BlockState state) {
239 // Set in the stored prefs.
240 // TODO(pkasting): http://b/1119651 This kind of thing should go in the
241 // preferences on the profile, not in the local state.
242 PrefService* pref = g_browser_process->local_state();
243 if (pref) { // May be NULL during testing.
244 DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes);
246 if (state == UNKNOWN) {
247 update_excluded_schemas->Remove(scheme, NULL);
248 } else {
249 update_excluded_schemas->SetBoolean(scheme, (state == BLOCK));
254 // static
255 void ExternalProtocolHandler::LaunchUrlWithDelegate(const GURL& url,
256 int render_process_host_id,
257 int tab_contents_id,
258 Delegate* delegate) {
259 DCHECK(base::MessageLoopForUI::IsCurrent());
261 // Escape the input scheme to be sure that the command does not
262 // have parameters unexpected by the external program.
263 std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec());
264 GURL escaped_url(escaped_url_string);
265 BlockState block_state =
266 GetBlockStateWithDelegate(escaped_url.scheme(), delegate);
267 if (block_state == BLOCK) {
268 if (delegate)
269 delegate->BlockRequest();
270 return;
273 g_accept_requests = false;
275 // The worker creates tasks with references to itself and puts them into
276 // message loops. When no tasks are left it will delete the observer and
277 // eventually be deleted itself.
278 ShellIntegration::DefaultWebClientObserver* observer =
279 new ExternalDefaultProtocolObserver(url,
280 render_process_host_id,
281 tab_contents_id,
282 block_state == UNKNOWN,
283 delegate);
284 scoped_refptr<ShellIntegration::DefaultProtocolClientWorker> worker =
285 CreateShellWorker(observer, escaped_url.scheme(), delegate);
287 // Start the check process running. This will send tasks to the FILE thread
288 // and when the answer is known will send the result back to the observer on
289 // the UI thread.
290 worker->StartCheckIsDefault();
293 // static
294 void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
295 const GURL& url,
296 int render_process_host_id,
297 int tab_contents_id) {
298 content::WebContents* web_contents = tab_util::GetWebContentsByID(
299 render_process_host_id, tab_contents_id);
300 if (!web_contents)
301 return;
303 platform_util::OpenExternal(
304 Profile::FromBrowserContext(web_contents->GetBrowserContext()), url);
307 // static
308 void ExternalProtocolHandler::RegisterPrefs(PrefRegistrySimple* registry) {
309 registry->RegisterDictionaryPref(prefs::kExcludedSchemes);
312 // static
313 void ExternalProtocolHandler::PermitLaunchUrl() {
314 DCHECK(base::MessageLoopForUI::IsCurrent());
315 g_accept_requests = true;