[go: nahoru, domu]

blob: f05fbe5c3022d10c7e1ce03d20e8cc2c18ef9db9 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/cors/preflight_controller.h"
#include <algorithm>
#include <vector>
#include "base/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/unguessable_token.h"
#include "net/base/load_flags.h"
#include "net/base/network_isolation_key.h"
#include "net/http/http_request_headers.h"
#include "net/log/net_log.h"
#include "net/log/net_log_source.h"
#include "net/log/net_log_with_source.h"
#include "services/network/cors/cors_util.h"
#include "services/network/network_service.h"
#include "services/network/public/cpp/constants.h"
#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/cpp/cors/cors_error_status.h"
#include "services/network/public/cpp/devtools_observer_util.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/devtools_observer.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/public/mojom/parsed_headers.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
namespace network {
namespace cors {
namespace {
const char kLowerCaseTrue[] = "true";
int RetrieveCacheFlags(int load_flags) {
return load_flags & (net::LOAD_VALIDATE_CACHE | net::LOAD_BYPASS_CACHE |
net::LOAD_DISABLE_CACHE);
}
absl::optional<std::string> GetHeaderString(
const scoped_refptr<net::HttpResponseHeaders>& headers,
const std::string& header_name) {
std::string header_value;
if (!headers || !headers->GetNormalizedHeader(header_name, &header_value))
return absl::nullopt;
return header_value;
}
bool ShouldEnforcePrivateNetworkAccessHeader(
PrivateNetworkAccessPreflightBehavior behavior) {
// Use a switch statement to guarantee this is updated when the enum
// definition changes.
switch (behavior) {
case PrivateNetworkAccessPreflightBehavior::kEnforce:
return true;
case PrivateNetworkAccessPreflightBehavior::kWarnWithTimeout:
case PrivateNetworkAccessPreflightBehavior::kWarn:
return false;
}
}
// Algorithm step 3 of the CORS-preflight fetch,
// https://fetch.spec.whatwg.org/#cors-preflight-fetch-0, that requires
// - CORS-safelisted request-headers excluded
// - duplicates excluded
// - sorted lexicographically
// - byte-lowercased
std::string CreateAccessControlRequestHeadersHeader(
const net::HttpRequestHeaders& headers,
bool is_revalidating) {
// Exclude the forbidden headers because they may be added by the user
// agent. They must be checked separately and rejected for
// JavaScript-initiated requests.
std::vector<std::string> filtered_headers =
CorsUnsafeNotForbiddenRequestHeaderNames(headers.GetHeaderVector(),
is_revalidating);
if (filtered_headers.empty())
return std::string();
// Sort header names lexicographically.
std::sort(filtered_headers.begin(), filtered_headers.end());
return base::JoinString(filtered_headers, ",");
}
std::unique_ptr<ResourceRequest> CreatePreflightRequest(
const ResourceRequest& request,
bool tainted,
const net::NetLogWithSource& net_log_for_actual_request,
const absl::optional<base::UnguessableToken>& devtools_request_id) {
DCHECK(!request.url.has_username());
DCHECK(!request.url.has_password());
std::unique_ptr<ResourceRequest> preflight_request =
std::make_unique<ResourceRequest>();
// Algorithm step 1 through 5 of the CORS-preflight fetch,
// https://fetch.spec.whatwg.org/#cors-preflight-fetch.
preflight_request->url = request.url;
preflight_request->method = net::HttpRequestHeaders::kOptionsMethod;
preflight_request->priority = request.priority;
preflight_request->destination = request.destination;
preflight_request->referrer = request.referrer;
preflight_request->referrer_policy = request.referrer_policy;
preflight_request->mode = mojom::RequestMode::kCors;
preflight_request->credentials_mode = mojom::CredentialsMode::kOmit;
preflight_request->load_flags = RetrieveCacheFlags(request.load_flags);
preflight_request->resource_type = request.resource_type;
preflight_request->fetch_window_id = request.fetch_window_id;
preflight_request->headers.SetHeader(net::HttpRequestHeaders::kAccept,
kDefaultAcceptHeaderValue);
preflight_request->headers.SetHeader(
header_names::kAccessControlRequestMethod, request.method);
std::string request_headers = CreateAccessControlRequestHeadersHeader(
request.headers, request.is_revalidating);
if (!request_headers.empty()) {
preflight_request->headers.SetHeader(
header_names::kAccessControlRequestHeaders, request_headers);
}
preflight_request->target_ip_address_space = request.target_ip_address_space;
if (preflight_request->target_ip_address_space !=
mojom::IPAddressSpace::kUnknown) {
// See the CORS-preflight fetch algorithm modifications laid out in the
// Private Network Access spec, in step 4 of the CORS preflight section as
// of writing: https://wicg.github.io/private-network-access/#cors-preflight
preflight_request->headers.SetHeader(
header_names::kAccessControlRequestPrivateNetwork, "true");
}
// Copy the client security state as well, if set in the request's trusted
// params. Note that the we clone the pointer unconditionally if the original
// request has trusted params, but that the cloned pointer may be null. It is
// unclear whether it is safe to copy all the trusted params, so we only copy
// what we need for PNA.
//
// This is useful when the client security state is not specified through the
// URL loader factory params, typically when a single URL loader factory is
// shared by a few different client contexts. This is the case for
// navigations and interest group auctions.
if (request.trusted_params.has_value()) {
preflight_request->trusted_params = ResourceRequest::TrustedParams();
preflight_request->trusted_params->client_security_state =
request.trusted_params->client_security_state.Clone();
}
DCHECK(request.request_initiator);
preflight_request->request_initiator = request.request_initiator;
preflight_request->headers.SetHeader(
net::HttpRequestHeaders::kOrigin,
(tainted ? url::Origin() : *request.request_initiator).Serialize());
// We normally set User-Agent down in the network stack, but the DevTools
// emulation override is applied on a higher level (renderer or browser),
// so copy User-Agent from the original request, if present.
// TODO(caseq, morlovich): do the same for client hints.
std::string user_agent;
if (request.headers.GetHeader(net::HttpRequestHeaders::kUserAgent,
&user_agent)) {
preflight_request->headers.SetHeader(net::HttpRequestHeaders::kUserAgent,
user_agent);
}
// Additional headers that the algorithm in the spec does not require, but
// it's better that CORS preflight requests have them.
preflight_request->headers.SetHeader("Sec-Fetch-Mode", "cors");
if (devtools_request_id) {
// Set `enable_load_timing` flag to make URLLoader fill the LoadTimingInfo
// in URLResponseHead, which will be sent to DevTools.
preflight_request->enable_load_timing = true;
// Set `devtools_request_id` to make URLLoader send the raw request and the
// raw response to DevTools.
preflight_request->devtools_request_id = devtools_request_id->ToString();
}
preflight_request->is_fetch_like_api = request.is_fetch_like_api;
preflight_request->is_favicon = request.is_favicon;
// Set `net_log_reference_info` to reference actual request from preflight
// request in NetLog.
preflight_request->net_log_reference_info =
net_log_for_actual_request.source();
net::NetLogSource net_log_source_for_preflight = net::NetLogSource(
net::NetLogSourceType::URL_REQUEST, net::NetLog::Get()->NextID());
net_log_for_actual_request.AddEventReferencingSource(
net::NetLogEventType::CORS_PREFLIGHT_URL_REQUEST,
net_log_source_for_preflight);
// Set `net_log_create_info` to specify NetLog source used in preflight
// URL Request.
preflight_request->net_log_create_info = net_log_source_for_preflight;
return preflight_request;
}
// Performs a CORS access check on the CORS-preflight response parameters.
// According to the note at https://fetch.spec.whatwg.org/#cors-preflight-fetch
// step 6, even for a preflight check, `credentials_mode` should be checked on
// the actual request rather than preflight one.
base::expected<void, CorsErrorStatus> CheckPreflightAccess(
const GURL& response_url,
const int response_status_code,
const absl::optional<std::string>& allow_origin_header,
const absl::optional<std::string>& allow_credentials_header,
mojom::CredentialsMode actual_credentials_mode,
const url::Origin& origin) {
// Step 7 of https://fetch.spec.whatwg.org/#cors-preflight-fetch
auto cors_result =
CheckAccess(response_url, allow_origin_header, allow_credentials_header,
actual_credentials_mode, origin);
const bool has_ok_status = IsOkStatus(response_status_code);
AccessCheckResult result = (!cors_result.has_value() || !has_ok_status)
? AccessCheckResult::kNotPermittedInPreflight
: AccessCheckResult::kPermittedInPreflight;
UMA_HISTOGRAM_ENUMERATION("Net.Cors.AccessCheckResult", result);
if (!network::IsOriginPotentiallyTrustworthy(origin)) {
UMA_HISTOGRAM_ENUMERATION("Net.Cors.AccessCheckResult.NotSecureRequestor",
result);
}
// Prefer using a preflight specific error code.
if (!cors_result.has_value()) {
switch (cors_result.error().cors_error) {
case mojom::CorsError::kWildcardOriginNotAllowed:
cors_result.error().cors_error =
mojom::CorsError::kPreflightWildcardOriginNotAllowed;
break;
case mojom::CorsError::kMissingAllowOriginHeader:
cors_result.error().cors_error =
mojom::CorsError::kPreflightMissingAllowOriginHeader;
break;
case mojom::CorsError::kMultipleAllowOriginValues:
cors_result.error().cors_error =
mojom::CorsError::kPreflightMultipleAllowOriginValues;
break;
case mojom::CorsError::kInvalidAllowOriginValue:
cors_result.error().cors_error =
mojom::CorsError::kPreflightInvalidAllowOriginValue;
break;
case mojom::CorsError::kAllowOriginMismatch:
cors_result.error().cors_error =
mojom::CorsError::kPreflightAllowOriginMismatch;
break;
case mojom::CorsError::kInvalidAllowCredentials:
cors_result.error().cors_error =
mojom::CorsError::kPreflightInvalidAllowCredentials;
break;
default:
NOTREACHED();
break;
}
} else if (!has_ok_status) {
cors_result = base::unexpected<CorsErrorStatus>(
mojom::CorsError::kPreflightInvalidStatus);
} else {
base::expected<void, CorsErrorStatus>();
}
return cors_result;
}
// Checks errors for the "Access-Control-Allow-Private-Network" header.
//
// See the CORS-preflight fetch algorithm modifications laid out in the Private
// Network Access spec, in step 4 of the CORS preflight section as of writing:
// https://wicg.github.io/private-network-access/#cors-preflight
absl::optional<CorsErrorStatus> CheckAllowPrivateNetworkHeader(
const mojom::URLResponseHead& head,
const ResourceRequest& original_request) {
if (original_request.target_ip_address_space ==
mojom::IPAddressSpace::kUnknown) {
// Not a Private Network Access preflight.
return absl::nullopt;
}
absl::optional<std::string> header = GetHeaderString(
head.headers, header_names::kAccessControlAllowPrivateNetwork);
if (!header) {
CorsErrorStatus status(
mojom::CorsError::kPreflightMissingAllowPrivateNetwork);
status.target_address_space = original_request.target_ip_address_space;
return status;
}
if (*header != kLowerCaseTrue) {
CorsErrorStatus status(
mojom::CorsError::kPreflightInvalidAllowPrivateNetwork, *header);
status.target_address_space = original_request.target_ip_address_space;
return status;
}
return absl::nullopt;
}
std::unique_ptr<PreflightResult> CreatePreflightResult(
const GURL& final_url,
const mojom::URLResponseHead& head,
const ResourceRequest& original_request,
bool tainted,
PrivateNetworkAccessPreflightBehavior private_network_access_behavior,
const mojom::ClientSecurityStatePtr& client_security_state,
mojom::DevToolsObserver* devtools_observer,
absl::optional<CorsErrorStatus>* detected_error_status) {
DCHECK(detected_error_status);
auto check_result = CheckPreflightAccess(
final_url, head.headers ? head.headers->response_code() : 0,
GetHeaderString(head.headers, header_names::kAccessControlAllowOrigin),
GetHeaderString(head.headers,
header_names::kAccessControlAllowCredentials),
original_request.credentials_mode,
tainted ? url::Origin() : *original_request.request_initiator);
if (!check_result.has_value()) {
*detected_error_status = std::move(check_result.error());
return nullptr;
}
*detected_error_status = absl::nullopt;
absl::optional<CorsErrorStatus> status =
CheckAllowPrivateNetworkHeader(head, original_request);
if (status) {
if (ShouldEnforcePrivateNetworkAccessHeader(
private_network_access_behavior)) {
*detected_error_status = std::move(status);
return nullptr;
}
// We only report these errors as warnings when they are suppressed, since
// `CorsURLLoader` already reports them otherwise.
if (devtools_observer) {
devtools_observer->OnCorsError(
original_request.devtools_request_id,
original_request.request_initiator, client_security_state.Clone(),
original_request.url, *status, /*is_warning=*/true);
}
base::UmaHistogramEnumeration(kPreflightWarningHistogramName,
status->cors_error);
}
absl::optional<mojom::CorsError> error;
auto result = PreflightResult::Create(
original_request.credentials_mode,
GetHeaderString(head.headers, header_names::kAccessControlAllowMethods),
GetHeaderString(head.headers, header_names::kAccessControlAllowHeaders),
GetHeaderString(head.headers, header_names::kAccessControlMaxAge),
&error);
if (error)
*detected_error_status = CorsErrorStatus(*error);
return result;
}
absl::optional<CorsErrorStatus> CheckPreflightResult(
const PreflightResult& result,
const ResourceRequest& original_request,
NonWildcardRequestHeadersSupport non_wildcard_request_headers_support) {
absl::optional<CorsErrorStatus> status =
result.EnsureAllowedCrossOriginMethod(original_request.method);
if (status)
return status;
return result.EnsureAllowedCrossOriginHeaders(
original_request.headers, original_request.is_revalidating,
non_wildcard_request_headers_support);
}
} // namespace
const char kPreflightErrorHistogramName[] = "Net.Cors.PreflightCheckError2";
const char kPreflightWarningHistogramName[] = "Net.Cors.PreflightCheckWarning";
class PreflightController::PreflightLoader final {
public:
PreflightLoader(
PreflightController* controller,
CompletionCallback completion_callback,
const ResourceRequest& request,
WithTrustedHeaderClient with_trusted_header_client,
NonWildcardRequestHeadersSupport non_wildcard_request_headers_support,
PrivateNetworkAccessPreflightBehavior private_network_access_behavior,
bool tainted,
const net::NetworkTrafficAnnotationTag& annotation_tag,
const net::NetworkIsolationKey& network_isolation_key,
mojom::ClientSecurityStatePtr client_security_state,
mojo::PendingRemote<mojom::DevToolsObserver> devtools_observer,
const net::NetLogWithSource net_log)
: controller_(controller),
completion_callback_(std::move(completion_callback)),
original_request_(request),
non_wildcard_request_headers_support_(
non_wildcard_request_headers_support),
private_network_access_behavior_(private_network_access_behavior),
tainted_(tainted),
network_isolation_key_(network_isolation_key),
client_security_state_(std::move(client_security_state)),
devtools_observer_(std::move(devtools_observer)),
net_log_(net_log) {
if (devtools_observer_)
devtools_request_id_ = base::UnguessableToken::Create();
auto preflight_request =
CreatePreflightRequest(request, tainted, net_log, devtools_request_id_);
if (devtools_observer_) {
DCHECK(devtools_request_id_);
network::mojom::URLRequestDevToolsInfoPtr request_info =
network::ExtractDevToolsInfo(*preflight_request);
devtools_observer_->OnCorsPreflightRequest(
*devtools_request_id_, preflight_request->headers,
std::move(request_info), original_request_.url,
original_request_.devtools_request_id.value_or(""));
}
loader_ =
SimpleURLLoader::Create(std::move(preflight_request), annotation_tag);
uint32_t options = mojom::kURLLoadOptionAsCorsPreflight;
if (with_trusted_header_client) {
options |= mojom::kURLLoadOptionUseHeaderClient;
}
loader_->SetURLLoaderFactoryOptions(options);
// When private network access preflights are sent in warning mode, we
// should not wait around forever for a response. Certain servers never
// respond, and that should not fail the overall request. Instead, we should
// wait a short while then move on. See also https://crbug.com/1299382.
if (private_network_access_behavior_ ==
PrivateNetworkAccessPreflightBehavior::kWarnWithTimeout &&
base::FeatureList::IsEnabled(
features::kPrivateNetworkAccessPreflightShortTimeout)) {
loader_->SetTimeoutDuration(base::Milliseconds(200));
}
}
PreflightLoader(const PreflightLoader&) = delete;
PreflightLoader& operator=(const PreflightLoader&) = delete;
void Request(mojom::URLLoaderFactory* loader_factory) {
DCHECK(loader_);
loader_->SetOnRedirectCallback(base::BindRepeating(
&PreflightLoader::HandleRedirect, base::Unretained(this)));
loader_->SetOnResponseStartedCallback(base::BindOnce(
&PreflightLoader::HandleResponseHeader, base::Unretained(this)));
loader_->DownloadToString(
loader_factory,
base::BindOnce(&PreflightLoader::HandleResponseBody,
base::Unretained(this)),
0);
}
private:
void HandleRedirect(const net::RedirectInfo& redirect_info,
const network::mojom::URLResponseHead& response_head,
std::vector<std::string>* to_be_removed_headers) {
if (devtools_observer_) {
DCHECK(devtools_request_id_);
devtools_observer_->OnCorsPreflightRequestCompleted(
*devtools_request_id_,
network::URLLoaderCompletionStatus(net::ERR_INVALID_REDIRECT));
}
std::move(completion_callback_)
.Run(net::ERR_FAILED,
CorsErrorStatus(mojom::CorsError::kPreflightDisallowedRedirect),
false);
RemoveFromController();
// `this` is deleted here.
}
void HandleResponseHeader(const GURL& final_url,
const mojom::URLResponseHead& head) {
if (devtools_observer_) {
DCHECK(devtools_request_id_);
mojom::URLResponseHeadDevToolsInfoPtr head_info =
ExtractDevToolsInfo(head);
devtools_observer_->OnCorsPreflightResponse(
*devtools_request_id_, original_request_.url, std::move(head_info));
devtools_observer_->OnCorsPreflightRequestCompleted(
*devtools_request_id_, network::URLLoaderCompletionStatus(net::OK));
}
absl::optional<CorsErrorStatus> detected_error_status;
bool has_authorization_covered_by_wildcard = false;
std::unique_ptr<PreflightResult> result = CreatePreflightResult(
final_url, head, original_request_, tainted_,
private_network_access_behavior_, client_security_state_,
devtools_observer_ ? devtools_observer_.get() : nullptr,
&detected_error_status);
if (result) {
// Only log if there is a result to log.
net_log_.AddEvent(net::NetLogEventType::CORS_PREFLIGHT_RESULT,
[&result] { return result->NetLogParams(); });
// Preflight succeeded. Check `original_request_` with `result`.
DCHECK(!detected_error_status);
detected_error_status = CheckPreflightResult(
*result, original_request_, non_wildcard_request_headers_support_);
has_authorization_covered_by_wildcard =
result->HasAuthorizationCoveredByWildcard(original_request_.headers);
}
if (!(original_request_.load_flags & net::LOAD_DISABLE_CACHE) &&
!detected_error_status) {
controller_->AppendToCache(*original_request_.request_initiator,
original_request_.url, network_isolation_key_,
original_request_.target_ip_address_space,
std::move(result));
}
std::move(completion_callback_)
.Run(detected_error_status ? net::ERR_FAILED : net::OK,
detected_error_status, has_authorization_covered_by_wildcard);
}
void HandleResponseBody(std::unique_ptr<std::string> response_body) {
const int error = loader_->NetError();
const absl::optional<URLLoaderCompletionStatus>& status =
loader_->CompletionStatus();
if (!completion_callback_.is_null()) {
// As HandleResponseHeader() isn't called due to a request failure, such
// as unknown hosts. unreachable remote, reset by peer, and so on, we
// still hold `completion_callback_` to invoke.
if (devtools_observer_) {
DCHECK(devtools_request_id_);
devtools_observer_->OnCorsPreflightRequestCompleted(
*devtools_request_id_, network::URLLoaderCompletionStatus(error));
}
std::move(completion_callback_)
.Run(error,
status.has_value() ? status->cors_error_status : absl::nullopt,
false);
}
RemoveFromController();
// `this` is deleted here.
}
// Removes `this` instance from `controller_`. Once the method returns, `this`
// is already removed.
void RemoveFromController() { controller_->RemoveLoader(this); }
// PreflightController owns all PreflightLoader instances, and should outlive.
const raw_ptr<PreflightController> controller_;
// Holds SimpleURLLoader instance for the CORS-preflight request.
std::unique_ptr<SimpleURLLoader> loader_;
// Holds caller's information.
PreflightController::CompletionCallback completion_callback_;
const ResourceRequest original_request_;
const NonWildcardRequestHeadersSupport non_wildcard_request_headers_support_;
const PrivateNetworkAccessPreflightBehavior private_network_access_behavior_;
const bool tainted_;
absl::optional<base::UnguessableToken> devtools_request_id_;
const net::NetworkIsolationKey network_isolation_key_;
const mojom::ClientSecurityStatePtr client_security_state_;
mojo::Remote<mojom::DevToolsObserver> devtools_observer_;
const net::NetLogWithSource net_log_;
};
// static
std::unique_ptr<ResourceRequest>
PreflightController::CreatePreflightRequestForTesting(
const ResourceRequest& request,
bool tainted) {
return CreatePreflightRequest(
request, tainted,
net::NetLogWithSource::Make(net::NetLog::Get(),
net::NetLogSourceType::URL_REQUEST),
/*devtools_request_id=*/absl::nullopt);
}
// static
std::unique_ptr<PreflightResult>
PreflightController::CreatePreflightResultForTesting(
const GURL& final_url,
const mojom::URLResponseHead& head,
const ResourceRequest& original_request,
bool tainted,
PrivateNetworkAccessPreflightBehavior private_network_access_behavior,
absl::optional<CorsErrorStatus>* detected_error_status) {
return CreatePreflightResult(final_url, head, original_request, tainted,
private_network_access_behavior,
/*client_security_state=*/nullptr,
/*devtools_observer=*/nullptr,
detected_error_status);
}
// static
base::expected<void, CorsErrorStatus>
PreflightController::CheckPreflightAccessForTesting(
const GURL& response_url,
const int response_status_code,
const absl::optional<std::string>& allow_origin_header,
const absl::optional<std::string>& allow_credentials_header,
mojom::CredentialsMode actual_credentials_mode,
const url::Origin& origin) {
return CheckPreflightAccess(response_url, response_status_code,
allow_origin_header, allow_credentials_header,
actual_credentials_mode, origin);
}
PreflightController::PreflightController(NetworkService* network_service)
: network_service_(network_service) {}
PreflightController::~PreflightController() = default;
void PreflightController::PerformPreflightCheck(
CompletionCallback callback,
const ResourceRequest& request,
WithTrustedHeaderClient with_trusted_header_client,
NonWildcardRequestHeadersSupport non_wildcard_request_headers_support,
PrivateNetworkAccessPreflightBehavior private_network_access_behavior,
bool tainted,
const net::NetworkTrafficAnnotationTag& annotation_tag,
mojom::URLLoaderFactory* loader_factory,
const net::IsolationInfo& isolation_info,
mojom::ClientSecurityStatePtr client_security_state,
mojo::PendingRemote<mojom::DevToolsObserver> devtools_observer,
const net::NetLogWithSource& net_log) {
DCHECK(request.request_initiator);
const net::NetworkIsolationKey& network_isolation_key =
!isolation_info.IsEmpty()
? isolation_info.network_isolation_key()
: request.trusted_params.has_value()
? request.trusted_params->isolation_info.network_isolation_key()
: net::NetworkIsolationKey();
if (!RetrieveCacheFlags(request.load_flags) &&
cache_.CheckIfRequestCanSkipPreflight(
request.request_initiator.value(), request.url, network_isolation_key,
request.target_ip_address_space, request.credentials_mode,
request.method, request.headers, request.is_revalidating, net_log)) {
std::move(callback).Run(net::OK, absl::nullopt, false);
return;
}
auto emplaced_pair = loaders_.emplace(std::make_unique<PreflightLoader>(
this, std::move(callback), request, with_trusted_header_client,
non_wildcard_request_headers_support, private_network_access_behavior,
tainted, annotation_tag, network_isolation_key,
std::move(client_security_state), std::move(devtools_observer), net_log));
(*emplaced_pair.first)->Request(loader_factory);
}
void PreflightController::RemoveLoader(PreflightLoader* loader) {
auto it = loaders_.find(loader);
DCHECK(it != loaders_.end());
loaders_.erase(it);
}
void PreflightController::AppendToCache(
const url::Origin& origin,
const GURL& url,
const net::NetworkIsolationKey& network_isolation_key,
mojom::IPAddressSpace target_ip_address_space,
std::unique_ptr<PreflightResult> result) {
cache_.AppendEntry(origin, url, network_isolation_key,
target_ip_address_space, std::move(result));
}
} // namespace cors
} // namespace network