[go: nahoru, domu]

blob: d724f1fa0ec2cd69eb74eaf4174e021d9237ccfc [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "android_webview/browser/network_service/aw_proxying_url_loader_factory.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "android_webview/browser/android_protocol_handler.h"
#include "android_webview/browser/aw_browser_context.h"
#include "android_webview/browser/aw_contents_client_bridge.h"
#include "android_webview/browser/aw_contents_io_thread_client.h"
#include "android_webview/browser/aw_contents_origin_matcher.h"
#include "android_webview/browser/aw_cookie_access_policy.h"
#include "android_webview/browser/aw_settings.h"
#include "android_webview/browser/network_service/aw_web_resource_intercept_response.h"
#include "android_webview/browser/network_service/net_helpers.h"
#include "android_webview/browser/renderer_host/auto_login_parser.h"
#include "android_webview/common/aw_features.h"
#include "android_webview/common/url_constants.h"
#include "base/android/build_info.h"
#include "base/barrier_closure.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "components/embedder_support/android/util/input_stream.h"
#include "components/embedder_support/android/util/response_delegate_impl.h"
#include "components/embedder_support/android/util/web_resource_response.h"
#include "components/safe_browsing/core/common/safebrowsing_constants.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/origin_trials_controller_delegate.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/url_utils.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/load_flags.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/record_ontransfersizeupdate_utils.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
#include "third_party/blink/public/mojom/origin_trial_feature/origin_trial_feature.mojom-shared.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace android_webview {
namespace {
const char kResponseHeaderViaShouldInterceptRequestName[] = "Client-Via";
const char kResponseHeaderViaShouldInterceptRequestValue[] =
"shouldInterceptRequest";
const char kAutoLoginHeaderName[] = "X-Auto-Login";
const char kRequestedWithHeaderWebView[] = "WebView";
// Argument struct for the |InterceptRequest::InterceptResponseReceived| method
// which can live on the heap and be populated by async callbacks.
struct InterceptResponseReceivedArgs {
std::unique_ptr<AwWebResourceInterceptResponse> intercept_response = nullptr;
bool xrw_origin_trial_enabled = false;
};
// Handles intercepted, in-progress requests/responses, so that they can be
// controlled and modified accordingly.
class InterceptedRequest : public network::mojom::URLLoader,
public network::mojom::URLLoaderClient {
public:
InterceptedRequest(
int frame_tree_node_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
bool intercept_only,
absl::optional<AwProxyingURLLoaderFactory::SecurityOptions>
security_options,
scoped_refptr<AwContentsOriginMatcher> xrw_allowlist_matcher,
scoped_refptr<AwBrowserContextIoThreadHandle> browser_context_handle);
InterceptedRequest(const InterceptedRequest&) = delete;
InterceptedRequest& operator=(const InterceptedRequest&) = delete;
~InterceptedRequest() override;
void Restart();
// network::mojom::URLLoaderClient
void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override;
void OnReceiveResponse(
network::mojom::URLResponseHeadPtr head,
mojo::ScopedDataPipeConsumerHandle body,
absl::optional<mojo_base::BigBuffer> cached_metadata) override;
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) override;
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) override;
void OnTransferSizeUpdated(int32_t transfer_size_diff) override;
void OnComplete(const network::URLLoaderCompletionStatus& status) override;
// network::mojom::URLLoader
void FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const absl::optional<GURL>& new_url) override;
void SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) override;
void PauseReadingBodyFromNet() override;
void ResumeReadingBodyFromNet() override;
void ContinueAfterIntercept();
void ContinueAfterInterceptWithOverride(
std::unique_ptr<embedder_support::WebResourceResponse> response);
void InterceptResponseReceived(
std::unique_ptr<InterceptResponseReceivedArgs> args);
// Returns true if the request was restarted or completed.
bool InputStreamFailed(bool restart_needed);
private:
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class CommittedRequestedWithHeaderMode {
kNoHeader = 0,
kAppPackageName = 1,
kConstantWebview = 2,
kClientOverridden = 3,
kMaxValue = kClientOverridden
};
std::unique_ptr<AwContentsIoThreadClient> GetIoThreadClient();
// This is called when the original URLLoaderClient has a connection error.
void OnURLLoaderClientError();
// This is called when the original URLLoader has a connection error.
void OnURLLoaderError(uint32_t custom_reason, const std::string& description);
// Call OnComplete on |target_client_|. If |wait_for_loader_error| is true
// then this object will wait for |proxied_loader_receiver_| to have a
// connection error before destructing.
void CallOnComplete(const network::URLLoaderCompletionStatus& status,
bool wait_for_loader_error);
void SendErrorAndCompleteImmediately(int error_code);
// TODO(timvolodine): consider factoring this out of this class.
bool ShouldNotInterceptRequest();
// Posts the error callback to the UI thread, ensuring that at most we send
// only one.
void SendErrorCallback(int error_code, bool safebrowsing_hit);
const int frame_tree_node_id_;
const int32_t request_id_;
const uint32_t options_;
bool input_stream_previously_failed_ = false;
bool request_was_redirected_ = false;
// To avoid sending multiple OnReceivedError callbacks.
bool sent_error_callback_ = false;
// When true, the loader will not not proceed unless the
// shouldInterceptRequest callback provided a non-null response.
bool intercept_only_ = false;
AwSettings::RequestedWithHeaderMode requested_with_header_mode;
absl::optional<AwProxyingURLLoaderFactory::SecurityOptions> security_options_;
// If the |target_loader_| called OnComplete with an error this stores it.
// That way the destructor can send it to OnReceivedError if safe browsing
// error didn't occur.
int error_status_ = net::OK;
network::ResourceRequest request_;
const net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
mojo::Receiver<network::mojom::URLLoader> proxied_loader_receiver_;
mojo::Remote<network::mojom::URLLoaderClient> target_client_;
mojo::Receiver<network::mojom::URLLoaderClient> proxied_client_receiver_{
this};
mojo::Remote<network::mojom::URLLoader> target_loader_;
mojo::Remote<network::mojom::URLLoaderFactory> target_factory_;
scoped_refptr<AwContentsOriginMatcher> xrw_allowlist_matcher_;
scoped_refptr<AwBrowserContextIoThreadHandle> browser_context_handle_;
base::WeakPtrFactory<InterceptedRequest> weak_factory_{this};
};
// A ResponseDelegate for responses returned by shouldInterceptRequest.
class InterceptResponseDelegate
: public embedder_support::ResponseDelegateImpl {
public:
InterceptResponseDelegate(
std::unique_ptr<embedder_support::WebResourceResponse> response,
base::WeakPtr<InterceptedRequest> request)
: ResponseDelegateImpl(std::move(response)), request_(request) {}
// AndroidStreamReaderURLLoader::ResponseDelegate implementation:
void AppendResponseHeaders(JNIEnv* env,
net::HttpResponseHeaders* headers) override {
embedder_support::ResponseDelegateImpl::AppendResponseHeaders(env, headers);
// Indicate that the response had been obtained via shouldInterceptRequest.
headers->SetHeader(kResponseHeaderViaShouldInterceptRequestName,
kResponseHeaderViaShouldInterceptRequestValue);
}
bool OnInputStreamOpenFailed() override {
// return true if there is no valid request, meaning it has completed or
// deleted.
return request_ ? request_->InputStreamFailed(false /* restart_needed */)
: true;
}
private:
base::WeakPtr<InterceptedRequest> request_;
};
// A ResponseDelegate based on top of AndroidProtocolHandler for special
// protocols, such as content://, file:///android_asset, and file:///android_res
// URLs.
class ProtocolResponseDelegate
: public embedder_support::AndroidStreamReaderURLLoader::ResponseDelegate {
public:
ProtocolResponseDelegate(const GURL& url,
base::WeakPtr<InterceptedRequest> request)
: url_(url), request_(request) {}
std::unique_ptr<embedder_support::InputStream> OpenInputStream(
JNIEnv* env) override {
return CreateInputStream(env, url_);
}
bool OnInputStreamOpenFailed() override {
// return true if there is no valid request, meaning it has completed or has
// been deleted.
return request_ ? request_->InputStreamFailed(true /* restart_needed */)
: true;
}
bool GetMimeType(JNIEnv* env,
const GURL& url,
embedder_support::InputStream* stream,
std::string* mime_type) override {
return GetInputStreamMimeType(env, url, stream, mime_type);
}
void GetCharset(JNIEnv* env,
const GURL& url,
embedder_support::InputStream* stream,
std::string* charset) override {
// TODO: We should probably be getting this from the managed side.
}
void AppendResponseHeaders(JNIEnv* env,
net::HttpResponseHeaders* headers) override {
// Indicate that the response had been obtained via shouldInterceptRequest.
// TODO(jam): why is this added for protocol handler (e.g. content scheme
// and file resources?). The old path does this as well.
headers->SetHeader(kResponseHeaderViaShouldInterceptRequestName,
kResponseHeaderViaShouldInterceptRequestValue);
}
private:
GURL url_;
base::WeakPtr<InterceptedRequest> request_;
};
InterceptedRequest::InterceptedRequest(
int frame_tree_node_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
mojo::PendingReceiver<network::mojom::URLLoader> loader_receiver,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory,
bool intercept_only,
absl::optional<AwProxyingURLLoaderFactory::SecurityOptions>
security_options,
scoped_refptr<AwContentsOriginMatcher> xrw_allowlist_matcher,
scoped_refptr<AwBrowserContextIoThreadHandle> browser_context_handle)
: frame_tree_node_id_(frame_tree_node_id),
request_id_(request_id),
options_(options),
intercept_only_(intercept_only),
requested_with_header_mode(
AwSettings::GetDefaultRequestedWithHeaderMode()),
security_options_(security_options),
request_(request),
traffic_annotation_(traffic_annotation),
proxied_loader_receiver_(this, std::move(loader_receiver)),
target_client_(std::move(client)),
target_factory_(std::move(target_factory)),
xrw_allowlist_matcher_(std::move(xrw_allowlist_matcher)),
browser_context_handle_(std::move(browser_context_handle)) {
// If there is a client error, clean up the request.
target_client_.set_disconnect_handler(base::BindOnce(
&InterceptedRequest::OnURLLoaderClientError, base::Unretained(this)));
proxied_loader_receiver_.set_disconnect_with_reason_handler(base::BindOnce(
&InterceptedRequest::OnURLLoaderError, base::Unretained(this)));
}
InterceptedRequest::~InterceptedRequest() {
if (error_status_ != net::OK)
SendErrorCallback(error_status_, false);
}
namespace {
// Persistent Origin Trials can only be checked on the UI thread.
// |result_args| is owned by a BarrierClosure that executes after this call.
void CheckXrwOriginTrialOnUiThread(GURL request_url,
int frame_tree_node_id,
blink::mojom::ResourceType resource_type,
InterceptResponseReceivedArgs* result_args) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::OriginTrialsControllerDelegate* delegate =
AwBrowserContext::GetDefault()->GetOriginTrialsControllerDelegate();
if (!delegate)
return;
// Use the request URL for main frame resources (main frame navigation).
// Use last committed origin of outermost main frame for all other requests.
// Fall back to an opaque origin if neither is available (not expected to
// happen).
url::Origin partition_origin;
if (resource_type == blink::mojom::ResourceType::kMainFrame) {
partition_origin = url::Origin::Create(request_url);
} else {
content::WebContents* wc =
content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
base::UmaHistogramBoolean(
"Android.WebView.RequestedWithHeader.HadWebContentsForPartitionOrigin",
wc);
if (wc) {
partition_origin = wc->GetPrimaryMainFrame()
->GetOutermostMainFrame()
->GetLastCommittedOrigin();
}
}
result_args->xrw_origin_trial_enabled = delegate->IsFeaturePersistedForOrigin(
url::Origin::Create(request_url), partition_origin,
blink::mojom::OriginTrialFeature::kWebViewXRequestedWithDeprecation,
base::Time::Now());
base::UmaHistogramBoolean(
"Android.WebView.RequestedWithHeader.OriginTrialEnabled",
result_args->xrw_origin_trial_enabled);
if (result_args->xrw_origin_trial_enabled &&
(request_url.SchemeIsHTTPOrHTTPS() || request_url.SchemeIsWSOrWSS())) {
base::UmaHistogramBoolean(
"Android.WebView.RequestedWithHeader.PageSchemeIsCryptographic",
request_url.SchemeIsCryptographic());
}
}
// Post a call to the UI thread to check if the XRW deprecation trial is enabled
// for |request_url|, saving the result in |result_args|.
// |result_args| is owned by the |done_callback|.
void CheckXrwOriginTrialAsync(GURL request_url,
int frame_tree_node_id,
blink::mojom::ResourceType resource_type,
InterceptResponseReceivedArgs* result_args,
base::OnceClosure done_callback) {
content::GetUIThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&CheckXrwOriginTrialOnUiThread, std::move(request_url),
frame_tree_node_id, resource_type,
base::Unretained(result_args)),
std::move(done_callback));
}
// Response callback for |AwContentsIoThreadClient::ShouldInterceptRequestAsync|
// which saves the result to |result_args|, which is owned by the
// |done_callback|.
// |async_result| is last argument to allow currying through bind.
void OnShouldInterceptRequestAsyncResult(
InterceptResponseReceivedArgs* result_args,
base::OnceClosure done_closure,
std::unique_ptr<AwWebResourceInterceptResponse> async_result) {
result_args->intercept_response = std::move(async_result);
std::move(done_closure).Run();
}
} // namespace
void InterceptedRequest::Restart() {
std::unique_ptr<AwContentsIoThreadClient> io_thread_client =
GetIoThreadClient();
if (ShouldBlockURL(request_.url, io_thread_client.get())) {
SendErrorAndCompleteImmediately(net::ERR_ACCESS_DENIED);
return;
}
if (requested_with_header_mode != AwSettings::APP_PACKAGE_NAME &&
xrw_allowlist_matcher_ &&
xrw_allowlist_matcher_->MatchesOrigin(
url::Origin::Create(request_.url))) {
requested_with_header_mode = AwSettings::APP_PACKAGE_NAME;
}
request_.load_flags =
UpdateLoadFlags(request_.load_flags, io_thread_client.get());
if (!io_thread_client || ShouldNotInterceptRequest()) {
// equivalent to no interception
std::unique_ptr<InterceptResponseReceivedArgs>
intercept_response_received_args =
std::make_unique<InterceptResponseReceivedArgs>();
CheckXrwOriginTrialAsync(
request_.url, frame_tree_node_id_,
static_cast<blink::mojom::ResourceType>(request_.resource_type),
intercept_response_received_args.get(),
base::BindOnce(&InterceptedRequest::InterceptResponseReceived,
weak_factory_.GetWeakPtr(),
std::move(intercept_response_received_args)));
} else {
if (request_.referrer.is_valid()) {
// intentionally override if referrer header already exists
request_.headers.SetHeader(net::HttpRequestHeaders::kReferer,
request_.referrer.spec());
}
base::RepeatingClosure arg_ready_closure;
// Pointer lifetime is tied to |arg_ready_closure|.
InterceptResponseReceivedArgs* intercept_response_received_args;
{
// Inner scope to prevent |call_args| from being accidentally used after
// being moved into the |arg_ready_closure|.
std::unique_ptr<InterceptResponseReceivedArgs> call_args =
std::make_unique<InterceptResponseReceivedArgs>();
intercept_response_received_args = call_args.get();
arg_ready_closure = base::BarrierClosure(
2, base::BindOnce(&InterceptedRequest::InterceptResponseReceived,
weak_factory_.GetWeakPtr(), std::move(call_args)));
}
CheckXrwOriginTrialAsync(
request_.url, frame_tree_node_id_,
static_cast<blink::mojom::ResourceType>(request_.resource_type),
intercept_response_received_args, arg_ready_closure);
// TODO: verify the case when WebContents::RenderFrameDeleted is called
// before network request is intercepted (i.e. if that's possible and
// whether it can result in any issues).
io_thread_client->ShouldInterceptRequestAsync(
AwWebResourceRequest(request_),
base::BindOnce(&OnShouldInterceptRequestAsyncResult,
base::Unretained(intercept_response_received_args),
arg_ready_closure));
}
}
// logic for when not to invoke shouldInterceptRequest callback
bool InterceptedRequest::ShouldNotInterceptRequest() {
if (request_was_redirected_)
return true;
// Do not call shouldInterceptRequest callback for special android urls,
// unless they fail to load on first attempt. Special android urls are urls
// such as "file:///android_asset/", "file:///android_res/" urls or
// "content:" scheme urls.
return !input_stream_previously_failed_ &&
(request_.url.SchemeIs(url::kContentScheme) ||
android_webview::IsAndroidSpecialFileUrl(request_.url));
}
void InterceptedRequest::InterceptResponseReceived(
std::unique_ptr<InterceptResponseReceivedArgs> args) {
DCHECK(args);
if (args->xrw_origin_trial_enabled) {
requested_with_header_mode =
AwSettings::RequestedWithHeaderMode::APP_PACKAGE_NAME;
}
// We send the application's package name in the X-Requested-With header for
// compatibility with previous WebView versions. This should not be visible to
// shouldInterceptRequest. It should also not trigger CORS prefetch if
// OOR-CORS is enabled.
std::string header = content::GetCorsExemptRequestedWithHeaderName();
CommittedRequestedWithHeaderMode committed_mode =
CommittedRequestedWithHeaderMode::kClientOverridden;
// Only overwrite if the header hasn't already been set
if (!request_.headers.HasHeader(header)) {
switch (requested_with_header_mode) {
case AwSettings::RequestedWithHeaderMode::NO_HEADER:
committed_mode = CommittedRequestedWithHeaderMode::kNoHeader;
break;
case AwSettings::RequestedWithHeaderMode::APP_PACKAGE_NAME:
request_.cors_exempt_headers.SetHeader(
header,
base::android::BuildInfo::GetInstance()->host_package_name());
committed_mode = CommittedRequestedWithHeaderMode::kAppPackageName;
break;
case AwSettings::RequestedWithHeaderMode::CONSTANT_WEBVIEW:
request_.cors_exempt_headers.SetHeader(header,
kRequestedWithHeaderWebView);
committed_mode = CommittedRequestedWithHeaderMode::kConstantWebview;
break;
default:
NOTREACHED()
<< "Invalid enum value for AwSettings:RequestedWithHeaderMode: "
<< requested_with_header_mode;
}
}
base::UmaHistogramEnumeration(
"Android.WebView.RequestedWithHeader.CommittedHeaderMode",
committed_mode);
JNIEnv* env = base::android::AttachCurrentThread();
if (args->intercept_response &&
args->intercept_response->RaisedException(env)) {
// The JNI handler has already raised an exception. Fail the resource load
// as it may be insecure to load on error.
SendErrorAndCompleteImmediately(net::ERR_UNEXPECTED);
return;
}
if (args->intercept_response && args->intercept_response->HasResponse(env)) {
// non-null response: make sure to use it as an override for the
// normal network data.
ContinueAfterInterceptWithOverride(
args->intercept_response->GetResponse(env));
return;
}
// Request was not intercepted/overridden. Proceed with loading from network,
// unless this is a special |intercept_only_| loader, which happens for
// external schemes: e.g. unsupported schemes and cid: schemes.
if (intercept_only_) {
SendErrorAndCompleteImmediately(net::ERR_UNKNOWN_URL_SCHEME);
return;
}
ContinueAfterIntercept();
}
// returns true if the request has been restarted or was completed.
bool InterceptedRequest::InputStreamFailed(bool restart_needed) {
DCHECK(!input_stream_previously_failed_);
if (intercept_only_) {
// This can happen for unsupported schemes, when no proper
// response from shouldInterceptRequest() is received, i.e.
// the provided input stream in response failed to load. In
// this case we send and error and stop loading.
SendErrorAndCompleteImmediately(net::ERR_UNKNOWN_URL_SCHEME);
return true; // request completed
}
if (!restart_needed) {
// request will not be restarted, error reporting will be done
// via other means e.g. setting appropriate response header status.
return false;
}
input_stream_previously_failed_ = true;
proxied_client_receiver_.reset();
Restart();
return true; // request restarted
}
void InterceptedRequest::ContinueAfterIntercept() {
// For WebViewClassic compatibility this job can only accept URLs that can be
// opened. URLs that cannot be opened should be resolved by the next handler.
//
// If a request is initially handled here but the job fails due to it being
// unable to open the InputStream for that request the request is marked as
// previously failed and restarted.
// Restarting a request involves creating a new job for that request. This
// handler will ignore requests known to have previously failed to 1) prevent
// an infinite loop, 2) ensure that the next handler in line gets the
// opportunity to create a job for the request.
if (!input_stream_previously_failed_ &&
(request_.url.SchemeIs(url::kContentScheme) ||
android_webview::IsAndroidSpecialFileUrl(request_.url))) {
embedder_support::AndroidStreamReaderURLLoader* loader =
new embedder_support::AndroidStreamReaderURLLoader(
request_, proxied_client_receiver_.BindNewPipeAndPassRemote(),
traffic_annotation_,
std::make_unique<ProtocolResponseDelegate>(
request_.url, weak_factory_.GetWeakPtr()),
security_options_);
loader->Start();
return;
}
if (!target_loader_ && target_factory_) {
target_factory_->CreateLoaderAndStart(
target_loader_.BindNewPipeAndPassReceiver(), request_id_, options_,
request_, proxied_client_receiver_.BindNewPipeAndPassRemote(),
traffic_annotation_);
}
}
void InterceptedRequest::ContinueAfterInterceptWithOverride(
std::unique_ptr<embedder_support::WebResourceResponse> response) {
embedder_support::AndroidStreamReaderURLLoader* loader =
new embedder_support::AndroidStreamReaderURLLoader(
request_, proxied_client_receiver_.BindNewPipeAndPassRemote(),
traffic_annotation_,
std::make_unique<InterceptResponseDelegate>(
std::move(response), weak_factory_.GetWeakPtr()),
absl::nullopt);
loader->Start();
}
namespace {
// TODO(timvolodine): consider factoring this out of this file.
AwContentsClientBridge* GetAwContentsClientBridgeFromID(
int frame_tree_node_id) {
content::WebContents* wc =
content::WebContents::FromFrameTreeNodeId(frame_tree_node_id);
return AwContentsClientBridge::FromWebContents(wc);
}
void OnReceivedHttpErrorOnUiThread(
int frame_tree_node_id,
const AwWebResourceRequest& request,
std::unique_ptr<AwContentsClientBridge::HttpErrorInfo> http_error_info) {
auto* client = GetAwContentsClientBridgeFromID(frame_tree_node_id);
if (!client) {
DLOG(WARNING) << "client is null, onReceivedHttpError dropped for "
<< request.url;
return;
}
client->OnReceivedHttpError(request, std::move(http_error_info));
}
void OnReceivedErrorOnUiThread(int frame_tree_node_id,
const AwWebResourceRequest& request,
int error_code,
bool safebrowsing_hit) {
auto* client = GetAwContentsClientBridgeFromID(frame_tree_node_id);
if (!client) {
DLOG(WARNING) << "client is null, onReceivedError dropped for "
<< request.url;
return;
}
client->OnReceivedError(request, error_code, safebrowsing_hit, true);
}
void OnNewLoginRequestOnUiThread(int frame_tree_node_id,
const std::string& realm,
const std::string& account,
const std::string& args) {
auto* client = GetAwContentsClientBridgeFromID(frame_tree_node_id);
if (!client) {
return;
}
client->NewLoginRequest(realm, account, args);
}
} // namespace
// URLLoaderClient methods.
void InterceptedRequest::OnReceiveEarlyHints(
network::mojom::EarlyHintsPtr early_hints) {
target_client_->OnReceiveEarlyHints(std::move(early_hints));
}
void InterceptedRequest::OnReceiveResponse(
network::mojom::URLResponseHeadPtr head,
mojo::ScopedDataPipeConsumerHandle body,
absl::optional<mojo_base::BigBuffer> cached_metadata) {
// intercept response headers here
// pause/resume |proxied_client_receiver_| if necessary
if (head->headers && head->headers->response_code() >= 400) {
// In Android WebView the WebViewClient.onReceivedHttpError callback
// is invoked for any resource (main page, iframe, image, etc.) with
// status code >= 400.
std::unique_ptr<AwContentsClientBridge::HttpErrorInfo> error_info =
AwContentsClientBridge::ExtractHttpErrorInfo(head->headers.get());
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&OnReceivedHttpErrorOnUiThread, frame_tree_node_id_,
AwWebResourceRequest(request_), std::move(error_info)));
}
if (request_.destination == network::mojom::RequestDestination::kDocument) {
// Check for x-auto-login-header
HeaderData header_data;
std::string header_string;
if (head->headers && head->headers->GetNormalizedHeader(
kAutoLoginHeaderName, &header_string)) {
if (ParseHeader(header_string, ALLOW_ANY_REALM, &header_data)) {
// TODO(timvolodine): consider simplifying this and above callback
// code, crbug.com/897149.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&OnNewLoginRequestOnUiThread,
frame_tree_node_id_, header_data.realm,
header_data.account, header_data.args));
}
}
}
target_client_->OnReceiveResponse(std::move(head), std::move(body),
std::move(cached_metadata));
}
void InterceptedRequest::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) {
// TODO(timvolodine): handle redirect override.
request_was_redirected_ = true;
target_client_->OnReceiveRedirect(redirect_info, std::move(head));
request_.url = redirect_info.new_url;
request_.method = redirect_info.new_method;
request_.site_for_cookies = redirect_info.new_site_for_cookies;
request_.referrer = GURL(redirect_info.new_referrer);
request_.referrer_policy = redirect_info.new_referrer_policy;
}
void InterceptedRequest::OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) {
target_client_->OnUploadProgress(current_position, total_size,
std::move(callback));
}
void InterceptedRequest::OnTransferSizeUpdated(int32_t transfer_size_diff) {
network::RecordOnTransferSizeUpdatedUMA(
network::OnTransferSizeUpdatedFrom::kInterceptedRequest);
target_client_->OnTransferSizeUpdated(transfer_size_diff);
}
void InterceptedRequest::OnComplete(
const network::URLLoaderCompletionStatus& status) {
// Only wait for the original loader to possibly have a custom error if the
// target loader succeeded. If the target loader failed, then it was a race as
// to whether that error or the safe browsing error would be reported.
CallOnComplete(status, status.error_code == net::OK);
}
// URLLoader methods.
void InterceptedRequest::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const absl::optional<GURL>& new_url) {
if (target_loader_) {
target_loader_->FollowRedirect(removed_headers, modified_headers,
modified_cors_exempt_headers, new_url);
}
// If |OnURLLoaderClientError| was called then we're just waiting for the
// connection error handler of |proxied_loader_receiver_|. Don't restart the
// job since that'll create another URLLoader
if (!target_client_)
return;
Restart();
}
void InterceptedRequest::SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) {
if (target_loader_)
target_loader_->SetPriority(priority, intra_priority_value);
}
void InterceptedRequest::PauseReadingBodyFromNet() {
if (target_loader_)
target_loader_->PauseReadingBodyFromNet();
}
void InterceptedRequest::ResumeReadingBodyFromNet() {
if (target_loader_)
target_loader_->ResumeReadingBodyFromNet();
}
std::unique_ptr<AwContentsIoThreadClient>
InterceptedRequest::GetIoThreadClient() {
// |frame_tree_node_id_| is set to no kNoFrameTreeNodeId for service
// workers. |request_.originated_from_service_worker| is insufficient here
// because it is not set to true on browser side requested main scripts.
if (frame_tree_node_id_ == content::RenderFrameHost::kNoFrameTreeNodeId)
return browser_context_handle_
? browser_context_handle_->GetServiceWorkerIoThreadClient()
: nullptr;
return AwContentsIoThreadClient::FromID(frame_tree_node_id_);
}
void InterceptedRequest::OnURLLoaderClientError() {
// We set |wait_for_loader_error| to true because if the loader did have a
// custom_reason error then the client would be reset as well and it would be
// a race as to which connection error we saw first.
CallOnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED),
true /* wait_for_loader_error */);
}
void InterceptedRequest::OnURLLoaderError(uint32_t custom_reason,
const std::string& description) {
if (custom_reason == network::mojom::URLLoader::kClientDisconnectReason) {
if (description == safe_browsing::kCustomCancelReasonForURLLoader) {
SendErrorCallback(safe_browsing::kNetErrorCodeForSafeBrowsing, true);
} else {
int parsed_error_code;
if (base::StringToInt(base::StringPiece(description),
&parsed_error_code)) {
SendErrorCallback(parsed_error_code, false);
}
}
}
// If CallOnComplete was already called, then this object is ready to be
// deleted.
if (!target_client_)
delete this;
}
void InterceptedRequest::CallOnComplete(
const network::URLLoaderCompletionStatus& status,
bool wait_for_loader_error) {
// Save an error status so that we call onReceiveError at destruction if there
// was no safe browsing error.
if (status.error_code != net::OK)
error_status_ = status.error_code;
if (target_client_)
target_client_->OnComplete(status);
if (proxied_loader_receiver_.is_bound() && wait_for_loader_error) {
// Since the original client is gone no need to continue loading the
// request.
proxied_client_receiver_.reset();
target_loader_.reset();
// Don't delete |this| yet, in case the |proxied_loader_receiver_|'s
// error_handler is called with a reason to indicate an error which we want
// to send to the client bridge. Also reset |target_client_| so we don't
// get its error_handler called and then delete |this|.
target_client_.reset();
// In case there are pending checks as to whether this request should be
// intercepted, we don't want that causing |target_client_| to be used
// later.
weak_factory_.InvalidateWeakPtrs();
} else {
delete this;
}
}
void InterceptedRequest::SendErrorAndCompleteImmediately(int error_code) {
auto status = network::URLLoaderCompletionStatus(error_code);
SendErrorCallback(status.error_code, false);
target_client_->OnComplete(status);
delete this;
}
void InterceptedRequest::SendErrorCallback(int error_code,
bool safebrowsing_hit) {
// Ensure we only send one error callback, e.g. to avoid sending two if
// there's both a networking error and safe browsing blocked the request.
if (sent_error_callback_)
return;
// We can't get a |AwContentsClientBridge| based on the |render_frame_id| of
// the |request_| initiated by the service worker, so interrupt it as soon as
// possible.
if (request_.originated_from_service_worker)
return;
sent_error_callback_ = true;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&OnReceivedErrorOnUiThread, frame_tree_node_id_,
AwWebResourceRequest(request_), error_code,
safebrowsing_hit));
}
} // namespace
//============================
// AwProxyingURLLoaderFactory
//============================
AwProxyingURLLoaderFactory::AwProxyingURLLoaderFactory(
int frame_tree_node_id,
mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_remote,
bool intercept_only,
absl::optional<SecurityOptions> security_options,
scoped_refptr<AwContentsOriginMatcher> xrw_allowlist_matcher,
scoped_refptr<AwBrowserContextIoThreadHandle> browser_context_handle)
: frame_tree_node_id_(frame_tree_node_id),
intercept_only_(intercept_only),
security_options_(security_options),
xrw_allowlist_matcher_(std::move(xrw_allowlist_matcher)),
browser_context_handle_(std::move(browser_context_handle)) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(!(intercept_only_ && target_factory_remote));
if (target_factory_remote) {
target_factory_.Bind(std::move(target_factory_remote));
target_factory_.set_disconnect_handler(
base::BindOnce(&AwProxyingURLLoaderFactory::OnTargetFactoryError,
base::Unretained(this)));
}
proxy_receivers_.Add(this, std::move(loader_receiver));
proxy_receivers_.set_disconnect_handler(
base::BindRepeating(&AwProxyingURLLoaderFactory::OnProxyBindingError,
base::Unretained(this)));
}
AwProxyingURLLoaderFactory::~AwProxyingURLLoaderFactory() = default;
// static
void AwProxyingURLLoaderFactory::CreateProxy(
int frame_tree_node_id,
mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver,
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_remote,
absl::optional<SecurityOptions> security_options,
scoped_refptr<AwContentsOriginMatcher> xrw_allowlist_matcher,
scoped_refptr<AwBrowserContextIoThreadHandle> browser_context_handle) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// will manage its own lifetime
new AwProxyingURLLoaderFactory(
frame_tree_node_id, std::move(loader_receiver),
std::move(target_factory_remote), false, security_options,
std::move(xrw_allowlist_matcher), std::move(browser_context_handle));
}
void AwProxyingURLLoaderFactory::CreateLoaderAndStart(
mojo::PendingReceiver<network::mojom::URLLoader> loader,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
// TODO(timvolodine): handle interception, modification (headers for
// webview), blocking, callbacks etc..
mojo::PendingRemote<network::mojom::URLLoaderFactory> target_factory_clone;
if (target_factory_) {
target_factory_->Clone(
target_factory_clone.InitWithNewPipeAndPassReceiver());
}
bool global_cookie_policy =
AwCookieAccessPolicy::GetInstance()->GetShouldAcceptCookies();
bool third_party_cookie_policy =
AwCookieAccessPolicy::GetInstance()->GetShouldAcceptThirdPartyCookies(
std::nullopt, frame_tree_node_id_);
if (!global_cookie_policy) {
options |= network::mojom::kURLLoadOptionBlockAllCookies;
} else if (!third_party_cookie_policy && !request.url.SchemeIsFile()) {
// Special case: if the application has asked that we allow file:// scheme
// URLs to set cookies, we need to avoid setting a cookie policy (as file://
// scheme URLs are third-party to everything).
options |= network::mojom::kURLLoadOptionBlockThirdPartyCookies;
}
// manages its own lifecycle
// TODO(timvolodine): consider keeping track of requests.
InterceptedRequest* req = new InterceptedRequest(
frame_tree_node_id_, request_id, options, request, traffic_annotation,
std::move(loader), std::move(client), std::move(target_factory_clone),
intercept_only_, security_options_, xrw_allowlist_matcher_,
browser_context_handle_);
req->Restart();
}
void AwProxyingURLLoaderFactory::OnTargetFactoryError() {
delete this;
}
void AwProxyingURLLoaderFactory::OnProxyBindingError() {
if (proxy_receivers_.empty())
delete this;
}
void AwProxyingURLLoaderFactory::Clone(
mojo::PendingReceiver<network::mojom::URLLoaderFactory> loader_receiver) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
proxy_receivers_.Add(this, std::move(loader_receiver));
}
} // namespace android_webview