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 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 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 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 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 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 175 void ExternalProtocolHandler::RegisterPrefs(PrefService* prefs) { 176 prefs->RegisterDictionaryPref(prefs::kExcludedSchemes); 177 } 178 179 // static 180 void ExternalProtocolHandler::PermitLaunchUrl() { 181 DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); 182 g_accept_requests = true; 183 } 184