blob: 622190e414ef1b4f3910ab09c99e7e27e15e5693 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/url_loader.h"
#include <algorithm>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/containers/enum_set.h"
#include "base/containers/fixed_flat_set.h"
#include "base/feature_list.h"
#include "base/files/file.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/not_fatal_until.h"
#include "base/numerics/safe_conversions.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/string_view_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/shared_remote.h"
#include "mojo/public/cpp/system/simple_watcher.h"
#include "net/base/completion_once_callback.h"
#include "net/base/features.h"
#include "net/base/isolation_info.h"
#include "net/base/load_flags.h"
#include "net/base/load_timing_info.h"
#include "net/base/load_timing_internal_info.h"
#include "net/base/mime_sniffer.h"
#include "net/base/net_error_details.h"
#include "net/base/net_errors.h"
#include "net/base/proxy_chain.h"
#include "net/base/schemeful_site.h"
#include "net/base/task/task_runner.h"
#include "net/base/transport_info.h"
#include "net/base/upload_data_stream.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_inclusion_status.h"
#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_store.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/site_for_cookies.h"
#include "net/cookies/static_cookie_policy.h"
#include "net/device_bound_sessions/session.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/filter/filter_source_stream.h"
#include "net/http/http_connection_info.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/http/structured_headers.h"
#include "net/log/net_log_source_type.h"
#include "net/log/net_log_util.h"
#include "net/log/net_log_with_source.h"
#include "net/ssl/client_cert_store.h"
#include "net/ssl/ssl_connection_status_flags.h"
#include "net/ssl/ssl_private_key.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "net/url_request/redirect_info.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 "services/network/accept_ch_frame_interceptor.h"
#include "services/network/ad_heuristic_cookie_overrides.h"
#include "services/network/cookie_settings.h"
#include "services/network/devtools_durable_msg.h"
#include "services/network/file_opener_for_upload.h"
#include "services/network/orb/orb_impl.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/cpp/cors/origin_access_list.h"
#include "services/network/public/cpp/cross_origin_resource_policy.h"
#include "services/network/public/cpp/empty_url_loader_client.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/header_util.h"
#include "services/network/public/cpp/ip_address_space_util.h"
#include "services/network/public/cpp/loading_params.h"
#include "services/network/public/cpp/net_adapters.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/parsed_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/sri_message_signatures.h"
#include "services/network/public/mojom/client_security_state.mojom-forward.h"
#include "services/network/public/mojom/client_security_state.mojom.h"
#include "services/network/public/mojom/cookie_access_observer.mojom-forward.h"
#include "services/network/public/mojom/cookie_access_observer.mojom.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "services/network/public/mojom/devtools_observer.mojom.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/http_raw_headers.mojom.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/network_context_client.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/resource_scheduler/resource_scheduler_client.h"
#include "services/network/sec_header_helpers.h"
#include "services/network/shared_dictionary/shared_dictionary_access_checker.h"
#include "services/network/shared_dictionary/shared_dictionary_manager.h"
#include "services/network/shared_dictionary/shared_dictionary_storage.h"
#include "services/network/shared_resource_checker.h"
#include "services/network/shared_storage/shared_storage_request_helper.h"
#include "services/network/slop_bucket.h"
#include "services/network/ssl_private_key_proxy.h"
#include "services/network/throttling/scoped_throttling_token.h"
#include "services/network/throttling/throttling_controller.h"
#include "services/network/throttling/throttling_network_interceptor.h"
#include "services/network/trust_tokens/trust_token_request_helper.h"
#include "services/network/trust_tokens/trust_token_url_loader_interceptor.h"
#include "services/network/url_loader_factory.h"
#include "services/network/url_loader_util.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"
#include "url/origin.h"
namespace network {
namespace {
BASE_FEATURE(kDelayedCookieNotification, base::FEATURE_DISABLED_BY_DEFAULT);
// Cannot use 0, because this means "default" in
// mojo::core::Core::CreateDataPipe
constexpr size_t kBlockedBodyAllocationSize = 1;
// Size to allocate for `discard_buffer_`.
constexpr size_t kDiscardBufferSize = 128 * 1024;
constexpr char kActivateStorageAccessHeader[] = "activate-storage-access";
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class StorageAccessRedirectKind {
// The `kStorageAccessGrantEligible` override was missing from the request.
kNoAccess = 0,
// The request had the `kStorageAccessGrantEligible` override, and was a
// same-origin redirect.
kSameOrigin = 1,
// The request had the `kStorageAccessGrantEligible` override, and was a
// cross-origin, same-site redirect.
kCrossOriginSameSite = 2,
// The request had the `kStorageAccessGrantEligible` override, and was a
// cross-site redirect.
kCrossSite = 3,
kMaxValue = kCrossSite
};
void RecordStorageAccessRedirectMetric(StorageAccessRedirectKind kind) {
base::UmaHistogramEnumeration("Net.HttpJob.StorageAccessRedirect", kind);
}
bool ShouldNotifyAboutCookie(net::CookieInclusionStatus status) {
// Notify about cookies actually used, and those blocked by preferences ---
// for purposes of cookie UI --- as well those carrying warnings pertaining to
// SameSite features and cookies with non-ASCII domain attributes, in order to
// issue a deprecation warning for them.
// Filter out tentative secure source scheme warnings. They're used for netlog
// debugging and not something we want to inform cookie observers about.
status.RemoveWarningReason(
net::CookieInclusionStatus::WarningReason::
WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME);
return status.IsInclude() || status.ShouldWarn() ||
status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
EXCLUDE_USER_PREFERENCES) ||
status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
EXCLUDE_THIRD_PARTY_PHASEOUT) ||
status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
EXCLUDE_DOMAIN_NON_ASCII) ||
status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
EXCLUDE_PORT_MISMATCH) ||
status.HasExclusionReason(net::CookieInclusionStatus::ExclusionReason::
EXCLUDE_SCHEME_MISMATCH);
}
scoped_refptr<RefCountedDeviceBoundSessionAccessObserverRemote>
MaybeInitializeDeviceBoundSessionAccessObserverSharedRemote(
ObserverWrapper<mojom::DeviceBoundSessionAccessObserver>& observer,
URLLoaderContext& context) {
if (!base::FeatureList::IsEnabled(
features::kDeviceBoundSessionAccessObserverSharedRemote)) {
return nullptr;
}
auto remote = observer.TakeRemote();
if (remote) {
return base::MakeRefCounted<
RefCountedDeviceBoundSessionAccessObserverRemote>(std::move(remote));
}
return context.GetDeviceBoundSessionAccessObserverSharedRemote();
}
net::HttpRequestHeaders AttachCookies(const net::HttpRequestHeaders& headers,
const std::string& cookies_from_browser) {
DCHECK(!cookies_from_browser.empty());
// Parse the existing cookie line.
std::string old_cookies = headers.GetHeader(net::HttpRequestHeaders::kCookie)
.value_or(std::string());
net::cookie_util::ParsedRequestCookies parsed_cookies;
net::cookie_util::ParseRequestCookieLine(old_cookies, &parsed_cookies);
net::cookie_util::ParsedRequestCookies parsed_cookies_from_browser;
net::cookie_util::ParseRequestCookieLine(cookies_from_browser,
&parsed_cookies_from_browser);
// Add the browser cookies to the request.
for (const auto& cookie : parsed_cookies_from_browser) {
DCHECK(!cookie.first.empty());
// Ensure we're not adding duplicate cookies.
auto it = std::find_if(
parsed_cookies.begin(), parsed_cookies.end(),
[&cookie](const net::cookie_util::ParsedRequestCookie& old_cookie) {
return old_cookie.first == cookie.first;
});
if (it != parsed_cookies.end())
continue;
parsed_cookies.emplace_back(cookie.first, cookie.second);
}
net::HttpRequestHeaders updated_headers = headers;
std::string updated_cookies =
net::cookie_util::SerializeRequestCookieLine(parsed_cookies);
updated_headers.SetHeader(net::HttpRequestHeaders::kCookie, updated_cookies);
return updated_headers;
}
std::vector<network::mojom::HttpRawHeaderPairPtr>
ResponseHeaderToRawHeaderPairs(
const net::HttpResponseHeaders& response_headers) {
std::vector<network::mojom::HttpRawHeaderPairPtr> header_array;
size_t iterator = 0;
std::string name, value;
while (response_headers.EnumerateHeaderLines(&iterator, &name, &value)) {
header_array.emplace_back(std::in_place, std::move(name), std::move(value));
}
return header_array;
}
bool IncludesValidLoadField(const net::HttpResponseHeaders* headers) {
if (!headers) {
return false;
}
std::optional<std::string> header_value =
headers->GetNormalizedHeader(kActivateStorageAccessHeader);
if (!header_value) {
return false;
}
const std::optional<net::structured_headers::ParameterizedItem> item =
net::structured_headers::ParseItem(*header_value);
if (!item.has_value()) {
return false;
}
return item->item.is_token() && item->item.GetString() == "load";
}
mojo::SharedRemote<mojom::DeviceBoundSessionAccessObserver> Clone(
mojom::DeviceBoundSessionAccessObserver& observer) {
TRACE_EVENT("loading", "CloneDeviceBoundSessionAccessObserver");
base::ScopedUmaHistogramTimer timer(
"NetworkService.URLLoader.CloneDeviceBoundSessionAccessObserver",
base::ScopedUmaHistogramTimer::ScopedHistogramTiming::kMicrosecondTimes);
mojo::SharedRemote<mojom::DeviceBoundSessionAccessObserver> new_observer;
observer.Clone(new_observer.BindNewPipeAndPassReceiver());
return new_observer;
}
int32_t PopulateOptions(int32_t initial_options,
bool is_orb_enabled,
bool has_devtools_request_id) {
int32_t options = initial_options;
if (options & mojom::kURLLoadOptionReadAndDiscardBody) {
CHECK(!(options & mojom::kURLLoadOptionSniffMimeType))
<< "options ReadAndDiscardBody and SniffMimeType cannot be used "
"together";
if (is_orb_enabled) {
// TODO(ricea): Make ReadAndDiscardBody and ORB work together.
LOG(WARNING) << "Disabling ReadAndDiscardBody because ORB is enabled";
options &= ~mojom::kURLLoadOptionReadAndDiscardBody;
}
}
if (has_devtools_request_id) {
options |= mojom::kURLLoadOptionSendSSLInfoWithResponse |
mojom::kURLLoadOptionSendSSLInfoForCertificateError;
}
return options;
}
const scoped_refptr<base::SingleThreadTaskRunner>& TaskRunner(
net::RequestPriority priority) {
if (features::kNetworkServiceTaskSchedulerURLLoader.Get()) {
return net::GetTaskRunner(priority);
}
return base::SingleThreadTaskRunner::GetCurrentDefault();
}
} // namespace
URLLoader::MaybeSyncURLLoaderClient::MaybeSyncURLLoaderClient(
mojo::PendingRemote<mojom::URLLoaderClient> mojo_client,
base::WeakPtr<mojom::URLLoaderClient> sync_client)
: mojo_client_(std::move(mojo_client)),
sync_client_(std::move(sync_client)) {}
URLLoader::MaybeSyncURLLoaderClient::~MaybeSyncURLLoaderClient() = default;
void URLLoader::MaybeSyncURLLoaderClient::Reset() {
mojo_client_.reset();
sync_client_.reset();
}
mojo::PendingReceiver<mojom::URLLoaderClient>
URLLoader::MaybeSyncURLLoaderClient::BindNewPipeAndPassReceiver() {
sync_client_.reset();
return mojo_client_.BindNewPipeAndPassReceiver();
}
mojom::URLLoaderClient* URLLoader::MaybeSyncURLLoaderClient::Get() {
if (sync_client_)
return sync_client_.get();
if (mojo_client_)
return mojo_client_.get();
return nullptr;
}
URLLoader::URLLoader(
URLLoaderContext& context,
DeleteCallback delete_callback,
mojo::PendingReceiver<mojom::URLLoader> url_loader_receiver,
int32_t options,
const ResourceRequest& request,
mojo::PendingRemote<mojom::URLLoaderClient> url_loader_client,
base::WeakPtr<mojom::URLLoaderClient> sync_url_loader_client,
const net::NetworkTrafficAnnotationTag& traffic_annotation,
base::StrictNumeric<int32_t> request_id,
int keepalive_request_size,
base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder,
std::unique_ptr<TrustTokenRequestHelperFactory> trust_token_helper_factory,
SharedDictionaryManager* shared_dictionary_manager,
std::unique_ptr<SharedDictionaryAccessChecker> shared_dictionary_checker,
ObserverWrapper<mojom::CookieAccessObserver> cookie_observer,
ObserverWrapper<mojom::TrustTokenAccessObserver> trust_token_observer,
ObserverWrapper<mojom::URLLoaderNetworkServiceObserver>
url_loader_network_observer,
ObserverWrapper<mojom::DevToolsObserver> devtools_observer,
ObserverWrapper<mojom::DeviceBoundSessionAccessObserver>
device_bound_session_observer,
mojo::PendingRemote<mojom::AcceptCHFrameObserver> accept_ch_frame_observer,
bool shared_storage_writable_eligible,
SharedResourceChecker& shared_resource_checker,
base::WeakPtr<DevtoolsDurableMessage> devtools_durable_message)
: url_request_context_(context.GetUrlRequestContext()),
network_context_client_(context.GetNetworkContextClient()),
delete_callback_(std::move(delete_callback)),
resource_type_(request.resource_type),
is_load_timing_enabled_(request.enable_load_timing),
factory_params_(context.GetFactoryParams()),
coep_reporter_(context.GetCoepReporter()),
dip_reporter_(context.GetDipReporter()),
request_id_(request_id),
keepalive_request_size_(keepalive_request_size),
keepalive_(request.keepalive),
client_security_state_(
request.trusted_params
? request.trusted_params->client_security_state.Clone()
: nullptr),
do_not_prompt_for_login_(request.do_not_prompt_for_login),
receiver_(this, std::move(url_loader_receiver)),
url_loader_client_(std::move(url_loader_client),
std::move(sync_url_loader_client)),
writable_handle_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
TaskRunner(request.priority)),
peer_closed_handle_watcher_(FROM_HERE,
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
TaskRunner(request.priority)),
per_factory_orb_state_(context.GetMutableOrbState()),
devtools_request_id_(request.devtools_request_id),
options_(PopulateOptions(options,
factory_params_->is_orb_enabled,
!!devtools_request_id())),
request_mode_(request.mode),
request_credentials_mode_(request.credentials_mode),
has_user_activation_(request.trusted_params &&
request.trusted_params->has_user_activation),
request_destination_(request.destination),
expected_public_keys_(request.expected_public_keys),
resource_scheduler_client_(context.GetResourceSchedulerClient()),
keepalive_statistics_recorder_(std::move(keepalive_statistics_recorder)),
fetch_window_id_(request.fetch_window_id),
private_network_access_interceptor_(request,
GetClientSecurityState(),
options_),
trust_token_interceptor_(TrustTokenUrlLoaderInterceptor::MaybeCreate(
std::move(trust_token_helper_factory))),
shared_dictionary_checker_(std::move(shared_dictionary_checker)),
origin_access_list_(context.GetOriginAccessList()),
cookie_observer_(std::move(cookie_observer)),
trust_token_observer_(std::move(trust_token_observer)),
url_loader_network_observer_(std::move(url_loader_network_observer)),
devtools_observer_(std::move(devtools_observer)),
device_bound_session_observer_(std::move(device_bound_session_observer)),
device_bound_session_observer_shared_remote_(
MaybeInitializeDeviceBoundSessionAccessObserverSharedRemote(
device_bound_session_observer_,
context)),
shared_storage_request_helper_(
std::make_unique<SharedStorageRequestHelper>(
shared_storage_writable_eligible,
url_loader_network_observer_.get())),
ad_auction_event_record_request_helper_(
request.attribution_reporting_eligibility,
url_loader_network_observer_.get()),
has_fetch_streaming_upload_body_(
url_loader_util::HasFetchStreamingUploadBody(request)),
accept_ch_frame_interceptor_(AcceptCHFrameInterceptor::MaybeCreate(
std::move(accept_ch_frame_observer),
request.trusted_params ? request.trusted_params->enabled_client_hints
: std::nullopt)),
allow_cookies_from_browser_(
request.trusted_params &&
request.trusted_params->allow_cookies_from_browser),
cookies_from_browser_(allow_cookies_from_browser_
? url_loader_util::GetCookiesFromHeaders(
request.headers,
request.cors_exempt_headers)
: std::string()),
include_request_cookies_with_response_(
request.trusted_params &&
request.trusted_params->include_request_cookies_with_response),
include_load_timing_internal_info_with_response_(
request.trusted_params.has_value()),
provide_data_use_updates_(context.DataUseUpdatesEnabled()),
partial_decoder_decoding_buffer_size_(net::kMaxBytesToSniff),
permissions_policy_(request.permissions_policy),
devtools_durable_message_(devtools_durable_message) {
DCHECK(delete_callback_);
if (options_ & mojom::kURLLoadOptionReadAndDiscardBody) {
if (!factory_params_->is_orb_enabled) {
discard_buffer_ =
base::MakeRefCounted<net::IOBufferWithSize>(kDiscardBufferSize);
}
}
mojom::TrustedURLLoaderHeaderClient* url_loader_header_client =
context.GetUrlLoaderHeaderClient();
if (url_loader_header_client &&
(options_ & mojom::kURLLoadOptionUseHeaderClient)) {
if (options_ & mojom::kURLLoadOptionAsCorsPreflight) {
url_loader_header_client->OnLoaderForCorsPreflightCreated(
request, header_client_.BindNewPipeAndPassReceiver());
} else {
url_loader_header_client->OnLoaderCreated(
request_id_, header_client_.BindNewPipeAndPassReceiver());
}
// Make sure the loader dies if |header_client_| has an error, otherwise
// requests can hang.
header_client_.set_disconnect_handler(
base::BindOnce(&URLLoader::OnMojoDisconnect, base::Unretained(this)));
}
receiver_.set_disconnect_handler(
base::BindOnce(&URLLoader::OnMojoDisconnect, base::Unretained(this)));
url_request_ = url_request_context_->CreateRequest(
request.url, request.priority, this, traffic_annotation,
/*is_for_websockets=*/false, request.net_log_create_info);
TRACE_EVENT("loading", "URLLoader::URLLoader",
net::NetLogWithSourceToFlow(url_request_->net_log()));
// Set up UserData (pointing to `this`) first.
url_request_->SetUserData(kUserDataKey,
std::make_unique<UnownedPointer>(this));
// Configure the main request parameters. This must happen after setting
// UserData, as `ConfigureUrlRequest` might internally retrieve data (e.g.,
// PermissionsPolicy) via the `url_request_`'s UserData pointer.
url_loader_util::ConfigureUrlRequest(request, *factory_params_,
*origin_access_list_, *url_request_,
shared_resource_checker);
if (context.ShouldRequireIsolationInfo()) {
DCHECK(!url_request_->isolation_info().IsEmpty());
}
SetUpUrlRequestCallbacks(shared_dictionary_manager);
throttling_token_ = network::ScopedThrottlingToken::MaybeCreate(
url_request_->net_log().source().id, request.throttling_profile_id);
if (keepalive_ && keepalive_statistics_recorder_) {
keepalive_statistics_recorder_->OnLoadStarted(
*factory_params_->top_frame_id, keepalive_request_size_);
}
if (request.net_log_reference_info) {
// Log source object that created the request, if available.
url_request_->net_log().AddEventReferencingSource(
net::NetLogEventType::CREATED_BY,
request.net_log_reference_info.value());
}
// Resolve elements from request_body and prepare upload data.
if (request.request_body.get()) {
OpenFilesForUpload(request);
return;
}
ProcessOutboundTrustTokenInterceptor(request);
}
void URLLoader::SetUpUrlRequestCallbacks(
SharedDictionaryManager* shared_dictionary_manager) {
url_request_->SetRequestHeadersCallback(base::BindRepeating(
&URLLoader::SetRawRequestHeadersAndNotify, base::Unretained(this)));
if (shared_dictionary_checker_) {
url_request_->SetIsSharedDictionaryReadAllowedCallback(base::BindRepeating(
&URLLoader::IsSharedDictionaryReadAllowed, base::Unretained(this)));
}
if (devtools_request_id()) {
url_request_->SetResponseHeadersCallback(base::BindRepeating(
&URLLoader::SetRawResponseHeaders, base::Unretained(this)));
}
url_request_->SetEarlyResponseHeadersCallback(base::BindRepeating(
&URLLoader::NotifyEarlyResponse, base::Unretained(this)));
if (shared_dictionary_manager) {
url_request_->SetSharedDictionaryGetter(
shared_dictionary_manager->MaybeCreateSharedDictionaryGetter(
url_request_->load_flags(), request_destination_));
}
// Device bound session access can happen asynchronously as a result
// of this URLRequest. So pass a refcounted shared Remote that will outlive
// `this`.
if (device_bound_session_observer_shared_remote_) {
url_request_->SetDeviceBoundSessionAccessCallback(base::BindRepeating(
[](scoped_refptr<RefCountedDeviceBoundSessionAccessObserverRemote>
shared_remote,
const net::device_bound_sessions::SessionAccess& access) {
shared_remote.get()->data->OnDeviceBoundSessionAccessed(access);
},
device_bound_session_observer_shared_remote_));
} else if (device_bound_session_observer_) {
// This is just for the experiment to measure the impact on the
// DeviceBoundSessionAccessObserverSharedRemote feature.
// TODO(crbug.com/407680127): Remove this and when the feature is enabled
// and the feature flag is removed.
url_request_->SetDeviceBoundSessionAccessCallback(base::BindRepeating(
&mojom::DeviceBoundSessionAccessObserver::OnDeviceBoundSessionAccessed,
Clone(*device_bound_session_observer_)));
}
}
void URLLoader::OpenFilesForUpload(const ResourceRequest& request) {
std::vector<base::FilePath> paths;
for (const auto& element : *request.request_body.get()->elements()) {
if (element.type() == mojom::DataElementDataView::Tag::kFile) {
paths.push_back(element.As<network::DataElementFile>().path());
}
}
if (paths.empty()) {
SetUpUpload(request, std::vector<base::File>());
return;
}
if (!network_context_client_) {
DLOG(ERROR) << "URLLoader couldn't upload a file because no "
"NetworkContextClient is set.";
// Defer calling NotifyCompleted to make sure the URLLoader finishes
// initializing before getting deleted.
TaskRunner(url_request_->priority())
->PostTask(FROM_HERE, base::BindOnce(&URLLoader::NotifyCompleted,
weak_ptr_factory_.GetWeakPtr(),
net::ERR_ACCESS_DENIED));
return;
}
url_request_->LogBlockedBy("Opening Files");
file_opener_for_upload_ = std::make_unique<FileOpenerForUpload>(
std::move(paths), url_request_->url(), factory_params_->process_id,
network_context_client_,
base::BindOnce(&URLLoader::SetUpUpload, base::Unretained(this), request));
file_opener_for_upload_->Start();
}
void URLLoader::SetUpUpload(
const ResourceRequest& request,
base::expected<std::vector<base::File>, net::Error> file_open_result) {
if (file_opener_for_upload_) {
file_opener_for_upload_.reset();
// This corresponds to the LogBlockedBy call made just before creating
// FileOpenerForUpload.
url_request_->LogUnblocked();
}
if (!file_open_result.has_value()) {
// Defer calling NotifyCompleted to make sure the URLLoader finishes
// initializing before getting deleted.
TaskRunner(url_request_->priority())
->PostTask(FROM_HERE, base::BindOnce(&URLLoader::NotifyCompleted,
weak_ptr_factory_.GetWeakPtr(),
file_open_result.error()));
return;
}
scoped_refptr<base::SequencedTaskRunner> task_runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE});
url_request_->set_upload(url_loader_util::CreateUploadDataStream(
request.request_body.get(), file_open_result.value(), task_runner.get()));
if (request.enable_upload_progress) {
upload_progress_tracker_ = std::make_unique<UploadProgressTracker>(
FROM_HERE,
base::BindRepeating(&URLLoader::SendUploadProgress,
base::Unretained(this)),
url_request_.get());
}
ProcessOutboundTrustTokenInterceptor(request);
}
void URLLoader::ProcessOutboundTrustTokenInterceptor(
const ResourceRequest& request) {
// If no Trust Token parameters are specified, proceed to the next
// interceptor.
if (!request.trust_token_params) {
ProcessOutboundSharedStorageInterceptor();
return;
}
// If trust_token_params exist, the interceptor MUST have been created in the
// URLLoader constructor.
CHECK(trust_token_interceptor_);
// Ask the interceptor if any special load flags are needed.
url_request_->SetLoadFlags(url_request_->load_flags() |
trust_token_interceptor_->GetAdditionalLoadFlags(
request.trust_token_params.value()));
// Delegate the Begin phase of the Trust Token operation to the interceptor.
// The interceptor will asynchronously handle helper creation, calling Begin,
// and determining the outcome.
trust_token_interceptor_->BeginOperation(
request.trust_token_params->operation, url_request_->url(),
url_request_->isolation_info().top_frame_origin().value_or(url::Origin()),
url_request_->extra_request_headers(), request.trust_token_params.value(),
url_request_->net_log(),
// Provide a getter for the TrustTokenAccessObserver.
base::BindOnce(
[](base::WeakPtr<URLLoader> weak_ptr)
-> mojom::TrustTokenAccessObserver* {
return weak_ptr ? weak_ptr->trust_token_observer_.get() : nullptr;
},
weak_ptr_factory_.GetWeakPtr()),
// Provide a getter for the DevTools reporting callback.
base::BindOnce(
[](base::WeakPtr<URLLoader> weak_ptr)
-> base::OnceCallback<void(mojom::TrustTokenOperationResultPtr)> {
if (weak_ptr && weak_ptr->devtools_observer_.get() &&
weak_ptr->devtools_request_id_) {
return base::BindOnce(
&mojom::DevToolsObserver::OnTrustTokenOperationDone,
base::Unretained(weak_ptr->devtools_observer_.get()),
*weak_ptr->devtools_request_id_);
}
return base::OnceCallback<void(
mojom::TrustTokenOperationResultPtr)>();
},
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&URLLoader::OnDoneBeginningTrustTokenOperation,
weak_ptr_factory_.GetWeakPtr()));
}
void URLLoader::OnDoneBeginningTrustTokenOperation(
base::expected<net::HttpRequestHeaders, net::Error> result) {
// If `result` does not have a value, the operation failed or completed
// locally.
if (!result.has_value()) {
// Defer calling NotifyCompleted to make sure the URLLoader
// finishes initializing before getting deleted.
TaskRunner(url_request_->priority())
->PostTask(FROM_HERE, base::BindOnce(&URLLoader::NotifyCompleted,
weak_ptr_factory_.GetWeakPtr(),
result.error()));
return;
}
// Operation succeeded and returned headers to add/overwrite.
// Apply the headers provided by the interceptor.
for (const auto& header_pair : result->GetHeaderVector()) {
url_request_->SetExtraRequestHeaderByName(
header_pair.key, header_pair.value, /*overwrite=*/true);
}
// Trust Token outbound processing is done, proceed to the next interceptor.
ProcessOutboundSharedStorageInterceptor();
}
void URLLoader::ProcessOutboundSharedStorageInterceptor() {
DCHECK(shared_storage_request_helper_);
shared_storage_request_helper_->ProcessOutgoingRequest(*url_request_);
ScheduleStart();
}
void URLLoader::ScheduleStart() {
TRACE_EVENT("loading", "URLLoader::ScheduleStart",
net::NetLogWithSourceToFlow(url_request_->net_log()));
bool defer = false;
if (resource_scheduler_client_) {
resource_scheduler_request_handle_ =
resource_scheduler_client_->ScheduleRequest(
!(options_ & mojom::kURLLoadOptionSynchronous), url_request_.get());
resource_scheduler_request_handle_->set_resume_callback(
base::BindOnce(&URLLoader::ResumeStart, base::Unretained(this)));
resource_scheduler_request_handle_->WillStartRequest(&defer);
}
if (defer)
url_request_->LogBlockedBy("ResourceScheduler");
else
url_request_->Start();
}
URLLoader::~URLLoader() {
TRACE_EVENT("loading", "URLLoader::~URLLoader",
net::NetLogWithSourceToFlow(url_request_->net_log()));
if (keepalive_ && keepalive_statistics_recorder_) {
keepalive_statistics_recorder_->OnLoadFinished(
*factory_params_->top_frame_id, keepalive_request_size_);
}
if (!cookie_access_details_.empty()) {
// In case the response wasn't received successfully sent the call now.
// Note `cookie_observer_` is guaranteed non-null since
// `cookie_access_details_` is only appended to when it is valid.
cookie_observer_->OnCookiesAccessed(std::move(cookie_access_details_));
}
}
// static
const void* const URLLoader::kUserDataKey = &URLLoader::kUserDataKey;
void URLLoader::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const std::optional<GURL>& new_url) {
if (!deferred_redirect_url_) {
NOTREACHED();
}
// Note: There are some ordering dependencies here.
// `CalculateStorageAccessStatus` depends on
// `url_request->cookie_setting_overrides()`. `SetFetchMetadataHeaders`
// depends on `url_request_->storage_access_status()`.
if (request_credentials_mode_ == mojom::CredentialsMode::kInclude) {
url_request_->set_storage_access_status(
url_request_->CalculateStorageAccessStatus());
} else {
url_request_->reset_storage_access_status();
}
// We may need to clear out old Sec- prefixed request headers. We'll attempt
// to do this before we re-add any.
MaybeRemoveSecHeaders(*url_request_, *deferred_redirect_url_);
SetFetchMetadataHeaders(*url_request_, request_mode_, has_user_activation_,
request_destination_, *deferred_redirect_url_,
*factory_params_, *origin_access_list_,
request_credentials_mode_);
ResetRawHeadersForRedirect();
// Removing headers can't make the set of pre-existing headers unsafe, but
// adding headers can.
if (!AreRequestHeadersSafe(modified_headers) ||
!AreRequestHeadersSafe(modified_cors_exempt_headers)) {
NotifyCompleted(net::ERR_INVALID_ARGUMENT);
// |this| may have been deleted.
return;
}
// Store any cookies passed from the browser process to later attach them to
// the request.
if (allow_cookies_from_browser_) {
cookies_from_browser_ = url_loader_util::GetCookiesFromHeaders(
modified_headers, modified_cors_exempt_headers);
}
// Reset the state of the PNA checker - redirects should be treated like new
// requests by the same client.
private_network_access_interceptor_.ResetForRedirect(
new_url ? *new_url : *deferred_redirect_url_);
// Propagate removal or restoration of shared storage eligiblity to the helper
// if the "Sec-Shared-Storage-Writable" request header has been removed or
// restored.
DCHECK(shared_storage_request_helper_);
shared_storage_request_helper_->UpdateSharedStorageWritableEligible(
removed_headers, modified_headers);
deferred_redirect_url_.reset();
new_redirect_url_ = new_url;
net::HttpRequestHeaders merged_modified_headers = modified_headers;
merged_modified_headers.MergeFrom(modified_cors_exempt_headers);
url_request_->FollowDeferredRedirect(removed_headers,
merged_modified_headers);
new_redirect_url_.reset();
}
void URLLoader::SetPriority(net::RequestPriority priority,
int32_t intra_priority_value) {
if (url_request_ && resource_scheduler_client_) {
resource_scheduler_client_->ReprioritizeRequest(
url_request_.get(), priority, intra_priority_value);
}
}
int URLLoader::OnConnected(net::URLRequest* url_request,
const net::TransportInfo& info,
net::CompletionOnceCallback callback) {
DCHECK_EQ(url_request, url_request_.get());
// Delegate the PNA check to the interceptor.
net::Error net_error = private_network_access_interceptor_.OnConnected(
url_request_->url(), info,
// Callback getter for async continuation:
base::BindOnce(
[](URLLoader* self, const net::TransportInfo* info_ptr,
net::CompletionOnceCallback* callback)
-> base::OnceCallback<void(net::Error)> {
return base::BindOnce(
&URLLoader::
ProcessLocalNetworkAccessPermissionResultOnConnected,
// Safe to use Unretained, as this callback will be owned by the
// interceptor which is owned by the URLLoader.
base::Unretained(self), *info_ptr, std::move(*callback));
},
// Safe to use Unretained, as this callback must be called
// synchronously.
base::Unretained(this), base::Unretained(&info),
base::Unretained(&callback)),
// Callback to set CORS error status:
base::BindOnce(
[](URLLoader* self, CorsErrorStatus cors_error_status) {
self->cors_error_status_ = std::move(cors_error_status);
},
// Safe to use Unretained, as this callback must be called
// synchronously.
base::Unretained(this)),
url_request_->net_log(), devtools_observer_.get(), devtools_request_id(),
url_loader_network_observer_.get());
// If PNA failed synchronously or requires async LNA (ERR_IO_PENDING), return
// now.
if (net_error != net::OK) {
return net_error;
}
// PNA passed synchronously. Proceed to Accept-CH handling.
return ProcessAcceptCHFrameOnConnected(info, std::move(callback));
}
void URLLoader::ProcessLocalNetworkAccessPermissionResultOnConnected(
const net::TransportInfo& info,
net::CompletionOnceCallback callback,
net::Error pna_result) {
// If LNA permission was denied, complete the request with the error.
if (pna_result != net::OK) {
std::move(callback).Run(pna_result);
return;
}
// LNA granted. Proceed to the next step: Accept-CH handling.
std::pair<net::CompletionOnceCallback, net::CompletionOnceCallback> split =
base::SplitOnceCallback(std::move(callback));
int result = ProcessAcceptCHFrameOnConnected(info, std::move(split.first));
if (result != net::ERR_IO_PENDING) {
std::move(split.second).Run(result);
}
}
int URLLoader::ProcessAcceptCHFrameOnConnected(
const net::TransportInfo& info,
net::CompletionOnceCallback callback) {
TRACE_EVENT("loading", "URLLoader::ProcessAcceptCHFrameOnConnected",
net::NetLogWithSourceToFlow(url_request_->net_log()), "url",
url_request_->url());
if (info.negotiated_protocol == net::NextProto::kProtoHTTP2) {
base::UmaHistogramBoolean("Net.URLLoader.AcceptCHFrameReceivedOnHttp2",
!info.accept_ch_frame.empty());
} else if (info.negotiated_protocol == net::NextProto::kProtoQUIC) {
base::UmaHistogramBoolean("Net.URLLoader.AcceptCHFrameReceivedOnHttp3",
!info.accept_ch_frame.empty());
}
if (!accept_ch_frame_interceptor_) {
return net::OK;
}
return accept_ch_frame_interceptor_->OnConnected(
url_request_->url(), info.accept_ch_frame,
url_request_->extra_request_headers(), std::move(callback));
}
mojom::URLResponseHeadPtr URLLoader::BuildResponseHead() const {
CHECK(request_cookies_.empty() || include_request_cookies_with_response_);
return url_loader_util::BuildResponseHead(
*url_request_, request_cookies_,
private_network_access_interceptor_.ClientAddressSpace(),
private_network_access_interceptor_.ResponseAddressSpace().value_or(
mojom::IPAddressSpace::kUnknown),
options_, ShouldSetLoadWithStorageAccess(), is_load_timing_enabled_,
include_load_timing_internal_info_with_response_,
/*response_start=*/base::TimeTicks::Now(), devtools_observer_.get(),
devtools_request_id().value_or(""));
}
void URLLoader::OnReceivedRedirect(net::URLRequest* url_request,
const net::RedirectInfo& redirect_info,
bool* defer_redirect) {
DCHECK(url_request == url_request_.get());
DCHECK(!deferred_redirect_url_);
deferred_redirect_url_ = std::make_unique<GURL>(redirect_info.new_url);
TRACE_EVENT("loading", "URLLoader::OnReceivedRedirect",
net::NetLogWithSourceToFlow(url_request_->net_log()), "new_url",
deferred_redirect_url_);
// Send the redirect response to the client, allowing them to inspect it and
// optionally follow the redirect.
*defer_redirect = true;
mojom::URLResponseHeadPtr response = BuildResponseHead();
DispatchOnRawResponse();
ReportFlaggedResponseCookies(false);
// Enforce the Cross-Origin-Resource-Policy (CORP) header.
const CrossOriginEmbedderPolicy kEmptyCoep;
const CrossOriginEmbedderPolicy& cross_origin_embedder_policy =
factory_params_->client_security_state
? factory_params_->client_security_state->cross_origin_embedder_policy
: kEmptyCoep;
const DocumentIsolationPolicy kEmptyDip;
const DocumentIsolationPolicy& document_isolation_policy =
factory_params_->client_security_state
? factory_params_->client_security_state->document_isolation_policy
: kEmptyDip;
if (std::optional<mojom::BlockedByResponseReason> blocked_reason =
CrossOriginResourcePolicy::IsBlocked(
url_request_->url(), url_request_->original_url(),
url_request_->initiator(), *response, request_mode_,
request_destination_, cross_origin_embedder_policy,
coep_reporter_, document_isolation_policy, dip_reporter_)) {
CompleteBlockedResponse(net::ERR_BLOCKED_BY_RESPONSE, false,
blocked_reason);
// TODO(crbug.com/40054032): Close the socket here.
// For more details see https://crbug.com/1154250#c17.
// Item 2 discusses redirect handling.
//
// "url_request_->AbortAndCloseConnection()" should ideally close the
// socket, but unfortunately, URLRequestHttpJob caches redirects in a way
// that ignores their response bodies, since they'll never be read. It does
// this by calling HttpCache::Transaction::StopCaching(), which also has the
// effect of detaching the HttpNetworkTransaction, which owns the socket,
// from the HttpCache::Transaction. To fix this, we'd either need to call
// StopCaching() later in the process, or make the HttpCache::Transaction
// continue to hang onto the HttpNetworkTransaction after this call.
DeleteSelf();
return;
}
url_loader_util::SetRequestCredentials(
redirect_info.new_url, factory_params_->client_security_state,
request_mode_, request_credentials_mode_, url_request_->initiator(),
*url_request_);
// Clear the Cookie header to ensure that cookies passed in through the
// `ResourceRequest` do not persist across redirects.
url_request_.get()->RemoveRequestHeaderByName(
net::HttpRequestHeaders::kCookie);
cookies_from_browser_.clear();
request_cookies_.clear();
const url::Origin origin = url::Origin::Create(url_request_->url());
const url::Origin pending_origin = url::Origin::Create(redirect_info.new_url);
const bool storage_access_eligible =
url_request_->cookie_setting_overrides().Has(
net::CookieSettingOverride::kStorageAccessGrantEligible);
using enum StorageAccessRedirectKind;
StorageAccessRedirectKind storage_access_redirect_kind =
storage_access_eligible ? kSameOrigin : kNoAccess;
if (!origin.IsSameOriginWith(pending_origin)) {
storage_access_redirect_kind =
storage_access_eligible ? kCrossOriginSameSite : kNoAccess;
url_request_->cookie_setting_overrides().Remove(
net::CookieSettingOverride::kStorageAccessGrantEligibleViaHeader);
if (storage_access_eligible) {
// TODO(https://crbug.com/379030052): the `CookieSettingOverride`s for
// Storage Access API and Storage Access Headers should be handled
// consistently during a same-site, cross-origin redirect.
bool cross_site = !net::SchemefulSite::IsSameSite(origin, pending_origin);
storage_access_redirect_kind =
cross_site ? kCrossSite : kCrossOriginSameSite;
if (cross_site ||
base::FeatureList::IsEnabled(
net::features::kStorageAccessApiFollowsSameOriginPolicy)) {
url_request_->cookie_setting_overrides().Remove(
net::CookieSettingOverride::kStorageAccessGrantEligible);
}
}
}
RecordStorageAccessRedirectMetric(storage_access_redirect_kind);
DCHECK_EQ(emitted_devtools_raw_request_, emitted_devtools_raw_response_);
response->emitted_extra_info = emitted_devtools_raw_request_;
ad_auction_event_record_request_helper_.HandleResponse(
*url_request_, GetPermissionsPolicy());
ProcessInboundSharedStorageInterceptorOnReceivedRedirect(redirect_info,
std::move(response));
}
void URLLoader::ProcessInboundSharedStorageInterceptorOnReceivedRedirect(
const net::RedirectInfo& redirect_info,
mojom::URLResponseHeadPtr response) {
DCHECK(shared_storage_request_helper_);
auto split = base::SplitOnceCallback(base::BindOnce(
&URLLoader::ContinueOnReceiveRedirect, weak_ptr_factory_.GetWeakPtr(),
redirect_info, std::move(response)));
if (!shared_storage_request_helper_->ProcessIncomingResponse(
*url_request_, std::move(split.first))) {
std::move(split.second).Run();
}
}
void URLLoader::ContinueOnReceiveRedirect(
const net::RedirectInfo& redirect_info,
mojom::URLResponseHeadPtr response) {
DCHECK(response);
url_loader_client_.Get()->OnReceiveRedirect(redirect_info,
std::move(response));
}
void URLLoader::OnAuthRequired(net::URLRequest* url_request,
const net::AuthChallengeInfo& auth_info) {
if (has_fetch_streaming_upload_body_) {
NotifyCompleted(net::ERR_FAILED);
// |this| may have been deleted.
return;
}
if (!url_loader_network_observer_) {
OnAuthCredentials(std::nullopt);
return;
}
if (do_not_prompt_for_login_) {
OnAuthCredentials(std::nullopt);
return;
}
DCHECK(!auth_challenge_responder_receiver_.is_bound());
url_loader_network_observer_->OnAuthRequired(
fetch_window_id_, request_id_, url_request_->url(), first_auth_attempt_,
auth_info, url_request->response_headers(),
auth_challenge_responder_receiver_.BindNewPipeAndPassRemote());
auth_challenge_responder_receiver_.set_disconnect_handler(
base::BindOnce(&URLLoader::DeleteSelf, base::Unretained(this)));
first_auth_attempt_ = false;
}
void URLLoader::OnCertificateRequested(net::URLRequest* unused,
net::SSLCertRequestInfo* cert_info) {
DCHECK(!client_cert_responder_receiver_.is_bound());
if (!url_loader_network_observer_) {
CancelRequest();
return;
}
// Set up mojo endpoints for ClientCertificateResponder and bind to the
// Receiver. This enables us to receive messages regarding the client
// certificate selection.
url_loader_network_observer_->OnCertificateRequested(
fetch_window_id_, cert_info,
client_cert_responder_receiver_.BindNewPipeAndPassRemote());
client_cert_responder_receiver_.set_disconnect_handler(
base::BindOnce(&URLLoader::CancelRequest, base::Unretained(this)));
}
void URLLoader::OnSSLCertificateError(net::URLRequest* request,
int net_error,
const net::SSLInfo& ssl_info,
bool fatal) {
if (!url_loader_network_observer_) {
OnSSLCertificateErrorResponse(ssl_info, net_error);
return;
}
url_loader_network_observer_->OnSSLCertificateError(
url_request_->url(), net_error, ssl_info, fatal,
base::BindOnce(&URLLoader::OnSSLCertificateErrorResponse,
weak_ptr_factory_.GetWeakPtr(), ssl_info));
}
void URLLoader::ProcessInboundSharedStorageInterceptorOnResponseStarted() {
DCHECK(shared_storage_request_helper_);
if (!shared_storage_request_helper_->ProcessIncomingResponse(
*url_request_, base::BindOnce(&URLLoader::ContinueOnResponseStarted,
weak_ptr_factory_.GetWeakPtr()))) {
ContinueOnResponseStarted();
}
}
void URLLoader::OnResponseStarted(net::URLRequest* url_request, int net_error) {
DCHECK(url_request == url_request_.get());
has_received_response_ = true;
if (keepalive_) {
base::UmaHistogramEnumeration(
"FetchKeepAlive.Requests2.Network",
internal::FetchKeepAliveRequestNetworkMetricType::kOnResponse);
}
// Wait to report for main frame navigations. This is because handling the
// cookie notification contends with ReadyToCommitNavigation, which is in the
// critical path for loading. Additionally, cookie observers for navigations
// now outlive the NavigationRequest.
bool delay_cookie_call =
url_request_->isolation_info().IsMainFrameRequest() &&
base::FeatureList::IsEnabled(kDelayedCookieNotification);
ReportFlaggedResponseCookies(!delay_cookie_call);
if (net_error != net::OK) {
NotifyCompleted(net_error);
// |this| may have been deleted.
return;
}
response_ = BuildResponseHead();
DispatchOnRawResponse();
ad_auction_event_record_request_helper_.HandleResponse(
*url_request_, GetPermissionsPolicy());
// Parse and remove the Trust Tokens response headers, if any are expected,
// potentially failing the request if an error occurs.
if (response_ && response_->headers && trust_token_interceptor_) {
DCHECK(response_);
trust_token_interceptor_->FinalizeOperation(
*response_->headers.get(),
base::BindOnce(&URLLoader::OnDoneFinalizingTrustTokenOperation,
weak_ptr_factory_.GetWeakPtr()));
// |this| may have been deleted.
return;
}
ProcessInboundSharedStorageInterceptorOnResponseStarted();
}
void URLLoader::OnDoneFinalizingTrustTokenOperation(net::Error error) {
if (error != net::OK) {
NotifyCompleted(error);
// |this| may have been deleted.
return;
}
ProcessInboundSharedStorageInterceptorOnResponseStarted();
}
void URLLoader::ContinueOnResponseStarted() {
// Do not account header bytes when reporting received body bytes to client.
reported_total_encoded_bytes_ = url_request_->GetTotalReceivedBytes();
if (upload_progress_tracker_) {
upload_progress_tracker_->OnUploadCompleted();
upload_progress_tracker_ = nullptr;
}
if (!(options_ & mojom::kURLLoadOptionReadAndDiscardBody)) {
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = GetDataPipeDefaultAllocationSize(
DataPipeAllocationSize::kLargerSizeIfPossible);
MojoResult result =
mojo::CreateDataPipe(&options, response_body_stream_, consumer_handle_);
if (result != MOJO_RESULT_OK) {
NotifyCompleted(net::ERR_INSUFFICIENT_RESOURCES);
return;
}
CHECK(response_body_stream_.is_valid());
CHECK(consumer_handle_.is_valid());
peer_closed_handle_watcher_.Watch(
response_body_stream_.get(), MOJO_HANDLE_SIGNAL_PEER_CLOSED,
base::BindRepeating(&URLLoader::OnResponseBodyStreamConsumerClosed,
base::Unretained(this)));
peer_closed_handle_watcher_.ArmOrNotify();
writable_handle_watcher_.Watch(
response_body_stream_.get(), MOJO_HANDLE_SIGNAL_WRITABLE,
base::BindRepeating(&URLLoader::OnResponseBodyStreamReady,
base::Unretained(this)));
}
// Enforce the Cross-Origin-Resource-Policy (CORP) header.
const CrossOriginEmbedderPolicy kEmptyCoep;
const CrossOriginEmbedderPolicy& cross_origin_embedder_policy =
factory_params_->client_security_state
? factory_params_->client_security_state->cross_origin_embedder_policy
: kEmptyCoep;
const DocumentIsolationPolicy kEmptyDip;
const DocumentIsolationPolicy& document_isolation_policy =
factory_params_->client_security_state
? factory_params_->client_security_state->document_isolation_policy
: kEmptyDip;
if (std::optional<mojom::BlockedByResponseReason> blocked_reason =
CrossOriginResourcePolicy::IsBlocked(
url_request_->url(), url_request_->original_url(),
url_request_->initiator(), *response_, request_mode_,
request_destination_, cross_origin_embedder_policy,
coep_reporter_, document_isolation_policy, dip_reporter_)) {
CompleteBlockedResponse(net::ERR_BLOCKED_BY_RESPONSE, false,
blocked_reason);
// Close the socket associated with the request, to prevent leaking
// information.
url_request_->AbortAndCloseConnection();
DeleteSelf();
return;
}
// Enforce SRI-compliant HTTP Message Signature headers.
//
// https://wicg.github.io/signature-based-sri/
if (std::optional<mojom::BlockedByResponseReason> blocked_reason =
MaybeBlockResponseForSRIMessageSignature(
*url_request_, *response_, expected_public_keys_,
devtools_observer_.get(), devtools_request_id().value_or(""))) {
CompleteBlockedResponse(net::ERR_BLOCKED_BY_RESPONSE, false,
blocked_reason);
// Close the socket associated with the request, to prevent leaking
// information.
url_request_->AbortAndCloseConnection();
DeleteSelf();
return;
}
// Enforce ad-auction-only signals -- the renderer process isn't allowed
// to read auction-only signals for ad auctions; only the browser process
// is allowed to read those, and only the browser process can issue trusted
// requests.
// TODO(crbug.com/40269364): Remove old names once API users have migrated to
// new names.
if (!factory_params_->is_trusted && response_->headers) {
std::optional<std::string> auction_only =
response_->headers->GetNormalizedHeader("Ad-Auction-Only");
if (!auction_only) {
auction_only =
response_->headers->GetNormalizedHeader("X-FLEDGE-Auction-Only");
}
if (auction_only &&
base::EqualsCaseInsensitiveASCII(*auction_only, "true")) {
CompleteBlockedResponse(net::ERR_BLOCKED_BY_RESPONSE, false);
url_request_->AbortAndCloseConnection();
DeleteSelf();
return;
}
}
// Figure out if we need to sniff (for MIME type detection or for Opaque
// Response Blocking / ORB).
if (factory_params_->is_orb_enabled) {
// TODO(ricea): Make ORB and ReadAndDiscardBody work together if necessary.
CHECK(!(options_ & mojom::kURLLoadOptionReadAndDiscardBody))
<< "ORB is incompatible with the ReadAndDiscardBody option";
orb_analyzer_ = orb::ResponseAnalyzer::Create(&*per_factory_orb_state_);
is_more_orb_sniffing_needed_ = true;
auto decision =
orb_analyzer_->Init(url_request_->url(), url_request_->initiator(),
request_mode_, request_destination_, *response_);
if (MaybeBlockResponseForOrb(decision)) {
return;
}
}
if ((options_ & mojom::kURLLoadOptionSniffMimeType)) {
if (ShouldSniffContent(url_request_->url(), *response_)) {
// We're going to look at the data before deciding what the content type
// is. That means we need to delay sending the response started IPC.
VLOG(1) << "Will sniff content for mime type: " << url_request_->url();
is_more_mime_sniffing_needed_ = true;
mime_type_before_sniffing_ = response_->mime_type;
} else if (response_->mime_type.empty()) {
// Ugg. The server told us not to sniff the content but didn't give us
// a mime type. What's a browser to do? Turns out, we're supposed to
// treat the response as "text/plain". This is the most secure option.
response_->mime_type.assign("text/plain");
}
}
// If client-side content decoding is requested, store the types of decoding
// to be used with the Durable Message so it can decode on retrieval.
if (devtools_durable_message_) {
devtools_durable_message_->set_client_decoding_types(
response_->client_side_content_decoding_types);
}
// If client-side content decoding is requested and either ORB or MIME
// sniffing is needed, use PartialDecoder to get decoded data for sniffing.
if (!response_->client_side_content_decoding_types.empty() &&
(is_more_orb_sniffing_needed_ || is_more_mime_sniffing_needed_)) {
// Create a PartialDecoder to decode the response body up to a certain limit
// for sniffing purposes.
partial_decoder_ = std::make_unique<PartialDecoder>(
base::BindRepeating(
[](base::WeakPtr<net::URLRequest> url_request, net::IOBuffer* dest,
int dest_size) {
CHECK(url_request);
return url_request->Read(dest, dest_size);
},
url_request_->GetWeakPtr()),
response_->client_side_content_decoding_types,
partial_decoder_decoding_buffer_size_);
// Start reading decoded data from the partial decoder.
ReadDecodedDataFromPartialDecoder();
return;
}
StartReading();
}
void URLLoader::ReadDecodedDataFromPartialDecoder() {
// Keep reading from the partial decoder until it signals pending or
// completes.
while (partial_decoder_) {
// Attempt to read more decoded data.
int result = partial_decoder_->ReadDecodedDataMore(
base::BindOnce(&URLLoader::OnReadDecodedDataFromPartialDecoder,
base::Unretained(this)));
if (result == net::ERR_IO_PENDING) {
// If the read is pending, return and wait for the callback.
return;
}
// Process the result immediately.
CheckPartialDecoderResult(result);
if (partial_decoder_result_) {
// Sniffing is complete, and we have the raw data. Stop using the
// partial decoder and proceed to read the rest of the response.
StartReading();
return;
}
}
}
void URLLoader::OnReadDecodedDataFromPartialDecoder(int result) {
// This is the callback from `PartialDecoder::ReadDecodedDataMore`.
// Process the result of the partial decode.
CheckPartialDecoderResult(result);
if (partial_decoder_result_) {
// Sniffing is complete. Proceed to read the full response.
StartReading();
} else if (partial_decoder_) {
// More sniffing might be needed, continue reading from the partial decoder.
ReadDecodedDataFromPartialDecoder();
}
}
void URLLoader::CheckPartialDecoderResult(int result) {
if (result < 0) {
// The partial decoder failed. Stop decoding and report the error.
partial_decoder_.reset();
// Defer calling NotifyCompleted to make sure the caller can still access
// |this|.
TaskRunner(url_request_->priority())
->PostTask(FROM_HERE,
base::BindOnce(&URLLoader::NotifyCompleted,
weak_ptr_factory_.GetWeakPtr(), result));
return;
}
// Check if we should stop sniffing after processing the current chunk of
// decoded data. This happens if the decoder returns 0 (end of stream) or
// if the decoded buffer is full.
const bool stop_sniffing_after_processing_current_data =
(result == 0 || !partial_decoder_->HasRemainingBuffer());
// Get a view of the decoded data, limited to the maximum sniffing size.
// Capping the size to net::kMaxBytesToSniff for tests which have called
// set_partial_decoder_decoding_buffer_size_for_testing().
const std::string_view decoded_data_to_sniff =
base::as_string_view(partial_decoder_->decoded_data())
.substr(0, net::kMaxBytesToSniff);
if (is_more_mime_sniffing_needed_) {
// Perform MIME sniffing using the decoded data.
CHECK(mime_type_before_sniffing_.has_value());
std::string new_type;
is_more_mime_sniffing_needed_ = !net::SniffMimeType(
decoded_data_to_sniff, url_request_->url(),
mime_type_before_sniffing_.value(),
net::ForceSniffFileUrlsForHtml::kDisabled, &new_type);
response_->mime_type = std::move(new_type);
response_->did_mime_sniff = true;
if (stop_sniffing_after_processing_current_data) {
is_more_mime_sniffing_needed_ = false;
}
}
if (is_more_orb_sniffing_needed_) {
// Perform ORB sniffing using the decoded data.
orb::ResponseAnalyzer::Decision orb_decision =
result == 0 ? orb::ResponseAnalyzer::Decision::kSniffMore
: orb_analyzer_->Sniff(decoded_data_to_sniff);
if (orb_decision == orb::ResponseAnalyzer::Decision::kSniffMore &&
stop_sniffing_after_processing_current_data) {
// If more sniffing was requested but we've reached the limit, get the
// final decision for the sniffed data.
orb_decision = orb_analyzer_->HandleEndOfSniffableResponseBody();
DCHECK_NE(orb::ResponseAnalyzer::Decision::kSniffMore, orb_decision);
}
if (MaybeBlockResponseForOrb(orb_decision)) {
partial_decoder_.reset();
return;
}
}
// If no more sniffing is needed, retrieve the accumulated raw data from the
// partial decoder.
if (!is_more_orb_sniffing_needed_ && !is_more_mime_sniffing_needed_) {
partial_decoder_result_ = std::move(*partial_decoder_).TakeResult();
}
}
void URLLoader::ReadMore() {
CHECK_NE(url_read_state_, URLReadState::kURLReadInProgress);
url_read_state_ = URLReadState::kWaitMojoPipeWritable;
if (partial_decoder_result_) {
// If we have buffered raw data from the partial decoder, send that first.
while (partial_decoder_result_->HasRawData()) {
MojoResult result = NetToMojoPendingBuffer::BeginWrite(
&response_body_stream_, &pending_write_);
switch (result) {
case MOJO_RESULT_OK:
break;
case MOJO_RESULT_SHOULD_WAIT:
// The Mojo data pipe is full. Wait for it to become writable.
// While a SlopBucket would be ideal when enabled, it's omitted here
// for simplicity.
//
// This scenario occurs when the PartialDecoder reads raw data
// exceeding the Mojo data pipe's capacity. However, since the
// PartialDecoder only reads decompressed data up to
// net::kMaxBytesToSniff, this situation is extremely rare in
// real-world scenarios.
writable_handle_watcher_.ArmOrNotify();
return;
default:
// The response body stream is in a bad state. This happens when the
// consumer handle of the body was synchronously released in
// URLLoaderClient::OnReceiveResponse().
NotifyCompleted(net::ERR_FAILED);
return;
}
// Copy data from the raw buffer into the Mojo pipe buffer.
pending_write_buffer_offset_ = partial_decoder_result_->ConsumeRawData(
base::as_writable_byte_span(*pending_write_));
CHECK(pending_write_buffer_offset_);
MaybeCollectDurableMessage(0, pending_write_buffer_offset_);
CompletePendingWrite(true);
}
// Check if the partial decoder finished with a specific status.
if (partial_decoder_result_->completion_status()) {
NotifyCompleted(*partial_decoder_result_->completion_status());
return;
}
partial_decoder_result_.reset();
}
// Once the MIME type is sniffed, all data is sent as soon as it is read from
// the network.
DCHECK(consumer_handle_.is_valid() || !pending_write_);
// TODO(ricea): Refactor this method and DidRead() to reduce duplication.
if (options_ & mojom::kURLLoadOptionReadAndDiscardBody) {
url_read_state_ = URLReadState::kURLReadInProgress;
int bytes_read =
url_request_->Read(discard_buffer_.get(), discard_buffer_->size());
if (bytes_read != net::ERR_IO_PENDING) {
DidRead(bytes_read, /*completed_synchronously=*/true,
/*into_slop_bucket=*/false);
// `this` may have been deleted.
}
return;
}
if (!pending_write_.get()) {
// TODO: we should use the abstractions in MojoAsyncResourceHandler.
DCHECK_EQ(0u, pending_write_buffer_offset_);
MojoResult result = NetToMojoPendingBuffer::BeginWrite(
&response_body_stream_, &pending_write_);
mojo_begin_write_count_for_uma_++;
switch (result) {
case MOJO_RESULT_OK:
break;
case MOJO_RESULT_SHOULD_WAIT: {
CHECK(!pending_write_);
bool should_wait = true;
if (base::FeatureList::IsEnabled(kSlopBucket) && !slop_bucket_) {
slop_bucket_ = SlopBucket::RequestSlopBucket(url_request_.get());
was_slop_bucket_enabled_ = true;
}
if (slop_bucket_ && !slop_bucket_->read_in_progress() &&
!slop_bucket_->IsComplete()) {
// Read into the slop bucket while we're waiting for the mojo data
// pipe to empty out.
std::optional<int> bytes_read_maybe = slop_bucket_->AttemptRead();
if (bytes_read_maybe.has_value()) {
url_read_state_ = URLReadState::kURLReadInProgress;
should_wait = false;
int bytes_read = bytes_read_maybe.value();
if (bytes_read != net::ERR_IO_PENDING) {
// DidRead() will not delete `this` when `into_slop_bucket` is
// true, so it is safe to access member variables after this call.
DidRead(bytes_read, /*completed_synchronously=*/true,
/*into_slop_bucket=*/true);
}
}
} // The pipe is full. We need to wait for it to have more space.
if (should_wait) {
mojo_blocked_write_count_for_uma_++;
}
writable_handle_watcher_.ArmOrNotify();
return;
}
default:
// The response body stream is in a bad state. Bail.
NotifyCompleted(net::ERR_FAILED);
return;
}
pending_write_buffer_size_ = pending_write_->size();
DCHECK_GT(static_cast<uint32_t>(std::numeric_limits<int>::max()),
pending_write_buffer_size_);
if (consumer_handle_.is_valid()) {
DCHECK_GE(pending_write_buffer_size_,
static_cast<uint32_t>(net::kMaxBytesToSniff));
}
// We may be able to fill up the buffer from the slop bucket.
if (slop_bucket_) {
const size_t consumed =
slop_bucket_->Consume(base::as_writable_byte_span(*pending_write_));
if (consumed) {
// TODO(ricea): Refactor the way pending writes work so we don't need to
// poke a value into `pending_write_buffer_offset_` here.
pending_write_buffer_offset_ = consumed;
CompletePendingWrite(true);
ReadMoreAsync();
return;
} else if (slop_bucket_->read_in_progress()) {
// There were no bytes available, but a read is in progress. Need to
// prevent starting another read until it completes.
CompletePendingWrite(true);
return;
} else if (slop_bucket_->IsComplete()) {
// The SlopBucket didn't have any bytes available. It has finished
// reading from the URLRequest and now the render process has caught up,
// so we can notify completion.
CompletePendingWrite(true);
NotifyCompleted(slop_bucket_->completion_code());
// `this` is deleted.
return;
}
// Nothing was available. Read from `url_request_` as usual.
}
}
CHECK(!slop_bucket_ || !slop_bucket_->IsComplete());
auto buf = base::MakeRefCounted<NetToMojoIOBuffer>(
pending_write_, pending_write_buffer_offset_);
url_read_state_ = URLReadState::kURLReadInProgress;
int bytes_read = url_request_->Read(
buf.get(), static_cast<int>(pending_write_buffer_size_ -
pending_write_buffer_offset_));
if (bytes_read != net::ERR_IO_PENDING) {
DidRead(bytes_read, /*completed_synchronously=*/true,
/*into_slop_bucket=*/false);
// |this| may have been deleted.
}
}
void URLLoader::ReadMoreAsync() {
CHECK_EQ(url_read_state_, URLReadState::kWaitMojoPipeWritable);
url_read_state_ = URLReadState::kReadMoreTaskPosted;
TaskRunner(url_request_->priority())
->PostTask(FROM_HERE, base::BindOnce(&URLLoader::ReadMore,
weak_ptr_factory_.GetWeakPtr()));
}
// Handles the completion of a read. `num_bytes` is the number of bytes read, 0
// if we reached the end of the response body, or a net::Error otherwise.
// `completed_synchronously` is true if the call to URLRequest::Read did not
// return net::ERR_IO_PENDING. `into_slop_bucket` is true if this was actually a
// read into `slop_bucket_` and not into our own buffer.
void URLLoader::DidRead(int num_bytes,
bool completed_synchronously,
bool into_slop_bucket) {
CHECK_EQ(url_read_state_, URLReadState::kURLReadInProgress);
url_read_state_ = URLReadState::kWaitMojoPipeWritable;
size_t new_data_offset = pending_write_buffer_offset_;
MaybeCollectDurableMessage(new_data_offset, num_bytes);
if (num_bytes > 0) {
if (!into_slop_bucket) {
pending_write_buffer_offset_ += num_bytes;
}
// Only notify client of download progress if we're done sniffing and
// started sending response.
if (!consumer_handle_.is_valid()) {
int64_t total_encoded_bytes = url_request_->GetTotalReceivedBytes();
if (ShouldSendTransferSizeUpdated()) {
int64_t delta = total_encoded_bytes - reported_total_encoded_bytes_;
DCHECK_LE(0, delta);
if (delta) {
url_loader_client_.Get()->OnTransferSizeUpdated(delta);
}
}
reported_total_encoded_bytes_ = total_encoded_bytes;
}
}
bool complete_read = true;
if (consumer_handle_.is_valid()) {
// `consumer_handle_` is only valid when we are sniffing. Sniffing is only
// applied to the first 1024 bytes. The mojo data pipe is always larger than
// 1024 bytes, therefore it will never fill up while we are sniffing.
// Therefore a SlopBucket will not have been created yet and
// `into_slop_bucket` can't be true.
CHECK(!into_slop_bucket);
// |pending_write_| may be null if the job self-aborts due to a suspend;
// this will have |consumer_handle_| valid when the loader is paused.
if (pending_write_) {
// Limit sniffing to the first net::kMaxBytesToSniff.
size_t data_length = pending_write_buffer_offset_;
if (data_length > net::kMaxBytesToSniff)
data_length = net::kMaxBytesToSniff;
std::string_view data(pending_write_->buffer(), data_length);
bool stop_sniffing_after_processing_current_data =
(num_bytes <= 0 ||
pending_write_buffer_offset_ >= net::kMaxBytesToSniff);
if (is_more_mime_sniffing_needed_) {
CHECK(mime_type_before_sniffing_.has_value());
std::string new_type;
is_more_mime_sniffing_needed_ = !net::SniffMimeType(
data, url_request_->url(), mime_type_before_sniffing_.value(),
net::ForceSniffFileUrlsForHtml::kDisabled, &new_type);
// SniffMimeType() returns false if there is not enough data to
// determine the mime type. However, even if it returns false, it
// returns a new type that is probably better than the current one.
response_->mime_type.assign(new_type);
response_->did_mime_sniff = true;
if (stop_sniffing_after_processing_current_data)
is_more_mime_sniffing_needed_ = false;
}
if (is_more_orb_sniffing_needed_) {
orb::ResponseAnalyzer::Decision orb_decision =
orb::ResponseAnalyzer::Decision::kSniffMore;
// `has_new_data_to_sniff` can be false at the end-of-stream.
bool has_new_data_to_sniff = new_data_offset < data.length();
if (has_new_data_to_sniff)
orb_decision = orb_analyzer_->Sniff(data);
if (orb_decision == orb::ResponseAnalyzer::Decision::kSniffMore &&
stop_sniffing_after_processing_current_data) {
orb_decision = orb_analyzer_->HandleEndOfSniffableResponseBody();
DCHECK_NE(orb::ResponseAnalyzer::Decision::kSniffMore, orb_decision);
}
if (MaybeBlockResponseForOrb(orb_decision)) {
return;
}
}
}
if (!is_more_mime_sniffing_needed_ && !is_more_orb_sniffing_needed_) {
SendResponseToClient();
} else {
complete_read = false;
}
}
if (num_bytes <= 0) {
// There may be no |pending_write_| if a URLRequestJob cancelled itself in
// URLRequestJob::OnSuspend() after receiving headers, while there was no
// pending read.
// TODO(mmenke): That case is rather unfortunate - something should be done
// at the socket layer instead, both to make for a better API (Only complete
// reads when there's a pending read), and to cover all TCP socket uses,
// since the concern is the effect that entering suspend mode has on
// sockets. See https://crbug.com/651120.
if (pending_write_) {
CHECK(!into_slop_bucket);
CompletePendingWrite(num_bytes == 0);
}
// If we are reading into the SlopBucket then notification of completion
// will be postponed until the data has been forwarded to the mojo data
// pipe.
if (!into_slop_bucket) {
NotifyCompleted(num_bytes);
// |this| will have been deleted.
}
return;
}
if (complete_read && !into_slop_bucket) {
CompletePendingWrite(true /* success */);
}
if (completed_synchronously) {
ReadMoreAsync();
} else {
ReadMore();
}
}
void URLLoader::OnReadCompleted(net::URLRequest* url_request, int bytes_read) {
DCHECK(url_request == url_request_.get());
if (partial_decoder_ && partial_decoder_->read_in_progress()) {
// When `partial_decoder_` is reading the body from `url_request`, pass the
// result to `partial_decoder_`.
partial_decoder_->OnReadRawDataCompleted(bytes_read);
return;
}
bool into_slop_bucket = false;
if (slop_bucket_ && slop_bucket_->read_in_progress()) {
slop_bucket_->OnReadCompleted(bytes_read);
into_slop_bucket = true;
}
DidRead(bytes_read, /*completed_synchronously=*/false, into_slop_bucket);
// |this| may have been deleted.
}
int URLLoader::OnBeforeStartTransaction(
const net::HttpRequestHeaders& headers,
net::NetworkDelegate::OnBeforeStartTransactionCallback callback) {
const net::HttpRequestHeaders* used_headers = &headers;
net::HttpRequestHeaders headers_with_bonus_cookies;
if (!cookies_from_browser_.empty()) {
headers_with_bonus_cookies = AttachCookies(headers, cookies_from_browser_);
used_headers = &headers_with_bonus_cookies;
}
if (include_request_cookies_with_response_) {
request_cookies_.clear();
std::string cookie_header =
used_headers->GetHeader(net::HttpRequestHeaders::kCookie)
.value_or(std::string());
net::cookie_util::ParseRequestCookieLine(cookie_header, &request_cookies_);
}
if (header_client_) {
header_client_->OnBeforeSendHeaders(
*used_headers,
base::BindOnce(&URLLoader::OnBeforeSendHeadersComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
return net::ERR_IO_PENDING;
}
// Additional cookies were added to the existing headers, so `callback` must
// be invoked to ensure that the cookies are included in the request.
if (!cookies_from_browser_.empty()) {
CHECK_EQ(used_headers, &headers_with_bonus_cookies);
TaskRunner(url_request_->priority())
->PostTask(FROM_HERE,
base::BindOnce(std::move(callback), net::OK,
std::move(headers_with_bonus_cookies)));
return net::ERR_IO_PENDING;
}
return net::OK;
}
int URLLoader::OnHeadersReceived(
net::CompletionOnceCallback callback,
const net::HttpResponseHeaders* original_response_headers,
scoped_refptr<net::HttpResponseHeaders>* override_response_headers,
const net::IPEndPoint& endpoint,
std::optional<GURL>* preserve_fragment_on_redirect_url) {
if (header_client_) {
header_client_->OnHeadersReceived(
original_response_headers->raw_headers(), endpoint,
base::BindOnce(&URLLoader::OnHeadersReceivedComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
override_response_headers,
preserve_fragment_on_redirect_url));
return net::ERR_IO_PENDING;
}
return net::OK;
}
net::LoadState URLLoader::GetLoadState() const {
return url_request_->GetLoadState().state;
}
net::UploadProgress URLLoader::GetUploadProgress() const {
return url_request_->GetUploadProgress();
}
int32_t URLLoader::GetProcessId() const {
return factory_params_->process_id;
}
uint32_t URLLoader::GetResourceType() const {
return resource_type_;
}
bool URLLoader::CookiesDisabled() const {
return options_ & mojom::kURLLoadOptionBlockAllCookies;
}
bool URLLoader::AllowCookie(const net::CanonicalCookie& cookie,
const GURL& url,
const net::SiteForCookies& site_for_cookies) const {
if (cookie.IsPartitioned() && !CookiesDisabled()) {
return true;
}
return AllowFullCookies(url, site_for_cookies);
}
bool URLLoader::AllowFullCookies(
const GURL& url,
const net::SiteForCookies& site_for_cookies) const {
net::StaticCookiePolicy::Type policy =
net::StaticCookiePolicy::ALLOW_ALL_COOKIES;
if (CookiesDisabled()) {
policy = net::StaticCookiePolicy::BLOCK_ALL_COOKIES;
} else if (options_ & mojom::kURLLoadOptionBlockThirdPartyCookies) {
policy = net::StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES;
} else {
return true;
}
return net::StaticCookiePolicy(policy).CanAccessCookies(
url, site_for_cookies) == net::OK;
}
// static
URLLoader* URLLoader::ForRequest(const net::URLRequest& request) {
auto* pointer =
static_cast<UnownedPointer*>(request.GetUserData(kUserDataKey));
if (!pointer)
return nullptr;
return pointer->get();
}
void URLLoader::OnAuthCredentials(
const std::optional<net::AuthCredentials>& credentials) {
auth_challenge_responder_receiver_.reset();
if (!credentials.has_value()) {
url_request_->CancelAuth();
} else {
// CancelAuth will proceed to the body, so cookies only need to be reported
// here.
ReportFlaggedResponseCookies(false);
url_request_->SetAuth(credentials.value());
}
}
void URLLoader::ContinueWithCertificate(
const scoped_refptr<net::X509Certificate>& x509_certificate,
const std::string& provider_name,
const std::vector<uint16_t>& algorithm_preferences,
mojo::PendingRemote<mojom::SSLPrivateKey> ssl_private_key) {
client_cert_responder_receiver_.reset();
auto key = base::MakeRefCounted<SSLPrivateKeyProxy>(
provider_name, algorithm_preferences, std::move(ssl_private_key));
url_request_->ContinueWithCertificate(std::move(x509_certificate),
std::move(key));
}
void URLLoader::ContinueWithoutCertificate() {
client_cert_responder_receiver_.reset();
url_request_->ContinueWithCertificate(nullptr, nullptr);
}
void URLLoader::CancelRequest() {
client_cert_responder_receiver_.reset();
url_request_->CancelWithError(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED);
}
void URLLoader::CancelRequestIfNonceMatchesAndUrlNotExempted(
const base::UnguessableToken& nonce,
const std::set<GURL>& exemptions) {
if (url_request_->isolation_info().nonce() == nonce) {
if (!exemptions.contains(
url_request_->original_url().GetWithoutFilename())) {
url_request_->CancelWithError(net::ERR_NETWORK_ACCESS_REVOKED);
}
}
}
void URLLoader::NotifyCompleted(int error_code) {
// Ensure sending the final upload progress message here, since
// OnResponseCompleted can be called without OnResponseStarted on cancellation
// or error cases.
if (upload_progress_tracker_) {
upload_progress_tracker_->OnUploadCompleted();
upload_progress_tracker_ = nullptr;
}
auto total_received = url_request_->GetTotalReceivedBytes();
auto total_sent = url_request_->GetTotalSentBytes();
if (total_received > 0) {
base::UmaHistogramCustomCounts("DataUse.BytesReceived3.Delegate",
total_received, 50, 10 * 1000 * 1000, 50);
mojo_begin_write_count_for_uma_ =
std::max(mojo_begin_write_count_for_uma_, 1);
const int proportion = mojo_blocked_write_count_for_uma_ * 100 /
mojo_begin_write_count_for_uma_;
base::UmaHistogramPercentage(
"Net.URLLoader.ProportionOfWritesBlockedByMojo", proportion);
if (was_slop_bucket_enabled_) {
base::UmaHistogramPercentage(
"Net.URLLoader.SlopBucket.ProportionOfWritesBlockedByMojo.All",
proportion);
using CacheEntryStatus = net::HttpResponseInfo::CacheEntryStatus;
const CacheEntryStatus& cache_entry_status =
url_request_->response_info().cache_entry_status;
if (cache_entry_status != CacheEntryStatus::ENTRY_USED &&
cache_entry_status != CacheEntryStatus::ENTRY_VALIDATED) {
base::UmaHistogramPercentage(
"Net.URLLoader.SlopBucket.ProportionOfWritesBlockedByMojo.NoCache",
proportion);
}
}
}
if (total_sent > 0) {
UMA_HISTOGRAM_COUNTS_1M("DataUse.BytesSent3.Delegate", total_sent);
}
url_loader_util::MaybeRecordSharedDictionaryUsedResponseMetrics(
error_code, request_destination_, url_request_->response_info(),
shared_dictionary_allowed_check_passed_);
if ((total_received > 0 || total_sent > 0)) {
if (url_loader_network_observer_ && provide_data_use_updates_) {
url_loader_network_observer_->OnDataUseUpdate(
url_request_->traffic_annotation().unique_id_hash_code,
total_received, total_sent);
}
}
if (url_loader_client_.Get()) {
if (consumer_handle_.is_valid())
SendResponseToClient();
URLLoaderCompletionStatus status;
status.error_code = error_code;
if (error_code == net::ERR_QUIC_PROTOCOL_ERROR) {
net::NetErrorDetails details;
url_request_->PopulateNetErrorDetails(&details);
status.extended_error_code = details.quic_connection_error;
} else if (error_code == net::ERR_INCONSISTENT_IP_ADDRESS_SPACE) {
// The error code is only used internally, translate it into a CORS error.
DCHECK(cors_error_status_.has_value());
status.error_code = net::ERR_FAILED;
}
status.exists_in_cache = url_request_->response_info().was_cached;
status.completion_time = base::TimeTicks::Now();
status.encoded_data_length = url_request_->GetTotalReceivedBytes();
status.encoded_body_length = url_request_->GetRawBodyBytes();
status.decoded_body_length = total_written_bytes_;
status.resolve_error_info =
url_request_->response_info().resolve_error_info;
if (trust_token_interceptor_ && trust_token_interceptor_->status()) {
status.trust_token_operation_status = *trust_token_interceptor_->status();
}
status.cors_error_status = cors_error_status_;
if ((options_ & mojom::kURLLoadOptionSendSSLInfoForCertificateError) &&
net::IsCertStatusError(url_request_->ssl_info().cert_status)) {
status.ssl_info = url_request_->ssl_info();
}
url_loader_client_.Get()->OnComplete(status);
}
DeleteSelf();
}
void URLLoader::OnMojoDisconnect() {
NotifyCompleted(net::ERR_FAILED);
}
void URLLoader::OnResponseBodyStreamConsumerClosed(MojoResult result) {
NotifyCompleted(net::ERR_FAILED);
}
void URLLoader::OnResponseBodyStreamReady(MojoResult result) {
if (result != MOJO_RESULT_OK) {
NotifyCompleted(net::ERR_FAILED);
return;
}
if (url_read_state_ == URLReadState::kWaitMojoPipeWritable) {
ReadMore();
}
}
void URLLoader::DeleteSelf() {
std::move(delete_callback_).Run(this);
}
void URLLoader::SendResponseToClient() {
TRACE_EVENT("loading", "network::URLLoader::SendResponseToClient",
net::NetLogWithSourceToFlow(url_request_->net_log()), "url",
url_request_->url());
DCHECK_EQ(emitted_devtools_raw_request_, emitted_devtools_raw_response_);
response_->emitted_extra_info = emitted_devtools_raw_request_;
url_loader_client_.Get()->OnReceiveResponse(
response_->Clone(), std::move(consumer_handle_), std::nullopt);
}
void URLLoader::CompletePendingWrite(bool success) {
if (success && pending_write_) {
// The write can only be completed immediately in case of a success, since
// doing so invalidates memory of any attached NetToMojoIOBuffer's; but in
// case of an abort, particularly one caused by a suspend, the failure may
// be delivered to URLLoader while the disk_cache layer is still hanging on
// to the now-invalid IOBuffer in some worker thread trying to commit it to
// disk. In case of an error, this will have to wait till everything is
// destroyed.
response_body_stream_ =
pending_write_->Complete(pending_write_buffer_offset_);
}
total_written_bytes_ += pending_write_buffer_offset_;
pending_write_ = nullptr;
pending_write_buffer_offset_ = 0;
}
void URLLoader::SetRawResponseHeaders(
scoped_refptr<const net::HttpResponseHeaders> headers) {
raw_response_headers_ = headers;
}
void URLLoader::NotifyEarlyResponse(
scoped_refptr<const net::HttpResponseHeaders> headers) {
DCHECK(!has_received_response_);
DCHECK(url_loader_client_.Get());
DCHECK(headers);
DCHECK_EQ(headers->response_code(), 103);
// Calculate IP address space.
mojom::ParsedHeadersPtr parsed_headers =
PopulateParsedHeaders(headers.get(), url_request_->url());
net::IPEndPoint transaction_endpoint;
bool has_endpoint =
url_request_->GetTransactionRemoteEndpoint(&transaction_endpoint);
DCHECK(has_endpoint);
CalculateClientAddressSpaceParams params{
.client_address_space_inherited_from_service_worker = std::nullopt,
.parsed_headers = &parsed_headers,
.remote_endpoint = &transaction_endpoint,
};
mojom::IPAddressSpace ip_address_space =
CalculateClientAddressSpace(url_request_->url(), params);
mojom::ReferrerPolicy referrer_policy = ParseReferrerPolicy(*headers);
url_loader_client_.Get()->OnReceiveEarlyHints(mojom::EarlyHints::New(
std::move(parsed_headers), referrer_policy, ip_address_space));
MaybeNotifyEarlyResponseToDevtools(*headers);
}
void URLLoader::MaybeNotifyEarlyResponseToDevtools(
const net::HttpResponseHeaders& headers) {
if (!devtools_observer_ || !devtools_request_id()) {
return;
}
devtools_observer_->OnEarlyHintsResponse(
devtools_request_id().value(), ResponseHeaderToRawHeaderPairs(headers));
}
void URLLoader::SetRawRequestHeadersAndNotify(
net::HttpRawRequestHeaders headers) {
// If we have emitted_devtools_raw_request_, don't notify DevTools
// to prevent duplicate ExtraInfo events.
if (!emitted_devtools_raw_request_ && devtools_observer_ &&
devtools_request_id()) {
std::vector<network::mojom::HttpRawHeaderPairPtr> header_array;
header_array.reserve(headers.headers().size());
for (const auto& header : headers.headers()) {
network::mojom::HttpRawHeaderPairPtr pair =
network::mojom::HttpRawHeaderPair::New();
pair->key = header.first;
pair->value = header.second;
header_array.push_back(std::move(pair));
}
DispatchOnRawRequest(std::move(header_array));
}
raw_request_line_size_ = headers.request_line().size();
// Each header's format is "{key}: {value}\r\n" so add 4 bytes for each
// header.
raw_request_headers_size_ = headers.headers().size() * 4;
for (const auto& [key, value] : headers.headers()) {
raw_request_headers_size_ += key.size() + value.size();
}
if (cookie_observer_) {
std::vector<mojom::CookieOrLineWithAccessResultPtr> reported_cookies;
for (const auto& cookie_with_access_result :
url_request_->maybe_sent_cookies()) {
if (ShouldNotifyAboutCookie(
cookie_with_access_result.access_result.status)) {
reported_cookies.push_back(mojom::CookieOrLineWithAccessResult::New(
mojom::CookieOrLine::NewCookie(cookie_with_access_result.cookie),
cookie_with_access_result.access_result));
}
}
if (!reported_cookies.empty()) {
cookie_access_details_.emplace_back(mojom::CookieAccessDetails::New(
mojom::CookieAccessDetails::Type::kRead, url_request_->url(),
url_request_->isolation_info().frame_origin(),
url_request_->isolation_info().top_frame_origin().value_or(
url::Origin()),
url_request_->site_for_cookies(), std::move(reported_cookies),
devtools_request_id(), url_request_->ad_tagged(),
url_request_->cookie_setting_overrides()));
}
}
}
bool URLLoader::IsSharedDictionaryReadAllowed() {
shared_dictionary_allowed_check_passed_ =
shared_dictionary_checker_->CheckAllowedToReadAndReport(
url_request_->url(), url_request_->site_for_cookies(),
url_request_->isolation_info(), url_request_->cookie_partition_key());
return shared_dictionary_allowed_check_passed_;
}
void URLLoader::DispatchOnRawRequest(
std::vector<network::mojom::HttpRawHeaderPairPtr> headers) {
DCHECK(devtools_observer_ && devtools_request_id());
net::LoadTimingInfo load_timing_info;
url_request_->GetLoadTimingInfo(&load_timing_info);
emitted_devtools_raw_request_ = true;
bool is_main_frame_navigation =
url_request_->isolation_info().IsMainFrameRequest() ||
url_request_->force_main_frame_for_same_site_cookies();
net::SchemefulSite request_site(url_request_->url());
// TODO when crbug.com/40093296 "Don't trust |site_for_cookies| provided by
// the renderer" is fixed. Update the FromNetworkIsolationKey method to use
// url_request_->isolation_info().site_for_cookies() instead of
// url_request_->site_for_cookies().
std::optional<net::CookiePartitionKey> partition_key =
net::CookiePartitionKey::FromNetworkIsolationKey(
url_request_->isolation_info().network_isolation_key(),
url_request_->site_for_cookies(), request_site,
is_main_frame_navigation);
std::optional<bool> site_has_cookie_in_other_partition;
if (partition_key.has_value()) {
site_has_cookie_in_other_partition =
url_request_->context()->cookie_store()->SiteHasCookieInOtherPartition(
request_site, partition_key.value());
}
network::mojom::OtherPartitionInfoPtr other_partition_info = nullptr;
if (site_has_cookie_in_other_partition.has_value()) {
other_partition_info = network::mojom::OtherPartitionInfo::New();
other_partition_info->site_has_cookie_in_other_partition =
*site_has_cookie_in_other_partition;
}
std::optional<base::UnguessableToken> applied_network_conditions_id;
if (throttling_token_) {
ThrottlingNetworkInterceptor* interceptor =
ThrottlingController::GetInterceptor(throttling_token_->source_id(),
url_request_->url());
if (interceptor) {
applied_network_conditions_id = interceptor->conditions().rule_id();
}
}
devtools_observer_->OnRawRequest(
devtools_request_id().value(), url_request_->maybe_sent_cookies(),
std::move(headers), load_timing_info.request_start,
private_network_access_interceptor_.CloneClientSecurityState(),
std::move(other_partition_info),
std::move(applied_network_conditions_id));
}
void URLLoader::DispatchOnRawResponse() {
if (!emitted_devtools_raw_request_) {
// TODO(ortuno): not sure why emitting of metrics is gated upon request not
// having been dispatched to DevTools, but this has been so since it raw
// header size metrics have been introduced by https://crrev.com/c/5824030.
if (url_request_->response_headers()) {
// Record request metrics here instead of in NotifyCompleted to account
// for redirects.
url_loader_util::RecordURLLoaderRequestMetrics(
*url_request_, raw_request_line_size_, raw_request_headers_size_);
}
// If there were no raw request headers, we assume no raw response headers
// either, to make client logic simpler.
// TODO(caseq): ensure this is actually an invariant?
return;
}
// Per `if (emitted_devtools_raw_request_)` above.
CHECK(devtools_observer_);
CHECK(devtools_request_id());
if (!url_request_->response_headers()) {
return;
}
const net::HttpResponseHeaders* response_headers =
raw_response_headers_ ? raw_response_headers_.get()
: url_request_->response_headers();
std::vector<network::mojom::HttpRawHeaderPairPtr> header_array =
ResponseHeaderToRawHeaderPairs(*response_headers);
// Only send the "raw" header text when the headers were actually send in
// text form (i.e. not QUIC or SPDY)
std::optional<std::string> raw_response_headers;
const net::HttpResponseInfo& response_info = url_request_->response_info();
if (!response_info.DidUseQuic() && !response_info.was_fetched_via_spdy) {
raw_response_headers =
std::make_optional(net::HttpUtil::ConvertHeadersBackToHTTPResponse(
response_headers->raw_headers()));
}
emitted_devtools_raw_response_ = true;
devtools_observer_->OnRawResponse(
devtools_request_id().value(), url_request_->maybe_stored_cookies(),
std::move(header_array), raw_response_headers,
private_network_access_interceptor_.ResponseAddressSpace().value_or(
mojom::IPAddressSpace::kUnknown),
response_headers->response_code(), url_request_->cookie_partition_key());
}
void URLLoader::SendUploadProgress(const net::UploadProgress& progress) {
url_loader_client_.Get()->OnUploadProgress(
progress.position(), progress.size(),
base::BindOnce(&URLLoader::OnUploadProgressACK,
weak_ptr_factory_.GetWeakPtr()));
}
void URLLoader::OnUploadProgressACK() {
if (upload_progress_tracker_)
upload_progress_tracker_->OnAckReceived();
}
void URLLoader::OnSSLCertificateErrorResponse(const net::SSLInfo& ssl_info,
int net_error) {
if (net_error == net::OK) {
url_request_->ContinueDespiteLastError();
return;
}
url_request_->CancelWithSSLError(net_error, ssl_info);
}
bool URLLoader::HasDataPipe() const {
return pending_write_ || response_body_stream_.is_valid();
}
void URLLoader::ResumeStart() {
url_request_->LogUnblocked();
url_request_->Start();
}
void URLLoader::OnBeforeSendHeadersComplete(
net::NetworkDelegate::OnBeforeStartTransactionCallback callback,
int result,
const std::optional<net::HttpRequestHeaders>& headers) {
if (include_request_cookies_with_response_ && headers) {
request_cookies_.clear();
std::string cookie_header =
headers->GetHeader(net::HttpRequestHeaders::kCookie)
.value_or(std::string());
net::cookie_util::ParseRequestCookieLine(cookie_header, &request_cookies_);
}
std::move(callback).Run(result, headers);
}
void URLLoader::OnHeadersReceivedComplete(
net::CompletionOnceCallback callback,
scoped_refptr<net::HttpResponseHeaders>* out_headers,
std::optional<GURL>* out_preserve_fragment_on_redirect_url,
int result,
const std::optional<std::string>& headers,
const std::optional<GURL>& preserve_fragment_on_redirect_url) {
if (headers) {
*out_headers =
base::MakeRefCounted<net::HttpResponseHeaders>(headers.value());
}
*out_preserve_fragment_on_redirect_url = preserve_fragment_on_redirect_url;
std::move(callback).Run(result);
}
void URLLoader::CompleteBlockedResponse(
int error_code,
bool should_report_orb_blocking,
std::optional<mojom::BlockedByResponseReason> reason) {
if (has_received_response_) {
// The response headers and body shouldn't yet be sent to the
// URLLoaderClient.
CHECK(response_);
CHECK(consumer_handle_.is_valid() ||
(options_ & mojom::kURLLoadOptionReadAndDiscardBody));
}
// Tell the URLLoaderClient that the response has been completed.
URLLoaderCompletionStatus status;
status.error_code = error_code;
status.completion_time = base::TimeTicks::Now();
status.encoded_data_length = 0;
status.encoded_body_length = 0;
status.decoded_body_length = 0;
status.should_report_orb_blocking = should_report_orb_blocking;
status.blocked_by_response_reason = reason;
url_loader_client_.Get()->OnComplete(status);
// Reset the connection to the URLLoaderClient. This helps ensure that we
// won't accidentally leak any data to the renderer from this point on.
url_loader_client_.Reset();
}
URLLoader::BlockResponseForOrbResult URLLoader::BlockResponseForOrb() {
// ORB should only do work after the response headers have been received.
DCHECK(has_received_response_);
// Caller should have set up a OrbAnalyzer for BlockResponseForOrb to be
// able to do its job.
DCHECK(orb_analyzer_);
// The response headers and body shouldn't yet be sent to the URLLoaderClient.
DCHECK(response_);
DCHECK(consumer_handle_.is_valid());
// Send stripped headers to the real URLLoaderClient.
orb::SanitizeBlockedResponseHeaders(*response_);
// Determine error code. This essentially handles the "ORB v0.1" and "ORB
// v0.2" difference.
int blocked_error_code =
(orb_analyzer_->ShouldHandleBlockedResponseAs() ==
orb::ResponseAnalyzer::BlockedResponseHandling::kEmptyResponse)
? net::OK
: net::ERR_BLOCKED_BY_ORB;
// Send empty body to the real URLLoaderClient. This preserves "ORB v0.1"
// behaviour and will also go away once
// OpaqueResponseBlockingErrorsForAllFetches is perma-enabled.
if (blocked_error_code == net::OK) {
mojo::ScopedDataPipeProducerHandle producer_handle;
mojo::ScopedDataPipeConsumerHandle consumer_handle;
MojoResult result = mojo::CreateDataPipe(kBlockedBodyAllocationSize,
producer_handle, consumer_handle);
if (result != MOJO_RESULT_OK) {
// Defer calling NotifyCompleted to make sure the caller can still access
// |this|.
TaskRunner(url_request_->priority())
->PostTask(FROM_HERE,
base::BindOnce(&URLLoader::NotifyCompleted,
weak_ptr_factory_.GetWeakPtr(),
net::ERR_INSUFFICIENT_RESOURCES));
return kWillCancelRequest;
}
producer_handle.reset();
// Tell the real URLLoaderClient that the response has been completed.
url_loader_client_.Get()->OnReceiveResponse(
response_->Clone(), std::move(consumer_handle), std::nullopt);
}
// At this point, orb_analyzer_ has done its duty. We'll reset it now
// to force UMA reporting to happen earlier, to support easier testing.
bool should_report_blocked_response =
orb_analyzer_->ShouldReportBlockedResponse();
orb_analyzer_.reset();
CompleteBlockedResponse(blocked_error_code, should_report_blocked_response);
// Close the socket associated with the request, to prevent leaking
// information.
url_request_->AbortAndCloseConnection();
// Delete self and cancel the request - the caller doesn't need to continue.
//
// DeleteSelf is posted asynchronously, to make sure that the callers (e.g.
// URLLoader::OnResponseStarted and/or URLLoader::DidRead instance methods)
// can still safely dereference |this|.
TaskRunner(url_request_->priority())
->PostTask(FROM_HERE, base::BindOnce(&URLLoader::DeleteSelf,
weak_ptr_factory_.GetWeakPtr()));
return kWillCancelRequest;
}
bool URLLoader::MaybeBlockResponseForOrb(
orb::ResponseAnalyzer::Decision orb_decision) {
DCHECK(orb_analyzer_);
DCHECK(is_more_orb_sniffing_needed_);
bool will_cancel = false;
switch (orb_decision) {
case network::orb::ResponseAnalyzer::Decision::kBlock: {
will_cancel = BlockResponseForOrb() == kWillCancelRequest;
orb_analyzer_.reset();
is_more_orb_sniffing_needed_ = false;
break;
}
case network::orb::ResponseAnalyzer::Decision::kAllow:
orb_analyzer_.reset();
is_more_orb_sniffing_needed_ = false;
break;
case network::orb::ResponseAnalyzer::Decision::kSniffMore:
break;
}
DCHECK_EQ(is_more_orb_sniffing_needed_, !!orb_analyzer_);
return will_cancel;
}
void URLLoader::ReportFlaggedResponseCookies(bool call_cookie_observer) {
if (!cookie_observer_) {
return;
}
std::vector<mojom::CookieOrLineWithAccessResultPtr> reported_cookies;
for (const auto& cookie_line_and_access_result :
url_request_->maybe_stored_cookies()) {
if (ShouldNotifyAboutCookie(
cookie_line_and_access_result.access_result.status)) {
mojom::CookieOrLinePtr cookie_or_line;
if (cookie_line_and_access_result.cookie.has_value()) {
cookie_or_line = mojom::CookieOrLine::NewCookie(
cookie_line_and_access_result.cookie.value());
} else {
cookie_or_line = mojom::CookieOrLine::NewCookieString(
cookie_line_and_access_result.cookie_string);
}
reported_cookies.push_back(mojom::CookieOrLineWithAccessResult::New(
std::move(cookie_or_line),
cookie_line_and_access_result.access_result));
}
}
if (!reported_cookies.empty()) {
cookie_access_details_.emplace_back(mojom::CookieAccessDetails::New(
mojom::CookieAccessDetails::Type::kChange, url_request_->url(),
url_request_->isolation_info().frame_origin(),
url_request_->isolation_info().top_frame_origin().value_or(
url::Origin()),
url_request_->site_for_cookies(), std::move(reported_cookies),
devtools_request_id(), url_request_->ad_tagged(),
url_request_->cookie_setting_overrides()));
if (call_cookie_observer) {
cookie_observer_->OnCookiesAccessed(std::move(cookie_access_details_));
}
}
}
void URLLoader::StartReading() {
if (!is_more_mime_sniffing_needed_ && !is_more_orb_sniffing_needed_) {
// Treat feed types as text/plain.
if (response_->mime_type == "application/rss+xml" ||
response_->mime_type == "application/atom+xml") {
response_->mime_type.assign("text/plain");
}
SendResponseToClient();
}
// Start reading...
ReadMore();
}
bool URLLoader::ShouldForceIgnoreSiteForCookies(
const ResourceRequest& request) {
// Ignore site for cookies in requests from an initiator covered by the
// same-origin-policy exclusions in `origin_access_list_` (typically requests
// initiated by Chrome Extensions).
if (request.request_initiator.has_value() &&
cors::OriginAccessList::AccessState::kAllowed ==
origin_access_list_->CheckAccessState(
request.request_initiator.value(), request.url)) {
return true;
}
// Convert `site_for_cookies` into an origin (an opaque origin if
// `net::SiteForCookies::IsNull()` returns true).
//
// Note that `site_for_cookies` is a _site_ rather than an _origin_, but for
// Chrome Extensions the _site_ and _origin_ of a host are the same extension
// id. Thanks to this, for Chrome Extensions, we can pass a _site_ into
// OriginAccessChecks (which normally expect an _origin_).
url::Origin site_origin =
url::Origin::Create(request.site_for_cookies.RepresentativeUrl());
// If `site_for_cookies` represents an origin that is granted access to the
// initiator and the target by `origin_access_list_` (typically such
// `site_for_cookies` represents a Chrome Extension), then we also should
// force ignoring of site for cookies if the initiator and the target are
// same-site.
//
// Ideally we would walk up the frame tree and check that each ancestor is
// first-party to the main frame (treating the `origin_access_list_`
// exceptions as "first-party"). But walking up the tree is not possible in
// //services/network and so we make do with just checking the direct
// initiator of the request.
//
// We also check same-siteness between the initiator and the requested URL,
// because setting `force_ignore_site_for_cookies` to true causes Strict
// cookies to be attached, and having the initiator be same-site to the
// request URL is a requirement for Strict cookies (see
// net::cookie_util::ComputeSameSiteContext).
if (!site_origin.opaque() && request.request_initiator.has_value()) {
bool site_can_access_target =
cors::OriginAccessList::AccessState::kAllowed ==
origin_access_list_->CheckAccessState(site_origin, request.url);
bool site_can_access_initiator =
cors::OriginAccessList::AccessState::kAllowed ==
origin_access_list_->CheckAccessState(
site_origin, request.request_initiator->GetURL());
net::SiteForCookies site_of_initiator =
net::SiteForCookies::FromOrigin(request.request_initiator.value());
bool are_initiator_and_target_same_site =
site_of_initiator.IsFirstParty(request.url);
if (site_can_access_initiator && site_can_access_target &&
are_initiator_and_target_same_site) {
return true;
}
}
return false;
}
bool URLLoader::ShouldSendTransferSizeUpdated() const {
return devtools_request_id() || url_request_->ad_tagged() ||
!base::FeatureList::IsEnabled(features::kReduceTransferSizeUpdatedIPC);
}
bool URLLoader::ShouldSetLoadWithStorageAccess() const {
CHECK(url_request_);
if (!IncludesValidLoadField(url_request_->response_headers())) {
return false;
}
auto determine_storage_access_load_outcome =
[&]() -> net::cookie_util::ActivateStorageAccessLoadOutcome {
if (!url_request_->storage_access_status().IsSet()) {
url_request_->set_storage_access_status(
url_request_->CalculateStorageAccessStatus());
}
if (!url_request_->storage_access_status()
.GetStatusForThirdPartyContext()) {
return net::cookie_util::ActivateStorageAccessLoadOutcome::
kFailureInvalidStatus;
}
switch (url_request_->storage_access_status()
.GetStatusForThirdPartyContext()
.value()) {
case net::cookie_util::StorageAccessStatus::kNone:
return net::cookie_util::ActivateStorageAccessLoadOutcome::
kFailureInvalidStatus;
case net::cookie_util::StorageAccessStatus::kInactive:
case net::cookie_util::StorageAccessStatus::kActive:
return net::cookie_util::ActivateStorageAccessLoadOutcome::kSuccess;
}
NOTREACHED();
};
auto outcome = determine_storage_access_load_outcome();
base::UmaHistogramEnumeration(
"API.StorageAccessHeader.ActivateStorageAccessLoadOutcome", outcome);
return outcome ==
net::cookie_util::ActivateStorageAccessLoadOutcome::kSuccess;
}
const mojom::ClientSecurityState* URLLoader::GetClientSecurityState() {
return url_loader_util::SelectClientSecurityState(
factory_params_->client_security_state.get(),
client_security_state_.get());
}
void URLLoader::ResetRawHeadersForRedirect() {
emitted_devtools_raw_request_ = false;
emitted_devtools_raw_response_ = false;
raw_request_line_size_ = 0;
raw_request_headers_size_ = 0;
raw_response_headers_ = nullptr;
}
void URLLoader::MaybeCollectDurableMessage(size_t new_data_offset,
int num_bytes) {
if (!pending_write_ || !devtools_durable_message_) {
return;
}
if (num_bytes <= 0) {
devtools_durable_message_->MarkComplete();
return;
}
int64_t raw_bytes_cur_size = url_request_->GetRawBodyBytes();
int64_t raw_bytes_delta =
raw_bytes_cur_size - devtools_durable_message_raw_size_;
devtools_durable_message_->AddBytes(
base::as_byte_span(
base::span(*pending_write_)
.subspan(new_data_offset, static_cast<size_t>(num_bytes))),
raw_bytes_delta);
devtools_durable_message_raw_size_ = raw_bytes_cur_size;
}
} // namespace network