[email protected] | 65c8114 | 2012-07-31 19:44:43 | [diff] [blame] | 1 | // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
license.bot | bf09a50 | 2008-08-24 00:55:55 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 4 | |
[email protected] | ed2b100 | 2011-05-25 14:12:10 | [diff] [blame] | 5 | #include "chrome/browser/external_protocol/external_protocol_handler.h" |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 6 | |
avi | 6846aef | 2015-12-26 01:09:38 | [diff] [blame] | 7 | #include <stddef.h> |
Will Cassella | c79ca56 | 2020-07-25 04:03:09 | [diff] [blame] | 8 | #include <utility> |
avi | 6846aef | 2015-12-26 01:09:38 | [diff] [blame] | 9 | |
[email protected] | 317c58f0 | 2011-11-09 02:15:03 | [diff] [blame] | 10 | #include "base/bind.h" |
Hans Wennborg | 1790e6b | 2020-04-24 19:10:33 | [diff] [blame] | 11 | #include "base/check_op.h" |
dominickn | 0c9a506 | 2016-10-06 20:49:00 | [diff] [blame] | 12 | #include "base/metrics/histogram_macros.h" |
Hans Wennborg | 1790e6b | 2020-04-24 19:10:33 | [diff] [blame] | 13 | #include "base/notreached.h" |
Avi Drissman | 5f0fb8c | 2018-12-25 23:20:49 | [diff] [blame] | 14 | #include "base/stl_util.h" |
[email protected] | d883056 | 2013-06-10 22:01:54 | [diff] [blame] | 15 | #include "base/strings/string_util.h" |
Callum May | 48091d5 | 2019-07-03 20:09:12 | [diff] [blame] | 16 | #include "build/build_config.h" |
Yuta Hijikata | 235fc62b | 2020-12-08 03:48:32 | [diff] [blame] | 17 | #include "build/chromeos_buildflags.h" |
Todd Sahl | 1395963 | 2020-06-18 07:40:11 | [diff] [blame] | 18 | #include "chrome/browser/external_protocol/auto_launch_protocols_policy_handler.h" |
[email protected] | 14a000d | 2010-04-29 21:44:24 | [diff] [blame] | 19 | #include "chrome/browser/platform_util.h" |
[email protected] | 7f0a3efa | 2013-12-12 17:16:12 | [diff] [blame] | 20 | #include "chrome/browser/profiles/profile.h" |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 21 | #include "chrome/common/pref_names.h" |
Todd Sahl | 1395963 | 2020-06-18 07:40:11 | [diff] [blame] | 22 | #include "components/policy/core/browser/url_util.h" |
brettw | b1fc1b8 | 2016-02-02 00:19:08 | [diff] [blame] | 23 | #include "components/prefs/pref_registry_simple.h" |
| 24 | #include "components/prefs/pref_service.h" |
| 25 | #include "components/prefs/scoped_user_pref_update.h" |
Todd Sahl | 1395963 | 2020-06-18 07:40:11 | [diff] [blame] | 26 | #include "components/url_matcher/url_matcher.h" |
[email protected] | ed10dd1 | 2011-12-07 12:03:42 | [diff] [blame] | 27 | #include "content/public/browser/browser_thread.h" |
[email protected] | 7f0a3efa | 2013-12-12 17:16:12 | [diff] [blame] | 28 | #include "content/public/browser/web_contents.h" |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 29 | #include "net/base/escape.h" |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 30 | #include "services/network/public/cpp/is_potentially_trustworthy.h" |
Hans Wennborg | 3e67bab | 2021-04-08 11:34:31 | [diff] [blame] | 31 | #include "third_party/blink/public/mojom/devtools/console_message.mojom.h" |
[email protected] | 761fa470 | 2013-07-02 15:25:15 | [diff] [blame] | 32 | #include "url/gurl.h" |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 33 | #include "url/origin.h" |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 34 | |
Callum May | 48091d5 | 2019-07-03 20:09:12 | [diff] [blame] | 35 | #if !defined(OS_ANDROID) |
Yasmin | 85bccc56 | 2019-08-12 17:25:01 | [diff] [blame] | 36 | #include "chrome/browser/sharing/click_to_call/click_to_call_ui_controller.h" |
Richard Knoll | 2355d93 | 2019-07-19 16:57:29 | [diff] [blame] | 37 | #include "chrome/browser/sharing/click_to_call/click_to_call_utils.h" |
Lei Zhang | ddd2248 | 2021-05-11 08:07:32 | [diff] [blame] | 38 | #include "chrome/browser/ui/browser.h" |
Callum May | 48091d5 | 2019-07-03 20:09:12 | [diff] [blame] | 39 | #include "chrome/browser/ui/browser_finder.h" |
| 40 | #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 41 | #endif |
| 42 | |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 43 | namespace { |
| 44 | |
Eric Lawrence | 1c3cc83 | 2020-12-18 11:57:36 | [diff] [blame] | 45 | // 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 Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 50 | bool g_accept_requests = true; |
[email protected] | 86fad30d | 2014-07-29 21:39:27 | [diff] [blame] | 51 | |
Daniel Bratell | af82221 | 2018-03-13 11:15:06 | [diff] [blame] | 52 | ExternalProtocolHandler::Delegate* g_external_protocol_handler_delegate = |
| 53 | nullptr; |
John Abd-El-Malek | a67add8 | 2018-03-09 18:22:01 | [diff] [blame] | 54 | |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 55 | constexpr const char* kDeniedSchemes[] = { |
Alex Moshchuk | 902f802b | 2019-08-21 05:28:28 | [diff] [blame] | 56 | "afp", |
| 57 | "data", |
| 58 | "disk", |
| 59 | "disks", |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 60 | // 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 Moshchuk | 902f802b | 2019-08-21 05:28:28 | [diff] [blame] | 64 | "file", |
| 65 | "hcp", |
| 66 | "ie.http", |
| 67 | "javascript", |
| 68 | "ms-help", |
| 69 | "nntp", |
| 70 | "res", |
| 71 | "shell", |
| 72 | "vbscript", |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 73 | // 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 Moshchuk | 902f802b | 2019-08-21 05:28:28 | [diff] [blame] | 76 | "view-source", |
| 77 | "vnd.ms.radio", |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 78 | }; |
| 79 | |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 80 | constexpr const char* kAllowedSchemes[] = { |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 81 | "mailto", "news", "snews", |
| 82 | }; |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 83 | |
| 84 | // Functions enabling unit testing. Using a NULL delegate will use the default |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 85 | // behavior; if a delegate is provided it will be used instead. |
| 86 | scoped_refptr<shell_integration::DefaultProtocolClientWorker> CreateShellWorker( |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 87 | const std::string& protocol, |
| 88 | ExternalProtocolHandler::Delegate* delegate) { |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 89 | if (delegate) |
Will Cassella | c79ca56 | 2020-07-25 04:03:09 | [diff] [blame] | 90 | return delegate->CreateShellWorker(protocol); |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 91 | return base::MakeRefCounted<shell_integration::DefaultProtocolClientWorker>( |
Will Cassella | c79ca56 | 2020-07-25 04:03:09 | [diff] [blame] | 92 | protocol); |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 93 | } |
| 94 | |
| 95 | ExternalProtocolHandler::BlockState GetBlockStateWithDelegate( |
| 96 | const std::string& scheme, |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 97 | const url::Origin* initiating_origin, |
ramyasharma | 2c618e17 | 2017-02-06 05:41:34 | [diff] [blame] | 98 | ExternalProtocolHandler::Delegate* delegate, |
| 99 | Profile* profile) { |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 100 | if (delegate) |
| 101 | return delegate->GetBlockState(scheme, profile); |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 102 | return ExternalProtocolHandler::GetBlockState(scheme, initiating_origin, |
| 103 | profile); |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 104 | } |
| 105 | |
| 106 | void RunExternalProtocolDialogWithDelegate( |
| 107 | const GURL& url, |
Elly Fong-Jones | ea38f4ef | 2019-04-15 16:26:39 | [diff] [blame] | 108 | content::WebContents* web_contents, |
qinmin | 7573e42 | 2015-05-06 18:42:31 | [diff] [blame] | 109 | ui::PageTransition page_transition, |
| 110 | bool has_user_gesture, |
Anton Bikineev | 46bbb97 | 2021-05-15 17:53:53 | [diff] [blame] | 111 | const absl::optional<url::Origin>& initiating_origin, |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 112 | ExternalProtocolHandler::Delegate* delegate) { |
Elly Fong-Jones | ea38f4ef | 2019-04-15 16:26:39 | [diff] [blame] | 113 | DCHECK(web_contents); |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 114 | if (delegate) { |
Elly Fong-Jones | ea38f4ef | 2019-04-15 16:26:39 | [diff] [blame] | 115 | delegate->RunExternalProtocolDialog(url, web_contents, page_transition, |
Emily Stark | 13b66bdf | 2019-10-04 17:11:45 | [diff] [blame] | 116 | has_user_gesture, initiating_origin); |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 117 | return; |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 118 | } |
Eric Lawrence | 89eb579 | 2020-06-24 20:02:06 | [diff] [blame] | 119 | |
Avi Drissman | 2e458df | 2020-07-29 16:24:31 | [diff] [blame] | 120 | #if defined(OS_MAC) || defined(OS_WIN) |
Eric Lawrence | 89eb579 | 2020-06-24 20:02:06 | [diff] [blame] | 121 | // 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 Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 132 | ExternalProtocolHandler::RunExternalProtocolDialog( |
Emily Stark | 13b66bdf | 2019-10-04 17:11:45 | [diff] [blame] | 133 | url, web_contents, page_transition, has_user_gesture, initiating_origin); |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 134 | } |
| 135 | |
| 136 | void LaunchUrlWithoutSecurityCheckWithDelegate( |
| 137 | const GURL& url, |
Trent Apted | 7225e16 | 2018-05-10 08:33:35 | [diff] [blame] | 138 | content::WebContents* web_contents, |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 139 | ExternalProtocolHandler::Delegate* delegate) { |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 140 | if (delegate) { |
davidsac | 1fe2ac646 | 2016-12-20 01:27:41 | [diff] [blame] | 141 | delegate->LaunchUrlWithoutSecurityCheck(url, web_contents); |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 142 | return; |
[email protected] | 7f0a3efa | 2013-12-12 17:16:12 | [diff] [blame] | 143 | } |
Trent Apted | 7225e16 | 2018-05-10 08:33:35 | [diff] [blame] | 144 | |
| 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 Lawrence | 89eb579 | 2020-06-24 20:02:06 | [diff] [blame] | 150 | web_contents->GetMainFrame()->AddMessageToConsole( |
| 151 | blink::mojom::ConsoleMessageLevel::kInfo, |
| 152 | "Launched external handler for '" + url.possibly_invalid_spec() + "'."); |
| 153 | |
Trent Apted | 7225e16 | 2018-05-10 08:33:35 | [diff] [blame] | 154 | platform_util::OpenExternal( |
| 155 | Profile::FromBrowserContext(web_contents->GetBrowserContext()), url); |
Callum May | 48091d5 | 2019-07-03 20:09:12 | [diff] [blame] | 156 | |
Yuta Hijikata | 235fc62b | 2020-12-08 03:48:32 | [diff] [blame] | 157 | #if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH) |
Callum May | 48091d5 | 2019-07-03 20:09:12 | [diff] [blame] | 158 | // 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] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 170 | } |
| 171 | |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 172 | // 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. |
| 175 | void OnDefaultProtocolClientWorkerFinished( |
| 176 | const GURL& escaped_url, |
Jeremy Roman | 111cdbb | 2021-06-10 19:42:52 | [diff] [blame^] | 177 | content::WebContents::Getter web_contents_getter, |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 178 | bool prompt_user, |
| 179 | ui::PageTransition page_transition, |
| 180 | bool has_user_gesture, |
Anton Bikineev | 46bbb97 | 2021-05-15 17:53:53 | [diff] [blame] | 181 | const absl::optional<url::Origin>& initiating_origin, |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 182 | ExternalProtocolHandler::Delegate* delegate, |
pmonette | b920414 | 2016-03-08 20:02:44 | [diff] [blame] | 183 | shell_integration::DefaultWebClientState state) { |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 184 | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 185 | |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 186 | if (delegate) |
| 187 | delegate->FinishedProcessingCheck(); |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 188 | |
Jeremy Roman | 111cdbb | 2021-06-10 19:42:52 | [diff] [blame^] | 189 | content::WebContents* web_contents = web_contents_getter.Run(); |
Richard Knoll | 2355d93 | 2019-07-19 16:57:29 | [diff] [blame] | 190 | |
| 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 Knoll | 5e85eb12 | 2019-09-05 11:15:33 | [diff] [blame] | 196 | // On ChromeOS, Click to Call is integrated into the external protocol dialog. |
Yuta Hijikata | 235fc62b | 2020-12-08 03:48:32 | [diff] [blame] | 197 | #if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH) |
Himanshu Jaju | 84497314 | 2019-08-21 06:24:19 | [diff] [blame] | 198 | if (web_contents && ShouldOfferClickToCallForURL( |
| 199 | web_contents->GetBrowserContext(), escaped_url)) { |
Richard Knoll | 2355d93 | 2019-07-19 16:57:29 | [diff] [blame] | 200 | // 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 Knoll | 8bd8d19 | 2019-10-15 14:01:12 | [diff] [blame] | 202 | ClickToCallUiController::ShowDialog(web_contents, initiating_origin, |
| 203 | escaped_url, chrome_is_default_handler); |
Richard Knoll | 2355d93 | 2019-07-19 16:57:29 | [diff] [blame] | 204 | return; |
| 205 | } |
| 206 | #endif |
| 207 | |
| 208 | if (chrome_is_default_handler) { |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 209 | if (delegate) |
| 210 | delegate->BlockRequest(); |
| 211 | return; |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 212 | } |
| 213 | |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 214 | // 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-Jones | ea38f4ef | 2019-04-15 16:26:39 | [diff] [blame] | 217 | // Never prompt the user without a web_contents. |
| 218 | if (!web_contents) |
| 219 | return; |
| 220 | |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 221 | // 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 Stark | 13b66bdf | 2019-10-04 17:11:45 | [diff] [blame] | 224 | RunExternalProtocolDialogWithDelegate(escaped_url, web_contents, |
| 225 | page_transition, has_user_gesture, |
| 226 | initiating_origin, delegate); |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 227 | return; |
| 228 | } |
| 229 | |
Trent Apted | 7225e16 | 2018-05-10 08:33:35 | [diff] [blame] | 230 | LaunchUrlWithoutSecurityCheckWithDelegate(escaped_url, web_contents, |
| 231 | delegate); |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 232 | } |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 233 | |
Todd Sahl | 1395963 | 2020-06-18 07:40:11 | [diff] [blame] | 234 | bool IsSchemeOriginPairAllowedByPolicy(const std::string& scheme, |
| 235 | const url::Origin* initiating_origin, |
| 236 | PrefService* prefs) { |
Todd Sahl | c98ade6 | 2020-07-29 23:37:49 | [diff] [blame] | 237 | if (!initiating_origin) |
| 238 | return false; |
| 239 | |
Todd Sahl | 1395963 | 2020-06-18 07:40:11 | [diff] [blame] | 240 | 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] | 65c8114 | 2012-07-31 19:44:43 | [diff] [blame] | 270 | } // namespace |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 271 | |
ramyasharma | ef194a5 | 2017-02-08 03:30:22 | [diff] [blame] | 272 | const char ExternalProtocolHandler::kHandleStateMetric[] = |
| 273 | "BrowserDialogs.ExternalProtocol.HandleState"; |
| 274 | |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 275 | // static |
John Abd-El-Malek | a67add8 | 2018-03-09 18:22:01 | [diff] [blame] | 276 | void ExternalProtocolHandler::SetDelegateForTesting(Delegate* delegate) { |
Daniel Bratell | af82221 | 2018-03-13 11:15:06 | [diff] [blame] | 277 | g_external_protocol_handler_delegate = delegate; |
John Abd-El-Malek | a67add8 | 2018-03-09 18:22:01 | [diff] [blame] | 278 | } |
| 279 | |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 280 | bool ExternalProtocolHandler::MayRememberAllowDecisionsForThisOrigin( |
| 281 | const url::Origin* initiating_origin) { |
| 282 | return initiating_origin && |
| 283 | network::IsOriginPotentiallyTrustworthy(*initiating_origin); |
| 284 | } |
| 285 | |
| 286 | // static. |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 287 | ExternalProtocolHandler::BlockState ExternalProtocolHandler::GetBlockState( |
ramyasharma | 2c618e17 | 2017-02-06 05:41:34 | [diff] [blame] | 288 | const std::string& scheme, |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 289 | const url::Origin* initiating_origin, |
ramyasharma | 2c618e17 | 2017-02-06 05:41:34 | [diff] [blame] | 290 | Profile* profile) { |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 291 | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 292 | |
Eric Lawrence | d2a713a | 2019-07-15 19:14:05 | [diff] [blame] | 293 | // If we are being flooded with requests, block the request. |
[email protected] | 86fad30d | 2014-07-29 21:39:27 | [diff] [blame] | 294 | if (!g_accept_requests) |
[email protected] | e7eaedde | 2009-09-25 20:09:49 | [diff] [blame] | 295 | return BLOCK; |
| 296 | |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 297 | 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 Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 304 | // Always block the hard-coded denied schemes. |
Avi Drissman | 5f0fb8c | 2018-12-25 23:20:49 | [diff] [blame] | 305 | for (size_t i = 0; i < base::size(kDeniedSchemes); ++i) { |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 306 | if (kDeniedSchemes[i] == scheme) |
| 307 | return BLOCK; |
| 308 | } |
| 309 | |
| 310 | // Always allow the hard-coded allowed schemes. |
Avi Drissman | 5f0fb8c | 2018-12-25 23:20:49 | [diff] [blame] | 311 | for (size_t i = 0; i < base::size(kAllowedSchemes); ++i) { |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 312 | if (kAllowedSchemes[i] == scheme) |
| 313 | return DONT_BLOCK; |
| 314 | } |
| 315 | |
ramyasharma | 2c618e17 | 2017-02-06 05:41:34 | [diff] [blame] | 316 | PrefService* profile_prefs = profile->GetPrefs(); |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 317 | if (profile_prefs) { // May be NULL during testing. |
Todd Sahl | 1395963 | 2020-06-18 07:40:11 | [diff] [blame] | 318 | if (IsSchemeOriginPairAllowedByPolicy(scheme, initiating_origin, |
| 319 | profile_prefs)) { |
| 320 | return DONT_BLOCK; |
| 321 | } |
| 322 | |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 323 | 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 Bikineev | 46bbb97 | 2021-05-15 17:53:53 | [diff] [blame] | 332 | absl::optional<bool> allow = |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 333 | allowed_protocols_for_origin->FindBoolKey(scheme); |
| 334 | if (allow.has_value() && allow.value()) |
| 335 | return DONT_BLOCK; |
| 336 | } |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 337 | } |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 338 | } |
| 339 | |
| 340 | return UNKNOWN; |
| 341 | } |
| 342 | |
| 343 | // static |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 344 | // 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. |
| 347 | void ExternalProtocolHandler::SetBlockState( |
| 348 | const std::string& scheme, |
| 349 | const url::Origin& initiating_origin, |
| 350 | BlockState state, |
| 351 | Profile* profile) { |
Ben Wells | cafcc828 | 2017-09-21 06:57:57 | [diff] [blame] | 352 | // Setting the state to BLOCK is no longer supported through the UI. |
| 353 | DCHECK_NE(state, BLOCK); |
| 354 | |
[email protected] | 9829948 | 2009-10-06 19:33:07 | [diff] [blame] | 355 | // Set in the stored prefs. |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 356 | 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] | 9829948 | 2009-10-06 19:33:07 | [diff] [blame] | 380 | } |
Elly Fong-Jones | 0257b4cf | 2019-10-21 19:54:45 | [diff] [blame] | 381 | |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 382 | if (g_external_protocol_handler_delegate) { |
| 383 | g_external_protocol_handler_delegate->OnSetBlockState( |
| 384 | scheme, initiating_origin, state); |
| 385 | } |
[email protected] | 9829948 | 2009-10-06 19:33:07 | [diff] [blame] | 386 | } |
| 387 | |
| 388 | // static |
Emily Stark | 13b66bdf | 2019-10-04 17:11:45 | [diff] [blame] | 389 | void ExternalProtocolHandler::LaunchUrl( |
| 390 | const GURL& url, |
Jeremy Roman | 111cdbb | 2021-06-10 19:42:52 | [diff] [blame^] | 391 | content::WebContents::Getter web_contents_getter, |
Emily Stark | 13b66bdf | 2019-10-04 17:11:45 | [diff] [blame] | 392 | ui::PageTransition page_transition, |
| 393 | bool has_user_gesture, |
Anton Bikineev | 46bbb97 | 2021-05-15 17:53:53 | [diff] [blame] | 394 | const absl::optional<url::Origin>& initiating_origin) { |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 395 | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
[email protected] | e7eaedde | 2009-09-25 20:09:49 | [diff] [blame] | 396 | |
Eric Lawrence | 1c3cc83 | 2020-12-18 11:57:36 | [diff] [blame] | 397 | // 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.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 406 | // Escape the input scheme to be sure that the command does not |
| 407 | // have parameters unexpected by the external program. |
Matt Giuca | 36fd3c9 | 2017-11-27 01:12:35 | [diff] [blame] | 408 | // 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] | c83d7d1 | 2011-11-06 14:34:29 | [diff] [blame] | 412 | std::string escaped_url_string = net::EscapeExternalHandlerValue(url.spec()); |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 413 | GURL escaped_url(escaped_url_string); |
ramyasharma | 2c618e17 | 2017-02-06 05:41:34 | [diff] [blame] | 414 | |
Jeremy Roman | 111cdbb | 2021-06-10 19:42:52 | [diff] [blame^] | 415 | content::WebContents* web_contents = web_contents_getter.Run(); |
ramyasharma | 2c618e17 | 2017-02-06 05:41:34 | [diff] [blame] | 416 | Profile* profile = nullptr; |
| 417 | if (web_contents) // Maybe NULL during testing. |
| 418 | profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
Daniel Bratell | af82221 | 2018-03-13 11:15:06 | [diff] [blame] | 419 | BlockState block_state = GetBlockStateWithDelegate( |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 420 | escaped_url.scheme(), base::OptionalOrNullptr(initiating_origin), |
| 421 | g_external_protocol_handler_delegate, profile); |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 422 | if (block_state == BLOCK) { |
Eric Lawrence | 89eb579 | 2020-06-24 20:02:06 | [diff] [blame] | 423 | 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 Bratell | af82221 | 2018-03-13 11:15:06 | [diff] [blame] | 428 | if (g_external_protocol_handler_delegate) |
| 429 | g_external_protocol_handler_delegate->BlockRequest(); |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 430 | return; |
| 431 | } |
| 432 | |
[email protected] | 86fad30d | 2014-07-29 21:39:27 | [diff] [blame] | 433 | g_accept_requests = false; |
| 434 | |
Anton Bikineev | 46bbb97 | 2021-05-15 17:53:53 | [diff] [blame] | 435 | absl::optional<url::Origin> initiating_origin_or_precursor; |
Emily Stark | 7cfe6fc | 2020-02-19 05:19:01 | [diff] [blame] | 436 | 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] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 449 | // The worker creates tasks with references to itself and puts them into |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 450 | // message loops. |
Will Cassella | c79ca56 | 2020-07-25 04:03:09 | [diff] [blame] | 451 | shell_integration::DefaultWebClientWorkerCallback callback = base::BindOnce( |
Emily Stark | 7cfe6fc | 2020-02-19 05:19:01 | [diff] [blame] | 452 | &OnDefaultProtocolClientWorkerFinished, escaped_url, |
Jeremy Roman | 111cdbb | 2021-06-10 19:42:52 | [diff] [blame^] | 453 | std::move(web_contents_getter), block_state == UNKNOWN, page_transition, |
| 454 | has_user_gesture, initiating_origin_or_precursor, |
Emily Stark | 7cfe6fc | 2020-02-19 05:19:01 | [diff] [blame] | 455 | g_external_protocol_handler_delegate); |
[email protected] | 956eabb | 2011-09-23 05:04:38 | [diff] [blame] | 456 | |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 457 | // 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 |
pmonette | 586ab5b3 | 2016-03-07 19:50:37 | [diff] [blame] | 459 | // OnDefaultProtocolClientWorkerFinished(). |
Will Cassella | c79ca56 | 2020-07-25 04:03:09 | [diff] [blame] | 460 | CreateShellWorker(escaped_url.scheme(), g_external_protocol_handler_delegate) |
| 461 | ->StartCheckIsDefault(std::move(callback)); |
[email protected] | 10f57b9 | 2009-09-03 21:33:21 | [diff] [blame] | 462 | } |
| 463 | |
| 464 | // static |
[email protected] | 7f0a3efa | 2013-12-12 17:16:12 | [diff] [blame] | 465 | void ExternalProtocolHandler::LaunchUrlWithoutSecurityCheck( |
| 466 | const GURL& url, |
davidsac | 1fe2ac646 | 2016-12-20 01:27:41 | [diff] [blame] | 467 | content::WebContents* web_contents) { |
Julian Pastarmov | 38081dc | 2020-05-21 03:11:17 | [diff] [blame] | 468 | // 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 Apted | 7225e16 | 2018-05-10 08:33:35 | [diff] [blame] | 478 | LaunchUrlWithoutSecurityCheckWithDelegate( |
Julian Pastarmov | 38081dc | 2020-05-21 03:11:17 | [diff] [blame] | 479 | escaped_url, web_contents, g_external_protocol_handler_delegate); |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 480 | } |
| 481 | |
| 482 | // static |
[email protected] | 86fad30d | 2014-07-29 21:39:27 | [diff] [blame] | 483 | void ExternalProtocolHandler::PermitLaunchUrl() { |
Lei Zhang | 943bceea | 2017-09-26 04:46:53 | [diff] [blame] | 484 | DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| 485 | |
[email protected] | 86fad30d | 2014-07-29 21:39:27 | [diff] [blame] | 486 | g_accept_requests = true; |
| 487 | } |
dominickn | 0c9a506 | 2016-10-06 20:49:00 | [diff] [blame] | 488 | |
| 489 | // static |
ramyasharma | ef194a5 | 2017-02-08 03:30:22 | [diff] [blame] | 490 | void 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 Wells | f227110 | 2017-09-06 03:07:56 | [diff] [blame] | 498 | handle_state = |
| 499 | checkbox_selected ? CHECKED_DONT_LAUNCH_DEPRECATED : DONT_LAUNCH; |
ramyasharma | ef194a5 | 2017-02-08 03:30:22 | [diff] [blame] | 500 | break; |
| 501 | case UNKNOWN: |
| 502 | NOTREACHED(); |
| 503 | return; |
| 504 | } |
Ben Wells | f227110 | 2017-09-06 03:07:56 | [diff] [blame] | 505 | DCHECK_NE(CHECKED_DONT_LAUNCH_DEPRECATED, handle_state); |
ramyasharma | ef194a5 | 2017-02-08 03:30:22 | [diff] [blame] | 506 | UMA_HISTOGRAM_ENUMERATION(kHandleStateMetric, handle_state, |
| 507 | HANDLE_STATE_LAST); |
dominickn | 0c9a506 | 2016-10-06 20:49:00 | [diff] [blame] | 508 | } |
| 509 | |
| 510 | // static |
| 511 | void ExternalProtocolHandler::RegisterPrefs(PrefRegistrySimple* registry) { |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 512 | registry->RegisterDictionaryPref( |
| 513 | prefs::kProtocolHandlerPerOriginAllowedProtocols); |
Todd Sahl | 1395963 | 2020-06-18 07:40:11 | [diff] [blame] | 514 | |
| 515 | registry->RegisterListPref(prefs::kAutoLaunchProtocolsFromOrigins); |
dominickn | 0c9a506 | 2016-10-06 20:49:00 | [diff] [blame] | 516 | } |
ramyasharma | 561a9cd | 2017-02-19 07:49:25 | [diff] [blame] | 517 | |
| 518 | // static |
| 519 | void ExternalProtocolHandler::ClearData(Profile* profile) { |
| 520 | PrefService* prefs = profile->GetPrefs(); |
Todd Sahl | 057d9c6 | 2020-04-17 18:41:14 | [diff] [blame] | 521 | prefs->ClearPref(prefs::kProtocolHandlerPerOriginAllowedProtocols); |
ramyasharma | 561a9cd | 2017-02-19 07:49:25 | [diff] [blame] | 522 | } |