blob: c095682aa1e6301fe34f611365ed470a522ab413 [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"
brettwb1fc1b82016-02-02 00:19:0822#include "components/prefs/pref_registry_simple.h"
23#include "components/prefs/pref_service.h"
24#include "components/prefs/scoped_user_pref_update.h"
Todd Sahl13959632020-06-18 07:40:1125#include "components/url_matcher/url_matcher.h"
Yann Dagoe65b7ee2022-01-04 19:01:3526#include "components/url_matcher/url_util.h"
[email protected]ed10dd12011-12-07 12:03:4227#include "content/public/browser/browser_thread.h"
Jeremy Roman04ad4e3f2021-12-22 18:54:5428#include "content/public/browser/weak_document_ptr.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
Xiaohan Wang5b9ac9a2022-01-15 22:52:4135#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_FUCHSIA) && \
36 !BUILDFLAG(IS_CHROMEOS_ASH)
Yasmin85bccc562019-08-12 17:25:0137#include "chrome/browser/sharing/click_to_call/click_to_call_ui_controller.h"
Richard Knoll2355d932019-07-19 16:57:2938#include "chrome/browser/sharing/click_to_call/click_to_call_utils.h"
Benjamin Lerman5389aa62021-08-03 10:11:5139#endif
40
Xiaohan Wang5b9ac9a2022-01-15 22:52:4141#if !BUILDFLAG(IS_ANDROID)
Lei Zhangddd22482021-05-11 08:07:3242#include "chrome/browser/ui/browser.h"
Callum May48091d52019-07-03 20:09:1243#include "chrome/browser/ui/browser_finder.h"
44#include "chrome/browser/ui/tabs/tab_strip_model.h"
45#endif
46
Ben Wellscafcc8282017-09-21 06:57:5747namespace {
48
Eric Lawrence1c3cc832020-12-18 11:57:3649// Anti-flood protection controls whether we accept requests for launching
50// external protocols. Set to false each time an external protocol is requested,
51// and set back to true on each user gesture, extension API call, and navigation
52// to an external handler via bookmarks or the omnibox. This variable should
53// only be accessed from the UI thread.
Lei Zhang943bceea2017-09-26 04:46:5354bool g_accept_requests = true;
[email protected]86fad30d2014-07-29 21:39:2755
Daniel Bratellaf822212018-03-13 11:15:0656ExternalProtocolHandler::Delegate* g_external_protocol_handler_delegate =
57 nullptr;
John Abd-El-Maleka67add82018-03-09 18:22:0158
Lei Zhang943bceea2017-09-26 04:46:5359constexpr const char* kDeniedSchemes[] = {
Alex Moshchuk902f802b2019-08-21 05:28:2860 "afp",
61 "data",
62 "disk",
63 "disks",
Ben Wellscafcc8282017-09-21 06:57:5764 // ShellExecuting file:///C:/WINDOWS/system32/notepad.exe will simply
65 // execute the file specified! Hopefully we won't see any "file" schemes
66 // because we think of file:// URLs as handled URLs, but better to be safe
67 // than to let an attacker format the user's hard drive.
Alex Moshchuk902f802b2019-08-21 05:28:2868 "file",
69 "hcp",
70 "ie.http",
71 "javascript",
Alex Moshchuk5576e532021-08-18 19:47:4172 "mk",
Alex Moshchuk902f802b2019-08-21 05:28:2873 "ms-help",
74 "nntp",
75 "res",
76 "shell",
77 "vbscript",
Ben Wellscafcc8282017-09-21 06:57:5778 // view-source is a special case in chrome. When it comes through an
79 // iframe or a redirect, it looks like an external protocol, but we don't
80 // want to shellexecute it.
Alex Moshchuk902f802b2019-08-21 05:28:2881 "view-source",
82 "vnd.ms.radio",
Ben Wellscafcc8282017-09-21 06:57:5783};
84
Lei Zhang943bceea2017-09-26 04:46:5385constexpr const char* kAllowedSchemes[] = {
Ben Wellscafcc8282017-09-21 06:57:5786 "mailto", "news", "snews",
87};
[email protected]956eabb2011-09-23 05:04:3888
Jeremy Roman04ad4e3f2021-12-22 18:54:5489void AddMessageToConsole(const content::WeakDocumentPtr& document,
90 blink::mojom::ConsoleMessageLevel level,
91 const std::string& message) {
92 if (content::RenderFrameHost* rfh = document.AsRenderFrameHostIfValid())
93 rfh->AddMessageToConsole(level, message);
94}
95
[email protected]956eabb2011-09-23 05:04:3896// Functions enabling unit testing. Using a NULL delegate will use the default
pmonette586ab5b32016-03-07 19:50:3797// behavior; if a delegate is provided it will be used instead.
98scoped_refptr<shell_integration::DefaultProtocolClientWorker> CreateShellWorker(
[email protected]956eabb2011-09-23 05:04:3899 const std::string& protocol,
100 ExternalProtocolHandler::Delegate* delegate) {
pmonette586ab5b32016-03-07 19:50:37101 if (delegate)
Will Cassellac79ca562020-07-25 04:03:09102 return delegate->CreateShellWorker(protocol);
Lei Zhang943bceea2017-09-26 04:46:53103 return base::MakeRefCounted<shell_integration::DefaultProtocolClientWorker>(
Will Cassellac79ca562020-07-25 04:03:09104 protocol);
[email protected]956eabb2011-09-23 05:04:38105}
106
107ExternalProtocolHandler::BlockState GetBlockStateWithDelegate(
108 const std::string& scheme,
Todd Sahl057d9c62020-04-17 18:41:14109 const url::Origin* initiating_origin,
ramyasharma2c618e172017-02-06 05:41:34110 ExternalProtocolHandler::Delegate* delegate,
111 Profile* profile) {
Lei Zhang943bceea2017-09-26 04:46:53112 if (delegate)
113 return delegate->GetBlockState(scheme, profile);
Todd Sahl057d9c62020-04-17 18:41:14114 return ExternalProtocolHandler::GetBlockState(scheme, initiating_origin,
115 profile);
[email protected]956eabb2011-09-23 05:04:38116}
117
118void RunExternalProtocolDialogWithDelegate(
119 const GURL& url,
Elly Fong-Jonesea38f4ef2019-04-15 16:26:39120 content::WebContents* web_contents,
qinmin7573e422015-05-06 18:42:31121 ui::PageTransition page_transition,
122 bool has_user_gesture,
Anton Bikineev46bbb972021-05-15 17:53:53123 const absl::optional<url::Origin>& initiating_origin,
Jeremy Roman04ad4e3f2021-12-22 18:54:54124 content::WeakDocumentPtr initiator_document,
[email protected]956eabb2011-09-23 05:04:38125 ExternalProtocolHandler::Delegate* delegate) {
Elly Fong-Jonesea38f4ef2019-04-15 16:26:39126 DCHECK(web_contents);
Lei Zhang943bceea2017-09-26 04:46:53127 if (delegate) {
Elly Fong-Jonesea38f4ef2019-04-15 16:26:39128 delegate->RunExternalProtocolDialog(url, web_contents, page_transition,
Emily Stark13b66bdf2019-10-04 17:11:45129 has_user_gesture, initiating_origin);
Lei Zhang943bceea2017-09-26 04:46:53130 return;
[email protected]956eabb2011-09-23 05:04:38131 }
Eric Lawrence89eb5792020-06-24 20:02:06132
Xiaohan Wang5b9ac9a2022-01-15 22:52:41133#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
Eric Lawrence89eb5792020-06-24 20:02:06134 // If the Shell does not have a registered name for the protocol,
135 // attempting to invoke the protocol will fail.
136 if (shell_integration::GetApplicationNameForProtocol(url).empty()) {
Jeremy Roman04ad4e3f2021-12-22 18:54:54137 AddMessageToConsole(
138 initiator_document, blink::mojom::ConsoleMessageLevel::kError,
Eric Lawrence89eb5792020-06-24 20:02:06139 "Failed to launch '" + url.possibly_invalid_spec() +
140 "' because the scheme does not have a registered handler.");
141 return;
142 }
143#endif
144
Lei Zhang943bceea2017-09-26 04:46:53145 ExternalProtocolHandler::RunExternalProtocolDialog(
Jeremy Roman04ad4e3f2021-12-22 18:54:54146 url, web_contents, page_transition, has_user_gesture, initiating_origin,
147 std::move(initiator_document));
[email protected]956eabb2011-09-23 05:04:38148}
149
150void LaunchUrlWithoutSecurityCheckWithDelegate(
151 const GURL& url,
Trent Apted7225e162018-05-10 08:33:35152 content::WebContents* web_contents,
Jeremy Roman04ad4e3f2021-12-22 18:54:54153 content::WeakDocumentPtr initiator_document,
[email protected]956eabb2011-09-23 05:04:38154 ExternalProtocolHandler::Delegate* delegate) {
Lei Zhang943bceea2017-09-26 04:46:53155 if (delegate) {
davidsac1fe2ac6462016-12-20 01:27:41156 delegate->LaunchUrlWithoutSecurityCheck(url, web_contents);
Lei Zhang943bceea2017-09-26 04:46:53157 return;
[email protected]7f0a3efa2013-12-12 17:16:12158 }
Trent Apted7225e162018-05-10 08:33:35159
160 // |web_contents| is only passed in to find browser context. Do not assume
161 // that the external protocol request came from the main frame.
162 if (!web_contents)
163 return;
164
Jeremy Roman04ad4e3f2021-12-22 18:54:54165 AddMessageToConsole(
166 initiator_document, blink::mojom::ConsoleMessageLevel::kInfo,
Eric Lawrence89eb5792020-06-24 20:02:06167 "Launched external handler for '" + url.possibly_invalid_spec() + "'.");
168
Trent Apted7225e162018-05-10 08:33:35169 platform_util::OpenExternal(
170 Profile::FromBrowserContext(web_contents->GetBrowserContext()), url);
Callum May48091d52019-07-03 20:09:12171
Xiaohan Wang5b9ac9a2022-01-15 22:52:41172#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
Callum May48091d52019-07-03 20:09:12173 // If the protocol navigation occurs in a new tab, close it.
174 // Avoid calling CloseContents if the tab is not in this browser's tab strip
175 // model; this can happen if the protocol was initiated by something
176 // internal to Chrome.
177 Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
178 if (browser && web_contents->GetController().IsInitialNavigation() &&
179 browser->tab_strip_model()->count() > 1 &&
180 browser->tab_strip_model()->GetIndexOfWebContents(web_contents) !=
181 TabStripModel::kNoTab) {
182 web_contents->Close();
183 }
184#endif
[email protected]956eabb2011-09-23 05:04:38185}
186
pmonette586ab5b32016-03-07 19:50:37187// When we are about to launch a URL with the default OS level application, we
188// check if the external application will be us. If it is we just ignore the
189// request.
190void OnDefaultProtocolClientWorkerFinished(
191 const GURL& escaped_url,
Jeremy Roman111cdbb2021-06-10 19:42:52192 content::WebContents::Getter web_contents_getter,
pmonette586ab5b32016-03-07 19:50:37193 bool prompt_user,
194 ui::PageTransition page_transition,
195 bool has_user_gesture,
Anton Bikineev46bbb972021-05-15 17:53:53196 const absl::optional<url::Origin>& initiating_origin,
Jeremy Roman04ad4e3f2021-12-22 18:54:54197 content::WeakDocumentPtr initiator_document,
pmonette586ab5b32016-03-07 19:50:37198 ExternalProtocolHandler::Delegate* delegate,
pmonetteb9204142016-03-08 20:02:44199 shell_integration::DefaultWebClientState state) {
Lei Zhang943bceea2017-09-26 04:46:53200 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
[email protected]956eabb2011-09-23 05:04:38201
pmonette586ab5b32016-03-07 19:50:37202 if (delegate)
203 delegate->FinishedProcessingCheck();
[email protected]956eabb2011-09-23 05:04:38204
Jeremy Roman111cdbb2021-06-10 19:42:52205 content::WebContents* web_contents = web_contents_getter.Run();
Richard Knoll2355d932019-07-19 16:57:29206
207 // The default handler is hidden if it is Chrome itself, as nothing will
208 // happen if it is selected (since this is invoked by the external protocol
209 // handling flow).
210 bool chrome_is_default_handler = state == shell_integration::IS_DEFAULT;
211
Richard Knoll5e85eb122019-09-05 11:15:33212 // On ChromeOS, Click to Call is integrated into the external protocol dialog.
Xiaohan Wang5b9ac9a2022-01-15 22:52:41213#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_FUCHSIA) && \
214 !BUILDFLAG(IS_CHROMEOS_ASH)
Himanshu Jaju844973142019-08-21 06:24:19215 if (web_contents && ShouldOfferClickToCallForURL(
216 web_contents->GetBrowserContext(), escaped_url)) {
Richard Knoll2355d932019-07-19 16:57:29217 // Handle tel links by opening the Click to Call dialog. This will call back
218 // into LaunchUrlWithoutSecurityCheck if the user selects a system handler.
Richard Knoll8bd8d192019-10-15 14:01:12219 ClickToCallUiController::ShowDialog(web_contents, initiating_origin,
Jeremy Roman04ad4e3f2021-12-22 18:54:54220 std::move(initiator_document),
Richard Knoll8bd8d192019-10-15 14:01:12221 escaped_url, chrome_is_default_handler);
Richard Knoll2355d932019-07-19 16:57:29222 return;
223 }
224#endif
225
226 if (chrome_is_default_handler) {
pmonette586ab5b32016-03-07 19:50:37227 if (delegate)
228 delegate->BlockRequest();
229 return;
[email protected]956eabb2011-09-23 05:04:38230 }
231
pmonette586ab5b32016-03-07 19:50:37232 // If we get here, either we are not the default or we cannot work out
233 // what the default is, so we proceed.
234 if (prompt_user) {
Elly Fong-Jonesea38f4ef2019-04-15 16:26:39235 // Never prompt the user without a web_contents.
236 if (!web_contents)
237 return;
238
pmonette586ab5b32016-03-07 19:50:37239 // Ask the user if they want to allow the protocol. This will call
240 // LaunchUrlWithoutSecurityCheck if the user decides to accept the
241 // protocol.
Jeremy Roman04ad4e3f2021-12-22 18:54:54242 RunExternalProtocolDialogWithDelegate(
243 escaped_url, web_contents, page_transition, has_user_gesture,
244 initiating_origin, std::move(initiator_document), delegate);
pmonette586ab5b32016-03-07 19:50:37245 return;
246 }
247
Jeremy Roman04ad4e3f2021-12-22 18:54:54248 LaunchUrlWithoutSecurityCheckWithDelegate(
249 escaped_url, web_contents, std::move(initiator_document), delegate);
pmonette586ab5b32016-03-07 19:50:37250}
[email protected]956eabb2011-09-23 05:04:38251
Todd Sahl13959632020-06-18 07:40:11252bool IsSchemeOriginPairAllowedByPolicy(const std::string& scheme,
253 const url::Origin* initiating_origin,
254 PrefService* prefs) {
Todd Sahlc98ade62020-07-29 23:37:49255 if (!initiating_origin)
256 return false;
257
Austin Sullivan7d219a252021-12-20 14:55:31258 const base::Value* exempted_protocols =
Todd Sahl13959632020-06-18 07:40:11259 prefs->GetList(prefs::kAutoLaunchProtocolsFromOrigins);
260 if (!exempted_protocols)
261 return false;
262
263 const base::Value* origin_patterns = nullptr;
Daniel Cheng354945d2022-02-02 23:39:17264 for (const base::Value& entry : exempted_protocols->GetListDeprecated()) {
Todd Sahl13959632020-06-18 07:40:11265 const base::DictionaryValue& protocol_origins_map =
266 base::Value::AsDictionaryValue(entry);
267 const std::string* protocol = protocol_origins_map.FindStringKey(
268 policy::AutoLaunchProtocolsPolicyHandler::kProtocolNameKey);
269 DCHECK(protocol);
270 if (*protocol == scheme) {
271 origin_patterns = protocol_origins_map.FindListKey(
272 policy::AutoLaunchProtocolsPolicyHandler::kOriginListKey);
273 break;
274 }
275 }
276 if (!origin_patterns)
277 return false;
278
279 url_matcher::URLMatcher matcher;
280 url_matcher::URLMatcherConditionSet::ID id(0);
Yann Dagoe65b7ee2022-01-04 19:01:35281 url_matcher::util::AddFilters(&matcher, true /* allowed */, &id,
282 &base::Value::AsListValue(*origin_patterns));
Todd Sahl13959632020-06-18 07:40:11283
284 auto matching_set = matcher.MatchURL(initiating_origin->GetURL());
285 return !matching_set.empty();
286}
287
[email protected]65c81142012-07-31 19:44:43288} // namespace
[email protected]956eabb2011-09-23 05:04:38289
ramyasharmaef194a52017-02-08 03:30:22290const char ExternalProtocolHandler::kHandleStateMetric[] =
291 "BrowserDialogs.ExternalProtocol.HandleState";
292
initial.commit09911bf2008-07-26 23:55:29293// static
John Abd-El-Maleka67add82018-03-09 18:22:01294void ExternalProtocolHandler::SetDelegateForTesting(Delegate* delegate) {
Daniel Bratellaf822212018-03-13 11:15:06295 g_external_protocol_handler_delegate = delegate;
John Abd-El-Maleka67add82018-03-09 18:22:01296}
297
Todd Sahl057d9c62020-04-17 18:41:14298bool ExternalProtocolHandler::MayRememberAllowDecisionsForThisOrigin(
299 const url::Origin* initiating_origin) {
300 return initiating_origin &&
301 network::IsOriginPotentiallyTrustworthy(*initiating_origin);
302}
303
304// static.
initial.commit09911bf2008-07-26 23:55:29305ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState(
ramyasharma2c618e172017-02-06 05:41:34306 const std::string& scheme,
Todd Sahl057d9c62020-04-17 18:41:14307 const url::Origin* initiating_origin,
ramyasharma2c618e172017-02-06 05:41:34308 Profile* profile) {
Lei Zhang943bceea2017-09-26 04:46:53309 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
310
Eric Lawrenced2a713a2019-07-15 19:14:05311 // If we are being flooded with requests, block the request.
[email protected]86fad30d2014-07-29 21:39:27312 if (!g_accept_requests)
[email protected]e7eaedde2009-09-25 20:09:49313 return BLOCK;
314
initial.commit09911bf2008-07-26 23:55:29315 if (scheme.length() == 1) {
316 // We have a URL that looks something like:
317 // C:/WINDOWS/system32/notepad.exe
318 // ShellExecuting this URL will cause the specified program to be executed.
319 return BLOCK;
320 }
321
Ben Wellscafcc8282017-09-21 06:57:57322 // Always block the hard-coded denied schemes.
Avi Drissman5f0fb8c2018-12-25 23:20:49323 for (size_t i = 0; i < base::size(kDeniedSchemes); ++i) {
Ben Wellscafcc8282017-09-21 06:57:57324 if (kDeniedSchemes[i] == scheme)
325 return BLOCK;
326 }
327
328 // Always allow the hard-coded allowed schemes.
Avi Drissman5f0fb8c2018-12-25 23:20:49329 for (size_t i = 0; i < base::size(kAllowedSchemes); ++i) {
Ben Wellscafcc8282017-09-21 06:57:57330 if (kAllowedSchemes[i] == scheme)
331 return DONT_BLOCK;
332 }
333
ramyasharma2c618e172017-02-06 05:41:34334 PrefService* profile_prefs = profile->GetPrefs();
Ben Wellscafcc8282017-09-21 06:57:57335 if (profile_prefs) { // May be NULL during testing.
Todd Sahl13959632020-06-18 07:40:11336 if (IsSchemeOriginPairAllowedByPolicy(scheme, initiating_origin,
337 profile_prefs)) {
338 return DONT_BLOCK;
339 }
340
Todd Sahl057d9c62020-04-17 18:41:14341 if (MayRememberAllowDecisionsForThisOrigin(initiating_origin)) {
342 // Check if there is a matching {Origin+Protocol} pair exemption:
Austin Sullivan7d219a252021-12-20 14:55:31343 const base::Value* allowed_origin_protocol_pairs =
Todd Sahl057d9c62020-04-17 18:41:14344 profile_prefs->GetDictionary(
345 prefs::kProtocolHandlerPerOriginAllowedProtocols);
346 const base::Value* allowed_protocols_for_origin =
347 allowed_origin_protocol_pairs->FindDictKey(
348 initiating_origin->Serialize());
349 if (allowed_protocols_for_origin) {
Anton Bikineev46bbb972021-05-15 17:53:53350 absl::optional<bool> allow =
Todd Sahl057d9c62020-04-17 18:41:14351 allowed_protocols_for_origin->FindBoolKey(scheme);
352 if (allow.has_value() && allow.value())
353 return DONT_BLOCK;
354 }
Ben Wellscafcc8282017-09-21 06:57:57355 }
initial.commit09911bf2008-07-26 23:55:29356 }
357
358 return UNKNOWN;
359}
360
361// static
Todd Sahl057d9c62020-04-17 18:41:14362// This is only called when the "remember" check box is selected from the
363// External Protocol Prompt dialog, and that check box is only shown when there
364// is a non-empty, potentially-trustworthy initiating origin.
365void ExternalProtocolHandler::SetBlockState(
366 const std::string& scheme,
367 const url::Origin& initiating_origin,
368 BlockState state,
369 Profile* profile) {
Ben Wellscafcc8282017-09-21 06:57:57370 // Setting the state to BLOCK is no longer supported through the UI.
371 DCHECK_NE(state, BLOCK);
372
[email protected]98299482009-10-06 19:33:07373 // Set in the stored prefs.
Todd Sahl057d9c62020-04-17 18:41:14374 if (MayRememberAllowDecisionsForThisOrigin(&initiating_origin)) {
375 PrefService* profile_prefs = profile->GetPrefs();
376 if (profile_prefs) { // May be NULL during testing.
Alex Turner0e3da5142022-01-14 00:23:33377 DictionaryPrefUpdate update_allowed_origin_protocol_pairs(
Todd Sahl057d9c62020-04-17 18:41:14378 profile_prefs, prefs::kProtocolHandlerPerOriginAllowedProtocols);
379
380 const std::string serialized_origin = initiating_origin.Serialize();
381 base::Value* allowed_protocols_for_origin =
382 update_allowed_origin_protocol_pairs->FindDictKey(serialized_origin);
383 if (!allowed_protocols_for_origin) {
384 update_allowed_origin_protocol_pairs->SetKey(
385 serialized_origin, base::Value(base::Value::Type::DICTIONARY));
386 allowed_protocols_for_origin =
387 update_allowed_origin_protocol_pairs->FindDictKey(
388 serialized_origin);
389 }
390 if (state == DONT_BLOCK) {
391 allowed_protocols_for_origin->SetBoolKey(scheme, true);
392 } else {
393 allowed_protocols_for_origin->RemoveKey(scheme);
394 if (allowed_protocols_for_origin->DictEmpty())
395 update_allowed_origin_protocol_pairs->RemoveKey(serialized_origin);
396 }
397 }
[email protected]98299482009-10-06 19:33:07398 }
Elly Fong-Jones0257b4cf2019-10-21 19:54:45399
Todd Sahl057d9c62020-04-17 18:41:14400 if (g_external_protocol_handler_delegate) {
401 g_external_protocol_handler_delegate->OnSetBlockState(
402 scheme, initiating_origin, state);
403 }
[email protected]98299482009-10-06 19:33:07404}
405
406// static
Emily Stark13b66bdf2019-10-04 17:11:45407void ExternalProtocolHandler::LaunchUrl(
408 const GURL& url,
Jeremy Roman111cdbb2021-06-10 19:42:52409 content::WebContents::Getter web_contents_getter,
Emily Stark13b66bdf2019-10-04 17:11:45410 ui::PageTransition page_transition,
411 bool has_user_gesture,
Jeremy Roman04ad4e3f2021-12-22 18:54:54412 const absl::optional<url::Origin>& initiating_origin,
413 content::WeakDocumentPtr initiator_document) {
Lei Zhang943bceea2017-09-26 04:46:53414 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
[email protected]e7eaedde2009-09-25 20:09:49415
Eric Lawrence1c3cc832020-12-18 11:57:36416 // Disable anti-flood protection if the user is invoking a bookmark or
417 // navigating directly using the omnibox.
418 if (!g_accept_requests &&
419 (PageTransitionCoreTypeIs(page_transition,
420 ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||
421 PageTransitionCoreTypeIs(page_transition, ui::PAGE_TRANSITION_TYPED))) {
422 g_accept_requests = true;
423 }
424
initial.commit09911bf2008-07-26 23:55:29425 // Escape the input scheme to be sure that the command does not
426 // have parameters unexpected by the external program.
Matt Giuca36fd3c92017-11-27 01:12:35427 // TODO(mgiuca): This essentially amounts to "remove illegal characters from
428 // the URL", something that probably should be done by the GURL constructor
429 // itself. The GURL constructor does do it in some cases (e.g., mailto) but
430 // not in general. https://crbug.com/788244.
[email protected]c83d7d12011-11-06 14:34:29431 std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec());
initial.commit09911bf2008-07-26 23:55:29432 GURL escaped_url(escaped_url_string);
ramyasharma2c618e172017-02-06 05:41:34433
Jeremy Roman111cdbb2021-06-10 19:42:52434 content::WebContents* web_contents = web_contents_getter.Run();
ramyasharma2c618e172017-02-06 05:41:34435 Profile* profile = nullptr;
436 if (web_contents) // Maybe NULL during testing.
437 profile = Profile::FromBrowserContext(web_contents->GetBrowserContext());
Daniel Bratellaf822212018-03-13 11:15:06438 BlockState block_state = GetBlockStateWithDelegate(
Todd Sahl057d9c62020-04-17 18:41:14439 escaped_url.scheme(), base::OptionalOrNullptr(initiating_origin),
440 g_external_protocol_handler_delegate, profile);
[email protected]956eabb2011-09-23 05:04:38441 if (block_state == BLOCK) {
Jeremy Roman04ad4e3f2021-12-22 18:54:54442 AddMessageToConsole(
443 initiator_document, blink::mojom::ConsoleMessageLevel::kError,
Eric Lawrence89eb5792020-06-24 20:02:06444 "Not allowed to launch '" + url.possibly_invalid_spec() + "'" +
445 (g_accept_requests ? "." : " because a user gesture is required."));
446
Daniel Bratellaf822212018-03-13 11:15:06447 if (g_external_protocol_handler_delegate)
448 g_external_protocol_handler_delegate->BlockRequest();
initial.commit09911bf2008-07-26 23:55:29449 return;
450 }
451
[email protected]86fad30d2014-07-29 21:39:27452 g_accept_requests = false;
453
Anton Bikineev46bbb972021-05-15 17:53:53454 absl::optional<url::Origin> initiating_origin_or_precursor;
Emily Stark7cfe6fc2020-02-19 05:19:01455 if (initiating_origin) {
456 // Transform the initiating origin to its precursor origin if it is
457 // opaque. |initiating_origin| is shown in the UI to attribute the external
458 // protocol request to a particular site, and showing an opaque origin isn't
459 // useful.
460 if (initiating_origin->opaque()) {
461 initiating_origin_or_precursor = url::Origin::Create(
462 initiating_origin->GetTupleOrPrecursorTupleIfOpaque().GetURL());
463 } else {
464 initiating_origin_or_precursor = initiating_origin;
465 }
466 }
467
[email protected]956eabb2011-09-23 05:04:38468 // The worker creates tasks with references to itself and puts them into
pmonette586ab5b32016-03-07 19:50:37469 // message loops.
Will Cassellac79ca562020-07-25 04:03:09470 shell_integration::DefaultWebClientWorkerCallback callback = base::BindOnce(
Emily Stark7cfe6fc2020-02-19 05:19:01471 &OnDefaultProtocolClientWorkerFinished, escaped_url,
Jeremy Roman111cdbb2021-06-10 19:42:52472 std::move(web_contents_getter), block_state == UNKNOWN, page_transition,
473 has_user_gesture, initiating_origin_or_precursor,
Jeremy Roman04ad4e3f2021-12-22 18:54:54474 std::move(initiator_document), g_external_protocol_handler_delegate);
[email protected]956eabb2011-09-23 05:04:38475
Lei Zhang943bceea2017-09-26 04:46:53476 // Start the check process running. This will send tasks to a worker task
477 // runner and when the answer is known will send the result back to
pmonette586ab5b32016-03-07 19:50:37478 // OnDefaultProtocolClientWorkerFinished().
Will Cassellac79ca562020-07-25 04:03:09479 CreateShellWorker(escaped_url.scheme(), g_external_protocol_handler_delegate)
480 ->StartCheckIsDefault(std::move(callback));
[email protected]10f57b92009-09-03 21:33:21481}
482
483// static
[email protected]7f0a3efa2013-12-12 17:16:12484void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck(
485 const GURL& url,
Jeremy Roman04ad4e3f2021-12-22 18:54:54486 content::WebContents* web_contents,
487 content::WeakDocumentPtr initiator_document) {
Julian Pastarmov38081dc2020-05-21 03:11:17488 // Escape the input scheme to be sure that the command does not
489 // have parameters unexpected by the external program. The url passed in the
490 // |url| parameter might already be escaped but the EscapeExternalHandlerValue
491 // is idempotent so it is safe to apply it again.
492 // TODO(788244): This essentially amounts to "remove illegal characters from
493 // the URL", something that probably should be done by the GURL constructor
494 // itself.
495 std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec());
496 GURL escaped_url(escaped_url_string);
497
Trent Apted7225e162018-05-10 08:33:35498 LaunchUrlWithoutSecurityCheckWithDelegate(
Jeremy Roman04ad4e3f2021-12-22 18:54:54499 escaped_url, web_contents, std::move(initiator_document),
500 g_external_protocol_handler_delegate);
initial.commit09911bf2008-07-26 23:55:29501}
502
503// static
[email protected]86fad30d2014-07-29 21:39:27504void ExternalProtocolHandler::PermitLaunchUrl() {
Lei Zhang943bceea2017-09-26 04:46:53505 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
506
[email protected]86fad30d2014-07-29 21:39:27507 g_accept_requests = true;
508}
dominickn0c9a5062016-10-06 20:49:00509
510// static
ramyasharmaef194a52017-02-08 03:30:22511void ExternalProtocolHandler::RecordHandleStateMetrics(bool checkbox_selected,
512 BlockState block_state) {
513 HandleState handle_state = DONT_LAUNCH;
514 switch (block_state) {
515 case DONT_BLOCK:
516 handle_state = checkbox_selected ? CHECKED_LAUNCH : LAUNCH;
517 break;
518 case BLOCK:
Ben Wellsf2271102017-09-06 03:07:56519 handle_state =
520 checkbox_selected ? CHECKED_DONT_LAUNCH_DEPRECATED : DONT_LAUNCH;
ramyasharmaef194a52017-02-08 03:30:22521 break;
522 case UNKNOWN:
523 NOTREACHED();
524 return;
525 }
Ben Wellsf2271102017-09-06 03:07:56526 DCHECK_NE(CHECKED_DONT_LAUNCH_DEPRECATED, handle_state);
ramyasharmaef194a52017-02-08 03:30:22527 UMA_HISTOGRAM_ENUMERATION(kHandleStateMetric, handle_state,
528 HANDLE_STATE_LAST);
dominickn0c9a5062016-10-06 20:49:00529}
530
531// static
532void ExternalProtocolHandler::RegisterPrefs(PrefRegistrySimple* registry) {
Todd Sahl057d9c62020-04-17 18:41:14533 registry->RegisterDictionaryPref(
534 prefs::kProtocolHandlerPerOriginAllowedProtocols);
Todd Sahl13959632020-06-18 07:40:11535
536 registry->RegisterListPref(prefs::kAutoLaunchProtocolsFromOrigins);
dominickn0c9a5062016-10-06 20:49:00537}
ramyasharma561a9cd2017-02-19 07:49:25538
539// static
540void ExternalProtocolHandler::ClearData(Profile* profile) {
541 PrefService* prefs = profile->GetPrefs();
Todd Sahl057d9c62020-04-17 18:41:14542 prefs->ClearPref(prefs::kProtocolHandlerPerOriginAllowedProtocols);
ramyasharma561a9cd2017-02-19 07:49:25543}