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