[go: nahoru, domu]

blob: 2d863c31b46537c0deb9214dfbf5580e608d1d6e [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/navigation_interception/intercept_navigation_delegate.h"
#include <memory>
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/escape.h"
#include "components/navigation_interception/jni_headers/InterceptNavigationDelegate_jni.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_visibility_state.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/redirect_util.h"
#include "services/network/public/cpp/parsed_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/single_request_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/android/gurl_android.h"
#include "url/gurl.h"
using base::android::ConvertUTF8ToJavaString;
using base::android::ScopedJavaLocalRef;
using content::BrowserThread;
using content::RenderViewHost;
using content::WebContents;
using ui::PageTransition;
namespace navigation_interception {
namespace {
const void* const kInterceptNavigationDelegateUserDataKey =
&kInterceptNavigationDelegateUserDataKey;
bool CheckIfShouldIgnoreNavigationOnUIThread(
content::NavigationHandle* navigation_handle) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(navigation_handle);
InterceptNavigationDelegate* intercept_navigation_delegate =
InterceptNavigationDelegate::Get(navigation_handle->GetWebContents());
if (!intercept_navigation_delegate)
return false;
return intercept_navigation_delegate->ShouldIgnoreNavigation(
navigation_handle);
}
class RedirectURLLoader : public network::mojom::URLLoader {
public:
RedirectURLLoader(const network::ResourceRequest& resource_request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client)
: client_(std::move(client)), request_(resource_request) {}
void DoRedirect(std::unique_ptr<GURL> url) {
net::HttpStatusCode response_code = net::HTTP_TEMPORARY_REDIRECT;
auto response_head = network::mojom::URLResponseHead::New();
response_head->encoded_data_length = 0;
response_head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
net::HttpUtil::AssembleRawHeaders("HTTP/1.1 307 Temporary Redirect"));
// Avoid a round-trip to the network service by pre-parsing headers.
// This doesn't violate: `docs/security/rule-of-2.md`, because the input is
// trusted, before appending the Location: <url> header.
response_head->parsed_headers =
network::PopulateParsedHeaders(response_head->headers.get(), *url);
response_head->headers->AddHeader("Location", url->spec());
auto first_party_url_policy =
request_.update_first_party_url_on_redirect
? net::RedirectInfo::FirstPartyURLPolicy::UPDATE_URL_ON_REDIRECT
: net::RedirectInfo::FirstPartyURLPolicy::NEVER_CHANGE_URL;
client_->OnReceiveRedirect(
net::RedirectInfo::ComputeRedirectInfo(
request_.method, request_.url, request_.site_for_cookies,
first_party_url_policy, request_.referrer_policy,
request_.referrer.spec(), response_code, *url, std::nullopt,
/*insecure_scheme_was_upgraded=*/false,
/*copy_fragment=*/false),
std::move(response_head));
}
void OnNonRedirectAsyncAction() {
client_->OnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED));
}
RedirectURLLoader(const RedirectURLLoader&) = delete;
RedirectURLLoader& operator=(const RedirectURLLoader&) = delete;
~RedirectURLLoader() override = default;
private:
// network::mojom::URLLoader overrides:
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const std::optional<GURL>& new_url) override {
NOTREACHED();
}
void SetPriority(net::RequestPriority priority,
int intra_priority_value) override {}
void PauseReadingBodyFromNet() override {}
void ResumeReadingBodyFromNet() override {}
mojo::Remote<network::mojom::URLLoaderClient> client_;
network::ResourceRequest request_;
};
} // namespace
// static
void InterceptNavigationDelegate::Associate(
WebContents* web_contents,
std::unique_ptr<InterceptNavigationDelegate> delegate) {
web_contents->SetUserData(kInterceptNavigationDelegateUserDataKey,
std::move(delegate));
}
// static
InterceptNavigationDelegate* InterceptNavigationDelegate::Get(
WebContents* web_contents) {
return static_cast<InterceptNavigationDelegate*>(
web_contents->GetUserData(kInterceptNavigationDelegateUserDataKey));
}
// static
std::unique_ptr<content::NavigationThrottle>
InterceptNavigationDelegate::MaybeCreateThrottleFor(
content::NavigationHandle* handle,
navigation_interception::SynchronyMode mode) {
// Navigations in a subframe or non-primary frame tree should not be
// intercepted. As examples of a non-primary frame tree, a navigation
// occurring in a Portal element or an unactivated prerendering page should
// not launch an app.
// TODO(bokan): This is a bit of a stopgap approach since we won't run
// throttles again when the prerender is activated which means links that are
// prerendered will avoid launching an app intent that a regular navigation
// would have. Longer term we'll want prerender activation to check for app
// intents, or have this throttle cancel the prerender if an intent would
// have been launched (without launching the intent). It's also not clear
// what the right behavior for <portal> elements is.
// https://crbug.com/1227659.
if (!handle->IsInPrimaryMainFrame())
return nullptr;
return std::make_unique<InterceptNavigationThrottle>(
handle, base::BindRepeating(&CheckIfShouldIgnoreNavigationOnUIThread),
mode);
}
InterceptNavigationDelegate::InterceptNavigationDelegate(
JNIEnv* env,
jobject jdelegate,
bool escape_external_handler_value)
: weak_jdelegate_(env, jdelegate),
escape_external_handler_value_(escape_external_handler_value) {}
InterceptNavigationDelegate::~InterceptNavigationDelegate() = default;
bool InterceptNavigationDelegate::ShouldIgnoreNavigation(
content::NavigationHandle* navigation_handle) {
GURL escaped_url = escape_external_handler_value_
? GURL(base::EscapeExternalHandlerValue(
navigation_handle->GetURL().spec()))
: navigation_handle->GetURL();
if (!escaped_url.is_valid())
return false;
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env);
if (jdelegate.is_null())
return false;
bool hidden_cross_frame = false;
// Only main frame navigations use this path, so we only need to check if the
// navigation is cross-frame to the main frame.
if (navigation_handle->GetInitiatorFrameToken() &&
navigation_handle->GetInitiatorFrameToken() !=
navigation_handle->GetWebContents()
->GetPrimaryMainFrame()
->GetFrameToken()) {
content::RenderFrameHost* initiator_frame_host =
content::RenderFrameHost::FromFrameToken(
content::GlobalRenderFrameHostToken(
navigation_handle->GetInitiatorProcessId(),
navigation_handle->GetInitiatorFrameToken().value()));
// If the initiator is gone treat it as not visible.
hidden_cross_frame =
!initiator_frame_host || initiator_frame_host->GetVisibilityState() !=
content::PageVisibilityState::kVisible;
}
// We don't care which sandbox flags are present, only that any sandbox flags
// are present, as we don't support persisting sandbox flags through fallback
// URL navigation.
bool is_sandboxed = navigation_handle->SandboxFlagsInherited() !=
network::mojom::WebSandboxFlags::kNone ||
navigation_handle->SandboxFlagsInitiator() !=
network::mojom::WebSandboxFlags::kNone;
return Java_InterceptNavigationDelegate_shouldIgnoreNavigation(
env, jdelegate, navigation_handle->GetJavaNavigationHandle(),
url::GURLAndroid::FromNativeGURL(env, escaped_url), hidden_cross_frame,
is_sandboxed);
}
void InterceptNavigationDelegate::HandleSubframeExternalProtocol(
const GURL& url,
ui::PageTransition page_transition,
bool has_user_gesture,
const std::optional<url::Origin>& initiating_origin,
mojo::PendingRemote<network::mojom::URLLoaderFactory>* out_factory) {
// If there's a pending async subframe action, don't consider external
// navigation for the current navigation.
if (subframe_redirect_url_ || url_loader_) {
return;
}
GURL escaped_url = escape_external_handler_value_
? GURL(base::EscapeExternalHandlerValue(url.spec()))
: url;
if (!escaped_url.is_valid())
return;
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env);
if (jdelegate.is_null())
return;
ScopedJavaLocalRef<jobject> j_gurl =
Java_InterceptNavigationDelegate_handleSubframeExternalProtocol(
env, jdelegate, url::GURLAndroid::FromNativeGURL(env, escaped_url),
page_transition, has_user_gesture,
initiating_origin ? initiating_origin->ToJavaObject() : nullptr);
if (j_gurl.is_null())
return;
subframe_redirect_url_ = url::GURLAndroid::ToNativeGURL(env, j_gurl);
mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver =
out_factory->InitWithNewPipeAndPassReceiver();
scoped_refptr<network::SharedURLLoaderFactory> loader_factory =
base::MakeRefCounted<network::SingleRequestURLLoaderFactory>(
base::BindOnce(&InterceptNavigationDelegate::LoaderCallback,
weak_ptr_factory_.GetWeakPtr()));
loader_factory->Clone(std::move(receiver));
}
void InterceptNavigationDelegate::LoaderCallback(
const network::ResourceRequest& resource_request,
mojo::PendingReceiver<network::mojom::URLLoader> pending_receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> pending_client) {
url_loader_ = mojo::MakeSelfOwnedReceiver(
std::make_unique<RedirectURLLoader>(resource_request,
std::move(pending_client)),
std::move(pending_receiver));
MaybeHandleSubframeAction();
}
void InterceptNavigationDelegate::MaybeHandleSubframeAction() {
// An empty subframe_redirect_url_ implies a pending async action.
if (!url_loader_ ||
(subframe_redirect_url_ && subframe_redirect_url_->is_empty())) {
return;
}
RedirectURLLoader* loader =
static_cast<RedirectURLLoader*>(url_loader_->impl());
if (!subframe_redirect_url_) {
loader->OnNonRedirectAsyncAction();
} else {
loader->DoRedirect(std::move(subframe_redirect_url_));
}
url_loader_.reset();
}
void InterceptNavigationDelegate::OnResourceRequestWithGesture() {
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env);
if (jdelegate.is_null())
return;
Java_InterceptNavigationDelegate_onResourceRequestWithGesture(env, jdelegate);
}
void InterceptNavigationDelegate::OnSubframeAsyncActionTaken(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& j_gurl) {
// subframe_redirect_url_ no longer empty indicates the async action has been
// taken.
subframe_redirect_url_ =
j_gurl.is_null() ? nullptr : url::GURLAndroid::ToNativeGURL(env, j_gurl);
MaybeHandleSubframeAction();
}
} // namespace navigation_interception