blob: a412a1841e12525cc20f408118b66c2783f10003 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/loader/navigation_url_loader_network_service.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/appcache/appcache_navigation_handle.h"
#include "content/browser/appcache/appcache_request_handler.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/navigation_request_info.h"
#include "content/browser/loader/navigation_resource_handler.h"
#include "content/browser/loader/navigation_resource_throttle.h"
#include "content/browser/loader/navigation_url_loader_delegate.h"
#include "content/browser/loader/url_loader_request_handler.h"
#include "content/browser/resource_context_impl.h"
#include "content/browser/service_worker/service_worker_navigation_handle.h"
#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
#include "content/browser/service_worker/service_worker_request_handler.h"
#include "content/browser/storage_partition_impl.h"
#include "content/browser/url_loader_factory_getter.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webui/url_data_manager_backend.h"
#include "content/browser/webui/web_ui_url_loader_factory.h"
#include "content/common/throttling_url_loader.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/global_request_id.h"
#include "content/public/browser/navigation_data.h"
#include "content/public/browser/navigation_ui_data.h"
#include "content/public/browser/ssl_status.h"
#include "content/public/browser/stream_handle.h"
#include "content/public/common/referrer.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/url_loader_factory.mojom.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/url_request_context.h"
#include "services/service_manager/public/cpp/connector.h"
namespace content {
namespace {
// Request ID for browser initiated requests. We start at -2 on the same lines
// as ResourceDispatcherHostImpl.
int g_next_request_id = -2;
WebContents* GetWebContentsFromFrameTreeNodeID(int frame_tree_node_id) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
if (!frame_tree_node)
return nullptr;
return WebContentsImpl::FromFrameTreeNode(frame_tree_node);
}
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("navigation_url_loader", R"(
semantics {
sender: "Navigation URL Loader"
description:
"This request is issued by a main frame navigation to fetch the "
"content of the page that is being navigated to."
trigger:
"Navigating Chrome (by clicking on a link, bookmark, history item, "
"using session restore, etc)."
data:
"Arbitrary site-controlled data can be included in the URL, HTTP "
"headers, and request body. Requests may include cookies and "
"site-specific credentials."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting: "This feature cannot be disabled."
policy_exception_justification:
"Not implemented, without this type of request, Chrome would be "
"unable to navigate to websites."
})");
} // namespace
// Kept around during the lifetime of the navigation request, and is
// responsible for dispatching a ResourceRequest to the appropriate
// URLLoader. In order to get the right URLLoader it builds a vector
// of URLLoaderRequestHandler's and successively calls MaybeCreateLoader
// on each until the request is successfully handled. The same sequence
// may be performed multiple times when redirects happen.
class NavigationURLLoaderNetworkService::URLLoaderRequestController
: public mojom::URLLoaderClient {
public:
URLLoaderRequestController(
std::unique_ptr<ResourceRequest> resource_request,
ResourceContext* resource_context,
scoped_refptr<URLLoaderFactoryGetter> default_url_loader_factory_getter,
const base::WeakPtr<NavigationURLLoaderNetworkService>& owner)
: resource_request_(std::move(resource_request)),
resource_context_(resource_context),
default_url_loader_factory_getter_(default_url_loader_factory_getter),
owner_(owner),
response_loader_binding_(this) {}
~URLLoaderRequestController() override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
void Start(
ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core,
AppCacheNavigationHandleCore* appcache_handle_core,
std::unique_ptr<NavigationRequestInfo> request_info,
mojom::URLLoaderFactoryPtrInfo factory_for_webui,
const base::Callback<WebContents*(void)>& web_contents_getter,
std::unique_ptr<service_manager::Connector> connector) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
web_contents_getter_ = web_contents_getter;
const ResourceType resource_type = request_info->is_main_frame
? RESOURCE_TYPE_MAIN_FRAME
: RESOURCE_TYPE_SUB_FRAME;
if (resource_request_->request_body) {
GetBodyBlobDataHandles(resource_request_->request_body.get(),
resource_context_, &blob_handles_);
}
// Requests to WebUI scheme won't get redirected to/from other schemes
// or be intercepted, so we just let it go here.
if (factory_for_webui.is_valid()) {
webui_factory_ptr_.Bind(std::move(factory_for_webui));
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
webui_factory_ptr_.get(),
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_),
0 /* routing_id? */, 0 /* request_id? */, mojom::kURLLoadOptionNone,
*resource_request_, this, kTrafficAnnotation);
return;
}
DCHECK(handlers_.empty());
if (service_worker_navigation_handle_core) {
RequestContextFrameType frame_type =
request_info->is_main_frame ? REQUEST_CONTEXT_FRAME_TYPE_TOP_LEVEL
: REQUEST_CONTEXT_FRAME_TYPE_NESTED;
storage::BlobStorageContext* blob_storage_context = GetBlobStorageContext(
GetChromeBlobStorageContextForResourceContext(resource_context_));
std::unique_ptr<URLLoaderRequestHandler> service_worker_handler =
ServiceWorkerRequestHandler::InitializeForNavigationNetworkService(
*resource_request_, resource_context_,
service_worker_navigation_handle_core, blob_storage_context,
request_info->begin_params.skip_service_worker, resource_type,
request_info->begin_params.request_context_type, frame_type,
request_info->are_ancestors_secure,
request_info->common_params.post_data, web_contents_getter);
if (service_worker_handler)
handlers_.push_back(std::move(service_worker_handler));
}
if (appcache_handle_core) {
std::unique_ptr<URLLoaderRequestHandler> appcache_handler =
AppCacheRequestHandler::InitializeForNavigationNetworkService(
*resource_request_, appcache_handle_core,
default_url_loader_factory_getter_.get());
if (appcache_handler)
handlers_.push_back(std::move(appcache_handler));
}
Restart();
}
// This could be called multiple times.
void Restart() {
handler_index_ = 0;
received_response_ = false;
MaybeStartLoader(StartLoaderCallback());
}
void MaybeStartLoader(StartLoaderCallback start_loader_callback) {
if (start_loader_callback) {
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
std::move(start_loader_callback),
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_),
*resource_request_, this, kTrafficAnnotation);
DCHECK_GT(handler_index_, 0U);
mojom::URLLoaderFactoryPtr subresource_loader_factory =
handlers_[handler_index_ - 1]->MaybeCreateSubresourceFactory();
if (subresource_loader_factory.get()) {
subresource_url_loader_factory_ptr_info_ =
subresource_loader_factory.PassInterface();
}
return;
}
if (handler_index_ < handlers_.size()) {
handlers_[handler_index_++]->MaybeCreateLoader(
*resource_request_, resource_context_,
base::BindOnce(&URLLoaderRequestController::MaybeStartLoader,
base::Unretained(this)));
return;
}
mojom::URLLoaderFactory* factory = nullptr;
DCHECK_EQ(handlers_.size(), handler_index_);
if (resource_request_->url.SchemeIs(url::kBlobScheme)) {
factory = default_url_loader_factory_getter_->GetBlobFactory()->get();
} else {
factory = default_url_loader_factory_getter_->GetNetworkFactory()->get();
default_loader_used_ = true;
}
url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart(
factory,
GetContentClient()->browser()->CreateURLLoaderThrottles(
web_contents_getter_),
0 /* routing_id? */, 0 /* request_id? */,
mojom::kURLLoadOptionSendSSLInfo | mojom::kURLLoadOptionSniffMimeType,
*resource_request_, this, kTrafficAnnotation);
}
void FollowRedirect() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(url_loader_);
DCHECK(!response_url_loader_);
url_loader_->FollowRedirect();
}
// Ownership of the URLLoaderFactoryPtrInfo instance is transferred to the
// caller.
mojom::URLLoaderFactoryPtrInfo GetSubresourceURLLoaderFactory() {
return std::move(subresource_url_loader_factory_ptr_info_);
}
private:
// mojom::URLLoaderClient implementation:
void OnReceiveResponse(
const ResourceResponseHead& head,
const base::Optional<net::SSLInfo>& ssl_info,
mojom::DownloadedTempFilePtr downloaded_file) override {
received_response_ = true;
// If the default loader (network) was used to handle the URL load request
// we need to see if the handlers want to potentially create a new loader
// for the response. e.g. AppCache.
if (MaybeCreateLoaderForResponse(head))
return;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NavigationURLLoaderNetworkService::OnReceiveResponse,
owner_, head, ssl_info, base::Passed(&downloaded_file)));
}
void OnReceiveRedirect(const net::RedirectInfo& redirect_info,
const ResourceResponseHead& head) override {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NavigationURLLoaderNetworkService::OnReceiveRedirect,
owner_, redirect_info, head));
}
void OnDataDownloaded(int64_t data_length, int64_t encoded_length) override {}
void OnUploadProgress(int64_t current_position,
int64_t total_size,
OnUploadProgressCallback callback) override {}
void OnReceiveCachedMetadata(const std::vector<uint8_t>& data) override {}
void OnTransferSizeUpdated(int32_t transfer_size_diff) override {}
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) override {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&NavigationURLLoaderNetworkService::OnStartLoadingResponseBody,
owner_, base::Passed(&body)));
}
void OnComplete(
const ResourceRequestCompletionStatus& completion_status) override {
if (completion_status.error_code != net::OK && !received_response_) {
// If the default loader (network) was used to handle the URL load
// request we need to see if the handlers want to potentially create a
// new loader for the response. e.g. AppCache.
if (MaybeCreateLoaderForResponse(ResourceResponseHead()))
return;
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&NavigationURLLoaderNetworkService::OnComplete, owner_,
completion_status));
}
// Returns true if a handler wants to handle the response, i.e. return a
// different response. For e.g. AppCache may have fallback content to be
// returned for a TLD.
bool MaybeCreateLoaderForResponse(const ResourceResponseHead& response) {
if (!default_loader_used_)
return false;
// URLLoaderClient request pointer for response loaders, i.e loaders created
// for handing responses received from the network URLLoader.
mojom::URLLoaderClientRequest response_client_request;
for (size_t index = 0; index < handlers_.size(); ++index) {
if (handlers_[index]->MaybeCreateLoaderForResponse(
response, &response_url_loader_, &response_client_request)) {
OnResponseHandlerFound(std::move(response_client_request));
return true;
}
}
return false;
}
// Called if we find a handler who delivers different content for the URL.
void OnResponseHandlerFound(
mojom::URLLoaderClientRequest response_client_request) {
response_loader_binding_.Bind(std::move(response_client_request));
// We reset this flag as we expect a new response from the handler.
default_loader_used_ = false;
// Disconnect from the network loader to stop receiving further data
// or notifications for the URL.
url_loader_->DisconnectClient();
}
std::vector<std::unique_ptr<URLLoaderRequestHandler>> handlers_;
size_t handler_index_ = 0;
std::unique_ptr<ResourceRequest> resource_request_;
ResourceContext* resource_context_;
base::Callback<WebContents*()> web_contents_getter_;
scoped_refptr<URLLoaderFactoryGetter> default_url_loader_factory_getter_;
mojom::URLLoaderFactoryPtr webui_factory_ptr_;
std::unique_ptr<ThrottlingURLLoader> url_loader_;
BlobHandles blob_handles_;
// Currently used by the AppCache loader to pass its factory to the
// renderer which enables it to handle subresources.
mojom::URLLoaderFactoryPtrInfo subresource_url_loader_factory_ptr_info_;
// This is referenced only on the UI thread.
base::WeakPtr<NavigationURLLoaderNetworkService> owner_;
// Set to true if the default URLLoader (network service) was used for the
// current navigation.
bool default_loader_used_ = false;
// URLLoaderClient binding for loaders created for responses received from the
// network loader.
mojo::Binding<mojom::URLLoaderClient> response_loader_binding_;
// URLLoader instance for response loaders, i.e loaders created for handing
// responses received from the network URLLoader.
mojom::URLLoaderPtr response_url_loader_;
// Set to true if we receive a valid response from a URLLoader, i.e.
// URLLoaderClient::OnReceivedResponse() is called.
bool received_response_ = false;
DISALLOW_COPY_AND_ASSIGN(URLLoaderRequestController);
};
NavigationURLLoaderNetworkService::NavigationURLLoaderNetworkService(
ResourceContext* resource_context,
StoragePartition* storage_partition,
std::unique_ptr<NavigationRequestInfo> request_info,
std::unique_ptr<NavigationUIData> navigation_ui_data,
ServiceWorkerNavigationHandle* service_worker_navigation_handle,
AppCacheNavigationHandle* appcache_handle,
NavigationURLLoaderDelegate* delegate)
: delegate_(delegate), weak_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP1(
"navigation", "Navigation timeToResponseStarted", this,
request_info->common_params.navigation_start, "FrameTreeNode id",
request_info->frame_tree_node_id);
// TODO(scottmg): Port over stuff from RDHI::BeginNavigationRequest() here.
auto new_request = base::MakeUnique<ResourceRequest>();
new_request->method = request_info->common_params.method;
new_request->url = request_info->common_params.url;
new_request->site_for_cookies = request_info->site_for_cookies;
new_request->priority = net::HIGHEST;
// The code below to set fields like request_initiator, referrer, etc has
// been copied from ResourceDispatcherHostImpl. We did not refactor the
// common code into a function, because RDHI uses accessor functions on the
// URLRequest class to set these fields. whereas we use ResourceRequest here.
new_request->request_initiator = request_info->begin_params.initiator_origin;
new_request->referrer = request_info->common_params.referrer.url;
new_request->referrer_policy = request_info->common_params.referrer.policy;
new_request->headers = request_info->begin_params.headers;
int load_flags = request_info->begin_params.load_flags;
load_flags |= net::LOAD_VERIFY_EV_CERT;
if (request_info->is_main_frame)
load_flags |= net::LOAD_MAIN_FRAME_DEPRECATED;
// Sync loads should have maximum priority and should be the only
// requests that have the ignore limits flag set.
DCHECK(!(load_flags & net::LOAD_IGNORE_LIMITS));
new_request->load_flags = load_flags;
new_request->request_body = request_info->common_params.post_data.get();
int frame_tree_node_id = request_info->frame_tree_node_id;
// Check if a web UI scheme wants to handle this request.
mojom::URLLoaderFactoryPtrInfo factory_for_webui;
const auto& schemes = URLDataManagerBackend::GetWebUISchemes();
if (std::find(schemes.begin(), schemes.end(), new_request->url.scheme()) !=
schemes.end()) {
FrameTreeNode* frame_tree_node =
FrameTreeNode::GloballyFindByID(frame_tree_node_id);
factory_for_webui = CreateWebUIURLLoader(frame_tree_node).PassInterface();
}
g_next_request_id--;
DCHECK(!request_controller_);
request_controller_ = base::MakeUnique<URLLoaderRequestController>(
std::move(new_request), resource_context,
static_cast<StoragePartitionImpl*>(storage_partition)
->url_loader_factory_getter(),
weak_factory_.GetWeakPtr());
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(
&URLLoaderRequestController::Start,
base::Unretained(request_controller_.get()),
service_worker_navigation_handle
? service_worker_navigation_handle->core()
: nullptr,
appcache_handle ? appcache_handle->core() : nullptr,
base::Passed(std::move(request_info)),
base::Passed(std::move(factory_for_webui)),
base::Bind(&GetWebContentsFromFrameTreeNodeID, frame_tree_node_id),
base::Passed(ServiceManagerConnection::GetForProcess()
->GetConnector()
->Clone())));
}
NavigationURLLoaderNetworkService::~NavigationURLLoaderNetworkService() {
BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE,
request_controller_.release());
}
void NavigationURLLoaderNetworkService::FollowRedirect() {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&URLLoaderRequestController::FollowRedirect,
base::Unretained(request_controller_.get())));
}
void NavigationURLLoaderNetworkService::ProceedWithResponse() {}
void NavigationURLLoaderNetworkService::OnReceiveResponse(
const ResourceResponseHead& head,
const base::Optional<net::SSLInfo>& ssl_info,
mojom::DownloadedTempFilePtr downloaded_file) {
// TODO(scottmg): This needs to do more of what
// NavigationResourceHandler::OnResponseStarted() does. Or maybe in
// OnStartLoadingResponseBody().
if (ssl_info && ssl_info->cert)
NavigationResourceHandler::GetSSLStatusForRequest(*ssl_info, &ssl_status_);
response_ = base::MakeRefCounted<ResourceResponse>();
response_->head = head;
}
void NavigationURLLoaderNetworkService::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
const ResourceResponseHead& head) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(kinuko): Perform the necessary check and call
// URLLoaderRequestController::Restart with the new URL??
scoped_refptr<ResourceResponse> response(new ResourceResponse());
response->head = head;
delegate_->OnRequestRedirected(redirect_info, response);
}
void NavigationURLLoaderNetworkService::OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle body) {
DCHECK(response_);
TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted", this,
"&NavigationURLLoaderNetworkService", this, "success",
true);
// Temporarily, we pass both a stream (null) and the data pipe to the
// delegate until PlzNavigate has shipped and we can be comfortable fully
// switching to the data pipe.
delegate_->OnResponseStarted(
response_, nullptr, std::move(body), ssl_status_,
std::unique_ptr<NavigationData>(), GlobalRequestID(-1, g_next_request_id),
false /* is_download? */, false /* is_stream */,
request_controller_->GetSubresourceURLLoaderFactory());
}
void NavigationURLLoaderNetworkService::OnComplete(
const ResourceRequestCompletionStatus& completion_status) {
if (completion_status.error_code != net::OK) {
TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted",
this, "&NavigationURLLoaderNetworkService", this,
"success", false);
delegate_->OnRequestFailed(completion_status.exists_in_cache,
completion_status.error_code);
}
}
} // namespace content