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