blob: 4ecedcbe4164312acd3aeb8e8267b605ac335c03 [file] [log] [blame]
Avi Drissman8ba1bad2022-09-13 19:22:361// Copyright 2012 The Chromium Authors
[email protected]4360ae72012-10-09 22:10:462// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]62885ab2013-01-23 03:55:165#include "components/navigation_interception/intercept_navigation_delegate.h"
[email protected]4360ae72012-10-09 22:10:466
Gyuyoung Kimcb7965e2018-01-25 00:39:017#include <memory>
8
[email protected]4360ae72012-10-09 22:10:469#include "base/android/jni_android.h"
10#include "base/android/jni_string.h"
Avi Drissman12be0312023-01-11 09:16:0911#include "base/functional/bind.h"
12#include "base/functional/callback.h"
Ryan Hamilton7f3bd3d2022-04-23 00:07:3913#include "base/strings/escape.h"
[email protected]4360ae72012-10-09 22:10:4614#include "content/public/browser/browser_thread.h"
David Bokan2a48f7bb2021-07-09 13:21:3615#include "content/public/browser/navigation_handle.h"
clamy40c9e142015-09-29 11:18:4716#include "content/public/browser/navigation_throttle.h"
jaekyun038903192015-03-31 14:15:5917#include "content/public/browser/render_frame_host.h"
[email protected]4360ae72012-10-09 22:10:4618#include "content/public/browser/render_view_host.h"
19#include "content/public/browser/web_contents.h"
Michael Thiessen41821c982023-08-14 21:45:5420#include "content/public/common/page_visibility_state.h"
Michael Thiessen1a49e4d52022-12-02 21:54:4021#include "mojo/public/cpp/bindings/self_owned_receiver.h"
22#include "net/http/http_status_code.h"
23#include "net/http/http_util.h"
24#include "net/url_request/redirect_info.h"
25#include "net/url_request/redirect_util.h"
26#include "services/network/public/cpp/parsed_headers.h"
27#include "services/network/public/cpp/resource_request.h"
28#include "services/network/public/cpp/single_request_url_loader_factory.h"
29#include "services/network/public/mojom/url_response_head.mojom.h"
Michael Thiessenca245a382022-02-21 16:11:1730#include "url/android/gurl_android.h"
[email protected]e3b599e2013-07-05 07:15:1731#include "url/gurl.h"
[email protected]4360ae72012-10-09 22:10:4632
Andrew Grieveecb885bb2024-05-29 18:14:1933// Must come after all headers that specialize FromJniType() / ToJniType().
34#include "components/navigation_interception/jni_headers/InterceptNavigationDelegate_jni.h"
35
[email protected]4360ae72012-10-09 22:10:4636using base::android::ConvertUTF8ToJavaString;
37using base::android::ScopedJavaLocalRef;
38using content::BrowserThread;
39using content::RenderViewHost;
40using content::WebContents;
Michael Thiessenca245a382022-02-21 16:11:1741using ui::PageTransition;
[email protected]4360ae72012-10-09 22:10:4642
[email protected]8812e3d02013-05-22 12:38:5343namespace navigation_interception {
[email protected]4360ae72012-10-09 22:10:4644
45namespace {
46
thestig3b6a2f12015-09-25 08:17:2047const void* const kInterceptNavigationDelegateUserDataKey =
[email protected]4360ae72012-10-09 22:10:4648 &kInterceptNavigationDelegateUserDataKey;
49
Michael Thiessen8c2b10e2025-01-23 17:54:1950void AllowNavigationToProceed(
51 content::NavigationHandle* navigation_handle,
52 bool should_run_async,
53 InterceptNavigationThrottle::ResultCallback result_callback) {
54 std::move(result_callback).Run(false);
[email protected]4360ae72012-10-09 22:10:4655}
56
Michael Thiessen1a49e4d52022-12-02 21:54:4057class RedirectURLLoader : public network::mojom::URLLoader {
58 public:
Michael Thiessen7b531172023-01-28 05:25:5959 RedirectURLLoader(const network::ResourceRequest& resource_request,
Michael Thiessen1a49e4d52022-12-02 21:54:4060 mojo::PendingRemote<network::mojom::URLLoaderClient> client)
Michael Thiessen7b531172023-01-28 05:25:5961 : client_(std::move(client)), request_(resource_request) {}
62
63 void DoRedirect(std::unique_ptr<GURL> url) {
Michael Thiessen1a49e4d52022-12-02 21:54:4064 net::HttpStatusCode response_code = net::HTTP_TEMPORARY_REDIRECT;
65 auto response_head = network::mojom::URLResponseHead::New();
66 response_head->encoded_data_length = 0;
67 response_head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
68 net::HttpUtil::AssembleRawHeaders("HTTP/1.1 307 Temporary Redirect"));
69
70 // Avoid a round-trip to the network service by pre-parsing headers.
71 // This doesn't violate: `docs/security/rule-of-2.md`, because the input is
72 // trusted, before appending the Location: <url> header.
73 response_head->parsed_headers =
Michael Thiessen7b531172023-01-28 05:25:5974 network::PopulateParsedHeaders(response_head->headers.get(), *url);
Michael Thiessen1a49e4d52022-12-02 21:54:4075
Michael Thiessen7b531172023-01-28 05:25:5976 response_head->headers->AddHeader("Location", url->spec());
Michael Thiessen1a49e4d52022-12-02 21:54:4077
78 auto first_party_url_policy =
Michael Thiessen7b531172023-01-28 05:25:5979 request_.update_first_party_url_on_redirect
Michael Thiessen1a49e4d52022-12-02 21:54:4080 ? net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT
81 : net::RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_URL;
82
83 client_->OnReceiveRedirect(
84 net::RedirectInfo::ComputeRedirectInfo(
Michael Thiessen7b531172023-01-28 05:25:5985 request_.method, request_.url, request_.site_for_cookies,
86 first_party_url_policy, request_.referrer_policy,
Arthur Sonzognic571efb2024-01-26 20:26:1887 request_.referrer.spec(), response_code, *url, std::nullopt,
Michael Thiessen1a49e4d52022-12-02 21:54:4088 /*insecure_scheme_was_upgraded=*/false,
89 /*copy_fragment=*/false),
90 std::move(response_head));
91 }
92
Michael Thiessen7b531172023-01-28 05:25:5993 void OnNonRedirectAsyncAction() {
94 client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED));
95 }
96
Michael Thiessen1a49e4d52022-12-02 21:54:4097 RedirectURLLoader(const RedirectURLLoader&) = delete;
98 RedirectURLLoader& operator=(const RedirectURLLoader&) = delete;
99
100 ~RedirectURLLoader() override = default;
101
102 private:
103 // network::mojom::URLLoader overrides:
104 void FollowRedirect(
105 const std::vector<std::string>& removed_headers,
106 const net::HttpRequestHeaders& modified_headers,
107 const net::HttpRequestHeaders& modified_cors_exempt_headers,
Arthur Sonzognic571efb2024-01-26 20:26:18108 const std::optional<GURL>& new_url) override {
Peter Boström77d21352024-11-13 22:26:11109 NOTREACHED();
Michael Thiessen1a49e4d52022-12-02 21:54:40110 }
111 void SetPriority(net::RequestPriority priority,
112 int intra_priority_value) override {}
Michael Thiessen1a49e4d52022-12-02 21:54:40113
114 mojo::Remote<network::mojom::URLLoaderClient> client_;
Michael Thiessen7b531172023-01-28 05:25:59115 network::ResourceRequest request_;
Michael Thiessen1a49e4d52022-12-02 21:54:40116};
117
[email protected]a8e69a742013-10-15 10:58:55118} // namespace
[email protected]4360ae72012-10-09 22:10:46119
120// static
121void InterceptNavigationDelegate::Associate(
122 WebContents* web_contents,
dcheng84c358e2016-04-26 07:05:53123 std::unique_ptr<InterceptNavigationDelegate> delegate) {
Michael Thiessen8595d4742025-03-11 17:06:55124 if (!delegate) {
125 web_contents->RemoveUserData(kInterceptNavigationDelegateUserDataKey);
126 } else {
127 web_contents->SetUserData(kInterceptNavigationDelegateUserDataKey,
128 std::move(delegate));
129 }
[email protected]4360ae72012-10-09 22:10:46130}
131
132// static
133InterceptNavigationDelegate* InterceptNavigationDelegate::Get(
134 WebContents* web_contents) {
dtrainor037df0d2014-10-08 18:05:24135 return static_cast<InterceptNavigationDelegate*>(
[email protected]4360ae72012-10-09 22:10:46136 web_contents->GetUserData(kInterceptNavigationDelegateUserDataKey));
137}
138
139// static
Takashi Toyoshima3e345112025-05-09 00:27:23140void InterceptNavigationDelegate::MaybeCreateAndAdd(
141 content::NavigationThrottleRegistry& registry,
Charlie Harrison3286ab72019-02-13 20:13:30142 navigation_interception::SynchronyMode mode) {
David Bokan2a48f7bb2021-07-09 13:21:36143 // Navigations in a subframe or non-primary frame tree should not be
144 // intercepted. As examples of a non-primary frame tree, a navigation
145 // occurring in a Portal element or an unactivated prerendering page should
146 // not launch an app.
147 // TODO(bokan): This is a bit of a stopgap approach since we won't run
148 // throttles again when the prerender is activated which means links that are
149 // prerendered will avoid launching an app intent that a regular navigation
150 // would have. Longer term we'll want prerender activation to check for app
151 // intents, or have this throttle cancel the prerender if an intent would
152 // have been launched (without launching the intent). It's also not clear
153 // what the right behavior for <portal> elements is.
154 // https://crbug.com/1227659.
Takashi Toyoshima3e345112025-05-09 00:27:23155 if (!registry.GetNavigationHandle().IsInPrimaryMainFrame()) {
156 return;
Michael Thiessen8c2b10e2025-01-23 17:54:19157 }
158
Takashi Toyoshima3e345112025-05-09 00:27:23159 InterceptNavigationDelegate* intercept_navigation_delegate =
160 InterceptNavigationDelegate::Get(
161 registry.GetNavigationHandle().GetWebContents());
162
163 if (!intercept_navigation_delegate) {
164 registry.AddThrottle(std::make_unique<InterceptNavigationThrottle>(
165 registry, base::BindRepeating(&AllowNavigationToProceed), mode,
166 base::DoNothing()));
167 } else {
168 registry.AddThrottle(std::make_unique<InterceptNavigationThrottle>(
169 registry,
Michael Thiessen8c2b10e2025-01-23 17:54:19170 base::BindRepeating(&InterceptNavigationDelegate::ShouldIgnoreNavigation,
171 base::Unretained(intercept_navigation_delegate)),
172 mode,
173 base::BindRepeating(
Michael Thiessen729a4672025-01-29 20:38:35174 &InterceptNavigationDelegate::RequestFinishPendingShouldIgnoreCheck,
Takashi Toyoshima3e345112025-05-09 00:27:23175 base::Unretained(intercept_navigation_delegate))));
176 }
[email protected]4360ae72012-10-09 22:10:46177}
178
179InterceptNavigationDelegate::InterceptNavigationDelegate(
Colin Blundell4695e8142020-03-16 11:13:12180 JNIEnv* env,
Andrew Grievedf3420a4e2024-08-30 21:02:48181 const jni_zero::JavaRef<jobject>& jdelegate,
Colin Blundell4695e8142020-03-16 11:13:12182 bool escape_external_handler_value)
183 : weak_jdelegate_(env, jdelegate),
184 escape_external_handler_value_(escape_external_handler_value) {}
[email protected]4360ae72012-10-09 22:10:46185
Michael Thiessen1a49e4d52022-12-02 21:54:40186InterceptNavigationDelegate::~InterceptNavigationDelegate() = default;
[email protected]4360ae72012-10-09 22:10:46187
Michael Thiessen8c2b10e2025-01-23 17:54:19188void InterceptNavigationDelegate::ShouldIgnoreNavigation(
189 content::NavigationHandle* navigation_handle,
190 bool should_run_async,
191 InterceptNavigationThrottle::ResultCallback result_callback) {
192 DCHECK_CURRENTLY_ON(BrowserThread::UI);
193 // Avoid having two outstanding checks at once for simplicity.
194 if (should_ignore_result_callback_) {
Michael Thiessenec04a4a2025-02-24 16:01:54195 std::move(result_callback).Run(false);
196 return;
Michael Thiessen8c2b10e2025-01-23 17:54:19197 }
Michael Thiessenca245a382022-02-21 16:11:17198 GURL escaped_url = escape_external_handler_value_
Ryan Hamilton7f3bd3d2022-04-23 00:07:39199 ? GURL(base::EscapeExternalHandlerValue(
Michael Thiessenca245a382022-02-21 16:11:17200 navigation_handle->GetURL().spec()))
201 : navigation_handle->GetURL();
Colin Blundell4695e8142020-03-16 11:13:12202
Michael Thiessen8c2b10e2025-01-23 17:54:19203 if (!escaped_url.is_valid()) {
204 std::move(result_callback).Run(false);
205 return;
206 }
[email protected]4360ae72012-10-09 22:10:46207
208 JNIEnv* env = base::android::AttachCurrentThread();
209 ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env);
210
Michael Thiessen8c2b10e2025-01-23 17:54:19211 if (jdelegate.is_null()) {
212 std::move(result_callback).Run(false);
213 return;
214 }
[email protected]4360ae72012-10-09 22:10:46215
Michael Thiessen41821c982023-08-14 21:45:54216 bool hidden_cross_frame = false;
Michael Thiessen884e6172023-02-13 17:50:31217 // Only main frame navigations use this path, so we only need to check if the
218 // navigation is cross-frame to the main frame.
Michael Thiessen41821c982023-08-14 21:45:54219 if (navigation_handle->GetInitiatorFrameToken() &&
220 navigation_handle->GetInitiatorFrameToken() !=
221 navigation_handle->GetWebContents()
222 ->GetPrimaryMainFrame()
223 ->GetFrameToken()) {
224 content::RenderFrameHost* initiator_frame_host =
225 content::RenderFrameHost::FromFrameToken(
Dave Tapuskae9b7c0f72023-11-06 16:38:01226 content::GlobalRenderFrameHostToken(
227 navigation_handle->GetInitiatorProcessId(),
228 navigation_handle->GetInitiatorFrameToken().value()));
Michael Thiessen41821c982023-08-14 21:45:54229 // If the initiator is gone treat it as not visible.
230 hidden_cross_frame =
231 !initiator_frame_host || initiator_frame_host->GetVisibilityState() !=
232 content::PageVisibilityState::kVisible;
233 }
Michael Thiessen884e6172023-02-13 17:50:31234
Michael Thiessen8fd3520b2023-03-10 20:55:47235 // We don't care which sandbox flags are present, only that any sandbox flags
236 // are present, as we don't support persisting sandbox flags through fallback
237 // URL navigation.
238 bool is_sandboxed = navigation_handle->SandboxFlagsInherited() !=
Michael Thiessend80a1af12023-08-21 17:17:35239 network::mojom::WebSandboxFlags::kNone ||
240 navigation_handle->SandboxFlagsInitiator() !=
241 network::mojom::WebSandboxFlags::kNone;
Arthur Sonzognidc8508ab2023-08-17 08:42:42242
Michael Thiessen8c2b10e2025-01-23 17:54:19243 should_ignore_result_callback_ = std::move(result_callback);
244 Java_InterceptNavigationDelegate_callShouldIgnoreNavigation(
Michael Thiessenca245a382022-02-21 16:11:17245 env, jdelegate, navigation_handle->GetJavaNavigationHandle(),
Michael Thiessen41821c982023-08-14 21:45:54246 url::GURLAndroid::FromNativeGURL(env, escaped_url), hidden_cross_frame,
Michael Thiessen8c2b10e2025-01-23 17:54:19247 is_sandboxed, should_run_async);
248}
249
250void InterceptNavigationDelegate::OnShouldIgnoreNavigationResult(
251 bool should_ignore) {
252 std::move(should_ignore_result_callback_).Run(should_ignore);
253}
254
Michael Thiessen729a4672025-01-29 20:38:35255void InterceptNavigationDelegate::RequestFinishPendingShouldIgnoreCheck() {
Michael Thiessen8c2b10e2025-01-23 17:54:19256 JNIEnv* env = base::android::AttachCurrentThread();
257 ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env);
258
259 if (jdelegate.is_null()) {
260 OnShouldIgnoreNavigationResult(false);
261 return;
262 }
Michael Thiessen729a4672025-01-29 20:38:35263 Java_InterceptNavigationDelegate_requestFinishPendingShouldIgnoreCheck(
264 env, jdelegate);
Michael Thiessenca245a382022-02-21 16:11:17265}
266
Michael Thiessen7cb129e2022-11-08 17:24:51267void InterceptNavigationDelegate::HandleSubframeExternalProtocol(
Michael Thiessenca245a382022-02-21 16:11:17268 const GURL& url,
269 ui::PageTransition page_transition,
270 bool has_user_gesture,
Arthur Sonzognic571efb2024-01-26 20:26:18271 const std::optional<url::Origin>& initiating_origin,
Michael Thiessen1a49e4d52022-12-02 21:54:40272 mojo::PendingRemote<network::mojom::URLLoaderFactory>* out_factory) {
Michael Thiessen7b531172023-01-28 05:25:59273 // If there's a pending async subframe action, don't consider external
274 // navigation for the current navigation.
275 if (subframe_redirect_url_ || url_loader_) {
276 return;
277 }
278
Michael Thiessenca245a382022-02-21 16:11:17279 GURL escaped_url = escape_external_handler_value_
Ryan Hamilton7f3bd3d2022-04-23 00:07:39280 ? GURL(base::EscapeExternalHandlerValue(url.spec()))
Michael Thiessenca245a382022-02-21 16:11:17281 : url;
Takashi Toyoshima3e345112025-05-09 00:27:23282 if (!escaped_url.is_valid()) {
Michael Thiessenca245a382022-02-21 16:11:17283 return;
Takashi Toyoshima3e345112025-05-09 00:27:23284 }
Michael Thiessenca245a382022-02-21 16:11:17285
286 JNIEnv* env = base::android::AttachCurrentThread();
287 ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env);
288
Takashi Toyoshima3e345112025-05-09 00:27:23289 if (jdelegate.is_null()) {
Michael Thiessenca245a382022-02-21 16:11:17290 return;
Takashi Toyoshima3e345112025-05-09 00:27:23291 }
Michael Thiessen1a49e4d52022-12-02 21:54:40292 ScopedJavaLocalRef<jobject> j_gurl =
293 Java_InterceptNavigationDelegate_handleSubframeExternalProtocol(
294 env, jdelegate, url::GURLAndroid::FromNativeGURL(env, escaped_url),
295 page_transition, has_user_gesture,
Andrew Grieve6a37bcd2024-09-26 21:47:44296 initiating_origin ? initiating_origin->ToJavaObject(env) : nullptr);
Takashi Toyoshima3e345112025-05-09 00:27:23297 if (j_gurl.is_null()) {
Michael Thiessen1a49e4d52022-12-02 21:54:40298 return;
Takashi Toyoshima3e345112025-05-09 00:27:23299 }
Andrew Grieve7912fd32024-04-15 17:23:05300 subframe_redirect_url_ =
301 std::make_unique<GURL>(url::GURLAndroid::ToNativeGURL(env, j_gurl));
Michael Thiessen1a49e4d52022-12-02 21:54:40302
303 mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver =
304 out_factory->InitWithNewPipeAndPassReceiver();
305 scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
306 base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
Michael Thiessen7b531172023-01-28 05:25:59307 base::BindOnce(&InterceptNavigationDelegate::LoaderCallback,
308 weak_ptr_factory_.GetWeakPtr()));
Michael Thiessen1a49e4d52022-12-02 21:54:40309 loader_factory->Clone(std::move(receiver));
[email protected]4360ae72012-10-09 22:10:46310}
311
Michael Thiessen7b531172023-01-28 05:25:59312void InterceptNavigationDelegate::LoaderCallback(
313 const network::ResourceRequest& resource_request,
314 mojo::PendingReceiver<network::mojom::URLLoader> pending_receiver,
315 mojo::PendingRemote<network::mojom::URLLoaderClient> pending_client) {
316 url_loader_ = mojo::MakeSelfOwnedReceiver(
317 std::make_unique<RedirectURLLoader>(resource_request,
318 std::move(pending_client)),
319 std::move(pending_receiver));
320 MaybeHandleSubframeAction();
321}
322
323void InterceptNavigationDelegate::MaybeHandleSubframeAction() {
324 // An empty subframe_redirect_url_ implies a pending async action.
325 if (!url_loader_ ||
326 (subframe_redirect_url_ && subframe_redirect_url_->is_empty())) {
327 return;
328 }
329 RedirectURLLoader* loader =
330 static_cast<RedirectURLLoader*>(url_loader_->impl());
331 if (!subframe_redirect_url_) {
332 loader->OnNonRedirectAsyncAction();
333 } else {
334 loader->DoRedirect(std::move(subframe_redirect_url_));
335 }
336 url_loader_.reset();
337}
338
Michael Thiessen332dadb62022-07-13 14:44:07339void InterceptNavigationDelegate::OnResourceRequestWithGesture() {
340 JNIEnv* env = base::android::AttachCurrentThread();
341 ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env);
Takashi Toyoshima3e345112025-05-09 00:27:23342 if (jdelegate.is_null()) {
Michael Thiessen332dadb62022-07-13 14:44:07343 return;
Takashi Toyoshima3e345112025-05-09 00:27:23344 }
Michael Thiessen332dadb62022-07-13 14:44:07345 Java_InterceptNavigationDelegate_onResourceRequestWithGesture(env, jdelegate);
Michael Thiessene5663522022-05-25 21:23:28346}
347
Michael Thiessen7b531172023-01-28 05:25:59348void InterceptNavigationDelegate::OnSubframeAsyncActionTaken(
349 JNIEnv* env,
350 const base::android::JavaParamRef<jobject>& j_gurl) {
351 // subframe_redirect_url_ no longer empty indicates the async action has been
352 // taken.
353 subframe_redirect_url_ =
Andrew Grieve7912fd32024-04-15 17:23:05354 j_gurl.is_null()
355 ? nullptr
356 : std::make_unique<GURL>(url::GURLAndroid::ToNativeGURL(env, j_gurl));
Michael Thiessen7b531172023-01-28 05:25:59357 MaybeHandleSubframeAction();
358}
359
Michael Thiessen8c2b10e2025-01-23 17:54:19360static void JNI_InterceptNavigationDelegate_OnShouldIgnoreNavigationResult(
361 JNIEnv* env,
362 const base::android::JavaParamRef<jobject>& jweb_contents,
363 jboolean should_ignore) {
364 content::WebContents* web_contents =
365 content::WebContents::FromJavaWebContents(jweb_contents);
366 if (!web_contents) {
367 return;
368 }
369 navigation_interception::InterceptNavigationDelegate* delegate =
370 navigation_interception::InterceptNavigationDelegate::Get(web_contents);
371 CHECK(delegate);
372 delegate->OnShouldIgnoreNavigationResult(should_ignore);
373}
374
[email protected]8812e3d02013-05-22 12:38:53375} // namespace navigation_interception