external_protocol_handler.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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. 4 5#include "chrome/browser/external_protocol/external_protocol_handler.h" 6 7#include <set> 8 9#include "base/bind.h" 10#include "base/logging.h" 11#include "base/message_loop.h" 12#include "base/string_util.h" 13#include "base/threading/thread.h" 14#include "build/build_config.h" 15#include "chrome/browser/browser_process.h" 16#include "chrome/browser/platform_util.h" 17#include "chrome/browser/prefs/pref_service.h" 18#include "chrome/browser/prefs/scoped_user_pref_update.h" 19#include "chrome/common/pref_names.h" 20#include "content/public/browser/browser_thread.h" 21#include "googleurl/src/gurl.h" 22#include "net/base/escape.h" 23 24using content::BrowserThread; 25 26// Whether we accept requests for launching external protocols. This is set to 27// false every time an external protocol is requested, and set back to true on 28// each user gesture. This variable should only be accessed from the UI thread. 29static bool g_accept_requests = true; 30 31namespace { 32 33// Functions enabling unit testing. Using a NULL delegate will use the default 34// behavior; if a delegate is provided it will be used instead. 35ShellIntegration::DefaultProtocolClientWorker* CreateShellWorker( 36 ShellIntegration::DefaultWebClientObserver* observer, 37 const std::string& protocol, 38 ExternalProtocolHandler::Delegate* delegate) { 39 if (!delegate) 40 return new ShellIntegration::DefaultProtocolClientWorker(observer, 41 protocol); 42 43 return delegate->CreateShellWorker(observer, protocol); 44} 45 46ExternalProtocolHandler::BlockState GetBlockStateWithDelegate( 47 const std::string& scheme, 48 ExternalProtocolHandler::Delegate* delegate) { 49 if (!delegate) 50 return ExternalProtocolHandler::GetBlockState(scheme); 51 52 return delegate->GetBlockState(scheme); 53} 54 55void RunExternalProtocolDialogWithDelegate( 56 const GURL& url, 57 int render_process_host_id, 58 int routing_id, 59 ExternalProtocolHandler::Delegate* delegate) { 60 if (!delegate) { 61 ExternalProtocolHandler::RunExternalProtocolDialog(url, 62 render_process_host_id, 63 routing_id); 64 } else { 65 delegate->RunExternalProtocolDialog(url, render_process_host_id, 66 routing_id); 67 } 68} 69 70void LaunchUrlWithoutSecurityCheckWithDelegate( 71 const GURL& url, 72 ExternalProtocolHandler::Delegate* delegate) { 73 if (!delegate) 74 ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(url); 75 else 76 delegate->LaunchUrlWithoutSecurityCheck(url); 77} 78 79// When we are about to launch a URL with the default OS level application, 80// we check if that external application will be us. If it is we just ignore 81// the request. 82class ExternalDefaultProtocolObserver 83 : public ShellIntegration::DefaultWebClientObserver { 84 public: 85 ExternalDefaultProtocolObserver(const GURL& escaped_url, 86 int render_process_host_id, 87 int tab_contents_id, 88 bool prompt_user, 89 ExternalProtocolHandler::Delegate* delegate) 90 : delegate_(delegate), 91 escaped_url_(escaped_url), 92 render_process_host_id_(render_process_host_id), 93 tab_contents_id_(tab_contents_id), 94 prompt_user_(prompt_user) {} 95 96 virtual void SetDefaultWebClientUIState( 97 ShellIntegration::DefaultWebClientUIState state) OVERRIDE { 98 DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); 99 100 // If we are still working out if we're the default, or we've found 101 // out we definately are the default, we end here. 102 if (state == ShellIntegration::STATE_PROCESSING) { 103 return; 104 } 105 106 if (delegate_) 107 delegate_->FinishedProcessingCheck(); 108 109 if (state == ShellIntegration::STATE_IS_DEFAULT) { 110 if (delegate_) 111 delegate_->BlockRequest(); 112 return; 113 } 114 115 // If we get here, either we are not the default or we cannot work out 116 // what the default is, so we proceed. 117 if (prompt_user_) { 118 // Ask the user if they want to allow the protocol. This will call 119 // LaunchUrlWithoutSecurityCheck if the user decides to accept the 120 // protocol. 121 RunExternalProtocolDialogWithDelegate(escaped_url_, 122 render_process_host_id_, tab_contents_id_, delegate_); 123 return; 124 } 125 126 LaunchUrlWithoutSecurityCheckWithDelegate(escaped_url_, delegate_); 127 } 128 129 virtual bool IsOwnedByWorker() OVERRIDE { return true; } 130 131 private: 132 ExternalProtocolHandler::Delegate* delegate_; 133 GURL escaped_url_; 134 int render_process_host_id_; 135 int tab_contents_id_; 136 bool prompt_user_; 137}; 138 139} // namespace 140 141// static 142void ExternalProtocolHandler::PrepopulateDictionary(DictionaryValue* win_pref) { 143 static bool is_warm = false; 144 if (is_warm) 145 return; 146 is_warm = true; 147 148 static const char* const denied_schemes[] = { 149 "afp", 150 "data", 151 "disk", 152 "disks", 153 // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply 154 // execute the file specified! Hopefully we won't see any "file" schemes 155 // because we think of file:// URLs as handled URLs, but better to be safe 156 // than to let an attacker format the user's hard drive. 157 "file", 158 "hcp", 159 "javascript", 160 "ms-help", 161 "nntp", 162 "shell", 163 "vbscript", 164 // view-source is a special case in chrome. When it comes through an 165 // iframe or a redirect, it looks like an external protocol, but we don't 166 // want to shellexecute it. 167 "view-source", 168 "vnd.ms.radio", 169 }; 170 171 static const char* const allowed_schemes[] = { 172 "mailto", 173 "news", 174 "snews", 175 }; 176 177 bool should_block; 178 for (size_t i = 0; i < arraysize(denied_schemes); ++i) { 179 if (!win_pref->GetBoolean(denied_schemes[i], &should_block)) { 180 win_pref->SetBoolean(denied_schemes[i], true); 181 } 182 } 183 184 for (size_t i = 0; i < arraysize(allowed_schemes); ++i) { 185 if (!win_pref->GetBoolean(allowed_schemes[i], &should_block)) { 186 win_pref->SetBoolean(allowed_schemes[i], false); 187 } 188 } 189} 190 191// static 192ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState( 193 const std::string& scheme) { 194 // If we are being carpet bombed, block the request. 195 if (!g_accept_requests) 196 return BLOCK; 197 198 if (scheme.length() == 1) { 199 // We have a URL that looks something like: 200 // C:/WINDOWS/system32/notepad.exe 201 // ShellExecuting this URL will cause the specified program to be executed. 202 return BLOCK; 203 } 204 205 // Check the stored prefs. 206 // TODO(pkasting): http://b/1119651 This kind of thing should go in the 207 // preferences on the profile, not in the local state. 208 PrefService* pref = g_browser_process->local_state(); 209 if (pref) { // May be NULL during testing. 210 DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes); 211 212 // Warm up the dictionary if needed. 213 PrepopulateDictionary(update_excluded_schemas.Get()); 214 215 bool should_block; 216 if (update_excluded_schemas->GetBoolean(scheme, &should_block)) 217 return should_block ? BLOCK : DONT_BLOCK; 218 } 219 220 return UNKNOWN; 221} 222 223// static 224void ExternalProtocolHandler::SetBlockState(const std::string& scheme, 225 BlockState state) { 226 // Set in the stored prefs. 227 // TODO(pkasting): http://b/1119651 This kind of thing should go in the 228 // preferences on the profile, not in the local state. 229 PrefService* pref = g_browser_process->local_state(); 230 if (pref) { // May be NULL during testing. 231 DictionaryPrefUpdate update_excluded_schemas(pref, prefs::kExcludedSchemes); 232 233 if (state == UNKNOWN) { 234 update_excluded_schemas->Remove(scheme, NULL); 235 } else { 236 update_excluded_schemas->SetBoolean(scheme, (state == BLOCK)); 237 } 238 } 239} 240 241// static 242void ExternalProtocolHandler::LaunchUrlWithDelegate(const GURL& url, 243 int render_process_host_id, 244 int tab_contents_id, 245 Delegate* delegate) { 246 DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); 247 248 // Escape the input scheme to be sure that the command does not 249 // have parameters unexpected by the external program. 250 std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec()); 251 GURL escaped_url(escaped_url_string); 252 BlockState block_state = GetBlockStateWithDelegate(escaped_url.scheme(), 253 delegate); 254 if (block_state == BLOCK) { 255 if (delegate) 256 delegate->BlockRequest(); 257 return; 258 } 259 260 g_accept_requests = false; 261 262 // The worker creates tasks with references to itself and puts them into 263 // message loops. When no tasks are left it will delete the observer and 264 // eventually be deleted itself. 265 ShellIntegration::DefaultWebClientObserver* observer = 266 new ExternalDefaultProtocolObserver(url, 267 render_process_host_id, 268 tab_contents_id, 269 block_state == UNKNOWN, 270 delegate); 271 scoped_refptr<ShellIntegration::DefaultProtocolClientWorker> worker = 272 CreateShellWorker(observer, escaped_url.scheme(), delegate); 273 274 // Start the check process running. This will send tasks to the FILE thread 275 // and when the answer is known will send the result back to the observer on 276 // the UI thread. 277 worker->StartCheckIsDefault(); 278} 279 280// static 281void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(const GURL& url) { 282#if defined(OS_MACOSX) 283 // This must run on the UI thread on OS X. 284 platform_util::OpenExternal(url); 285#else 286 // Otherwise put this work on the file thread. On Windows ShellExecute may 287 // block for a significant amount of time, and it shouldn't hurt on Linux. 288 BrowserThread::PostTask( 289 BrowserThread::FILE, 290 FROM_HERE, 291 base::Bind(&platform_util::OpenExternal, url)); 292#endif 293} 294 295// static 296void ExternalProtocolHandler::RegisterPrefs(PrefService* prefs) { 297 prefs->RegisterDictionaryPref(prefs::kExcludedSchemes); 298} 299 300// static 301void ExternalProtocolHandler::PermitLaunchUrl() { 302 DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); 303 g_accept_requests = true; 304} 305