blob: cc9106acff5044cfda3913313f64ad9e1a2b1daa [file] [log] [blame]
[email protected]65c81142012-07-31 19:44:431// Copyright (c) 2012 The Chromium Authors. All rights reserved.
license.botbf09a502008-08-24 00:55:552// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
initial.commit09911bf2008-07-26 23:55:294
[email protected]ed2b1002011-05-25 14:12:105#include "chrome/browser/external_protocol/external_protocol_handler.h"
initial.commit09911bf2008-07-26 23:55:296
avi6846aef2015-12-26 01:09:387#include <stddef.h>
Will Cassellac79ca562020-07-25 04:03:098#include <utility>
avi6846aef2015-12-26 01:09:389
[email protected]317c58f02011-11-09 02:15:0310#include "base/bind.h"
Hans Wennborg1790e6b2020-04-24 19:10:3311#include "base/check_op.h"
dominickn0c9a5062016-10-06 20:49:0012#include "base/metrics/histogram_macros.h"
Hans Wennborg1790e6b2020-04-24 19:10:3313#include "base/notreached.h"
Avi Drissman5f0fb8c2018-12-25 23:20:4914#include "base/stl_util.h"
[email protected]d8830562013-06-10 22:01:5415#include "base/strings/string_util.h"
Callum May48091d52019-07-03 20:09:1216#include "build/build_config.h"
Yuta Hijikata235fc62b2020-12-08 03:48:3217#include "build/chromeos_buildflags.h"
Todd Sahl13959632020-06-18 07:40:1118#include "chrome/browser/external_protocol/auto_launch_protocols_policy_handler.h"
[email protected]14a000d2010-04-29 21:44:2419#include "chrome/browser/platform_util.h"
[email protected]7f0a3efa2013-12-12 17:16:1220#include "chrome/browser/profiles/profile.h"
initial.commit09911bf2008-07-26 23:55:2921#include "chrome/common/pref_names.h"
Todd Sahl13959632020-06-18 07:40:1122#include "components/policy/core/browser/url_util.h"
brettwb1fc1b82016-02-02 00:19:0823#include "components/prefs/pref_registry_simple.h"
24#include "components/prefs/pref_service.h"
25#include "components/prefs/scoped_user_pref_update.h"
Todd Sahl13959632020-06-18 07:40:1126#include "components/url_matcher/url_matcher.h"
[email protected]ed10dd12011-12-07 12:03:4227#include "content/public/browser/browser_thread.h"
[email protected]7f0a3efa2013-12-12 17:16:1228#include "content/public/browser/web_contents.h"
initial.commit09911bf2008-07-26 23:55:2929#include "net/base/escape.h"
Todd Sahl057d9c62020-04-17 18:41:1430#include "services/network/public/cpp/is_potentially_trustworthy.h"
Hans Wennborg3e67bab2021-04-08 11:34:3131#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
[email protected]761fa4702013-07-02 15:25:1532#include "url/gurl.h"
Todd Sahl057d9c62020-04-17 18:41:1433#include "url/origin.h"
initial.commit09911bf2008-07-26 23:55:2934
Callum May48091d52019-07-03 20:09:1235#if !defined(OS_ANDROID)
Yasmin85bccc562019-08-12 17:25:0136#include "chrome/browser/sharing/click_to_call/click_to_call_ui_controller.h"
Richard Knoll2355d932019-07-19 16:57:2937#include "chrome/browser/sharing/click_to_call/click_to_call_utils.h"
Lei Zhangddd22482021-05-11 08:07:3238#include "chrome/browser/ui/browser.h"
Callum May48091d52019-07-03 20:09:1239#include "chrome/browser/ui/browser_finder.h"
40#include "chrome/browser/ui/tabs/tab_strip_model.h"
41#endif
42
Ben Wellscafcc8282017-09-21 06:57:5743namespace {
44
Eric Lawrence1c3cc832020-12-18 11:57:3645// Anti-flood protection controls whether we accept requests for launching
46// external protocols. Set to false each time an external protocol is requested,
47// and set back to true on each user gesture, extension API call, and navigation
48// to an external handler via bookmarks or the omnibox. This variable should
49// only be accessed from the UI thread.
Lei Zhang943bceea2017-09-26 04:46:5350bool g_accept_requests = true;
[email protected]86fad30d2014-07-29 21:39:2751
Daniel Bratellaf822212018-03-13 11:15:0652ExternalProtocolHandler::Delegate* g_external_protocol_handler_delegate =
53 nullptr;
John Abd-El-Maleka67add82018-03-09 18:22:0154
Lei Zhang943bceea2017-09-26 04:46:5355constexpr const char* kDeniedSchemes[] = {
Alex Moshchuk902f802b2019-08-21 05:28:2856 "afp",
57 "data",
58 "disk",
59 "disks",
Ben Wellscafcc8282017-09-21 06:57:5760 // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
61 // execute the file specified! Hopefully we won't see any "file" schemes
62 // because we think of file:// URLs as handled URLs, but better to be safe
63 // than to let an attacker format the user's hard drive.
Alex Moshchuk902f802b2019-08-21 05:28:2864 "file",
65 "hcp",
66 "ie.http",
67 "javascript",
68 "ms-help",
69 "nntp",
70 "res",
71 "shell",
72 "vbscript",
Ben Wellscafcc8282017-09-21 06:57:5773 // view-source is a special case in chrome. When it comes through an
74 // iframe or a redirect, it looks like an external protocol, but we don't
75 // want to shellexecute it.
Alex Moshchuk902f802b2019-08-21 05:28:2876 "view-source",
77 "vnd.ms.radio",
Ben Wellscafcc8282017-09-21 06:57:5778};
79
Lei Zhang943bceea2017-09-26 04:46:5380constexpr const char* kAllowedSchemes[] = {
Ben Wellscafcc8282017-09-21 06:57:5781 "mailto", "news", "snews",
82};
[email protected]956eabb2011-09-23 05:04:3883
84// Functions enabling unit testing. Using a NULL delegate will use the default
pmonette586ab5b32016-03-07 19:50:3785// behavior; if a delegate is provided it will be used instead.
86scoped_refptr<shell_integration::DefaultProtocolClientWorker> CreateShellWorker(
[email protected]956eabb2011-09-23 05:04:3887 const std::string& protocol,
88 ExternalProtocolHandler::Delegate* delegate) {
pmonette586ab5b32016-03-07 19:50:3789 if (delegate)
Will Cassellac79ca562020-07-25 04:03:0990 return delegate->CreateShellWorker(protocol);
Lei Zhang943bceea2017-09-26 04:46:5391 return base::MakeRefCounted<shell_integration::DefaultProtocolClientWorker>(
Will Cassellac79ca562020-07-25 04:03:0992 protocol);
[email protected]956eabb2011-09-23 05:04:3893}
94
95ExternalProtocolHandler::BlockState GetBlockStateWithDelegate(
96 const std::string& scheme,
Todd Sahl057d9c62020-04-17 18:41:1497 const url::Origin* initiating_origin,
ramyasharma2c618e172017-02-06 05:41:3498 ExternalProtocolHandler::Delegate* delegate,
99 Profile* profile) {
Lei Zhang943bceea2017-09-26 04:46:53100 if (delegate)
101 return delegate->GetBlockState(scheme, profile);
Todd Sahl057d9c62020-04-17 18:41:14102 return ExternalProtocolHandler::GetBlockState(scheme, initiating_origin,
103 profile);
[email protected]956eabb2011-09-23 05:04:38104}
105
106void RunExternalProtocolDialogWithDelegate(
107 const GURL& url,
Elly Fong-Jonesea38f4ef2019-04-15 16:26:39108 content::WebContents* web_contents,
qinmin7573e422015-05-06 18:42:31109 ui::PageTransition page_transition,
110 bool has_user_gesture,
Anton Bikineev46bbb972021-05-15 17:53:53111 const absl::optional<url::Origin>& initiating_origin,
[email protected]956eabb2011-09-23 05:04:38112 ExternalProtocolHandler::Delegate* delegate) {
Elly Fong-Jonesea38f4ef2019-04-15 16:26:39113 DCHECK(web_contents);
Lei Zhang943bceea2017-09-26 04:46:53114 if (delegate) {
Elly Fong-Jonesea38f4ef2019-04-15 16:26:39115 delegate->RunExternalProtocolDialog(url, web_contents, page_transition,
Emily Stark13b66bdf2019-10-04 17:11:45116 has_user_gesture, initiating_origin);
Lei Zhang943bceea2017-09-26 04:46:53117 return;
[email protected]956eabb2011-09-23 05:04:38118 }
Eric Lawrence89eb5792020-06-24 20:02:06119
Avi Drissman2e458df2020-07-29 16:24:31120#if defined(OS_MAC) || defined(OS_WIN)
Eric Lawrence89eb5792020-06-24 20:02:06121 // If the Shell does not have a registered name for the protocol,
122 // attempting to invoke the protocol will fail.
123 if (shell_integration::GetApplicationNameForProtocol(url).empty()) {
124 web_contents->GetMainFrame()->AddMessageToConsole(
125 blink::mojom::ConsoleMessageLevel::kError,
126 "Failed to launch '" + url.possibly_invalid_spec() +
127 "' because the scheme does not have a registered handler.");
128 return;
129 }
130#endif
131
Lei Zhang943bceea2017-09-26 04:46:53132 ExternalProtocolHandler::RunExternalProtocolDialog(
Emily Stark13b66bdf2019-10-04 17:11:45133 url, web_contents, page_transition, has_user_gesture, initiating_origin);
[email protected]956eabb2011-09-23 05:04:38134}
135
136void LaunchUrlWithoutSecurityCheckWithDelegate(
137 const GURL& url,
Trent Apted7225e162018-05-10 08:33:35138 content::WebContents* web_contents,
[email protected]956eabb2011-09-23 05:04:38139 ExternalProtocolHandler::Delegate* delegate) {
Lei Zhang943bceea2017-09-26 04:46:53140 if (delegate) {
davidsac1fe2ac6462016-12-20 01:27:41141 delegate->LaunchUrlWithoutSecurityCheck(url, web_contents);
Lei Zhang943bceea2017-09-26 04:46:53142 return;
[email protected]7f0a3efa2013-12-12 17:16:12143 }
Trent Apted7225e162018-05-10 08:33:35144
145 // |web_contents| is only passed in to find browser context. Do not assume
146 // that the external protocol request came from the main frame.
147 if (!web_contents)
148 return;
149
Eric Lawrence89eb5792020-06-24 20:02:06150 web_contents->GetMainFrame()->AddMessageToConsole(
151 blink::mojom::ConsoleMessageLevel::kInfo,
152 "Launched external handler for '" + url.possibly_invalid_spec() + "'.");
153
Trent Apted7225e162018-05-10 08:33:35154 platform_util::OpenExternal(
155 Profile::FromBrowserContext(web_contents->GetBrowserContext()), url);
Callum May48091d52019-07-03 20:09:12156
Yuta Hijikata235fc62b2020-12-08 03:48:32157#if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
Callum May48091d52019-07-03 20:09:12158 // If the protocol navigation occurs in a new tab, close it.
159 // Avoid calling CloseContents if the tab is not in this browser's tab strip
160 // model; this can happen if the protocol was initiated by something
161 // internal to Chrome.
162 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
163 if (browser && web_contents->GetController().IsInitialNavigation() &&
164 browser->tab_strip_model()->count() > 1 &&
165 browser->tab_strip_model()->GetIndexOfWebContents(web_contents) !=
166 TabStripModel::kNoTab) {
167 web_contents->Close();
168 }
169#endif
[email protected]956eabb2011-09-23 05:04:38170}
171
pmonette586ab5b32016-03-07 19:50:37172// When we are about to launch a URL with the default OS level application, we
173// check if the external application will be us. If it is we just ignore the
174// request.
175void OnDefaultProtocolClientWorkerFinished(
176 const GURL& escaped_url,
Jeremy Roman111cdbb2021-06-10 19:42:52177 content::WebContents::Getter web_contents_getter,
pmonette586ab5b32016-03-07 19:50:37178 bool prompt_user,
179 ui::PageTransition page_transition,
180 bool has_user_gesture,
Anton Bikineev46bbb972021-05-15 17:53:53181 const absl::optional<url::Origin>& initiating_origin,
pmonette586ab5b32016-03-07 19:50:37182 ExternalProtocolHandler::Delegate* delegate,
pmonetteb9204142016-03-08 20:02:44183 shell_integration::DefaultWebClientState state) {
Lei Zhang943bceea2017-09-26 04:46:53184 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
[email protected]956eabb2011-09-23 05:04:38185
pmonette586ab5b32016-03-07 19:50:37186 if (delegate)
187 delegate->FinishedProcessingCheck();
[email protected]956eabb2011-09-23 05:04:38188
Jeremy Roman111cdbb2021-06-10 19:42:52189 content::WebContents* web_contents = web_contents_getter.Run();
Richard Knoll2355d932019-07-19 16:57:29190
191 // The default handler is hidden if it is Chrome itself, as nothing will
192 // happen if it is selected (since this is invoked by the external protocol
193 // handling flow).
194 bool chrome_is_default_handler = state == shell_integration::IS_DEFAULT;
195
Richard Knoll5e85eb122019-09-05 11:15:33196 // On ChromeOS, Click to Call is integrated into the external protocol dialog.
Yuta Hijikata235fc62b2020-12-08 03:48:32197#if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
Himanshu Jaju844973142019-08-21 06:24:19198 if (web_contents && ShouldOfferClickToCallForURL(
199 web_contents->GetBrowserContext(), escaped_url)) {
Richard Knoll2355d932019-07-19 16:57:29200 // Handle tel links by opening the Click to Call dialog. This will call back
201 // into LaunchUrlWithoutSecurityCheck if the user selects a system handler.
Richard Knoll8bd8d192019-10-15 14:01:12202 ClickToCallUiController::ShowDialog(web_contents, initiating_origin,
203 escaped_url, chrome_is_default_handler);
Richard Knoll2355d932019-07-19 16:57:29204 return;
205 }
206#endif
207
208 if (chrome_is_default_handler) {
pmonette586ab5b32016-03-07 19:50:37209 if (delegate)
210 delegate->BlockRequest();
211 return;
[email protected]956eabb2011-09-23 05:04:38212 }
213
pmonette586ab5b32016-03-07 19:50:37214 // If we get here, either we are not the default or we cannot work out
215 // what the default is, so we proceed.
216 if (prompt_user) {
Elly Fong-Jonesea38f4ef2019-04-15 16:26:39217 // Never prompt the user without a web_contents.
218 if (!web_contents)
219 return;
220
pmonette586ab5b32016-03-07 19:50:37221 // Ask the user if they want to allow the protocol. This will call
222 // LaunchUrlWithoutSecurityCheck if the user decides to accept the
223 // protocol.
Emily Stark13b66bdf2019-10-04 17:11:45224 RunExternalProtocolDialogWithDelegate(escaped_url, web_contents,
225 page_transition, has_user_gesture,
226 initiating_origin, delegate);
pmonette586ab5b32016-03-07 19:50:37227 return;
228 }
229
Trent Apted7225e162018-05-10 08:33:35230 LaunchUrlWithoutSecurityCheckWithDelegate(escaped_url, web_contents,
231 delegate);
pmonette586ab5b32016-03-07 19:50:37232}
[email protected]956eabb2011-09-23 05:04:38233
Todd Sahl13959632020-06-18 07:40:11234bool IsSchemeOriginPairAllowedByPolicy(const std::string& scheme,
235 const url::Origin* initiating_origin,
236 PrefService* prefs) {
Todd Sahlc98ade62020-07-29 23:37:49237 if (!initiating_origin)
238 return false;
239
Todd Sahl13959632020-06-18 07:40:11240 const base::ListValue* exempted_protocols =
241 prefs->GetList(prefs::kAutoLaunchProtocolsFromOrigins);
242 if (!exempted_protocols)
243 return false;
244
245 const base::Value* origin_patterns = nullptr;
246 for (const base::Value& entry : exempted_protocols->GetList()) {
247 const base::DictionaryValue& protocol_origins_map =
248 base::Value::AsDictionaryValue(entry);
249 const std::string* protocol = protocol_origins_map.FindStringKey(
250 policy::AutoLaunchProtocolsPolicyHandler::kProtocolNameKey);
251 DCHECK(protocol);
252 if (*protocol == scheme) {
253 origin_patterns = protocol_origins_map.FindListKey(
254 policy::AutoLaunchProtocolsPolicyHandler::kOriginListKey);
255 break;
256 }
257 }
258 if (!origin_patterns)
259 return false;
260
261 url_matcher::URLMatcher matcher;
262 url_matcher::URLMatcherConditionSet::ID id(0);
263 policy::url_util::AddFilters(&matcher, true /* allowed */, &id,
264 &base::Value::AsListValue(*origin_patterns));
265
266 auto matching_set = matcher.MatchURL(initiating_origin->GetURL());
267 return !matching_set.empty();
268}
269
[email protected]65c81142012-07-31 19:44:43270} // namespace
[email protected]956eabb2011-09-23 05:04:38271
ramyasharmaef194a52017-02-08 03:30:22272const char ExternalProtocolHandler::kHandleStateMetric[] =
273 "BrowserDialogs.ExternalProtocol.HandleState";
274
initial.commit09911bf2008-07-26 23:55:29275// static
John Abd-El-Maleka67add82018-03-09 18:22:01276void ExternalProtocolHandler::SetDelegateForTesting(Delegate* delegate) {
Daniel Bratellaf822212018-03-13 11:15:06277 g_external_protocol_handler_delegate = delegate;
John Abd-El-Maleka67add82018-03-09 18:22:01278}
279
Todd Sahl057d9c62020-04-17 18:41:14280bool ExternalProtocolHandler::MayRememberAllowDecisionsForThisOrigin(
281 const url::Origin* initiating_origin) {
282 return initiating_origin &&
283 network::IsOriginPotentiallyTrustworthy(*initiating_origin);
284}
285
286// static.
initial.commit09911bf2008-07-26 23:55:29287ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
ramyasharma2c618e172017-02-06 05:41:34288 const std::string& scheme,
Todd Sahl057d9c62020-04-17 18:41:14289 const url::Origin* initiating_origin,
ramyasharma2c618e172017-02-06 05:41:34290 Profile* profile) {
Lei Zhang943bceea2017-09-26 04:46:53291 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
292
Eric Lawrenced2a713a2019-07-15 19:14:05293 // If we are being flooded with requests, block the request.
[email protected]86fad30d2014-07-29 21:39:27294 if (!g_accept_requests)
[email protected]e7eaedde2009-09-25 20:09:49295 return BLOCK;
296
initial.commit09911bf2008-07-26 23:55:29297 if (scheme.length() == 1) {
298 // We have a URL that looks something like:
299 // C:/WINDOWS/system32/notepad.exe
300 // ShellExecuting this URL will cause the specified program to be executed.
301 return BLOCK;
302 }
303
Ben Wellscafcc8282017-09-21 06:57:57304 // Always block the hard-coded denied schemes.
Avi Drissman5f0fb8c2018-12-25 23:20:49305 for (size_t i = 0; i < base::size(kDeniedSchemes); ++i) {
Ben Wellscafcc8282017-09-21 06:57:57306 if (kDeniedSchemes[i] == scheme)
307 return BLOCK;
308 }
309
310 // Always allow the hard-coded allowed schemes.
Avi Drissman5f0fb8c2018-12-25 23:20:49311 for (size_t i = 0; i < base::size(kAllowedSchemes); ++i) {
Ben Wellscafcc8282017-09-21 06:57:57312 if (kAllowedSchemes[i] == scheme)
313 return DONT_BLOCK;
314 }
315
ramyasharma2c618e172017-02-06 05:41:34316 PrefService* profile_prefs = profile->GetPrefs();
Ben Wellscafcc8282017-09-21 06:57:57317 if (profile_prefs) { // May be NULL during testing.
Todd Sahl13959632020-06-18 07:40:11318 if (IsSchemeOriginPairAllowedByPolicy(scheme, initiating_origin,
319 profile_prefs)) {
320 return DONT_BLOCK;
321 }
322
Todd Sahl057d9c62020-04-17 18:41:14323 if (MayRememberAllowDecisionsForThisOrigin(initiating_origin)) {
324 // Check if there is a matching {Origin+Protocol} pair exemption:
325 const base::DictionaryValue* allowed_origin_protocol_pairs =
326 profile_prefs->GetDictionary(
327 prefs::kProtocolHandlerPerOriginAllowedProtocols);
328 const base::Value* allowed_protocols_for_origin =
329 allowed_origin_protocol_pairs->FindDictKey(
330 initiating_origin->Serialize());
331 if (allowed_protocols_for_origin) {
Anton Bikineev46bbb972021-05-15 17:53:53332 absl::optional<bool> allow =
Todd Sahl057d9c62020-04-17 18:41:14333 allowed_protocols_for_origin->FindBoolKey(scheme);
334 if (allow.has_value() && allow.value())
335 return DONT_BLOCK;
336 }
Ben Wellscafcc8282017-09-21 06:57:57337 }
initial.commit09911bf2008-07-26 23:55:29338 }
339
340 return UNKNOWN;
341}
342
343// static
Todd Sahl057d9c62020-04-17 18:41:14344// This is only called when the "remember" check box is selected from the
345// External Protocol Prompt dialog, and that check box is only shown when there
346// is a non-empty, potentially-trustworthy initiating origin.
347void ExternalProtocolHandler::SetBlockState(
348 const std::string& scheme,
349 const url::Origin& initiating_origin,
350 BlockState state,
351 Profile* profile) {
Ben Wellscafcc8282017-09-21 06:57:57352 // Setting the state to BLOCK is no longer supported through the UI.
353 DCHECK_NE(state, BLOCK);
354
[email protected]98299482009-10-06 19:33:07355 // Set in the stored prefs.
Todd Sahl057d9c62020-04-17 18:41:14356 if (MayRememberAllowDecisionsForThisOrigin(&initiating_origin)) {
357 PrefService* profile_prefs = profile->GetPrefs();
358 if (profile_prefs) { // May be NULL during testing.
359 DictionaryPrefUpdate update_allowed_origin_protocol_pairs(
360 profile_prefs, prefs::kProtocolHandlerPerOriginAllowedProtocols);
361
362 const std::string serialized_origin = initiating_origin.Serialize();
363 base::Value* allowed_protocols_for_origin =
364 update_allowed_origin_protocol_pairs->FindDictKey(serialized_origin);
365 if (!allowed_protocols_for_origin) {
366 update_allowed_origin_protocol_pairs->SetKey(
367 serialized_origin, base::Value(base::Value::Type::DICTIONARY));
368 allowed_protocols_for_origin =
369 update_allowed_origin_protocol_pairs->FindDictKey(
370 serialized_origin);
371 }
372 if (state == DONT_BLOCK) {
373 allowed_protocols_for_origin->SetBoolKey(scheme, true);
374 } else {
375 allowed_protocols_for_origin->RemoveKey(scheme);
376 if (allowed_protocols_for_origin->DictEmpty())
377 update_allowed_origin_protocol_pairs->RemoveKey(serialized_origin);
378 }
379 }
[email protected]98299482009-10-06 19:33:07380 }
Elly Fong-Jones0257b4cf2019-10-21 19:54:45381
Todd Sahl057d9c62020-04-17 18:41:14382 if (g_external_protocol_handler_delegate) {
383 g_external_protocol_handler_delegate->OnSetBlockState(
384 scheme, initiating_origin, state);
385 }
[email protected]98299482009-10-06 19:33:07386}
387
388// static
Emily Stark13b66bdf2019-10-04 17:11:45389void ExternalProtocolHandler::LaunchUrl(
390 const GURL& url,
Jeremy Roman111cdbb2021-06-10 19:42:52391 content::WebContents::Getter web_contents_getter,
Emily Stark13b66bdf2019-10-04 17:11:45392 ui::PageTransition page_transition,
393 bool has_user_gesture,
Anton Bikineev46bbb972021-05-15 17:53:53394 const absl::optional<url::Origin>& initiating_origin) {
Lei Zhang943bceea2017-09-26 04:46:53395 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
[email protected]e7eaedde2009-09-25 20:09:49396
Eric Lawrence1c3cc832020-12-18 11:57:36397 // Disable anti-flood protection if the user is invoking a bookmark or
398 // navigating directly using the omnibox.
399 if (!g_accept_requests &&
400 (PageTransitionCoreTypeIs(page_transition,
401 ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||
402 PageTransitionCoreTypeIs(page_transition, ui::PAGE_TRANSITION_TYPED))) {
403 g_accept_requests = true;
404 }
405
initial.commit09911bf2008-07-26 23:55:29406 // Escape the input scheme to be sure that the command does not
407 // have parameters unexpected by the external program.
Matt Giuca36fd3c92017-11-27 01:12:35408 // TODO(mgiuca): This essentially amounts to "remove illegal characters from
409 // the URL", something that probably should be done by the GURL constructor
410 // itself. The GURL constructor does do it in some cases (e.g., mailto) but
411 // not in general. https://crbug.com/788244.
[email protected]c83d7d12011-11-06 14:34:29412 std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec());
initial.commit09911bf2008-07-26 23:55:29413 GURL escaped_url(escaped_url_string);
ramyasharma2c618e172017-02-06 05:41:34414
Jeremy Roman111cdbb2021-06-10 19:42:52415 content::WebContents* web_contents = web_contents_getter.Run();
ramyasharma2c618e172017-02-06 05:41:34416 Profile* profile = nullptr;
417 if (web_contents) // Maybe NULL during testing.
418 profile = Profile::FromBrowserContext(web_contents->GetBrowserContext());
Daniel Bratellaf822212018-03-13 11:15:06419 BlockState block_state = GetBlockStateWithDelegate(
Todd Sahl057d9c62020-04-17 18:41:14420 escaped_url.scheme(), base::OptionalOrNullptr(initiating_origin),
421 g_external_protocol_handler_delegate, profile);
[email protected]956eabb2011-09-23 05:04:38422 if (block_state == BLOCK) {
Eric Lawrence89eb5792020-06-24 20:02:06423 web_contents->GetMainFrame()->AddMessageToConsole(
424 blink::mojom::ConsoleMessageLevel::kError,
425 "Not allowed to launch '" + url.possibly_invalid_spec() + "'" +
426 (g_accept_requests ? "." : " because a user gesture is required."));
427
Daniel Bratellaf822212018-03-13 11:15:06428 if (g_external_protocol_handler_delegate)
429 g_external_protocol_handler_delegate->BlockRequest();
initial.commit09911bf2008-07-26 23:55:29430 return;
431 }
432
[email protected]86fad30d2014-07-29 21:39:27433 g_accept_requests = false;
434
Anton Bikineev46bbb972021-05-15 17:53:53435 absl::optional<url::Origin> initiating_origin_or_precursor;
Emily Stark7cfe6fc2020-02-19 05:19:01436 if (initiating_origin) {
437 // Transform the initiating origin to its precursor origin if it is
438 // opaque. |initiating_origin| is shown in the UI to attribute the external
439 // protocol request to a particular site, and showing an opaque origin isn't
440 // useful.
441 if (initiating_origin->opaque()) {
442 initiating_origin_or_precursor = url::Origin::Create(
443 initiating_origin->GetTupleOrPrecursorTupleIfOpaque().GetURL());
444 } else {
445 initiating_origin_or_precursor = initiating_origin;
446 }
447 }
448
[email protected]956eabb2011-09-23 05:04:38449 // The worker creates tasks with references to itself and puts them into
pmonette586ab5b32016-03-07 19:50:37450 // message loops.
Will Cassellac79ca562020-07-25 04:03:09451 shell_integration::DefaultWebClientWorkerCallback callback = base::BindOnce(
Emily Stark7cfe6fc2020-02-19 05:19:01452 &OnDefaultProtocolClientWorkerFinished, escaped_url,
Jeremy Roman111cdbb2021-06-10 19:42:52453 std::move(web_contents_getter), block_state == UNKNOWN, page_transition,
454 has_user_gesture, initiating_origin_or_precursor,
Emily Stark7cfe6fc2020-02-19 05:19:01455 g_external_protocol_handler_delegate);
[email protected]956eabb2011-09-23 05:04:38456
Lei Zhang943bceea2017-09-26 04:46:53457 // Start the check process running. This will send tasks to a worker task
458 // runner and when the answer is known will send the result back to
pmonette586ab5b32016-03-07 19:50:37459 // OnDefaultProtocolClientWorkerFinished().
Will Cassellac79ca562020-07-25 04:03:09460 CreateShellWorker(escaped_url.scheme(), g_external_protocol_handler_delegate)
461 ->StartCheckIsDefault(std::move(callback));
[email protected]10f57b92009-09-03 21:33:21462}
463
464// static
[email protected]7f0a3efa2013-12-12 17:16:12465void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
466 const GURL& url,
davidsac1fe2ac6462016-12-20 01:27:41467 content::WebContents* web_contents) {
Julian Pastarmov38081dc2020-05-21 03:11:17468 // Escape the input scheme to be sure that the command does not
469 // have parameters unexpected by the external program. The url passed in the
470 // |url| parameter might already be escaped but the EscapeExternalHandlerValue
471 // is idempotent so it is safe to apply it again.
472 // TODO(788244): This essentially amounts to "remove illegal characters from
473 // the URL", something that probably should be done by the GURL constructor
474 // itself.
475 std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec());
476 GURL escaped_url(escaped_url_string);
477
Trent Apted7225e162018-05-10 08:33:35478 LaunchUrlWithoutSecurityCheckWithDelegate(
Julian Pastarmov38081dc2020-05-21 03:11:17479 escaped_url, web_contents, g_external_protocol_handler_delegate);
initial.commit09911bf2008-07-26 23:55:29480}
481
482// static
[email protected]86fad30d2014-07-29 21:39:27483void ExternalProtocolHandler::PermitLaunchUrl() {
Lei Zhang943bceea2017-09-26 04:46:53484 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
485
[email protected]86fad30d2014-07-29 21:39:27486 g_accept_requests = true;
487}
dominickn0c9a5062016-10-06 20:49:00488
489// static
ramyasharmaef194a52017-02-08 03:30:22490void ExternalProtocolHandler::RecordHandleStateMetrics(bool checkbox_selected,
491 BlockState block_state) {
492 HandleState handle_state = DONT_LAUNCH;
493 switch (block_state) {
494 case DONT_BLOCK:
495 handle_state = checkbox_selected ? CHECKED_LAUNCH : LAUNCH;
496 break;
497 case BLOCK:
Ben Wellsf2271102017-09-06 03:07:56498 handle_state =
499 checkbox_selected ? CHECKED_DONT_LAUNCH_DEPRECATED : DONT_LAUNCH;
ramyasharmaef194a52017-02-08 03:30:22500 break;
501 case UNKNOWN:
502 NOTREACHED();
503 return;
504 }
Ben Wellsf2271102017-09-06 03:07:56505 DCHECK_NE(CHECKED_DONT_LAUNCH_DEPRECATED, handle_state);
ramyasharmaef194a52017-02-08 03:30:22506 UMA_HISTOGRAM_ENUMERATION(kHandleStateMetric, handle_state,
507 HANDLE_STATE_LAST);
dominickn0c9a5062016-10-06 20:49:00508}
509
510// static
511void ExternalProtocolHandler::RegisterPrefs(PrefRegistrySimple* registry) {
Todd Sahl057d9c62020-04-17 18:41:14512 registry->RegisterDictionaryPref(
513 prefs::kProtocolHandlerPerOriginAllowedProtocols);
Todd Sahl13959632020-06-18 07:40:11514
515 registry->RegisterListPref(prefs::kAutoLaunchProtocolsFromOrigins);
dominickn0c9a5062016-10-06 20:49:00516}
ramyasharma561a9cd2017-02-19 07:49:25517
518// static
519void ExternalProtocolHandler::ClearData(Profile* profile) {
520 PrefService* prefs = profile->GetPrefs();
Todd Sahl057d9c62020-04-17 18:41:14521 prefs->ClearPref(prefs::kProtocolHandlerPerOriginAllowedProtocols);
ramyasharma561a9cd2017-02-19 07:49:25522}