// 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/cors/cors_url_loader.h"

#include <algorithm>
#include <optional>
#include <sstream>
#include <utility>

#include "base/containers/flat_set.h"
#include "base/dcheck_is_on.h"
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/types/optional_util.h"
#include "net/base/load_flags.h"
#include "net/base/request_priority.h"
#include "net/cert/cert_status_flags.h"
#include "net/cookies/cookie_partition_key.h"
#include "net/cookies/cookie_setting_override.h"
#include "net/cookies/cookie_util.h"
#include "net/http/http_log_util.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/log/net_log_util.h"
#include "net/log/net_log_values.h"
#include "net/shared_dictionary/shared_dictionary.h"
#include "net/url_request/redirect_util.h"
#include "net/url_request/url_request_context.h"
#include "services/network/cookie_manager.h"
#include "services/network/cors/cors_url_loader_factory.h"
#include "services/network/cors/cors_util.h"
#include "services/network/cors/preflight_controller.h"
#include "services/network/network_context.h"
#include "services/network/public/cpp/cors/cors.h"
#include "services/network/public/cpp/cors/origin_access_list.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/header_util.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/record_ontransfersizeupdate_utils.h"
#include "services/network/public/cpp/request_mode.h"
#include "services/network/public/cpp/timing_allow_origin_parser.h"
#include "services/network/public/mojom/device_bound_sessions.mojom-shared.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/ip_address_space.mojom.h"
#include "services/network/public/mojom/shared_dictionary_error.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/shared_dictionary/shared_dictionary_access_checker.h"
#include "services/network/shared_dictionary/shared_dictionary_constants.h"
#include "services/network/shared_dictionary/shared_dictionary_data_pipe_writer.h"
#include "services/network/shared_dictionary/shared_dictionary_manager.h"
#include "services/network/shared_dictionary/shared_dictionary_storage.h"
#include "services/network/shared_dictionary/shared_dictionary_writer.h"
#include "services/network/trust_tokens/trust_token_operation_metrics_recorder.h"
#include "services/network/url_loader.h"
#include "services/network/url_loader_factory.h"
#include "services/network/url_loader_util.h"
#include "url/scheme_host_port.h"
#include "url/url_util.h"

namespace network::cors {

namespace {

enum class PreflightRequiredReason {
  kCorsWithForcedPreflightMode,
  kDisallowedMethod,
  kDisallowedHeader
};

bool IsRevalidatingRequest(const ResourceRequest& request) {
  if (base::FeatureList::IsEnabled(features::kSafeRevalidation)) {
    return request.revalidation_etag.has_value() ||
           request.revalidation_last_modified.has_value();
  }
  return request.is_revalidating;
}

// Returns std::nullopt when a preflight isn't needed. Otherwise returns the
// reason why a preflight is needed.
std::optional<PreflightRequiredReason> NeedsPreflight(
    const ResourceRequest& request) {
  if (!IsCorsEnabledRequestMode(request.mode))
    return std::nullopt;

  if (request.mode == mojom::RequestMode::kCorsWithForcedPreflight) {
    return PreflightRequiredReason::kCorsWithForcedPreflightMode;
  }

  if (!IsCorsSafelistedMethod(request.method))
    return PreflightRequiredReason::kDisallowedMethod;

  bool is_ad_auction_trusted_signals_request =
      request.trusted_params &&
      request.trusted_params->is_ad_auction_trusted_signals_request;

  const bool is_revalidating_for_headers =
      base::FeatureList::IsEnabled(features::kSafeRevalidation)
          ? false
          : request.is_revalidating;

  if (!CorsUnsafeNotForbiddenRequestHeaderNames(
           request.headers.GetHeaderVector(), is_revalidating_for_headers,
           is_ad_auction_trusted_signals_request)
           .empty()) {
    return PreflightRequiredReason::kDisallowedHeader;
  }

  return std::nullopt;
}

base::DictValue NetLogCorsURLLoaderStartParams(
    const ResourceRequest& request,
    net::NetLogCaptureMode capture_mode) {
  std::string cors_preflight_policy;
  switch (request.cors_preflight_policy) {
    case mojom::CorsPreflightPolicy::kConsiderPreflight:
      cors_preflight_policy = "consider_preflight";
      break;
    case mojom::CorsPreflightPolicy::kPreventPreflight:
      cors_preflight_policy = "prevent_preflight";
      break;
  }

  auto params = base::DictValue()
                    .Set("url", SanitizeUrlForNetLog(request.url, capture_mode))
                    .Set("is_revalidating", request.is_revalidating)
                    .Set("cors_preflight_policy", cors_preflight_policy);

  if (request.url.is_valid()) {
    std::string request_line = net::HttpUtil::GenerateRequestLine(
        request.method, request.url, /*is_for_get_to_http_proxy=*/false);
    params.Set("request_headers",
               request.headers.NetLogParams(request_line, capture_mode));
  }
  return params;
}

base::DictValue NetLogPreflightRequiredParams(
    std::optional<PreflightRequiredReason> preflight_required_reason) {
  auto dict = base::DictValue().Set("preflight_required",
                                    preflight_required_reason.has_value());
  if (preflight_required_reason) {
    std::string preflight_required_reason_param;
    switch (preflight_required_reason.value()) {
      case PreflightRequiredReason::kCorsWithForcedPreflightMode:
        preflight_required_reason_param = "cors_with_forced_preflight_mode";
        break;
      case PreflightRequiredReason::kDisallowedMethod:
        preflight_required_reason_param = "disallowed_method";
        break;
      case PreflightRequiredReason::kDisallowedHeader:
        preflight_required_reason_param = "disallowed_header";
        break;
    }
    dict.Set("preflight_required_reason", preflight_required_reason_param);
  }
  return dict;
}

// Returns net log params for the `CORS_PREFLIGHT_ERROR` event type.
base::DictValue NetLogPreflightErrorParams(
    int net_error,
    const std::optional<CorsErrorStatus>& status) {
  auto dict =
      base::DictValue().Set("error", net::ErrorToShortString(net_error));
  if (status) {
    dict.Set("cors-error", static_cast<int>(status->cors_error));
    if (!status->failed_parameter.empty()) {
      dict.Set("failed-parameter", status->failed_parameter);
    }
  }

  return dict;
}

// Returns the response tainting value
// (https://fetch.spec.whatwg.org/#concept-request-response-tainting) for a
// request and the CORS flag, as specified in
// https://fetch.spec.whatwg.org/#main-fetch.
// Keep this in sync with the identical function
// blink::cors::CalculateResponseTainting.
mojom::FetchResponseType CalculateResponseTainting(
    const GURL& url,
    mojom::RequestMode request_mode,
    const std::optional<url::Origin>& origin,
    const std::optional<url::Origin>& isolated_world_origin,
    bool cors_flag,
    bool tainted_origin,
    const OriginAccessList& origin_access_list) {
  if (url.SchemeIs(url::kDataScheme))
    return mojom::FetchResponseType::kBasic;

  if (cors_flag) {
    DCHECK(IsCorsEnabledRequestMode(request_mode));
    return mojom::FetchResponseType::kCors;
  }

  if (!origin) {
    // This is actually not defined in the fetch spec, but in this case CORS
    // is disabled so no one should care this value.
    return mojom::FetchResponseType::kBasic;
  }

  // OriginAccessList is in practice used to disable CORS for Chrome Extensions.
  // The extension origin can be found in either:
  // 1) `isolated_world_origin` (if this is a request from a content
  //    script;  in this case there is no point looking at (2) below.
  // 2) `origin` (if this is a request from an extension
  //    background page or from other extension frames).
  //
  // Note that similar code is present in OriginAccessList::CheckAccessState.
  //
  // TODO(lukasza): https://crbug.com/936310 and https://crbug.com/920638:
  // Once 1) there is no global OriginAccessList and 2) per-factory
  // OriginAccessList is only populated for URLLoaderFactory used by allowlisted
  // content scripts, then 3) there should no longer be a need to use origins as
  // a key in an OriginAccessList.
  const url::Origin& source_origin = isolated_world_origin.value_or(*origin);

  if (request_mode == mojom::RequestMode::kNoCors) {
    if (tainted_origin ||
        (!origin->IsSameOriginWith(url) &&
         origin_access_list.CheckAccessState(source_origin, url) !=
             OriginAccessList::AccessState::kAllowed)) {
      return mojom::FetchResponseType::kOpaque;
    }
  }
  return mojom::FetchResponseType::kBasic;
}

// Given a redirected-to URL, checks if the location is allowed
// according to CORS. That is:
// - the URL has a CORS supported scheme and
// - the URL does not contain the userinfo production.
std::optional<CorsErrorStatus> CheckRedirectLocation(
    const GURL& url,
    mojom::RequestMode request_mode,
    const std::optional<url::Origin>& origin,
    bool cors_flag,
    bool tainted) {
  // If `actualResponse`’s location URL’s scheme is not an HTTP(S) scheme,
  // then return a network error (4.4 HTTP-redirect fetch, step 6). This
  // should be addressed earlier in //net at URLRequestJob::CanFollowRedirect.

  // Note: The redirect count check (steps 7 and 8) is done prior to
  // calling CheckRedirectLocation in CorsURLLoader::OnReceiveRedirect.

  const bool url_has_credentials = url.has_username() || url.has_password();
  // If `request`’s mode is "cors", `actualResponse`’s location URL includes
  // credentials, and either `request`’s tainted origin flag is set or
  // `request`’s origin is not same origin with `actualResponse`’s location
  // URL’s origin, then return a network error.
  // See 4.4. HTTP-redirect fetch
  // (https://fetch.spec.whatwg.org/#http-redirect-fetch), step 9.
  DCHECK(!IsCorsEnabledRequestMode(request_mode) || origin);
  if (IsCorsEnabledRequestMode(request_mode) && url_has_credentials &&
      (tainted || !origin->IsSameOriginWith(url))) {
    return CorsErrorStatus(mojom::CorsError::kRedirectContainsCredentials);
  }

  // If CORS flag is set and `actualResponse`’s location URL includes
  // credentials, then return a network error.
  // See 4.4. HTTP-redirect fetch
  // (https://fetch.spec.whatwg.org/#http-redirect-fetch), step 10.
  if (cors_flag && url_has_credentials)
    return CorsErrorStatus(mojom::CorsError::kRedirectContainsCredentials);

  return std::nullopt;
}

void RecordNetworkLoaderCompletionTime(const char* source,
                                       net::RequestPriority priority,
                                       base::TimeDelta elapsed) {
  base::UmaHistogramTimes(
      base::StrCat({"NetworkService.NetworkLoaderCompletionTime2.", source}),
      elapsed);
  base::UmaHistogramTimes(
      base::StrCat({"NetworkService.NetworkLoaderCompletionTime2.", source, ".",
                    net::RequestPriorityToString(priority)}),
      elapsed);
}

constexpr const char kTimingAllowOrigin[] = "Timing-Allow-Origin";

}  // namespace

CorsURLLoader::CorsURLLoader(
    mojo::PendingReceiver<mojom::URLLoader> loader_receiver,
    OriginatingProcessId process_id,
    int32_t request_id,
    uint32_t options,
    DeleteCallback delete_callback,
    ResourceRequest resource_request,
    bool ignore_isolated_world_origin,
    bool skip_cors_enabled_scheme_check,
    mojo::PendingRemote<mojom::URLLoaderClient> client,
    const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
    mojom::URLLoaderFactory* network_loader_factory,
    URLLoaderFactory* sync_network_loader_factory,
    const OriginAccessList* origin_access_list,
    const net::IsolationInfo& isolation_info,
    mojo::PendingRemote<mojom::DevToolsObserver> devtools_observer,
    const mojom::ClientSecurityState* factory_client_security_state,
    mojo::Remote<mojom::URLLoaderNetworkServiceObserver>*
        url_loader_network_service_observer,
    const CrossOriginEmbedderPolicy& cross_origin_embedder_policy,
    scoped_refptr<SharedDictionaryStorage> shared_dictionary_storage,
    raw_ptr<mojom::SharedDictionaryAccessObserver> shared_dictionary_observer,
    NetworkContext* context,
    std::optional<base::UnguessableToken> network_restrictions_id,
    net::CookieSettingOverrides factory_cookie_setting_overrides,
    net::CookieSettingOverrides devtools_cookie_setting_overrides)
    : receiver_(this, std::move(loader_receiver)),
      process_id_(process_id),
      request_id_(request_id),
      options_(options),
      delete_callback_(std::move(delete_callback)),
      network_loader_factory_(network_loader_factory),
      sync_network_loader_factory_(sync_network_loader_factory),
      request_(std::move(resource_request)),
      forwarding_client_(std::move(client)),
      traffic_annotation_(traffic_annotation),
      origin_access_list_(origin_access_list),
      skip_cors_enabled_scheme_check_(skip_cors_enabled_scheme_check),
      isolation_info_(isolation_info),
      factory_client_security_state_(factory_client_security_state),
      url_loader_network_service_observer_(url_loader_network_service_observer),
      cross_origin_embedder_policy_(cross_origin_embedder_policy),
      devtools_observer_(std::move(devtools_observer)),
      weak_devtools_observer_factory_(&devtools_observer_),
      // CORS preflight related events are logged in a series of URL_REQUEST
      // logs.
      net_log_(net::NetLogWithSource::Make(net::NetLog::Get(),
                                           net::NetLogSourceType::URL_REQUEST)),
      context_(context),
      network_restrictions_id_(network_restrictions_id),
      shared_dictionary_storage_(std::move(shared_dictionary_storage)),
      shared_dictionary_observer_(shared_dictionary_observer),
      factory_cookie_setting_overrides_(factory_cookie_setting_overrides),
      devtools_cookie_setting_overrides_(devtools_cookie_setting_overrides) {
  TRACE_EVENT("loading", "CorsURLLoader::CorsURLLoader",
              net::NetLogWithSourceToFlow(net_log_), "url", request_.url.spec(),
              "process_id", process_id_, "request_id", request_id_,
              "traffic_annotation_id", traffic_annotation_.unique_id_hash_code);
  CHECK(url_loader_network_service_observer_ != nullptr);
  if (ignore_isolated_world_origin)
    request_.isolated_world_origin = std::nullopt;

  receiver_.set_disconnect_handler(
      base::BindOnce(&CorsURLLoader::OnMojoDisconnect, base::Unretained(this)));
  request_.net_log_create_info = net_log_.source();
  DCHECK(network_loader_factory_);
  DCHECK(origin_access_list_);
  SetCorsFlagIfNeeded();

  if (shared_dictionary_storage_) {
    if (request_.mode != mojom::RequestMode::kNoCors) {
      request_.load_flags |= net::LOAD_CAN_USE_SHARED_DICTIONARY;
    } else if (request_.request_initiator &&
               request_.request_initiator->IsSameOriginWith(request_.url)) {
      // For no-cors mode requests, we can use shared dictionaries only for same
      // origin requests. When redirected to another origin,
      // net::URLRequest::Redirect() disables the LOAD_CAN_USE_SHARED_DICTIONARY
      // flag.
      request_.load_flags |= net::LOAD_CAN_USE_SHARED_DICTIONARY;
      request_.load_flags |=
          net::LOAD_DISABLE_SHARED_DICTIONARY_AFTER_CROSS_ORIGIN_REDIRECT;
    }

    // Experiment with limiting the early loading of dictionaries to document
    // requests.
    if (!base::FeatureList::IsEnabled(
            features::kCompressionDictionaryLimitEarlyMatching) ||
        request_.destination == mojom::RequestDestination::kDocument) {
      // This is intended to load the dictionary as soon as possible. Without
      // this, the dictionary will be loaded from the disk when
      // `HttpNetworkTransaction` builds the request header just before sending
      // it to the server.
      shared_dictionary_storage_->GetDictionary(
          request_.url, request_.destination,
          base::BindOnce(
              [](base::WeakPtr<CorsURLLoader> loader,
                 scoped_refptr<net::SharedDictionary> shared_dictionary) {
                if (loader) {
                  loader->shared_dictionary_ = std::move(shared_dictionary);
                }
              },
              weak_factory_.GetWeakPtr()));
    }
  }
}

CorsURLLoader::~CorsURLLoader() {
  TRACE_EVENT("loading", "CorsURLLoader::~CorsURLLoader",
              net::NetLogWithSourceToFlow(net_log_));
  // Reset pipes first to ignore possible subsequent callback invocations
  // caused by `network_loader_`
  network_client_receiver_.reset();
}

void CorsURLLoader::Start() {
  TRACE_EVENT("loading", "CorsURLLoader::Start",
              net::NetLogWithSourceToFlow(net_log_));
  if (fetch_cors_flag_ && IsCorsEnabledRequestMode(request_.mode)) {
    // Username and password should be stripped in a CORS-enabled request.
    if (request_.url.has_username() || request_.url.has_password()) {
      GURL::Replacements replacements;
      replacements.SetUsernameStr("");
      replacements.SetPasswordStr("");
      request_.url = request_.url.ReplaceComponents(replacements);
    }
  }

  last_response_url_ = request_.url;

  net_log_.BeginEvent(net::NetLogEventType::CORS_REQUEST,
                      [&](net::NetLogCaptureMode capture_mode) {
                        return NetLogCorsURLLoaderStartParams(request_,
                                                              capture_mode);
                      });
  StartRequest();
}

void CorsURLLoader::FollowRedirect(
    network::HttpRequestHeadersUpdateParams headers_update_params,
    const std::optional<GURL>& new_url) {
  // If this is a navigation from a renderer, then its a service worker
  // passthrough of a navigation request.  Since this case uses manual
  // redirect mode FollowRedirect() should never be called.
  if (!process_id_.is_browser() &&
      request_.mode == mojom::RequestMode::kNavigate) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_FAILED));
    mojo::ReportBadMessage(
        "CorsURLLoader: navigate from non-browser-process should not call "
        "FollowRedirect");
    return;
  }

  if (!network_loader_ || !deferred_redirect_url_) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_FAILED));
    return;
  }

  if (new_url && (new_url->DeprecatedGetOriginAsURL() !=
                  deferred_redirect_url_->DeprecatedGetOriginAsURL())) {
    NOTREACHED() << "Can only change the URL within the same origin.";
  }

  deferred_redirect_url_.reset();

  // When the redirect mode is "error", the client is not expected to
  // call this function. Let's abort the request.
  if (request_.redirect_mode == mojom::RedirectMode::kError) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_FAILED));
    return;
  }

  std::string forbidden_header;
  if (!process_id_.is_browser() &&
      ContainsForbiddenSecurityHeader(headers_update_params.modified_headers,
                                      &forbidden_header)) {
    SCOPED_CRASH_KEY_STRING32("network", "forbidden_sec_header",
                              forbidden_header);
    if (features::kRestrictForbiddenSecurityHeadersDump.Get()) {
      mojo::ReportBadMessage(
          "CorsURLLoader: Forbidden Sec- header from renderer in "
          "FollowRedirect");
    }
    HandleComplete(URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
    return;
  }

  // Does not allow modifying headers that are stored in `cors_exempt_headers`.
  for (const auto& header :
       headers_update_params.modified_headers.GetHeaderVector()) {
    if (request_.cors_exempt_headers.HasHeader(header.key)) {
      LOG(WARNING) << "A client is trying to modify header value for '"
                   << header.key << "', but it is not permitted.";
      HandleComplete(URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
      return;
    }
  }

  std::optional<std::string> modified_origin_header =
      headers_update_params.modified_headers.GetHeader(
          net::HttpRequestHeaders::kOrigin);
  if (modified_origin_header &&
      base::FeatureList::IsEnabled(
          features::kBlockInvalidOriginHeaderModificationOnRedirect) &&
      !HasValidOriginHeader(*modified_origin_header)) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
    mojo::ReportBadMessage(
        "CorsURLLoader: Invalid Origin header modification on redirect is not "
        "permitted");
    return;
  }

  for (const auto& name : headers_update_params.removed_headers) {
    request_.headers.RemoveHeader(name);
    request_.cors_exempt_headers.RemoveHeader(name);
  }

  request_.headers.MergeFrom(headers_update_params.modified_headers);

  if (!CorsURLLoaderFactory::IsValidCorsExemptHeaders(
          *context_->cors_exempt_header_list(),
          headers_update_params.modified_cors_exempt_headers)) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
    return;
  }
  request_.cors_exempt_headers.MergeFrom(
      headers_update_params.modified_cors_exempt_headers);

  if (!AreRequestHeadersSafe(request_.headers)) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
    return;
  }

  const std::string original_method = std::move(request_.method);
  request_.UpdateOnRedirect(redirect_info_);

  // Update isolation_info_ and the shared dictionary storage location if they
  // changed as a result of the redirect for a browser-initiated request (e.g.
  // navigation, prefetch).
  if (request_.trusted_params &&
      !request_.trusted_params->isolation_info.IsEmpty()) {
    isolation_info_ = request_.trusted_params->isolation_info;
    if (shared_dictionary_storage_) {
      // `client_security_state` is not set for top-level navigation requests.
      const bool secure_context =
          request_.trusted_params->client_security_state
              ? request_.trusted_params->client_security_state
                    ->is_web_secure_context
              : network::IsUrlPotentiallyTrustworthy(request_.url);
      const auto shared_dictionary_isolation_key =
          (secure_context && context_->GetSharedDictionaryManager())
              ? net::SharedDictionaryIsolationKey::MaybeCreate(isolation_info_)
              : std::nullopt;
      if (!shared_dictionary_isolation_key) {
        shared_dictionary_storage_.reset();
      } else if (shared_dictionary_storage_->isolation_key() !=
                 *shared_dictionary_isolation_key) {
        shared_dictionary_storage_ =
            context_->GetSharedDictionaryManager()->GetStorage(
                *shared_dictionary_isolation_key);
      }
    }
  }

  // The request method can be changed to "GET". In this case we need to
  // reset the request body manually.
  if (request_.method == net::HttpRequestHeaders::kGetMethod)
    request_.request_body = nullptr;

  const bool original_fetch_cors_flag = fetch_cors_flag_;
  SetCorsFlagIfNeeded();

  // We cannot use FollowRedirect for a request with preflight (i.e., when
  // `fetch_cors_flag_` is true and `NeedsPreflight(request_)` is not nullopt).
  //
  // When `original_fetch_cors_flag` is false, `fetch_cors_flag_` is true and
  // `NeedsPreflight(request)` is nullopt, the net/ implementation won't attach
  // an "origin" header on redirect, as the original request didn't have one.
  //
  // When the request method is changed (due to 302 status code, for example),
  // the net/ implementation removes the origin header.
  //
  // In such cases we need to re-issue a request manually in order to attach the
  // correct origin header. For "no-cors" requests we rely on redirect logic in
  // net/ (specifically in net/url_request/redirect_util.cc).
  //
  // After both OOR-CORS and network service are fully shipped, we may be able
  // to remove the logic in net/.
  if ((fetch_cors_flag_ && NeedsPreflight(request_)) ||
      (!original_fetch_cors_flag && fetch_cors_flag_) ||
      (fetch_cors_flag_ && original_method != request_.method)) {
    DCHECK_NE(request_.mode, mojom::RequestMode::kNoCors);
    network_client_receiver_.reset();
    sync_client_receiver_factory_.InvalidateWeakPtrs();
    StartRequest();
    return;
  }

  response_tainting_ = CalculateResponseTainting(
      request_.url, request_.mode, request_.request_initiator,
      request_.isolated_world_origin, fetch_cors_flag_, tainted_,
      *origin_access_list_);
  network_loader_->FollowRedirect(std::move(headers_update_params), new_url);
}

void CorsURLLoader::SetPriority(net::RequestPriority priority,
                                int32_t intra_priority_value) {
  if (network_loader_)
    network_loader_->SetPriority(priority, intra_priority_value);
}

void CorsURLLoader::OnReceiveEarlyHints(mojom::EarlyHintsPtr early_hints) {
  DCHECK(network_loader_);
  DCHECK(forwarding_client_);

  // Only forward Early Hints for navigation.
  if (request_.mode == mojom::RequestMode::kNavigate)
    forwarding_client_->OnReceiveEarlyHints(std::move(early_hints));
}

void CorsURLLoader::OnReceiveResponse(
    mojom::URLResponseHeadPtr response_head,
    mojo::ScopedDataPipeConsumerHandle body,
    std::optional<mojo_base::BigBuffer> cached_metadata) {
  DCHECK(network_loader_);
  DCHECK(forwarding_client_);
  DCHECK(!deferred_redirect_url_);

  // See 10.7.4 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
  const bool is_304_for_revalidation =
      IsRevalidatingRequest(request_) && response_head->headers &&
      response_head->headers->response_code() == 304;
  if (fetch_cors_flag_ && !is_304_for_revalidation) {
    const auto result = CheckAccess(
        request_.url,
        GetHeaderString(*response_head,
                        header_names::kAccessControlAllowOrigin),
        GetHeaderString(*response_head,
                        header_names::kAccessControlAllowCredentials),
        request_.credentials_mode,
        tainted_ ? url::Origin() : *request_.request_initiator);
    if (!result.has_value()) {
      HandleComplete(URLLoaderCompletionStatus(result.error()));
      return;
    }
  }

  std::optional<std::string> use_as_dictionary_header = GetHeaderString(
      *response_head, shared_dictionary::kUseAsDictionaryHeaderName);
  if (use_as_dictionary_header &&
      !net::IsCertStatusError(response_head->cert_status)) {
    // Write pervasive dictionary responses into the pervasive-specific storage
    // if it is enabled.
    SharedDictionaryStorage* dictionary_storage =
        shared_dictionary_storage_.get();
    scoped_refptr<SharedDictionaryStorage> pervasive_storage;
    if (dictionary_storage && response_head->is_shared_resource &&
        context_->GetSharedDictionaryManager()) {
      pervasive_storage =
          context_->GetSharedDictionaryManager()->GetPervasiveStorage();
      if (pervasive_storage) {
        dictionary_storage = pervasive_storage.get();
      }
    }
    base::expected<scoped_refptr<SharedDictionaryWriter>,
                   mojom::SharedDictionaryError>
        writer_or_error = SharedDictionaryStorage::MaybeCreateWriter(
            *use_as_dictionary_header,
            request_.shared_dictionary_writer_enabled, dictionary_storage,
            request_.mode, response_tainting_, request_.url,
            response_head->request_time, response_head->response_time,
            *response_head->headers, response_head->was_fetched_via_cache,
            base::BindOnce(
                &SharedDictionaryAccessChecker::CheckAllowedToWriteAndReport,
                std::make_unique<SharedDictionaryAccessChecker>(
                    *context_, shared_dictionary_observer_),
                request_.url, request_.site_for_cookies, isolation_info_));
    if (writer_or_error.has_value()) {
      CHECK(writer_or_error.value());
      shared_dictionary_data_pipe_writer_ =
          SharedDictionaryDataPipeWriter::Create(
              body, std::move(writer_or_error.value()),
              base::BindOnce(&CorsURLLoader::OnSharedDictionaryWritten,
                             base::Unretained(this)));
      if (!shared_dictionary_data_pipe_writer_) {
        MaybeReportSharedDictionaryErrorToDevTools(
            mojom::SharedDictionaryError::kWriteErrorInsufficientResources);
        HandleComplete(
            URLLoaderCompletionStatus(net::ERR_INSUFFICIENT_RESOURCES));
        return;
      }
    } else {
      MaybeReportSharedDictionaryErrorToDevTools(writer_or_error.error());
    }
  }

  if (!response_head->did_use_shared_dictionary &&
      response_head->did_send_available_dictionary) {
    MaybeReportSharedDictionaryErrorToDevTools(
        mojom::SharedDictionaryError::kUseErrorMatchingDictionaryNotUsed);
  }

  // Opaque response tainting requests must not use shared dictionary.
  CHECK(!(response_head->did_use_shared_dictionary &&
          (response_tainting_ == mojom::FetchResponseType::kOpaque)));

  // OnReceiveResponse() can be called at most once. This check is added to
  // debug crbug.com/463388771.
  CHECK(!has_forwarded_response_);
  has_forwarded_response_ = true;
  timing_allow_failed_flag_ = !PassesTimingAllowOriginCheck(*response_head);

  response_head->response_type = response_tainting_;
  response_head->timing_allow_passed = !timing_allow_failed_flag_;
  response_head->has_authorization_covered_by_wildcard_on_preflight =
      has_authorization_covered_by_wildcard_;
  if (response_head->response_type != mojom::FetchResponseType::kBasic) {
    response_head->device_bound_session_usage =
        mojom::DeviceBoundSessionUsage::kUnknown;
    response_head->did_use_server_http_auth = false;
    response_head->was_cookie_in_request = false;
  }

  forwarding_client_->OnReceiveResponse(
      std::move(response_head), std::move(body), std::move(cached_metadata));
}

void CorsURLLoader::CheckTainted(const net::RedirectInfo& redirect_info) {
  // If `actualResponse`’s location URL’s origin is not same origin with
  // `request`’s current url’s origin and `request`’s origin is not same origin
  // with `request`’s current url’s origin, then set `request`’s tainted origin
  // flag.
  if (request_.request_initiator &&
      (!url::IsSameOriginWith(redirect_info.new_url, request_.url) &&
       !request_.request_initiator->IsSameOriginWith(request_.url))) {
    tainted_ = true;
  }
}

void CorsURLLoader::OnReceiveRedirect(const net::RedirectInfo& redirect_info,
                                      mojom::URLResponseHeadPtr response_head) {
  DCHECK(network_loader_);
  DCHECK(forwarding_client_);
  DCHECK(!deferred_redirect_url_);

  if (redirect_count_ == 0 && network_restrictions_id_) {
    if (!context_->IsNetworkForNetworkRestrictionsIdAndUrlAllowed(
            *network_restrictions_id_, request_.url,
            isolation_info_.network_anonymization_key(),
            /*is_redirect=*/true)) {
      HandleComplete(URLLoaderCompletionStatus(net::ERR_UNSAFE_REDIRECT));
      return;
    }
  }

  // If `CORS flag` is set and a CORS check for `request` and `response` returns
  // failure, then return a network error.
  if (fetch_cors_flag_ && IsCorsEnabledRequestMode(request_.mode)) {
    const auto result = CheckAccess(
        request_.url,
        GetHeaderString(*response_head,
                        header_names::kAccessControlAllowOrigin),
        GetHeaderString(*response_head,
                        header_names::kAccessControlAllowCredentials),
        request_.credentials_mode,
        tainted_ ? url::Origin() : *request_.request_initiator);
    if (!result.has_value()) {
      HandleComplete(URLLoaderCompletionStatus(result.error()));
      return;
    }
  }

  timing_allow_failed_flag_ = !PassesTimingAllowOriginCheck(*response_head);
  last_response_url_ = redirect_info.new_url;

  if (request_.redirect_mode == mojom::RedirectMode::kManual) {
    CheckTainted(redirect_info);
    // For security, censor non-HTTP(S) redirect URLs to just "data:," when
    // in manual redirect mode. This limits risk if filtering is forgotten
    // somewhere downstream. All non-HTTP(S) URLs are censored to "data:,"
    // including data: URLs themselves (to prevent malicious data URL content).
    // Browser-initiated navigations are exempt since the browser process
    // handles these redirects safely. Service worker pass-through navigations
    // (renderer process with kNavigate mode) ARE censored because the
    // redirect URL is sent to the renderer via IPC.
    net::RedirectInfo censored_redirect_info = redirect_info;
    const bool is_browser_navigation =
        request_.mode == mojom::RequestMode::kNavigate &&
        process_id_ == OriginatingProcessId::browser();
    if (!is_browser_navigation &&
        !redirect_info.new_url.SchemeIsHTTPOrHTTPS()) {
      censored_redirect_info.new_url = GURL("data:,");
    }
    deferred_redirect_url_ =
        std::make_unique<GURL>(censored_redirect_info.new_url);
    response_head->response_type = mojom::FetchResponseType::kOpaqueRedirect;
    response_head->timing_allow_passed = !timing_allow_failed_flag_;
    response_head->device_bound_session_usage =
        mojom::DeviceBoundSessionUsage::kUnknown;
    response_head->did_use_server_http_auth = false;
    response_head->was_cookie_in_request = false;
    forwarding_client_->OnReceiveRedirect(censored_redirect_info,
                                          std::move(response_head));
    return;
  }

  // Because we initiate a new request on redirect in some cases, we cannot
  // rely on the redirect logic in the network stack. Hence we need to
  // implement some logic in
  // https://fetch.spec.whatwg.org/#http-redirect-fetch here.

  // If `request`’s redirect count is twenty, return a network error.
  // Increase `request`’s redirect count by one.
  // See 4.4. HTTP-redirect fetch
  // (https://fetch.spec.whatwg.org/#http-redirect-fetch), steps 7 and 8.
  if (redirect_count_++ == 20) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_TOO_MANY_REDIRECTS));
    return;
  }

  // Implements 4.4. HTTP-redirect fetch
  // (https://fetch.spec.whatwg.org/#http-redirect-fetch), steps 9 and 10.
  const auto error_status = CheckRedirectLocation(
      redirect_info.new_url, request_.mode, request_.request_initiator,
      fetch_cors_flag_, tainted_);
  if (error_status) {
    HandleComplete(URLLoaderCompletionStatus(*error_status));
    return;
  }

  // If `actualResponse`’s status is not 303, `request`’s body is non-null, and
  // `request`’s body’s source is null, then return a network error.
  // See 4.4. HTTP-redirect fetch
  // (https://fetch.spec.whatwg.org/#http-redirect-fetch), step 11.
  if (redirect_info.status_code != net::HTTP_SEE_OTHER &&
      url_loader_util::HasFetchStreamingUploadBody(request_)) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
    return;
  }

  CheckTainted(redirect_info);

  if (base::FeatureList::IsEnabled(features::kUpdateRequestForCorsRedirect)) {
    // Completes step 12 of 4.4 HTTP-redirect fetch
    // (https://fetch.spec.whatwg.org/#http-redirect-fetch). The status code
    // check and method update to GET is handled earlier in
    // RedirectInfo::ComputeRedirectInfo. UpdateHttpRequest checks to see if
    // the method has been updated, and if so, sets clear_body to true so that
    // the request body can be cleared here (step 12.1) and removes the
    // "request-body-headers" (step 12.2).
    bool clear_body = false;
    net::RedirectUtil::UpdateHttpRequest(
        request_.url, request_.method, redirect_info,
        /*removed_headers=*/std::nullopt, /*modified_headers=*/std::nullopt,
        &request_.headers, &clear_body);
    if (clear_body) {
      request_.request_body.reset();
    }

    // Set request's referrer policy on redirect. The algorithm is invoked
    // earlier in RedirectInfo::ComputeRedirectInfo, so simply update to the
    // computed value here. See 4.4. HTTP-redirect fetch
    // (https://fetch.spec.whatwg.org/#http-redirect-fetch), step 19.
    request_.referrer_policy = redirect_info.new_referrer_policy;
  }

  redirect_info_ = redirect_info;

  deferred_redirect_url_ = std::make_unique<GURL>(redirect_info.new_url);

  if (request_.redirect_mode == mojom::RedirectMode::kManual) {
    response_head->response_type = mojom::FetchResponseType::kOpaqueRedirect;
  } else {
    response_head->response_type = response_tainting_;
  }
  response_head->timing_allow_passed = !timing_allow_failed_flag_;
  if (response_head->response_type != mojom::FetchResponseType::kBasic) {
    response_head->device_bound_session_usage =
        mojom::DeviceBoundSessionUsage::kUnknown;
    response_head->did_use_server_http_auth = false;
    response_head->was_cookie_in_request = false;
  }
  forwarding_client_->OnReceiveRedirect(redirect_info,
                                        std::move(response_head));
}

void CorsURLLoader::OnUploadProgress(int64_t current_position,
                                     int64_t total_size,
                                     OnUploadProgressCallback ack_callback) {
  DCHECK(network_loader_);
  DCHECK(forwarding_client_);
  forwarding_client_->OnUploadProgress(current_position, total_size,
                                       std::move(ack_callback));
}

void CorsURLLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) {
  DCHECK(network_loader_);
  DCHECK(forwarding_client_);
  DCHECK(!deferred_redirect_url_);
  network::RecordOnTransferSizeUpdatedUMA(
      network::OnTransferSizeUpdatedFrom::kCorsURLLoader);
  forwarding_client_->OnTransferSizeUpdated(transfer_size_diff);
}

void CorsURLLoader::OnComplete(const URLLoaderCompletionStatus& status) {
  DCHECK(network_loader_);
  DCHECK(forwarding_client_);

  if (status.error_code == net::ERR_DICTIONARY_LOAD_FAILED) {
    MaybeReportSharedDictionaryErrorToDevTools(
        mojom::SharedDictionaryError::kUseErrorDictionaryLoadFailure);
  } else if (status.error_code ==
             net::ERR_UNEXPECTED_CONTENT_DICTIONARY_HEADER) {
    MaybeReportSharedDictionaryErrorToDevTools(
        mojom::SharedDictionaryError::
            kUseErrorUnexpectedContentDictionaryHeader);
  }

  // `network_loader_` will call OnComplete at anytime when a problem happens
  // inside the URLLoader, e.g. on URLLoader::OnMojoDisconnect call. We need
  // to expect it also happens even during redirect handling.
  DCHECK(!deferred_redirect_url_ || status.error_code != net::OK);

  if (shared_dictionary_data_pipe_writer_) {
    deferred_completion_status_ = status;
    shared_dictionary_data_pipe_writer_->OnComplete(status.error_code ==
                                                    net::OK);
  } else {
    HandleComplete(status);
  }
}

std::optional<net::cookie_util::StorageAccessStatus>
CorsURLLoader::GetStorageAccessStatus() {
  if (isolation_info_.network_isolation_key().GetNonce()) {
    return net::cookie_util::StorageAccessStatus::kNone;
  }

  return context_->cookie_manager()->cookie_settings().GetStorageAccessStatus(
      request_.url, request_.site_for_cookies,
      isolation_info_.top_frame_origin(),
      url_loader_util::CalculateCookieSettingOverrides(
          factory_cookie_setting_overrides_, devtools_cookie_setting_overrides_,
          request_,
          /*emit_metrics=*/false),
      /*cookie_partition_key=*/std::nullopt, request_.permissions_policy);
}

bool CorsURLLoader::AllowUnsafeHeaders() const {
  return process_id_.is_browser() ||
         cors::ShouldAllowUnsafeHeaders(*origin_access_list_,
                                        request_.isolated_world_origin
                                            ? request_.isolated_world_origin
                                            : request_.request_initiator,
                                        request_.url);
}

bool CorsURLLoader::HasValidOriginHeader(
    const std::string& origin_header_value) const {
  if (AllowUnsafeHeaders()) {
    return true;
  }

  // "null" is always allowed (e.g. tainted or opaque origins).
  if (origin_header_value == url::Origin().Serialize()) {
    return true;
  }

  // Check against legitimate candidate origins for this request context.
  const std::optional<url::Origin> candidate_origins[] = {
      request_.isolated_world_origin,
      request_.request_initiator,
      isolation_info_.frame_origin(),
  };
  for (const auto& origin : candidate_origins) {
    if (origin.has_value() && origin_header_value == origin->Serialize()) {
      return true;
    }
  }

  return false;
}

void CorsURLLoader::StartRequest() {
  TRACE_EVENT("loading", "CorsURLLoader::StartRequest",
              net::NetLogWithSourceToFlow(net_log_));
  if (fetch_cors_flag_ && !skip_cors_enabled_scheme_check_ &&
      !std::ranges::contains(url::GetCorsEnabledSchemes(),
                             request_.url.GetScheme())) {
    HandleComplete(URLLoaderCompletionStatus(
        CorsErrorStatus(mojom::CorsError::kCorsDisabledScheme)));
    return;
  }

  auto should_include_origin_header = [&]() -> bool {
    if (!request_.request_initiator) {
      return false;
    }

    if (request_.credentials_mode == mojom::CredentialsMode::kInclude &&
        GetStorageAccessStatus() ==
            net::cookie_util::StorageAccessStatus::kInactive) {
      // Lower layers will add the Sec-Fetch-Storage-Access header, and the
      // server may respond with a "retry" header. The server needs to know the
      // origin in that event.
      return true;
    }

    // If the `CORS flag` is set, `httpRequest`’s method is neither `GET` nor
    // `HEAD`, or `httpRequest`’s mode is "websocket", then append
    // `Origin`/the result of serializing a request origin with `httpRequest`,
    // to `httpRequest`’s header list.
    //
    // We exclude navigation requests to keep the existing behavior.
    // TODO(yhirano): Reconsider this.
    if (request_.mode == network::mojom::RequestMode::kNavigate) {
      return false;
    }
    if (fetch_cors_flag_) {
      return true;
    }
    return request_.method != net::HttpRequestHeaders::kGetMethod &&
           request_.method != net::HttpRequestHeaders::kHeadMethod;
  };

  std::optional<std::string> origin_header_value =
      request_.headers.GetHeader(net::HttpRequestHeaders::kOrigin);
  if (origin_header_value &&
      base::FeatureList::IsEnabled(features::kBlockInvalidOriginHeader) &&
      !HasValidOriginHeader(*origin_header_value)) {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_INVALID_ARGUMENT));
    mojo::ReportBadMessage(
        "CorsURLLoader: Invalid Origin header is not permitted for this "
        "request");
    return;
  }

  if (should_include_origin_header()) {
    // If the Origin header is given, check if the initiator has a permission to
    // override unsafe headers for the target URL. This Allowlist is given from
    // a trustworthy process per factory, and safe to trust as a secondary
    // security check here in the network service.
    const bool has_custom_origin_header_with_bypass =
        request_.headers.HasHeader(net::HttpRequestHeaders::kOrigin) &&
        AllowUnsafeHeaders();

    if (!has_custom_origin_header_with_bypass) {
      if (tainted_) {
        request_.headers.SetHeader(net::HttpRequestHeaders::kOrigin,
                                   url::Origin().Serialize());
      } else {
        request_.headers.SetHeader(net::HttpRequestHeaders::kOrigin,
                                   request_.request_initiator->Serialize());
      }
    }
  }

  if (fetch_cors_flag_ && request_.mode == mojom::RequestMode::kSameOrigin) {
    DCHECK(request_.request_initiator);
    HandleComplete(URLLoaderCompletionStatus(
        CorsErrorStatus(mojom::CorsError::kDisallowedByMode)));
    return;
  }

  response_tainting_ = CalculateResponseTainting(
      request_.url, request_.mode, request_.request_initiator,
      request_.isolated_world_origin, fetch_cors_flag_, tainted_,
      *origin_access_list_);

  // Note that even when `needs_preflight` holds we might not make a preflight
  // request. This happens when `fetch_cors_flag_` is false, e.g. when the
  // origin of the url is equal to the origin of the request.
  std::optional<PreflightRequiredReason> needs_preflight =
      NeedsPreflight(request_);
  bool preflight_required = needs_preflight.has_value() && fetch_cors_flag_;
  net_log_.AddEvent(net::NetLogEventType::CHECK_CORS_PREFLIGHT_REQUIRED, [&] {
    return NetLogPreflightRequiredParams(needs_preflight);
  });

  has_authorization_covered_by_wildcard_ = false;
  if (!preflight_required) {
    StartNetworkRequest();
    return;
  }

  // Since we're doing a preflight, we won't reuse the original request. Cancel
  // it now to free up the socket.
  network_loader_.reset();

  mojo::PendingRemote<mojom::URLLoaderNetworkServiceObserver> remote_observer;

  context_->cors_preflight_controller()->PerformPreflightCheck(
      base::BindOnce(&CorsURLLoader::OnPreflightRequestComplete,
                     weak_factory_.GetWeakPtr()),
      request_id_, request_,
      PreflightController::WithTrustedHeaderClient(
          options_ & mojom::kURLLoadOptionUseHeaderClient),
      context_->cors_non_wildcard_request_headers_support(), tainted_,
      net::NetworkTrafficAnnotationTag(traffic_annotation_),
      network_loader_factory_, isolation_info_,
      weak_devtools_observer_factory_.GetWeakPtr(), net_log_,
      context_->acam_preflight_spec_conformant(), std::move(remote_observer));
}

void CorsURLLoader::ReportCorsErrorToDevTools(const CorsErrorStatus& status,
                                              bool is_warning) {
  DCHECK(devtools_observer_);

  devtools_observer_->OnCorsError(
      request_.devtools_request_id, request_.request_initiator,
      CloneClientSecurityState(), request_.url, status, is_warning);
}

void CorsURLLoader::ReportOrbErrorToDevTools() {
  devtools_observer_->OnOrbError(request_.devtools_request_id, request_.url);
}

void CorsURLLoader::MaybeReportSharedDictionaryErrorToDevTools(
    mojom::SharedDictionaryError error) {
  // No need to send AlreadyRegistered error to DevTools.
  if (error == mojom::SharedDictionaryError::kWriteErrorAlreadyRegistered) {
    return;
  }
  if (devtools_observer_ && request_.devtools_request_id) {
    devtools_observer_->OnSharedDictionaryError(*request_.devtools_request_id,
                                                request_.url, error);
  }
}

std::optional<URLLoaderCompletionStatus> CorsURLLoader::ConvertPreflightResult(
    int net_error,
    std::optional<CorsErrorStatus> status) {
  std::optional<PreflightRequiredReason> reason = NeedsPreflight(request_);
  CHECK(reason.has_value());  // Otherwise we should not have sent a preflight.

  if (net_error == net::OK) {
    if (status) {
      DCHECK(status->cors_error != mojom::CorsError::kInvalidResponse);
    }
    return std::nullopt;
  }

  net_log_.AddEvent(net::NetLogEventType::CORS_PREFLIGHT_ERROR, [&] {
    return NetLogPreflightErrorParams(net_error, status);
  });

  if (status) {
    DCHECK(status->cors_error != mojom::CorsError::kInvalidResponse);
  }

  // Failure.
  return status ? URLLoaderCompletionStatus(*std::move(status))
                : URLLoaderCompletionStatus(net_error);
}

void CorsURLLoader::OnPreflightRequestComplete(
    int net_error,
    std::optional<CorsErrorStatus> status,
    bool has_authorization_covered_by_wildcard) {
  has_authorization_covered_by_wildcard_ =
      has_authorization_covered_by_wildcard;

  std::optional<URLLoaderCompletionStatus> completion_status =
      ConvertPreflightResult(net_error, std::move(status));
  if (completion_status) {
    HandleComplete(*std::move(completion_status));
    return;
  }

  StartNetworkRequest();
}

void CorsURLLoader::StartNetworkRequest() {
  TRACE_EVENT("loading", "CorsURLLoader::StartNetworkRequest",
              net::NetLogWithSourceToFlow(net_log_));
  // Here we overwrite the credentials mode sent to URLLoader because
  // network::URLLoader doesn't understand |kSameOrigin|.
  // TODO(crbug.com/40619226): Fix this.
  auto original_credentials_mode = request_.credentials_mode;
  if (original_credentials_mode == mojom::CredentialsMode::kSameOrigin) {
    request_.credentials_mode =
        CalculateCredentialsFlag(original_credentials_mode, response_tainting_)
            ? mojom::CredentialsMode::kInclude
            : mojom::CredentialsMode::kOmit;
  }

  // Binding |this| as an unretained pointer is safe because
  // |network_client_receiver_| shares this object's lifetime.
  network_loader_.reset();

  network_loader_start_time_ = base::TimeTicks::Now();

  if (base::FeatureList::IsEnabled(features::kSafeRevalidation)) {
    if (request_.revalidation_etag) {
      request_.headers.SetHeader(net::HttpRequestHeaders::kIfNoneMatch,
                                 *request_.revalidation_etag);
    }
    if (request_.revalidation_last_modified) {
      request_.headers.SetHeader(net::HttpRequestHeaders::kIfModifiedSince,
                                 *request_.revalidation_last_modified);
    }
  }

  if (sync_network_loader_factory_) {
    sync_network_loader_factory_->CreateLoaderAndStartWithSyncClient(
        network_loader_.BindNewPipeAndPassReceiver(), request_id_, options_,
        request_, network_client_receiver_.BindNewPipeAndPassRemote(),
        sync_client_receiver_factory_.GetWeakPtr(), traffic_annotation_);
  } else {
    network_loader_factory_->CreateLoaderAndStart(
        network_loader_.BindNewPipeAndPassReceiver(), request_id_, options_,
        request_, network_client_receiver_.BindNewPipeAndPassRemote(),
        traffic_annotation_);
  }
  network_client_receiver_.set_disconnect_handler(base::BindOnce(
      &CorsURLLoader::OnNetworkClientMojoDisconnect, base::Unretained(this)));

  request_.credentials_mode = original_credentials_mode;
}

void CorsURLLoader::HandleComplete(URLLoaderCompletionStatus status) {
  TRACE_EVENT("loading", "CorsURLLoader::HandleComplete",
              net::NetLogWithSourceToFlow(net_log_), "error_code",
              status.error_code);

  if (request_.trust_token_params) {
    HistogramTrustTokenOperationNetError(request_.trust_token_params->operation,
                                         status.trust_token_operation_status,
                                         status.error_code);
  }

  if (status.error_code == net::OK) {
    DCHECK_GE(status.completion_time, network_loader_start_time_);
    base::TimeDelta elapsed =
        status.completion_time - network_loader_start_time_;
    if (status.exists_in_cache) {
      RecordNetworkLoaderCompletionTime("DiskCache", request_.priority,
                                        elapsed);
    } else {
      RecordNetworkLoaderCompletionTime("Network", request_.priority, elapsed);
    }
  }

  if (devtools_observer_ && status.cors_error_status) {
    ReportCorsErrorToDevTools(*status.cors_error_status);
  }
  // ORB "v0.1" (and earlier) signal ORB-related errors with a flag.
  // ORB "v0.2" (and later) use a network error code. We should always report
  // the error-code style error to DevTools, since it has a less spammy
  // way of displaying them compared to just dumping them on the console.
  if (devtools_observer_ && (status.should_report_orb_blocking ||
                             status.error_code == net::ERR_BLOCKED_BY_ORB)) {
    ReportOrbErrorToDevTools();
  }

  net_log_.EndEvent(net::NetLogEventType::CORS_REQUEST);
  forwarding_client_->OnComplete(std::move(status));
  std::move(delete_callback_).Run(this);
  // |this| is deleted here.
}

void CorsURLLoader::OnMojoDisconnect() {
  HandleComplete(URLLoaderCompletionStatus(net::ERR_ABORTED));
}

void CorsURLLoader::OnNetworkClientMojoDisconnect() {
  if (shared_dictionary_data_pipe_writer_) {
    // If we already received URLLoaderCompletionStatus, ignores this disconnect
    // error.
    if (!deferred_completion_status_) {
      deferred_completion_status_ = URLLoaderCompletionStatus(net::ERR_ABORTED);
      shared_dictionary_data_pipe_writer_->OnComplete(/*success=*/false);
    }
  } else {
    HandleComplete(URLLoaderCompletionStatus(net::ERR_ABORTED));
  }
}

// This should be identical to CalculateCorsFlag defined in
// //third_party/blink/renderer/platform/loader/cors/cors.cc.
void CorsURLLoader::SetCorsFlagIfNeeded() {
  if (fetch_cors_flag_) {
    return;
  }

  if (!network::cors::ShouldCheckCors(request_.url, request_.request_initiator,
                                      request_.mode)) {
    return;
  }

  if (HasSpecialAccessToDestination())
    return;

  fetch_cors_flag_ = true;
}

bool CorsURLLoader::HasSpecialAccessToDestination() const {
  // The source origin and destination URL pair may be in the allow list.
  switch (origin_access_list_->CheckAccessState(request_)) {
    case OriginAccessList::AccessState::kAllowed:
      return true;
    case OriginAccessList::AccessState::kBlocked:
    case OriginAccessList::AccessState::kNotListed:
      return false;
  }
}

// static
mojom::FetchResponseType CorsURLLoader::CalculateResponseTaintingForTesting(
    const GURL& url,
    mojom::RequestMode request_mode,
    const std::optional<url::Origin>& origin,
    const std::optional<url::Origin>& isolated_world_origin,
    bool cors_flag,
    bool tainted_origin,
    const OriginAccessList& origin_access_list) {
  return CalculateResponseTainting(url, request_mode, origin,
                                   isolated_world_origin, cors_flag,
                                   tainted_origin, origin_access_list);
}

// static
std::optional<CorsErrorStatus> CorsURLLoader::CheckRedirectLocationForTesting(
    const GURL& url,
    mojom::RequestMode request_mode,
    const std::optional<url::Origin>& origin,
    bool cors_flag,
    bool tainted) {
  return CheckRedirectLocation(url, request_mode, origin, cors_flag, tainted);
}

// https://fetch.spec.whatwg.org/#tao-check
bool CorsURLLoader::PassesTimingAllowOriginCheck(
    const mojom::URLResponseHead& response) const {
  // If request’s timing allow failed flag is set, then return failure.
  if (timing_allow_failed_flag_)
    return false;

  // Let values be the result of getting, decoding, and splitting
  // `Timing-Allow-Origin` from response’s header list.
  std::optional<std::string> tao_header_value =
      GetHeaderString(response, kTimingAllowOrigin);

  if (tao_header_value && request_.request_initiator) {
    mojom::TimingAllowOriginPtr tao = ParseTimingAllowOrigin(*tao_header_value);
    url::Origin origin = tainted_ ? url::Origin() : *request_.request_initiator;

    if (TimingAllowOriginCheck(tao, origin))
      return true;
  }

  // If request’s mode is "navigate" and request’s current URL’s origin is not
  // same origin with request’s origin, then return failure.
  if (request_.mode == mojom::RequestMode::kNavigate &&
      request_.request_initiator &&
      (tainted_ ||
       !request_.request_initiator->IsSameOriginWith(last_response_url_))) {
    return false;
  }

  // If request’s response tainting is "basic", then return success.
  if (response_tainting_ == mojom::FetchResponseType::kBasic)
    return true;

  return false;
}

const mojom::ClientSecurityState* CorsURLLoader::GetClientSecurityState()
    const {
  return url_loader_util::SelectClientSecurityState(
      factory_client_security_state_,
      request_.trusted_params
          ? request_.trusted_params->client_security_state.get()
          : nullptr);
}

mojom::ClientSecurityStatePtr CorsURLLoader::CloneClientSecurityState() const {
  const mojom::ClientSecurityState* state = GetClientSecurityState();
  if (!state) {
    return nullptr;
  }

  return state->Clone();
}

void CorsURLLoader::OnSharedDictionaryWritten(bool success) {
  shared_dictionary_data_pipe_writer_.reset();
  if (!success) {
    MaybeReportSharedDictionaryErrorToDevTools(
        mojom::SharedDictionaryError::kWriteErrorRequestAborted);
  }
  if (deferred_completion_status_) {
    HandleComplete(*deferred_completion_status_);
    return;
  }
}

// static
std::optional<std::string> CorsURLLoader::GetHeaderString(
    const mojom::URLResponseHead& response,
    const std::string& header_name) {
  if (!response.headers) {
    return std::nullopt;
  }
  return response.headers->GetNormalizedHeader(header_name);
}


}  // namespace network::cors
