| // 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_impl.h" |
| |
| #include <memory> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/optional.h" |
| #include "base/stl_util.h" |
| #include "base/task/post_task.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "components/download/public/common/download_stats.h" |
| #include "content/browser/appcache/appcache_navigation_handle.h" |
| #include "content/browser/appcache/appcache_navigation_handle_core.h" |
| #include "content/browser/appcache/appcache_request_handler.h" |
| #include "content/browser/blob_storage/chrome_blob_storage_context.h" |
| #include "content/browser/devtools/devtools_instrumentation.h" |
| #include "content/browser/file_url_loader_factory.h" |
| #include "content/browser/fileapi/file_system_url_loader_factory.h" |
| #include "content/browser/frame_host/frame_tree_node.h" |
| #include "content/browser/frame_host/navigation_request_info.h" |
| #include "content/browser/loader/navigation_loader_interceptor.h" |
| #include "content/browser/loader/navigation_url_loader_delegate.h" |
| #include "content/browser/loader/prefetch_url_loader_service.h" |
| #include "content/browser/loader/resource_dispatcher_host_impl.h" |
| #include "content/browser/loader/resource_request_info_impl.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/web_package/signed_exchange_consts.h" |
| #include "content/browser/web_package/signed_exchange_request_handler.h" |
| #include "content/browser/web_package/signed_exchange_url_loader_factory_for_non_network_service.h" |
| #include "content/browser/web_package/signed_exchange_utils.h" |
| #include "content/browser/webui/url_data_manager_backend.h" |
| #include "content/browser/webui/web_ui_url_loader_factory_internal.h" |
| #include "content/common/mime_sniffing_throttle.h" |
| #include "content/common/navigation_subresource_loader_params.h" |
| #include "content/common/net/record_load_histograms.h" |
| #include "content/common/throttling_url_loader.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/download_utils.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/plugin_service.h" |
| #include "content/public/browser/resource_dispatcher_host_delegate.h" |
| #include "content/public/browser/shared_cors_origin_access_list.h" |
| #include "content/public/browser/ssl_status.h" |
| #include "content/public/browser/url_loader_request_interceptor.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/referrer.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/common/url_utils.h" |
| #include "content/public/common/webplugininfo.h" |
| #include "net/base/load_flags.h" |
| #include "net/cert/sct_status_flags.h" |
| #include "net/cert/signed_certificate_timestamp_and_status.h" |
| #include "net/http/http_content_disposition.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "net/ssl/ssl_info.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "net/url_request/redirect_util.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "ppapi/buildflags/buildflags.h" |
| #include "services/network/loader_util.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/cpp/wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/request_context_frame_type.mojom.h" |
| #include "services/network/public/mojom/url_loader_factory.mojom.h" |
| #include "third_party/blink/public/common/mime_util/mime_util.h" |
| #include "third_party/blink/public/common/service_worker/service_worker_utils.h" |
| |
| #if defined(OS_ANDROID) |
| #include "content/browser/android/content_url_loader_factory.h" |
| #endif |
| |
| namespace content { |
| |
| namespace { |
| |
| class NavigationLoaderInterceptorBrowserContainer |
| : public NavigationLoaderInterceptor { |
| public: |
| explicit NavigationLoaderInterceptorBrowserContainer( |
| std::unique_ptr<URLLoaderRequestInterceptor> browser_interceptor) |
| : browser_interceptor_(std::move(browser_interceptor)) {} |
| |
| ~NavigationLoaderInterceptorBrowserContainer() override = default; |
| |
| void MaybeCreateLoader( |
| const network::ResourceRequest& tentative_resource_request, |
| ResourceContext* resource_context, |
| LoaderCallback callback, |
| FallbackCallback fallback_callback) override { |
| browser_interceptor_->MaybeCreateLoader( |
| tentative_resource_request, resource_context, std::move(callback)); |
| } |
| |
| private: |
| std::unique_ptr<URLLoaderRequestInterceptor> browser_interceptor_; |
| }; |
| |
| // Only used on the IO thread. |
| base::LazyInstance<NavigationURLLoaderImpl::BeginNavigationInterceptor>::Leaky |
| g_interceptor = LAZY_INSTANCE_INITIALIZER; |
| |
| // Only used on the UI thread. |
| base::LazyInstance<NavigationURLLoaderImpl::URLLoaderFactoryInterceptor>::Leaky |
| g_loader_factory_interceptor = LAZY_INSTANCE_INITIALIZER; |
| |
| // Returns true if interception by NavigationLoaderInterceptors is enabled. |
| // Both ServiceWorkerServicification and SignedExchange require the loader |
| // interception. So even if NetworkService is not enabled, returns true when one |
| // of them is enabled. |
| bool IsLoaderInterceptionEnabled() { |
| return base::FeatureList::IsEnabled(network::features::kNetworkService) || |
| blink::ServiceWorkerUtils::IsServicificationEnabled() || |
| signed_exchange_utils::IsSignedExchangeHandlingEnabled(); |
| } |
| |
| // Request ID for browser initiated requests. We start at -2 on the same lines |
| // as ResourceDispatcherHostImpl. |
| int g_next_request_id = -2; |
| GlobalRequestID MakeGlobalRequestID() { |
| return GlobalRequestID(-1, g_next_request_id--); |
| } |
| |
| size_t GetCertificateChainsSizeInKB(const net::SSLInfo& ssl_info) { |
| base::Pickle cert_pickle; |
| ssl_info.cert->Persist(&cert_pickle); |
| base::Pickle unverified_cert_pickle; |
| ssl_info.unverified_cert->Persist(&unverified_cert_pickle); |
| return (cert_pickle.size() + unverified_cert_pickle.size()) / 1000; |
| } |
| |
| 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 kNavigationUrlLoaderTrafficAnnotation = |
| 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." |
| chrome_policy { |
| URLBlacklist { |
| URLBlacklist: { entries: '*' } |
| } |
| } |
| chrome_policy { |
| URLWhitelist { |
| URLWhitelist { } |
| } |
| } |
| } |
| comments: |
| "Chrome would be unable to navigate to websites without this type of " |
| "request. Using either URLBlacklist or URLWhitelist policies (or a " |
| "combination of both) limits the scope of these requests." |
| )"); |
| |
| std::unique_ptr<network::ResourceRequest> CreateResourceRequest( |
| NavigationRequestInfo* request_info, |
| int frame_tree_node_id, |
| bool allow_download) { |
| // TODO(scottmg): Port over stuff from RDHI::BeginNavigationRequest() here. |
| auto new_request = std::make_unique<network::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->top_frame_origin = request_info->top_frame_origin; |
| |
| net::RequestPriority net_priority = net::HIGHEST; |
| if (!request_info->is_main_frame && |
| base::FeatureList::IsEnabled(features::kLowPriorityIframes)) { |
| net_priority = net::LOWEST; |
| } |
| new_request->priority = net_priority; |
| |
| new_request->render_frame_id = frame_tree_node_id; |
| |
| // 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 = Referrer::ReferrerPolicyForUrlRequest( |
| request_info->common_params.referrer.policy); |
| new_request->headers.AddHeadersFromString( |
| request_info->begin_params->headers); |
| |
| std::string accept_value = network::kFrameAcceptHeader; |
| if (signed_exchange_utils::ShouldAdvertiseAcceptHeader( |
| url::Origin::Create(request_info->common_params.url))) { |
| DCHECK(!accept_value.empty()); |
| accept_value.append(kAcceptHeaderSignedExchangeSuffix); |
| } |
| |
| new_request->headers.SetHeader(network::kAcceptHeader, accept_value); |
| |
| new_request->resource_type = request_info->is_main_frame |
| ? RESOURCE_TYPE_MAIN_FRAME |
| : RESOURCE_TYPE_SUB_FRAME; |
| if (request_info->is_main_frame) |
| new_request->update_first_party_url_on_redirect = true; |
| |
| int load_flags = request_info->begin_params->load_flags; |
| 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(); |
| new_request->report_raw_headers = request_info->report_raw_headers; |
| new_request->allow_download = allow_download; |
| new_request->has_user_gesture = request_info->common_params.has_user_gesture; |
| new_request->enable_load_timing = true; |
| |
| new_request->fetch_request_mode = network::mojom::FetchRequestMode::kNavigate; |
| new_request->fetch_credentials_mode = |
| network::mojom::FetchCredentialsMode::kInclude; |
| new_request->fetch_redirect_mode = network::mojom::FetchRedirectMode::kManual; |
| new_request->fetch_request_context_type = |
| static_cast<int>(request_info->begin_params->request_context_type); |
| new_request->upgrade_if_insecure = request_info->upgrade_if_insecure; |
| new_request->throttling_profile_id = request_info->devtools_frame_token; |
| new_request->transition_type = request_info->common_params.transition; |
| new_request->previews_state = request_info->common_params.previews_state; |
| return new_request; |
| } |
| |
| // Used only when NetworkService is disabled but IsLoaderInterceptionEnabled() |
| // is true. |
| std::unique_ptr<NavigationRequestInfo> CreateNavigationRequestInfoForRedirect( |
| const NavigationRequestInfo& previous_request_info, |
| const network::ResourceRequest& updated_resource_request) { |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| DCHECK(IsLoaderInterceptionEnabled()); |
| |
| CommonNavigationParams new_common_params = |
| previous_request_info.common_params; |
| new_common_params.url = updated_resource_request.url; |
| new_common_params.referrer = |
| Referrer(updated_resource_request.referrer, |
| Referrer::NetReferrerPolicyToBlinkReferrerPolicy( |
| updated_resource_request.referrer_policy)); |
| new_common_params.method = updated_resource_request.method; |
| new_common_params.post_data = updated_resource_request.request_body; |
| |
| mojom::BeginNavigationParamsPtr new_begin_params = |
| previous_request_info.begin_params.Clone(); |
| new_begin_params->headers = updated_resource_request.headers.ToString(); |
| |
| return std::make_unique<NavigationRequestInfo>( |
| std::move(new_common_params), std::move(new_begin_params), |
| updated_resource_request.site_for_cookies, |
| updated_resource_request.top_frame_origin, |
| previous_request_info.is_main_frame, |
| previous_request_info.parent_is_main_frame, |
| previous_request_info.are_ancestors_secure, |
| previous_request_info.frame_tree_node_id, |
| previous_request_info.is_for_guests_only, |
| previous_request_info.report_raw_headers, |
| previous_request_info.is_prerendering, |
| previous_request_info.upgrade_if_insecure, |
| nullptr /* blob_url_loader_factory */, |
| previous_request_info.devtools_navigation_token, |
| previous_request_info.devtools_frame_token); |
| } |
| |
| // Called for requests that we don't have a URLLoaderFactory for. |
| void UnknownSchemeCallback( |
| bool handled_externally, |
| const network::ResourceRequest& /* resource_request */, |
| network::mojom::URLLoaderRequest request, |
| network::mojom::URLLoaderClientPtr client) { |
| client->OnComplete(network::URLLoaderCompletionStatus( |
| handled_externally ? net::ERR_ABORTED : net::ERR_UNKNOWN_URL_SCHEME)); |
| } |
| |
| // Returns whether this URL can be handled by the default network service |
| // URLLoader. |
| bool IsURLHandledByDefaultLoader(const GURL& url) { |
| // Data URLs are only handled by the network service if |
| // |enable_data_url_support| is set in NetworkContextParams. This is set to |
| // true for the context used by NavigationURLLoaderImpl, so in addition to |
| // checking whether the URL is handled by the network service, we also need to |
| // check for the data scheme. |
| return IsURLHandledByNetworkService(url) || url.SchemeIs(url::kDataScheme); |
| } |
| |
| // Determines whether it is safe to redirect from |from_url| to |to_url|. |
| bool IsRedirectSafe(const GURL& from_url, |
| const GURL& to_url, |
| ResourceContext* resource_context) { |
| return IsSafeRedirectTarget(from_url, to_url) && |
| GetContentClient()->browser()->IsSafeRedirectTarget(to_url, |
| resource_context); |
| } |
| |
| // URLLoaderFactory for handling about: URLs. This treats everything as |
| // about:blank since no other about: features should be available to web |
| // content. |
| class AboutURLLoaderFactory : public network::mojom::URLLoaderFactory { |
| private: |
| // network::mojom::URLLoaderFactory: |
| void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader, |
| int32_t routing_id, |
| int32_t request_id, |
| uint32_t options, |
| const network::ResourceRequest& request, |
| network::mojom::URLLoaderClientPtr client, |
| const net::MutableNetworkTrafficAnnotationTag& |
| traffic_annotation) override { |
| network::ResourceResponseHead response_head; |
| response_head.mime_type = "text/html"; |
| client->OnReceiveResponse(response_head); |
| |
| // Create a data pipe for transmitting the empty response. The |producer| |
| // doesn't add any data. |
| mojo::ScopedDataPipeProducerHandle producer; |
| mojo::ScopedDataPipeConsumerHandle consumer; |
| if (CreateDataPipe(nullptr, &producer, &consumer) != MOJO_RESULT_OK) { |
| client->OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES)); |
| return; |
| } |
| |
| client->OnStartLoadingResponseBody(std::move(consumer)); |
| client->OnComplete(network::URLLoaderCompletionStatus(net::OK)); |
| } |
| |
| void Clone(network::mojom::URLLoaderFactoryRequest loader) override { |
| bindings_.AddBinding(this, std::move(loader)); |
| } |
| |
| mojo::BindingSet<network::mojom::URLLoaderFactory> bindings_; |
| }; |
| |
| // Creates a URLLoaderFactory that uses |header_client|. This should have the |
| // same settings as the factory from the URLLoaderFactoryGetter. |
| std::unique_ptr<network::SharedURLLoaderFactoryInfo> |
| CreateNetworkFactoryInfoWithHeaderClient( |
| network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client, |
| StoragePartitionImpl* partition) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| network::mojom::URLLoaderFactoryPtrInfo factory_info; |
| network::mojom::URLLoaderFactoryParamsPtr params = |
| network::mojom::URLLoaderFactoryParams::New(); |
| params->header_client = std::move(header_client); |
| params->process_id = network::mojom::kBrowserProcessId; |
| params->is_corb_enabled = false; |
| params->disable_web_security = |
| base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableWebSecurity); |
| auto factory_request = mojo::MakeRequest(&factory_info); |
| |
| if (g_loader_factory_interceptor.Get()) |
| g_loader_factory_interceptor.Get().Run(&factory_request); |
| |
| partition->GetNetworkContext()->CreateURLLoaderFactory( |
| std::move(factory_request), std::move(params)); |
| return std::make_unique<network::WrapperSharedURLLoaderFactoryInfo>( |
| std::move(factory_info)); |
| } |
| |
| } // 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 NavigationLoaderInterceptors and successively calls MaybeCreateLoader |
| // on each until the request is successfully handled. The same sequence |
| // may be performed multiple times when redirects happen. |
| // TODO(michaeln): Expose this class and add more unittests. |
| class NavigationURLLoaderImpl::URLLoaderRequestController |
| : public network::mojom::URLLoaderClient { |
| public: |
| URLLoaderRequestController( |
| std::vector<std::unique_ptr<NavigationLoaderInterceptor>> |
| initial_interceptors, |
| std::unique_ptr<network::ResourceRequest> resource_request, |
| ResourceContext* resource_context, |
| const GURL& url, |
| bool is_main_frame, |
| network::mojom::URLLoaderFactoryRequest proxied_factory_request, |
| network::mojom::URLLoaderFactoryPtrInfo proxied_factory_info, |
| std::set<std::string> known_schemes, |
| bool bypass_redirect_checks, |
| const base::WeakPtr<NavigationURLLoaderImpl>& owner) |
| : interceptors_(std::move(initial_interceptors)), |
| resource_request_(std::move(resource_request)), |
| resource_context_(resource_context), |
| url_(url), |
| is_main_frame_(is_main_frame), |
| owner_(owner), |
| response_loader_binding_(this), |
| proxied_factory_request_(std::move(proxied_factory_request)), |
| proxied_factory_info_(std::move(proxied_factory_info)), |
| known_schemes_(std::move(known_schemes)), |
| bypass_redirect_checks_(bypass_redirect_checks), |
| weak_factory_(this) {} |
| |
| ~URLLoaderRequestController() override { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // If neither OnCompleted nor OnReceivedResponse has been invoked, the |
| // request was canceled before receiving a response, so log a cancellation. |
| // Results after receiving a non-error response are logged in the renderer, |
| // if the request is passed to one. If it's a download, or not passed to a |
| // renderer for some other reason, results will not be logged for the |
| // request. The net::OK check may not be necessary - the case where OK is |
| // received without receiving any headers looks broken, anyways. |
| if (!received_response_ && (!status_ || status_->error_code != net::OK)) { |
| RecordLoadHistograms(url_, resource_request_->resource_type, |
| status_ ? status_->error_code : net::ERR_ABORTED); |
| } |
| } |
| |
| static uint32_t GetURLLoaderOptions(bool is_main_frame) { |
| uint32_t options = network::mojom::kURLLoadOptionNone; |
| |
| // Ensure that Mime sniffing works. |
| options |= network::mojom::kURLLoadOptionSniffMimeType; |
| |
| if (is_main_frame) { |
| // SSLInfo is not needed on subframe responses because users can inspect |
| // only the certificate for the main frame when using the info bubble. |
| options |= network::mojom::kURLLoadOptionSendSSLInfoWithResponse; |
| options |= network::mojom::kURLLoadOptionSendSSLInfoForCertificateError; |
| } |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| // TODO(arthursonzogni): This is a temporary option. Remove this as soon |
| // as the InterceptingResourceHandler is removed. |
| // See https://crbug.com/791049. |
| options |= network::mojom::kURLLoadOptionPauseOnResponseStarted; |
| } |
| |
| return options; |
| } |
| |
| SingleRequestURLLoaderFactory::RequestHandler |
| CreateDefaultRequestHandlerForNonNetworkService( |
| net::URLRequestContextGetter* url_request_context_getter, |
| storage::FileSystemContext* upload_file_system_context, |
| ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core, |
| AppCacheNavigationHandleCore* appcache_handle_core, |
| bool was_request_intercepted) const { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| DCHECK(started_); |
| |
| return base::BindOnce( |
| &URLLoaderRequestController::CreateNonNetworkServiceURLLoader, |
| weak_factory_.GetWeakPtr(), |
| base::Unretained(url_request_context_getter), |
| base::Unretained(upload_file_system_context), |
| // If the request has already been intercepted, the request should not |
| // be intercepted again. |
| // S13nServiceWorker: Requests are intercepted by S13nServiceWorker |
| // before the default request handler when needed, so we never need to |
| // pass |service_worker_navigation_handle_core| here. |
| base::Unretained( |
| blink::ServiceWorkerUtils::IsServicificationEnabled() || |
| was_request_intercepted |
| ? nullptr |
| : service_worker_navigation_handle_core), |
| base::Unretained(was_request_intercepted ? nullptr |
| : appcache_handle_core)); |
| } |
| |
| void CreateNonNetworkServiceURLLoader( |
| net::URLRequestContextGetter* url_request_context_getter, |
| storage::FileSystemContext* upload_file_system_context, |
| ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core, |
| AppCacheNavigationHandleCore* appcache_handle_core, |
| const network::ResourceRequest& /* resource_request */, |
| network::mojom::URLLoaderRequest url_loader, |
| network::mojom::URLLoaderClientPtr url_loader_client) { |
| // |resource_request| is unused here. We don't propagate the fields to |
| // |request_info_| here because the request will usually go to |
| // ResourceDispatcherHost which does its own request modifications. |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| DCHECK(started_); |
| |
| default_loader_used_ = true; |
| uint32_t options = GetURLLoaderOptions(request_info_->is_main_frame); |
| |
| // A URLLoaderThrottle may have changed the headers. |
| request_info_->begin_params->headers = |
| resource_request_->headers.ToString(); |
| request_info_->begin_params->load_flags = resource_request_->load_flags; |
| |
| bool intercepted = false; |
| if (g_interceptor.Get()) { |
| // Recreate the ResourceRequest for the interceptor, in case a |
| // URLLoaderThrottle had changed request_info_. |
| auto latest_resource_request = |
| CreateResourceRequest(request_info_.get(), frame_tree_node_id_, |
| resource_request_->allow_download); |
| latest_resource_request->headers.AddHeadersFromString( |
| request_info_->begin_params->headers); |
| intercepted = g_interceptor.Get().Run( |
| &url_loader, frame_tree_node_id_, 0 /* request_id */, options, |
| *latest_resource_request, &url_loader_client, |
| net::MutableNetworkTrafficAnnotationTag( |
| kNavigationUrlLoaderTrafficAnnotation)); |
| } |
| |
| // The ResourceDispatcherHostImpl can be null in unit tests. |
| ResourceDispatcherHostImpl* rdh = ResourceDispatcherHostImpl::Get(); |
| if (!intercepted && rdh) { |
| rdh->BeginNavigationRequest( |
| resource_context_, url_request_context_getter->GetURLRequestContext(), |
| upload_file_system_context, *request_info_, |
| std::move(navigation_ui_data_), std::move(url_loader_client), |
| std::move(url_loader), service_worker_navigation_handle_core, |
| appcache_handle_core, options, resource_request_->priority, |
| global_request_id_); |
| |
| if (!blink::ServiceWorkerUtils::IsServicificationEnabled()) { |
| // Get the SWProviderHost for non-S13nSW path. For S13nSW path, |
| // |service_worker_provider_host_| must be set in |
| // CreateServiceWorkerInterceptor(). |
| net::URLRequest* url_request = rdh->GetURLRequest(global_request_id_); |
| ServiceWorkerProviderHost* service_worker_provider_host = |
| ServiceWorkerRequestHandler::GetProviderHost(url_request); |
| if (service_worker_provider_host) { |
| service_worker_provider_host_ = |
| service_worker_provider_host->AsWeakPtr(); |
| } |
| } |
| } |
| |
| // TODO(arthursonzogni): Detect when the ResourceDispatcherHost didn't |
| // create a URLLoader. When it doesn't, do not send OnRequestStarted(). |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&NavigationURLLoaderImpl::OnRequestStarted, owner_, |
| base::TimeTicks::Now())); |
| } |
| |
| void StartWithoutNetworkService( |
| net::URLRequestContextGetter* url_request_context_getter, |
| storage::FileSystemContext* upload_file_system_context, |
| ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core, |
| AppCacheNavigationHandleCore* appcache_handle_core, |
| scoped_refptr<SignedExchangePrefetchMetricRecorder> |
| signed_exchange_prefetch_metric_recorder, |
| std::unique_ptr<NavigationRequestInfo> request_info, |
| std::unique_ptr<NavigationUIData> navigation_ui_data) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| DCHECK(!started_); |
| started_ = true; |
| request_info_ = std::move(request_info); |
| frame_tree_node_id_ = request_info_->frame_tree_node_id; |
| web_contents_getter_ = base::BindRepeating( |
| &GetWebContentsFromFrameTreeNodeID, frame_tree_node_id_); |
| navigation_ui_data_ = std::move(navigation_ui_data); |
| // The ResourceDispatcherHostImpl can be null in unit tests. |
| ResourceDispatcherHostImpl* rph = ResourceDispatcherHostImpl::Get(); |
| if (rph) |
| global_request_id_ = rph->MakeGlobalRequestID(); |
| |
| default_request_handler_factory_ = base::BindRepeating( |
| &URLLoaderRequestController:: |
| CreateDefaultRequestHandlerForNonNetworkService, |
| // base::Unretained(this) is safe since |
| // |default_request_handler_factory_| could be called only from |this|. |
| base::Unretained(this), base::Unretained(url_request_context_getter), |
| base::Unretained(upload_file_system_context), |
| base::Unretained(service_worker_navigation_handle_core), |
| base::Unretained(appcache_handle_core)); |
| |
| StartInternal(request_info_.get(), service_worker_navigation_handle_core, |
| nullptr /* appcache_handle_core */, |
| std::move(signed_exchange_prefetch_metric_recorder), |
| {} /* factory_for_webui */, url_request_context_getter); |
| } |
| |
| void Start( |
| std::unique_ptr<network::SharedURLLoaderFactoryInfo> |
| network_loader_factory_info, |
| ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core, |
| AppCacheNavigationHandleCore* appcache_handle_core, |
| scoped_refptr<SignedExchangePrefetchMetricRecorder> |
| signed_exchange_prefetch_metric_recorder, |
| std::unique_ptr<NavigationRequestInfo> request_info, |
| std::unique_ptr<NavigationUIData> navigation_ui_data, |
| network::mojom::URLLoaderFactoryPtrInfo factory_for_webui, |
| int frame_tree_node_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| DCHECK(!started_); |
| global_request_id_ = MakeGlobalRequestID(); |
| frame_tree_node_id_ = frame_tree_node_id; |
| started_ = true; |
| web_contents_getter_ = |
| base::Bind(&GetWebContentsFromFrameTreeNodeID, frame_tree_node_id); |
| navigation_ui_data_ = std::move(navigation_ui_data); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&NavigationURLLoaderImpl::OnRequestStarted, owner_, |
| base::TimeTicks::Now())); |
| |
| DCHECK(network_loader_factory_info); |
| network_loader_factory_ = network::SharedURLLoaderFactory::Create( |
| std::move(network_loader_factory_info)); |
| |
| if (resource_request_->request_body) { |
| GetBodyBlobDataHandles(resource_request_->request_body.get(), |
| resource_context_, &blob_handles_); |
| } |
| |
| StartInternal(request_info.get(), service_worker_navigation_handle_core, |
| appcache_handle_core, |
| std::move(signed_exchange_prefetch_metric_recorder), |
| std::move(factory_for_webui), |
| nullptr /* url_request_context_getter */); |
| } |
| |
| // Common setup routines, called by both StartWithoutNetworkService() and |
| // Start(). Most parameters (except for |request_info| and |
| // |url_request_context_getter|) are for setting up feature-specific |
| // loaders and interceptors, and they can be null depending on the flags. |
| // |url_request_context_getter| is non-null only for non-NetworkService |
| // code paths. |
| // TODO(kinuko): Merge this back to Start() once NetworkService is fully |
| // shipped. |
| void StartInternal( |
| NavigationRequestInfo* request_info, |
| ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core, |
| AppCacheNavigationHandleCore* appcache_handle_core, |
| scoped_refptr<SignedExchangePrefetchMetricRecorder> |
| signed_exchange_prefetch_metric_recorder, |
| network::mojom::URLLoaderFactoryPtrInfo factory_for_webui, |
| net::URLRequestContextGetter* url_request_context_getter) { |
| // NetworkService cases only. |
| // 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()) { |
| DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart( |
| base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>( |
| std::move(factory_for_webui)), |
| CreateURLLoaderThrottles(), 0 /* routing_id */, |
| global_request_id_.request_id, network::mojom::kURLLoadOptionNone, |
| resource_request_.get(), this, kNavigationUrlLoaderTrafficAnnotation, |
| base::ThreadTaskRunnerHandle::Get()); |
| return; |
| } |
| |
| // Requests to Blob scheme won't get redirected to/from other schemes |
| // or be intercepted, so we just let it go here. |
| if (request_info->common_params.url.SchemeIsBlob() && |
| request_info->blob_url_loader_factory) { |
| url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart( |
| network::SharedURLLoaderFactory::Create( |
| std::move(request_info->blob_url_loader_factory)), |
| CreateURLLoaderThrottles(), 0 /* routing_id */, |
| global_request_id_.request_id, network::mojom::kURLLoadOptionNone, |
| resource_request_.get(), this, kNavigationUrlLoaderTrafficAnnotation, |
| base::ThreadTaskRunnerHandle::Get()); |
| return; |
| } |
| |
| // Set-up an interceptor for service workers if S13nSW is enabled and |
| // non-null |service_worker_navigation_handle_core| is given. |
| if (service_worker_navigation_handle_core && |
| blink::ServiceWorkerUtils::IsServicificationEnabled()) { |
| std::unique_ptr<NavigationLoaderInterceptor> service_worker_interceptor = |
| CreateServiceWorkerInterceptor(*request_info, |
| service_worker_navigation_handle_core); |
| // The interceptor for service worker may not be created for some reasons |
| // (e.g. the origin is not secure). |
| if (service_worker_interceptor) |
| interceptors_.push_back(std::move(service_worker_interceptor)); |
| } |
| |
| // NetworkService cases only. |
| // Set-up an interceptor for AppCache if non-null |appcache_handle_core| |
| // is given. |
| if (appcache_handle_core) { |
| DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| std::unique_ptr<NavigationLoaderInterceptor> appcache_interceptor = |
| AppCacheRequestHandler::InitializeForMainResourceNetworkService( |
| *resource_request_, appcache_handle_core->host()->GetWeakPtr()); |
| if (appcache_interceptor) |
| interceptors_.push_back(std::move(appcache_interceptor)); |
| } |
| |
| // Set-up an interceptor for SignedExchange handling if it is enabled. |
| if (signed_exchange_utils::IsSignedExchangeHandlingEnabled()) { |
| auto network_loader_factory = network_loader_factory_; |
| if (!network_loader_factory) { |
| DCHECK( |
| !base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| DCHECK(url_request_context_getter); |
| network_loader_factory = base::MakeRefCounted< |
| SignedExchangeURLLoaderFactoryForNonNetworkService>( |
| resource_context_, url_request_context_getter); |
| } |
| interceptors_.push_back(CreateSignedExchangeRequestHandler( |
| *request_info, std::move(network_loader_factory), |
| std::move(signed_exchange_prefetch_metric_recorder))); |
| } |
| |
| // See if embedders want to add interceptors. |
| std::vector<std::unique_ptr<URLLoaderRequestInterceptor>> |
| browser_interceptors = GetContentClient() |
| ->browser() |
| ->WillCreateURLLoaderRequestInterceptors( |
| navigation_ui_data_.get(), |
| request_info->frame_tree_node_id); |
| if (!browser_interceptors.empty()) { |
| for (auto& browser_interceptor : browser_interceptors) { |
| interceptors_.push_back( |
| std::make_unique<NavigationLoaderInterceptorBrowserContainer>( |
| std::move(browser_interceptor))); |
| } |
| } |
| |
| // Non-NetworkService cases only. |
| // If an interceptor is not created, we no longer have to go through the |
| // rest of the network service code. |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService) && |
| interceptors_.empty()) { |
| DCHECK(default_request_handler_factory_); |
| url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart( |
| base::MakeRefCounted<SingleRequestURLLoaderFactory>( |
| default_request_handler_factory_.Run( |
| false /* was_request_intercepted */)), |
| CreateURLLoaderThrottles(), -1 /* routing_id */, 0 /* request_id */, |
| network::mojom::kURLLoadOptionNone, resource_request_.get(), |
| this /* client */, kNavigationUrlLoaderTrafficAnnotation, |
| base::ThreadTaskRunnerHandle::Get()); |
| return; |
| } |
| |
| Restart(); |
| } |
| |
| // This could be called multiple times to follow a chain of redirects. |
| void Restart() { |
| DCHECK(IsLoaderInterceptionEnabled()); |
| // Clear |url_loader_| if it's not the default one (network). This allows |
| // the restarted request to use a new loader, instead of, e.g., reusing the |
| // AppCache or service worker loader. For an optimization, we keep and reuse |
| // the default url loader if the all |interceptors_| doesn't handle the |
| // redirected request. If the network service is enabled, only certain |
| // schemes are handled by the default URL loader. We need to make sure the |
| // redirected URL is a handled scheme, otherwise reset the loader so the |
| // correct non-network service loader can be used. |
| if (!default_loader_used_ || |
| (base::FeatureList::IsEnabled(network::features::kNetworkService) && |
| !IsURLHandledByDefaultLoader(resource_request_->url))) { |
| url_loader_.reset(); |
| } |
| interceptor_index_ = 0; |
| received_response_ = false; |
| MaybeStartLoader(nullptr /* interceptor */, |
| {} /* single_request_handler */); |
| } |
| |
| // |interceptor| is non-null if this is called by one of the interceptors |
| // (via a LoaderCallback). |
| // |single_request_handler| is the RequestHandler given by the |interceptor|, |
| // non-null if the interceptor wants to handle the request. |
| void MaybeStartLoader( |
| NavigationLoaderInterceptor* interceptor, |
| SingleRequestURLLoaderFactory::RequestHandler single_request_handler) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(IsLoaderInterceptionEnabled()); |
| DCHECK(started_); |
| |
| if (single_request_handler) { |
| // |interceptor| wants to handle the request with |
| // |single_request_handler|. |
| DCHECK(interceptor); |
| |
| std::vector<std::unique_ptr<URLLoaderThrottle>> throttles = |
| CreateURLLoaderThrottles(); |
| // Intercepted requests need MimeSniffingThrottle to do mime sniffing. |
| // Non-intercepted requests usually go through the regular network |
| // URLLoader, which does mime sniffing. |
| throttles.push_back(std::make_unique<MimeSniffingThrottle>()); |
| |
| default_loader_used_ = false; |
| url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart( |
| base::MakeRefCounted<SingleRequestURLLoaderFactory>( |
| std::move(single_request_handler)), |
| std::move(throttles), frame_tree_node_id_, |
| global_request_id_.request_id, network::mojom::kURLLoadOptionNone, |
| resource_request_.get(), this, kNavigationUrlLoaderTrafficAnnotation, |
| base::ThreadTaskRunnerHandle::Get()); |
| |
| subresource_loader_params_ = |
| interceptor->MaybeCreateSubresourceLoaderParams(); |
| |
| return; |
| } |
| |
| // Before falling back to the next interceptor, see if |interceptor| still |
| // wants to give additional info to the frame for subresource loading. In |
| // that case we will just fall back to the default loader (i.e. won't go on |
| // to the next interceptors) but send the subresource_loader_params to the |
| // child process. This is necessary for correctness in the cases where, e.g. |
| // there's a controlling service worker that doesn't have a fetch event |
| // handler so it doesn't intercept requests. In that case we still want to |
| // skip AppCache. |
| if (interceptor) { |
| subresource_loader_params_ = |
| interceptor->MaybeCreateSubresourceLoaderParams(); |
| |
| // If non-null |subresource_loader_params_| is returned, make sure |
| // we skip the next interceptors. |
| if (subresource_loader_params_) |
| interceptor_index_ = interceptors_.size(); |
| } |
| |
| // See if the next interceptor wants to handle the request. |
| if (interceptor_index_ < interceptors_.size()) { |
| auto* next_interceptor = interceptors_[interceptor_index_++].get(); |
| next_interceptor->MaybeCreateLoader( |
| *resource_request_, resource_context_, |
| base::BindOnce(&URLLoaderRequestController::MaybeStartLoader, |
| base::Unretained(this), next_interceptor), |
| base::BindOnce( |
| &URLLoaderRequestController::FallbackToNonInterceptedRequest, |
| base::Unretained(this))); |
| return; |
| } |
| |
| // If we already have the default |url_loader_| we must come here after a |
| // redirect. No interceptors wanted to intercept the redirected request, so |
| // let the loader just follow the redirect. |
| if (url_loader_) { |
| DCHECK(!redirect_info_.new_url.is_empty()); |
| url_loader_->FollowRedirect( |
| std::move(url_loader_modified_request_headers_)); |
| return; |
| } |
| |
| // No interceptors wanted to handle this request. |
| uint32_t options = network::mojom::kURLLoadOptionNone; |
| scoped_refptr<network::SharedURLLoaderFactory> factory = |
| PrepareForNonInterceptedRequest(&options); |
| url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart( |
| std::move(factory), CreateURLLoaderThrottles(), frame_tree_node_id_, |
| global_request_id_.request_id, options, resource_request_.get(), |
| this /* client */, kNavigationUrlLoaderTrafficAnnotation, |
| base::ThreadTaskRunnerHandle::Get()); |
| } |
| |
| // This is the |fallback_callback| passed to |
| // NavigationLoaderInterceptor::MaybeCreateLoader. It allows an interceptor |
| // to initially elect to handle a request, and later decide to fallback to |
| // the default behavior. This is needed for service worker network fallback |
| // and signed exchange (SXG) fallback redirect. |
| void FallbackToNonInterceptedRequest(bool reset_subresource_loader_params) { |
| if (reset_subresource_loader_params) |
| subresource_loader_params_.reset(); |
| |
| // Non-NetworkService: |
| // Cancel state on ResourceDispatcherHostImpl so it doesn't complain about |
| // reusing the request_id after redirects. Otherwise the following sequence |
| // can happen: |
| // case 1. RDHI Start(request_id) -> Redirect -> SW interception -> SW |
| // fallback to network -> RDHI Start(request_id). |
| // case 2. RDHI Start(request_id) -> SXG interception -> SXG fallback to |
| // network -> RDHI Start(request_id). |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| DCHECK(ResourceDispatcherHostImpl::Get()); |
| ResourceDispatcherHostImpl::Get()->CancelRequest( |
| global_request_id_.child_id, global_request_id_.request_id); |
| } |
| |
| uint32_t options = network::mojom::kURLLoadOptionNone; |
| scoped_refptr<network::SharedURLLoaderFactory> factory = |
| PrepareForNonInterceptedRequest(&options); |
| if (url_loader_) { |
| // |url_loader_| is using the factory for the interceptor that decided to |
| // fallback, so restart it with the non-interceptor factory. |
| url_loader_->RestartWithFactory(std::move(factory), options); |
| } else { |
| // In SXG cases we don't have |url_loader_| because it was reset when the |
| // SXG interceptor intercepted the response in |
| // MaybeCreateLoaderForResponse. |
| DCHECK(response_loader_binding_); |
| response_loader_binding_.Close(); |
| url_loader_ = ThrottlingURLLoader::CreateLoaderAndStart( |
| std::move(factory), CreateURLLoaderThrottles(), frame_tree_node_id_, |
| global_request_id_.request_id, options, resource_request_.get(), |
| this /* client */, kNavigationUrlLoaderTrafficAnnotation, |
| base::ThreadTaskRunnerHandle::Get()); |
| } |
| } |
| |
| scoped_refptr<network::SharedURLLoaderFactory> |
| PrepareForNonInterceptedRequest(uint32_t* out_options) { |
| // If NetworkService is not enabled (which means we come here because one of |
| // the loader interceptors is enabled), use the default request handler |
| // instead of going through the NetworkService path. |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| DCHECK(!interceptors_.empty()); |
| DCHECK(default_request_handler_factory_); |
| // The only way to come here is to enable ServiceWorkerServicification or |
| // SignedExchange without NetworkService. We know that their request |
| // interceptors have already intercepted and decided not to handle the |
| // request. |
| DCHECK(blink::ServiceWorkerUtils::IsServicificationEnabled() || |
| signed_exchange_utils::IsSignedExchangeHandlingEnabled()); |
| default_loader_used_ = true; |
| // Update |request_info_| when following a redirect. |
| if (url_chain_.size() > 0) { |
| request_info_ = CreateNavigationRequestInfoForRedirect( |
| *request_info_, *resource_request_); |
| } |
| |
| // When |subresource_loader_params_| has its value, the request should not |
| // be intercepted by any other interceptors since it means that a request |
| // interceptor already intercepted the request and it attached its info to |
| // the request. |
| bool was_request_intercepted = subresource_loader_params_.has_value(); |
| |
| // TODO(falken): Determine whether GetURLLoaderOptions() can be called |
| // here like below. It looks like |default_request_handler_factory_| just |
| // calls that. |
| *out_options = network::mojom::kURLLoadOptionNone; |
| return base::MakeRefCounted<SingleRequestURLLoaderFactory>( |
| default_request_handler_factory_.Run(was_request_intercepted)); |
| } |
| |
| // TODO(https://crbug.com/796425): We temporarily wrap raw |
| // mojom::URLLoaderFactory pointers into SharedURLLoaderFactory. Need to |
| // further refactor the factory getters to avoid this. |
| scoped_refptr<network::SharedURLLoaderFactory> factory; |
| |
| if (!IsURLHandledByDefaultLoader(resource_request_->url)) { |
| if (known_schemes_.find(resource_request_->url.scheme()) == |
| known_schemes_.end()) { |
| bool handled = GetContentClient()->browser()->HandleExternalProtocol( |
| resource_request_->url, web_contents_getter_, |
| ChildProcessHost::kInvalidUniqueID, navigation_ui_data_.get(), |
| resource_request_->resource_type == RESOURCE_TYPE_MAIN_FRAME, |
| static_cast<ui::PageTransition>(resource_request_->transition_type), |
| resource_request_->has_user_gesture, resource_request_->method, |
| resource_request_->headers); |
| factory = base::MakeRefCounted<SingleRequestURLLoaderFactory>( |
| base::BindOnce(UnknownSchemeCallback, handled)); |
| } else { |
| network::mojom::URLLoaderFactoryPtr& non_network_factory = |
| non_network_url_loader_factories_[resource_request_->url.scheme()]; |
| if (!non_network_factory.is_bound()) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&NavigationURLLoaderImpl :: |
| BindNonNetworkURLLoaderFactoryRequest, |
| owner_, frame_tree_node_id_, |
| resource_request_->url, |
| mojo::MakeRequest(&non_network_factory))); |
| } |
| factory = |
| base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>( |
| non_network_factory.get()); |
| } |
| } else { |
| default_loader_used_ = true; |
| |
| // NOTE: We only support embedders proxying network-service-bound requests |
| // not handled by NavigationLoaderInterceptors above (e.g. Service Worker |
| // or AppCache). Hence this code is only reachable when one of the above |
| // interceptors isn't used and the URL is either a data URL or has a |
| // scheme which is handled by the network service. We explicitly avoid |
| // proxying the data URL case here. |
| if (proxied_factory_request_.is_pending() && |
| !resource_request_->url.SchemeIs(url::kDataScheme)) { |
| DCHECK(proxied_factory_info_.is_valid()); |
| // We don't worry about reconnection since it's a single navigation. |
| network_loader_factory_->Clone(std::move(proxied_factory_request_)); |
| factory = base::MakeRefCounted<network::WrapperSharedURLLoaderFactory>( |
| std::move(proxied_factory_info_)); |
| } else { |
| factory = network_loader_factory_; |
| } |
| } |
| url_chain_.push_back(resource_request_->url); |
| *out_options = GetURLLoaderOptions(resource_request_->resource_type == |
| RESOURCE_TYPE_MAIN_FRAME); |
| return factory; |
| } |
| |
| void FollowRedirect( |
| const base::Optional<net::HttpRequestHeaders>& modified_request_headers) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| DCHECK(!redirect_info_.new_url.is_empty()); |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| auto* common_params = |
| const_cast<CommonNavigationParams*>(&request_info_->common_params); |
| common_params->url = redirect_info_.new_url; |
| common_params->referrer.url = GURL(redirect_info_.new_referrer); |
| common_params->method = redirect_info_.new_method; |
| } |
| |
| if (!IsLoaderInterceptionEnabled()) { |
| url_loader_->FollowRedirect(modified_request_headers); |
| return; |
| } |
| |
| // Update |resource_request_| and call Restart to give our |interceptors_| a |
| // chance at handling the new location. If no interceptor wants to take |
| // over, we'll use the existing url_loader to follow the redirect, see |
| // MaybeStartLoader. |
| // TODO(michaeln): This is still WIP and is based on URLRequest::Redirect, |
| // there likely remains more to be done. |
| // a. For subframe navigations, the Origin header may need to be modified |
| // differently? |
| |
| bool should_clear_upload = false; |
| net::RedirectUtil::UpdateHttpRequest( |
| resource_request_->url, resource_request_->method, redirect_info_, |
| modified_request_headers, &resource_request_->headers, |
| &should_clear_upload); |
| if (should_clear_upload) { |
| // The request body is no longer applicable. |
| resource_request_->request_body = nullptr; |
| blob_handles_.clear(); |
| } |
| |
| resource_request_->url = redirect_info_.new_url; |
| resource_request_->method = redirect_info_.new_method; |
| resource_request_->site_for_cookies = redirect_info_.new_site_for_cookies; |
| resource_request_->top_frame_origin = redirect_info_.new_top_frame_origin; |
| resource_request_->referrer = GURL(redirect_info_.new_referrer); |
| resource_request_->referrer_policy = redirect_info_.new_referrer_policy; |
| url_chain_.push_back(redirect_info_.new_url); |
| |
| // Need to cache modified headers for |url_loader_| since it doesn't use |
| // |resource_request_| during redirect. |
| url_loader_modified_request_headers_ = modified_request_headers; |
| |
| if (signed_exchange_utils::NeedToCheckRedirectedURLForAcceptHeader()) { |
| // Currently we send the SignedExchange accept header only for the limited |
| // origins when SignedHTTPExchangeOriginTrial feature is enabled without |
| // SignedHTTPExchange feature. We need to put the SignedExchange accept |
| // header on when redirecting to the origins in the OriginList of |
| // SignedHTTPExchangeAcceptHeader field trial, and need to remove it when |
| // redirecting to out of the OriginList. |
| if (!url_loader_modified_request_headers_) |
| url_loader_modified_request_headers_ = net::HttpRequestHeaders(); |
| std::string accept_value = network::kFrameAcceptHeader; |
| if (signed_exchange_utils::ShouldAdvertiseAcceptHeader( |
| url::Origin::Create(resource_request_->url))) { |
| DCHECK(!accept_value.empty()); |
| accept_value.append(kAcceptHeaderSignedExchangeSuffix); |
| } |
| url_loader_modified_request_headers_->SetHeader(network::kAcceptHeader, |
| accept_value); |
| resource_request_->headers.SetHeader(network::kAcceptHeader, |
| accept_value); |
| } |
| |
| Restart(); |
| } |
| |
| base::Optional<SubresourceLoaderParams> TakeSubresourceLoaderParams() { |
| return std::move(subresource_loader_params_); |
| } |
| |
| private: |
| // network::mojom::URLLoaderClient implementation: |
| void OnReceiveResponse(const network::ResourceResponseHead& head) override { |
| // Record the SCT histogram before checking if anything wants to intercept |
| // the response, so interceptors like AppCache and extensions can't hide |
| // values from the histograms. |
| RecordSCTHistogramIfNeeded(head.ssl_info); |
| |
| received_response_ = true; |
| |
| // If the default loader (network) was used to handle the URL load request |
| // we need to see if the interceptors want to potentially create a new |
| // loader for the response. e.g. AppCache. |
| if (MaybeCreateLoaderForResponse(head)) |
| return; |
| |
| network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints; |
| |
| if (url_loader_) { |
| url_loader_client_endpoints = url_loader_->Unbind(); |
| } else { |
| url_loader_client_endpoints = |
| network::mojom::URLLoaderClientEndpoints::New( |
| response_url_loader_.PassInterface(), |
| response_loader_binding_.Unbind()); |
| } |
| |
| // 304 responses should abort the navigation, rather than display the page. |
| // This needs to be after the URLLoader has been moved to |
| // |url_loader_client_endpoints| in order to abort the request, to avoid |
| // receiving unexpected call. |
| if (head.headers && |
| head.headers->response_code() == net::HTTP_NOT_MODIFIED) { |
| OnComplete(network::URLLoaderCompletionStatus(net::ERR_ABORTED)); |
| return; |
| } |
| |
| bool is_download; |
| bool is_stream; |
| |
| std::unique_ptr<NavigationData> cloned_navigation_data; |
| |
| if (IsLoaderInterceptionEnabled()) { |
| bool must_download = download_utils::MustDownload( |
| url_, head.headers.get(), head.mime_type); |
| bool known_mime_type = blink::IsSupportedMimeType(head.mime_type); |
| |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| if (!head.intercepted_by_plugin && !must_download && !known_mime_type) { |
| // No plugin throttles intercepted the response. Ask if the plugin |
| // registered to PluginService wants to handle the request. |
| CheckPluginAndContinueOnReceiveResponse( |
| head, std::move(url_loader_client_endpoints), |
| true /* is_download_if_not_handled_by_plugin */, |
| std::vector<WebPluginInfo>()); |
| return; |
| } |
| #endif |
| |
| // When a plugin intercepted the response, we don't want to download it. |
| is_download = |
| !head.intercepted_by_plugin && (must_download || !known_mime_type); |
| is_stream = false; |
| |
| // If NetworkService is on, or an interceptor handled the request, the |
| // request doesn't use ResourceDispatcherHost so |
| // CallOnReceivedResponse and return here. |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService) || |
| !default_loader_used_) { |
| CallOnReceivedResponse(head, std::move(url_loader_client_endpoints), |
| std::move(cloned_navigation_data), is_download, |
| is_stream); |
| return; |
| } |
| } |
| |
| // NetworkService is off and an interceptor didn't handle the request, |
| // so it went to ResourceDispatcherHost. |
| ResourceDispatcherHostImpl* rdh = ResourceDispatcherHostImpl::Get(); |
| net::URLRequest* url_request = rdh->GetURLRequest(global_request_id_); |
| |
| // The |url_request| maybe have been removed from the resource dispatcher |
| // host during the time it took for OnReceiveResponse() to be received. |
| if (url_request) { |
| ResourceRequestInfoImpl* info = |
| ResourceRequestInfoImpl::ForRequest(url_request); |
| is_download = !head.intercepted_by_plugin && info->IsDownload(); |
| is_stream = info->is_stream(); |
| if (rdh->delegate()) { |
| NavigationData* navigation_data = |
| rdh->delegate()->GetNavigationData(url_request); |
| |
| // Clone the embedder's NavigationData before moving it to the UI |
| // thread. |
| if (navigation_data) |
| cloned_navigation_data = navigation_data->Clone(); |
| } |
| |
| // non-S13nServiceWorker: |
| // This is similar to what is done in |
| // ServiceWorkerControlleeHandler::MaybeCreateSubresourceLoaderParams() |
| // (which is used when S13nServiceWorker is on). It takes the matching |
| // ControllerServiceWorkerInfo (if any) associated with the request. It |
| // will be sent to the renderer process and used to intercept requests. |
| ServiceWorkerProviderHost* sw_provider_host = |
| ServiceWorkerRequestHandler::GetProviderHost(url_request); |
| if (sw_provider_host && sw_provider_host->controller()) { |
| DCHECK(!blink::ServiceWorkerUtils::IsServicificationEnabled()); |
| subresource_loader_params_ = SubresourceLoaderParams(); |
| subresource_loader_params_->controller_service_worker_info = |
| blink::mojom::ControllerServiceWorkerInfo::New(); |
| subresource_loader_params_->controller_service_worker_info->mode = |
| sw_provider_host->GetControllerMode(); |
| base::WeakPtr<ServiceWorkerObjectHost> sw_object_host = |
| sw_provider_host->GetOrCreateServiceWorkerObjectHost( |
| sw_provider_host->controller()); |
| if (sw_object_host) { |
| subresource_loader_params_->controller_service_worker_object_host = |
| sw_object_host; |
| subresource_loader_params_->controller_service_worker_info |
| ->object_info = sw_object_host->CreateIncompleteObjectInfo(); |
| } |
| } |
| } else { |
| is_download = is_stream = false; |
| } |
| |
| CallOnReceivedResponse(head, std::move(url_loader_client_endpoints), |
| std::move(cloned_navigation_data), is_download, |
| is_stream); |
| } |
| |
| #if BUILDFLAG(ENABLE_PLUGINS) |
| void CheckPluginAndContinueOnReceiveResponse( |
| const network::ResourceResponseHead& head, |
| network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, |
| bool is_download_if_not_handled_by_plugin, |
| const std::vector<WebPluginInfo>& plugins) { |
| bool stale; |
| WebPluginInfo plugin; |
| // It's ok to pass -1 for the render process and frame ID since that's |
| // only used for plugin overridding. We don't actually care if we get an |
| // overridden plugin or not, since all we care about is the presence of a |
| // plugin. Note that this is what the MimeSniffingResourceHandler code |
| // path does as well for navigations. |
| bool has_plugin = PluginService::GetInstance()->GetPluginInfo( |
| -1 /* render_process_id */, -1 /* render_frame_id */, resource_context_, |
| resource_request_->url, url::Origin(), head.mime_type, |
| false /* allow_wildcard */, &stale, &plugin, nullptr); |
| |
| if (stale) { |
| // Refresh the plugins asynchronously. |
| PluginService::GetInstance()->GetPlugins(base::BindOnce( |
| &URLLoaderRequestController::CheckPluginAndContinueOnReceiveResponse, |
| weak_factory_.GetWeakPtr(), head, |
| std::move(url_loader_client_endpoints), |
| is_download_if_not_handled_by_plugin)); |
| return; |
| } |
| |
| bool is_download = !has_plugin && is_download_if_not_handled_by_plugin; |
| |
| CallOnReceivedResponse(head, std::move(url_loader_client_endpoints), |
| nullptr, is_download, false /* is_stream */); |
| } |
| #endif |
| |
| void CallOnReceivedResponse( |
| const network::ResourceResponseHead& head, |
| network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, |
| std::unique_ptr<NavigationData> cloned_navigation_data, |
| bool is_download, |
| bool is_stream) { |
| scoped_refptr<network::ResourceResponse> response( |
| new network::ResourceResponse()); |
| response->head = head; |
| |
| // Make a copy of the ResourceResponse before it is passed to another |
| // thread. |
| // |
| // TODO(davidben): This copy could be avoided if ResourceResponse weren't |
| // reference counted and the loader stack passed unique ownership of the |
| // response. https://crbug.com/416050 |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&NavigationURLLoaderImpl::OnReceiveResponse, owner_, |
| response->DeepCopy(), |
| std::move(url_loader_client_endpoints), |
| std::move(cloned_navigation_data), global_request_id_, |
| is_download, is_stream)); |
| } |
| |
| void OnReceiveRedirect(const net::RedirectInfo& redirect_info, |
| const network::ResourceResponseHead& head) override { |
| if (base::FeatureList::IsEnabled(network::features::kNetworkService) && |
| !bypass_redirect_checks_ && |
| !IsRedirectSafe(url_, redirect_info.new_url, resource_context_)) { |
| OnComplete(network::URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT)); |
| return; |
| } |
| |
| if (--redirect_limit_ == 0) { |
| OnComplete( |
| network::URLLoaderCompletionStatus(net::ERR_TOO_MANY_REDIRECTS)); |
| return; |
| } |
| |
| // Store the redirect_info for later use in FollowRedirect where we give |
| // our interceptors_ a chance to intercept the request for the new location. |
| redirect_info_ = redirect_info; |
| |
| scoped_refptr<network::ResourceResponse> response( |
| new network::ResourceResponse()); |
| response->head = head; |
| url_ = redirect_info.new_url; |
| |
| // Make a copy of the ResourceResponse before it is passed to another |
| // thread. |
| // |
| // TODO(davidben): This copy could be avoided if ResourceResponse weren't |
| // reference counted and the loader stack passed unique ownership of the |
| // response. https://crbug.com/416050 |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&NavigationURLLoaderImpl::OnReceiveRedirect, owner_, |
| redirect_info, response->DeepCopy())); |
| } |
| |
| 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) override { |
| // Not reached. At this point, the loader and client endpoints must have |
| // been unbound and forwarded to the renderer. |
| CHECK(false); |
| } |
| |
| void OnComplete(const network::URLLoaderCompletionStatus& status) override { |
| RecordSCTHistogramIfNeeded(status.ssl_info); |
| |
| UMA_HISTOGRAM_BOOLEAN( |
| "Navigation.URLLoaderNetworkService.OnCompleteHasSSLInfo", |
| status.ssl_info.has_value()); |
| if (status.ssl_info.has_value()) { |
| UMA_HISTOGRAM_MEMORY_KB( |
| "Navigation.URLLoaderNetworkService.OnCompleteCertificateChainsSize", |
| GetCertificateChainsSizeInKB(status.ssl_info.value())); |
| } |
| |
| if (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 interceptors want to potentially create a |
| // new loader for the response. e.g. AppCache. |
| if (MaybeCreateLoaderForResponse(network::ResourceResponseHead())) |
| return; |
| } |
| status_ = status; |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(&NavigationURLLoaderImpl::OnComplete, owner_, status)); |
| } |
| |
| // Returns true if an interceptor wants to handle the response, i.e. return a |
| // different response. For e.g. AppCache may have fallback content. |
| bool MaybeCreateLoaderForResponse( |
| const network::ResourceResponseHead& response) { |
| if (!IsLoaderInterceptionEnabled()) |
| return false; |
| |
| if (!default_loader_used_) |
| return false; |
| |
| for (size_t i = 0u; i < interceptors_.size(); ++i) { |
| NavigationLoaderInterceptor* interceptor = interceptors_[i].get(); |
| network::mojom::URLLoaderClientRequest response_client_request; |
| bool skip_other_interceptors = false; |
| if (interceptor->MaybeCreateLoaderForResponse( |
| url_, response, &response_url_loader_, &response_client_request, |
| url_loader_.get(), &skip_other_interceptors)) { |
| if (response_loader_binding_.is_bound()) |
| response_loader_binding_.Close(); |
| response_loader_binding_.Bind(std::move(response_client_request)); |
| default_loader_used_ = false; |
| url_loader_.reset(); |
| if (skip_other_interceptors) { |
| std::vector<std::unique_ptr<NavigationLoaderInterceptor>> |
| new_interceptors; |
| new_interceptors.push_back(std::move(interceptors_[i])); |
| new_interceptors.swap(interceptors_); |
| if (service_worker_provider_host_) { |
| // Reset the state of ServiceWorkerProviderHost. |
| // Currently we don't support Service Worker in Signed Exchange |
| // pages. The page will not be controlled by service workers. And |
| // Service Worker related APIs will fail with NoDocumentURL error. |
| // TODO(crbug/898733): Support SignedExchange loading and Service |
| // Worker integration. |
| service_worker_provider_host_->SetControllerRegistration( |
| nullptr, false /* notify_controllerchange */); |
| service_worker_provider_host_->UpdateUrls(GURL(), GURL()); |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::vector<std::unique_ptr<URLLoaderThrottle>> CreateURLLoaderThrottles() { |
| return GetContentClient()->browser()->CreateURLLoaderThrottles( |
| *resource_request_, resource_context_, web_contents_getter_, |
| navigation_ui_data_.get(), frame_tree_node_id_); |
| } |
| |
| std::unique_ptr<NavigationLoaderInterceptor> CreateServiceWorkerInterceptor( |
| const NavigationRequestInfo& request_info, |
| ServiceWorkerNavigationHandleCore* |
| service_worker_navigation_handle_core) { |
| const ResourceType resource_type = request_info.is_main_frame |
| ? RESOURCE_TYPE_MAIN_FRAME |
| : RESOURCE_TYPE_SUB_FRAME; |
| network::mojom::RequestContextFrameType frame_type = |
| request_info.is_main_frame |
| ? network::mojom::RequestContextFrameType::kTopLevel |
| : network::mojom::RequestContextFrameType::kNested; |
| storage::BlobStorageContext* blob_storage_context = GetBlobStorageContext( |
| GetChromeBlobStorageContextForResourceContext(resource_context_)); |
| return ServiceWorkerRequestHandler::InitializeForNavigationNetworkService( |
| resource_request_->url, 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_, &service_worker_provider_host_); |
| } |
| |
| std::unique_ptr<SignedExchangeRequestHandler> |
| CreateSignedExchangeRequestHandler( |
| const NavigationRequestInfo& request_info, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| scoped_refptr<SignedExchangePrefetchMetricRecorder> |
| signed_exchange_prefetch_metric_recorder) { |
| // It is safe to pass the callback of CreateURLLoaderThrottles with the |
| // unretained |this|, because the passed callback will be used by a |
| // SignedExchangeHandler which is indirectly owned by |this| until its |
| // header is verified and parsed, that's where the getter is used. |
| return std::make_unique<SignedExchangeRequestHandler>( |
| url::Origin::Create(request_info.common_params.url), |
| GetURLLoaderOptions(request_info.is_main_frame), |
| request_info.frame_tree_node_id, request_info.devtools_navigation_token, |
| request_info.devtools_frame_token, request_info.report_raw_headers, |
| request_info.begin_params->load_flags, std::move(url_loader_factory), |
| base::BindRepeating( |
| &URLLoaderRequestController::CreateURLLoaderThrottles, |
| base::Unretained(this)), |
| std::move(signed_exchange_prefetch_metric_recorder)); |
| } |
| |
| void RecordSCTHistogramIfNeeded( |
| const base::Optional<net::SSLInfo>& ssl_info) { |
| if (is_main_frame_ && url_.SchemeIsCryptographic() && |
| ssl_info.has_value()) { |
| int num_valid_scts = 0; |
| for (const auto& signed_certificate_timestamps : |
| ssl_info->signed_certificate_timestamps) { |
| if (signed_certificate_timestamps.status == net::ct::SCT_STATUS_OK) |
| ++num_valid_scts; |
| } |
| UMA_HISTOGRAM_COUNTS_100( |
| "Net.CertificateTransparency.MainFrameValidSCTCount", num_valid_scts); |
| } |
| } |
| |
| std::vector<std::unique_ptr<NavigationLoaderInterceptor>> interceptors_; |
| size_t interceptor_index_ = 0; |
| |
| std::unique_ptr<network::ResourceRequest> resource_request_; |
| // Non-NetworkService: |request_info_| is updated along with |
| // |resource_request_| on redirects. |
| std::unique_ptr<NavigationRequestInfo> request_info_; |
| int frame_tree_node_id_ = 0; |
| GlobalRequestID global_request_id_; |
| net::RedirectInfo redirect_info_; |
| int redirect_limit_ = net::URLRequest::kMaxRedirects; |
| ResourceContext* resource_context_; |
| base::Callback<WebContents*()> web_contents_getter_; |
| std::unique_ptr<NavigationUIData> navigation_ui_data_; |
| scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory_; |
| |
| std::unique_ptr<ThrottlingURLLoader> url_loader_; |
| |
| // Caches the modified request headers provided by clients during redirect, |
| // will be consumed by next |url_loader_->FollowRedirect()|. |
| base::Optional<net::HttpRequestHeaders> url_loader_modified_request_headers_; |
| |
| BlobHandles blob_handles_; |
| std::vector<GURL> url_chain_; |
| |
| // Current URL that is being navigated, updated after redirection. |
| GURL url_; |
| |
| const bool is_main_frame_; |
| |
| // Currently used by the AppCache loader to pass its factory to the |
| // renderer which enables it to handle subresources. |
| base::Optional<SubresourceLoaderParams> subresource_loader_params_; |
| |
| // This is referenced only on the UI thread. |
| base::WeakPtr<NavigationURLLoaderImpl> 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<network::mojom::URLLoaderClient> response_loader_binding_; |
| |
| // URLLoader instance for response loaders, i.e loaders created for handing |
| // responses received from the network URLLoader. |
| network::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; |
| |
| bool started_ = false; |
| |
| // Lazily initialized and used in the case of non-network resource |
| // navigations. Keyed by URL scheme. |
| std::map<std::string, network::mojom::URLLoaderFactoryPtr> |
| non_network_url_loader_factories_; |
| |
| // Non-NetworkService: |
| // Generator of a request handler for sending request to the network. This |
| // captures all of parameters to create a |
| // SingleRequestURLLoaderFactory::RequestHandler. Used only when |
| // NetworkService is disabled but IsLoaderInterceptionEnabled() is true. |
| // Set |was_request_intercepted| to true if the request was intercepted by an |
| // interceptor and the request is falling back to the network. In that case, |
| // any interceptors won't intercept the request. |
| base::RepeatingCallback<SingleRequestURLLoaderFactory::RequestHandler( |
| bool /* was_request_intercepted */)> |
| default_request_handler_factory_; |
| |
| // The completion status if it has been received. This is needed to handle |
| // the case that the response is intercepted by download, and OnComplete() is |
| // already called while we are transferring the |url_loader_| and response |
| // body to download code. |
| base::Optional<network::URLLoaderCompletionStatus> status_; |
| |
| // Before creating this URLLoaderRequestController on UI thread, the embedder |
| // may have elected to proxy the URLLoaderFactory request, in which case these |
| // fields will contain input (info) and output (request) endpoints for the |
| // proxy. If this controller is handling a request for which proxying is |
| // supported, requests will be plumbed through these endpoints. |
| // |
| // Note that these are only used for requests that go to the Network Service. |
| network::mojom::URLLoaderFactoryRequest proxied_factory_request_; |
| network::mojom::URLLoaderFactoryPtrInfo proxied_factory_info_; |
| |
| // The schemes that this loader can use. For anything else we'll try external |
| // protocol handlers. |
| std::set<std::string> known_schemes_; |
| |
| // If true, redirect checks will be handled in a proxy, and not here. |
| bool bypass_redirect_checks_; |
| |
| // Used to reset the state of ServiceWorkerProviderHost when |
| // SignedExchangeRequestHandler will handle the response. |
| base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host_; |
| |
| mutable base::WeakPtrFactory<URLLoaderRequestController> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(URLLoaderRequestController); |
| }; |
| |
| // TODO(https://crbug.com/790734): pass |navigation_ui_data| along with the |
| // request so that it could be modified. |
| NavigationURLLoaderImpl::NavigationURLLoaderImpl( |
| 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, |
| std::vector<std::unique_ptr<NavigationLoaderInterceptor>> |
| initial_interceptors) |
| : delegate_(delegate), |
| download_policy_(request_info->common_params.download_policy), |
| weak_factory_(this) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| int frame_tree_node_id = request_info->frame_tree_node_id; |
| |
| TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP1( |
| "navigation", "Navigation timeToResponseStarted", this, |
| request_info->common_params.navigation_start, "FrameTreeNode id", |
| frame_tree_node_id); |
| |
| ServiceWorkerNavigationHandleCore* service_worker_navigation_handle_core = |
| service_worker_navigation_handle |
| ? service_worker_navigation_handle->core() |
| : nullptr; |
| |
| AppCacheNavigationHandleCore* appcache_handle_core = |
| appcache_handle ? appcache_handle->core() : nullptr; |
| |
| std::unique_ptr<network::ResourceRequest> new_request = |
| CreateResourceRequest(request_info.get(), frame_tree_node_id, |
| IsNavigationDownloadAllowed(download_policy_)); |
| |
| auto* partition = static_cast<StoragePartitionImpl*>(storage_partition); |
| scoped_refptr<SignedExchangePrefetchMetricRecorder> |
| signed_exchange_prefetch_metric_recorder = |
| partition->GetPrefetchURLLoaderService() |
| ->signed_exchange_prefetch_metric_recorder(); |
| |
| if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) { |
| DCHECK(!request_controller_); |
| request_controller_ = std::make_unique<URLLoaderRequestController>( |
| /* initial_interceptors = */ |
| std::vector<std::unique_ptr<NavigationLoaderInterceptor>>(), |
| std::move(new_request), resource_context, |
| request_info->common_params.url, request_info->is_main_frame, |
| /* proxied_url_loader_factory_request */ nullptr, |
| /* proxied_url_loader_factory_info */ nullptr, std::set<std::string>(), |
| /* bypass_redirect_checks */ false, weak_factory_.GetWeakPtr()); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce( |
| &URLLoaderRequestController::StartWithoutNetworkService, |
| base::Unretained(request_controller_.get()), |
| base::RetainedRef(storage_partition->GetURLRequestContext()), |
| base::Unretained(storage_partition->GetFileSystemContext()), |
| base::Unretained(service_worker_navigation_handle_core), |
| base::Unretained(appcache_handle_core), |
| base::RetainedRef(signed_exchange_prefetch_metric_recorder), |
| std::move(request_info), std::move(navigation_ui_data))); |
| return; |
| } |
| |
| // Check if a web UI scheme wants to handle this request. |
| FrameTreeNode* frame_tree_node = |
| FrameTreeNode::GloballyFindByID(frame_tree_node_id); |
| network::mojom::URLLoaderFactoryPtrInfo factory_for_webui; |
| const auto& schemes = URLDataManagerBackend::GetWebUISchemes(); |
| std::string scheme = new_request->url.scheme(); |
| if (base::ContainsValue(schemes, scheme)) { |
| factory_for_webui = CreateWebUIURLLoaderBinding( |
| frame_tree_node->current_frame_host(), scheme) |
| .PassInterface(); |
| } |
| |
| network::mojom::URLLoaderFactoryPtrInfo proxied_factory_info; |
| network::mojom::URLLoaderFactoryRequest proxied_factory_request; |
| network::mojom::TrustedURLLoaderHeaderClientPtrInfo header_client; |
| bool bypass_redirect_checks = false; |
| if (frame_tree_node) { |
| // |frame_tree_node| may be null in some unit test environments. |
| GetContentClient() |
| ->browser() |
| ->RegisterNonNetworkNavigationURLLoaderFactories( |
| frame_tree_node_id, &non_network_url_loader_factories_); |
| |
| // Navigation requests are not associated with any particular |
| // |network::ResourceRequest::request_initiator| origin - using an opaque |
| // origin instead. |
| url::Origin navigation_request_initiator = url::Origin(); |
| // The embedder may want to proxy all network-bound URLLoaderFactory |
| // requests that it can. If it elects to do so, we'll pass its proxy |
| // endpoints off to the URLLoaderRequestController where wthey will be |
| // connected if the request type supports proxying. |
| network::mojom::URLLoaderFactoryPtrInfo factory_info; |
| auto factory_request = mojo::MakeRequest(&factory_info); |
| bool use_proxy = GetContentClient()->browser()->WillCreateURLLoaderFactory( |
| partition->browser_context(), frame_tree_node->current_frame_host(), |
| frame_tree_node->current_frame_host()->GetProcess()->GetID(), |
| true /* is_navigation */, navigation_request_initiator, |
| &factory_request, &header_client, &bypass_redirect_checks); |
| if (devtools_instrumentation::WillCreateURLLoaderFactory( |
| frame_tree_node->current_frame_host(), true, false, |
| &factory_request)) { |
| use_proxy = true; |
| } |
| if (use_proxy) { |
| proxied_factory_request = std::move(factory_request); |
| proxied_factory_info = std::move(factory_info); |
| } |
| |
| const std::string storage_domain; |
| non_network_url_loader_factories_[url::kFileSystemScheme] = |
| CreateFileSystemURLLoaderFactory(frame_tree_node->current_frame_host(), |
| /*is_navigation=*/true, |
| partition->GetFileSystemContext(), |
| storage_domain); |
| } |
| |
| non_network_url_loader_factories_[url::kAboutScheme] = |
| std::make_unique<AboutURLLoaderFactory>(); |
| |
| non_network_url_loader_factories_[url::kFileScheme] = |
| std::make_unique<FileURLLoaderFactory>( |
| partition->browser_context()->GetPath(), |
| BrowserContext::GetSharedCorsOriginAccessList( |
| partition->browser_context()), |
| base::CreateSequencedTaskRunnerWithTraits( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})); |
| |
| #if defined(OS_ANDROID) |
| non_network_url_loader_factories_[url::kContentScheme] = |
| std::make_unique<ContentURLLoaderFactory>( |
| base::CreateSequencedTaskRunnerWithTraits( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})); |
| #endif |
| |
| std::set<std::string> known_schemes; |
| for (auto& iter : non_network_url_loader_factories_) |
| known_schemes.insert(iter.first); |
| |
| std::unique_ptr<network::SharedURLLoaderFactoryInfo> network_factory_info = |
| partition->url_loader_factory_getter()->GetNetworkFactoryInfo(); |
| if (header_client) { |
| network_factory_info = CreateNetworkFactoryInfoWithHeaderClient( |
| std::move(header_client), partition); |
| } |
| |
| DCHECK(!request_controller_); |
| request_controller_ = std::make_unique<URLLoaderRequestController>( |
| std::move(initial_interceptors), std::move(new_request), resource_context, |
| request_info->common_params.url, request_info->is_main_frame, |
| std::move(proxied_factory_request), std::move(proxied_factory_info), |
| std::move(known_schemes), bypass_redirect_checks, |
| weak_factory_.GetWeakPtr()); |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&URLLoaderRequestController::Start, |
| base::Unretained(request_controller_.get()), |
| std::move(network_factory_info), |
| service_worker_navigation_handle_core, |
| appcache_handle_core, |
| std::move(signed_exchange_prefetch_metric_recorder), |
| std::move(request_info), std::move(navigation_ui_data), |
| std::move(factory_for_webui), frame_tree_node_id)); |
| } |
| |
| NavigationURLLoaderImpl::~NavigationURLLoaderImpl() { |
| BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, |
| request_controller_.release()); |
| } |
| |
| void NavigationURLLoaderImpl::FollowRedirect( |
| const base::Optional<std::vector<std::string>>& |
| to_be_removed_request_headers, |
| const base::Optional<net::HttpRequestHeaders>& modified_request_headers) { |
| base::PostTaskWithTraits( |
| FROM_HERE, {BrowserThread::IO}, |
| base::BindOnce(&URLLoaderRequestController::FollowRedirect, |
| base::Unretained(request_controller_.get()), |
| modified_request_headers)); |
| } |
| |
| void NavigationURLLoaderImpl::ProceedWithResponse() {} |
| |
| void NavigationURLLoaderImpl::OnReceiveResponse( |
| scoped_refptr<network::ResourceResponse> response, |
| network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints, |
| std::unique_ptr<NavigationData> navigation_data, |
| const GlobalRequestID& global_request_id, |
| bool is_download, |
| bool is_stream) { |
| TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted", this, |
| "&NavigationURLLoaderImpl", this, "success", true); |
| |
| if (is_download) { |
| UMA_HISTOGRAM_ENUMERATION("Navigation.DownloadPolicy", download_policy_); |
| } |
| |
| // TODO(scottmg): This needs to do more of what |
| // NavigationResourceHandler::OnResponseStarted() does. |
| delegate_->OnResponseStarted( |
| std::move(response), std::move(url_loader_client_endpoints), |
| std::move(navigation_data), global_request_id, is_download, |
| download_policy_, is_stream, |
| request_controller_->TakeSubresourceLoaderParams()); |
| } |
| |
| void NavigationURLLoaderImpl::OnReceiveRedirect( |
| const net::RedirectInfo& redirect_info, |
| scoped_refptr<network::ResourceResponse> response) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| delegate_->OnRequestRedirected(redirect_info, std::move(response)); |
| } |
| |
| void NavigationURLLoaderImpl::OnComplete( |
| const network::URLLoaderCompletionStatus& status) { |
| if (status.error_code == net::OK) |
| return; |
| |
| TRACE_EVENT_ASYNC_END2("navigation", "Navigation timeToResponseStarted", this, |
| "&NavigationURLLoaderImpl", this, "success", false); |
| |
| delegate_->OnRequestFailed(status); |
| } |
| |
| void NavigationURLLoaderImpl::SetBeginNavigationInterceptorForTesting( |
| const BeginNavigationInterceptor& interceptor) { |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::IO) || |
| BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK(!base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| g_interceptor.Get() = interceptor; |
| } |
| |
| void NavigationURLLoaderImpl::SetURLLoaderFactoryInterceptorForTesting( |
| const URLLoaderFactoryInterceptor& interceptor) { |
| DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) || |
| BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService)); |
| g_loader_factory_interceptor.Get() = interceptor; |
| } |
| |
| void NavigationURLLoaderImpl::OnRequestStarted(base::TimeTicks timestamp) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| delegate_->OnRequestStarted(timestamp); |
| } |
| |
| void NavigationURLLoaderImpl::BindNonNetworkURLLoaderFactoryRequest( |
| int frame_tree_node_id, |
| const GURL& url, |
| network::mojom::URLLoaderFactoryRequest factory) { |
| auto it = non_network_url_loader_factories_.find(url.scheme()); |
| if (it == non_network_url_loader_factories_.end()) { |
| DVLOG(1) << "Ignoring request with unknown scheme: " << url.spec(); |
| return; |
| } |
| |
| // Navigation requests are not associated with any particular |
| // |network::ResourceRequest::request_initiator| origin - using an opaque |
| // origin instead. |
| url::Origin navigation_request_initiator = url::Origin(); |
| |
| FrameTreeNode* frame_tree_node = |
| FrameTreeNode::GloballyFindByID(frame_tree_node_id); |
| auto* frame = frame_tree_node->current_frame_host(); |
| GetContentClient()->browser()->WillCreateURLLoaderFactory( |
| frame->GetSiteInstance()->GetBrowserContext(), frame, |
| frame->GetProcess()->GetID(), true /* is_navigation */, |
| navigation_request_initiator, &factory, nullptr /* header_client */, |
| nullptr /* bypass_redirect_checks */); |
| it->second->Clone(std::move(factory)); |
| } |
| |
| } // namespace content |