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/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" 27 28 using content::BrowserThread; 29 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; 34 35 namespace { 36 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); 46 47 return delegate->CreateShellWorker(observer, protocol); 48 } 49 50 ExternalProtocolHandler::BlockState GetBlockStateWithDelegate( 51 const std::string& scheme, 52 ExternalProtocolHandler::Delegate* delegate) { 53 if (!delegate) 54 return ExternalProtocolHandler::GetBlockState(scheme); 55 56 return delegate->GetBlockState(scheme); 57 } 58 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); 71 } 72 } 73 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); 84 } 85 } 86 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) {} 103 104 virtual void SetDefaultWebClientUIState( 105 ShellIntegration::DefaultWebClientUIState state) OVERRIDE { 106 DCHECK(base::MessageLoopForUI::IsCurrent()); 107 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; 112 } 113 114 if (delegate_) 115 delegate_->FinishedProcessingCheck(); 116 117 if (state == ShellIntegration::STATE_IS_DEFAULT) { 118 if (delegate_) 119 delegate_->BlockRequest(); 120 return; 121 } 122 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; 132 } 133 134 LaunchUrlWithoutSecurityCheckWithDelegate( 135 escaped_url_, render_process_host_id_, tab_contents_id_, delegate_); 136 } 137 138 virtual bool IsOwnedByWorker() OVERRIDE { return true; } 139 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_; 146 }; 147 148 } // namespace 149 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; 157 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", 179 }; 180 181 static const char* const allowed_schemes[] = { 182 "mailto", 183 "news", 184 "snews", 185 #if defined(OS_WIN) 186 "ms-windows-store", 187 #endif 188 }; 189 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); 194 } 195 } 196 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); 200 } 201 } 202 } 203 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; 210 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; 216 } 217 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); 224 225 // Warm up the dictionary if needed. 226 PrepopulateDictionary(update_excluded_schemas.Get()); 227 228 bool should_block; 229 if (update_excluded_schemas->GetBoolean(scheme, &should_block)) 230 return should_block ? BLOCK : DONT_BLOCK; 231 } 232 233 return UNKNOWN; 234 } 235 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); 245 246 if (state == UNKNOWN) { 247 update_excluded_schemas->Remove(scheme, NULL); 248 } else { 249 update_excluded_schemas->SetBoolean(scheme, (state == BLOCK)); 250 } 251 } 252 } 253 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()); 260 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; 271 } 272 273 g_accept_requests = false; 274 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); 286 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(); 291 } 292 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; 302 303 platform_util::OpenExternal( 304 Profile::FromBrowserContext(web_contents->GetBrowserContext()), url); 305 } 306 307 // static 308 void ExternalProtocolHandler::RegisterPrefs(PrefRegistrySimple* registry) { 309 registry->RegisterDictionaryPref(prefs::kExcludedSchemes); 310 } 311 312 // static 313 void ExternalProtocolHandler::PermitLaunchUrl() { 314 DCHECK(base::MessageLoopForUI::IsCurrent()); 315 g_accept_requests = true; 316 } 317