910e62b5创建于 1月15日历史提交
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/cronet/url_request_context_config.h"

#include <algorithm>
#include <memory>
#include <type_traits>
#include <utility>

#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/cronet/cronet_proxy_delegate.h"
#include "net/base/address_family.h"
#include "net/cert/caching_cert_verifier.h"
#include "net/cert/cert_verifier.h"
#include "net/cert/cert_verify_proc.h"
#include "net/cert/multi_threaded_cert_verifier.h"
#include "net/dns/context_host_resolver.h"
#include "net/dns/host_resolver.h"
#include "net/dns/mapped_host_resolver.h"
#include "net/dns/stale_host_resolver.h"
#include "net/http/http_network_session.h"
#include "net/http/http_server_properties.h"
#include "net/log/net_log.h"
#include "net/nqe/network_quality_estimator_params.h"
#include "net/quic/set_quic_flag.h"
#include "net/socket/ssl_client_socket.h"
#include "net/ssl/ssl_key_logger_impl.h"
#include "net/third_party/quiche/src/quiche/quic/core/crypto/crypto_protocol.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_packets.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_tag.h"
#include "net/third_party/quiche/src/quiche/quic/core/quic_types.h"
#include "net/url_request/url_request_context_builder.h"
#include "url/origin.h"

#if BUILDFLAG(ENABLE_REPORTING)
#include "net/reporting/reporting_policy.h"
#endif  // BUILDFLAG(ENABLE_REPORTING)

namespace cronet {

// There's still a risk where setting a tag to be ON does not
// necessarily mean that it will actually be used as it could be
// conflicting with another flag, for example: Enable TBBR will indicate
// that QUICHE should use BBR as a congestion control algorithm. However,
// if RENO is also declared, then QUICHE will end up using RENO instead of
// BBR due to how the code is structured. This means that the flag user
// should be aware of how the tag is used in QUICHE.
//
// The above warning applies to both client copts and copts.
BASE_FEATURE(kOverrideConnectionOptions, base::FEATURE_DISABLED_BY_DEFAULT);
// The expected format for this flag is comma-separated tags.
BASE_FEATURE_PARAM(std::string,
                   kConnectionOptionsForceOn,
                   &kOverrideConnectionOptions,
                   "ForceOn",
                   "");
// The expected format for this flag is comma-separated tags.
BASE_FEATURE_PARAM(std::string,
                   kConnectionOptionsForceOff,
                   &kOverrideConnectionOptions,
                   "ForceOff",
                   "");

BASE_FEATURE(kOverrideClientConnectionOptions,
             base::FEATURE_DISABLED_BY_DEFAULT);
// The expected format for this flag is comma-separated tags.
BASE_FEATURE_PARAM(std::string,
                   kClientConnectionOptionsForceOn,
                   &kOverrideClientConnectionOptions,
                   "ForceOn",
                   "");
// The expected format for this flag is comma-separated tags.
BASE_FEATURE_PARAM(std::string,
                   kClientConnectionOptionsForceOff,
                   &kOverrideClientConnectionOptions,
                   "ForceOff",
                   "");

namespace {

// Name of disk cache directory.
const base::FilePath::CharType kDiskCacheDirectoryName[] =
    FILE_PATH_LITERAL("disk_cache");
const char kQuicFieldTrialName[] = "QUIC";
const char kQuicConnectionOptions[] = "connection_options";
const char kQuicClientConnectionOptions[] = "client_connection_options";
const char kQuicStoreServerConfigsInProperties[] =
    "store_server_configs_in_properties";
const char kQuicMaxServerConfigsStoredInProperties[] =
    "max_server_configs_stored_in_properties";
const char kQuicIdleConnectionTimeoutSeconds[] =
    "idle_connection_timeout_seconds";
const char kQuicMaxTimeBeforeCryptoHandshakeSeconds[] =
    "max_time_before_crypto_handshake_seconds";
const char kQuicMaxIdleTimeBeforeCryptoHandshakeSeconds[] =
    "max_idle_time_before_crypto_handshake_seconds";
const char kQuicCloseSessionsOnIpChange[] = "close_sessions_on_ip_change";
const char kQuicGoAwaySessionsOnIpChange[] = "goaway_sessions_on_ip_change";
const char kQuicAllowServerMigration[] = "allow_server_migration";
const char kQuicMigrateSessionsOnNetworkChangeV2[] =
    "migrate_sessions_on_network_change_v2";
const char kQuicMigrateIdleSessions[] = "migrate_idle_sessions";
const char kQuicRetransmittableOnWireTimeoutMilliseconds[] =
    "retransmittable_on_wire_timeout_milliseconds";
const char kQuicIdleSessionMigrationPeriodSeconds[] =
    "idle_session_migration_period_seconds";
const char kQuicMaxTimeOnNonDefaultNetworkSeconds[] =
    "max_time_on_non_default_network_seconds";
const char kQuicMaxMigrationsToNonDefaultNetworkOnWriteError[] =
    "max_migrations_to_non_default_network_on_write_error";
const char kQuicMaxMigrationsToNonDefaultNetworkOnPathDegrading[] =
    "max_migrations_to_non_default_network_on_path_degrading";
const char kQuicMigrateSessionsEarlyV2[] = "migrate_sessions_early_v2";
const char kQuicRetryOnAlternateNetworkBeforeHandshake[] =
    "retry_on_alternate_network_before_handshake";
const char kQuicHostWhitelist[] = "host_whitelist";
const char kQuicVersion[] = "quic_version";
const char kQuicFlags[] = "set_quic_flags";
const char kRetryWithoutAltSvcOnQuicErrors[] =
    "retry_without_alt_svc_on_quic_errors";
const char kInitialDelayForBrokenAlternativeServiceSeconds[] =
    "initial_delay_for_broken_alternative_service_seconds";
const char kExponentialBackoffOnInitialDelay[] =
    "exponential_backoff_on_initial_delay";
const char kDelayMainJobWithAvailableSpdySession[] =
    "delay_main_job_with_available_spdy_session";

// AsyncDNS experiment dictionary name.
const char kAsyncDnsFieldTrialName[] = "AsyncDNS";
// Name of boolean to enable AsyncDNS experiment.
const char kAsyncDnsEnable[] = "enable";

// Stale DNS (StaleHostResolver) experiment dictionary name.
const char kStaleDnsFieldTrialName[] = "StaleDNS";
// Name of boolean to enable stale DNS experiment.
const char kStaleDnsEnable[] = "enable";
// Name of integer delay in milliseconds before a stale DNS result will be
// used.
const char kStaleDnsDelayMs[] = "delay_ms";
// Name of integer maximum age (past expiration) in milliseconds of a stale DNS
// result that will be used, or 0 for no limit.
const char kStaleDnsMaxExpiredTimeMs[] = "max_expired_time_ms";
// Name of integer maximum times each stale DNS result can be used, or 0 for no
// limit.
const char kStaleDnsMaxStaleUses[] = "max_stale_uses";
// Name of boolean to allow stale DNS results from other networks to be used on
// the current network.
const char kStaleDnsAllowOtherNetwork[] = "allow_other_network";
// Name of boolean to enable persisting the DNS cache to disk.
const char kStaleDnsPersist[] = "persist_to_disk";
// Name of integer minimum time in milliseconds between writes to disk for DNS
// cache persistence. Default value is one minute. Only relevant if
// "persist_to_disk" is true.
const char kStaleDnsPersistTimer[] = "persist_delay_ms";
// Name of boolean to allow use of stale DNS results when network resolver
// returns ERR_NAME_NOT_RESOLVED.
const char kStaleDnsUseStaleOnNameNotResolved[] =
    "use_stale_on_name_not_resolved";

// Rules to override DNS resolution. Intended for testing.
// See explanation of format in net/dns/mapped_host_resolver.h.
const char kHostResolverRulesFieldTrialName[] = "HostResolverRules";
const char kHostResolverRules[] = "host_resolver_rules";

// NetworkQualityEstimator (NQE) experiment dictionary name.
const char kNetworkQualityEstimatorFieldTrialName[] = "NetworkQualityEstimator";

// Network Error Logging experiment dictionary name.
const char kNetworkErrorLoggingFieldTrialName[] = "NetworkErrorLogging";
// Name of boolean to enable Reporting API.
const char kNetworkErrorLoggingEnable[] = "enable";
// Name of list of preloaded "Report-To" headers.
const char kNetworkErrorLoggingPreloadedReportToHeaders[] =
    "preloaded_report_to_headers";
// Name of list of preloaded "NEL" headers.
const char kNetworkErrorLoggingPreloadedNELHeaders[] = "preloaded_nel_headers";
// Name of key (for above two lists) for header origin.
const char kNetworkErrorLoggingOrigin[] = "origin";
// Name of key (for above two lists) for header value.
const char kNetworkErrorLoggingValue[] = "value";

// Disable IPv6 when on WiFi. This is a workaround for a known issue on certain
// Android phones, and should not be necessary when not on one of those devices.
// See https://crbug.com/696569 for details.
const char kDisableIPv6OnWifi[] = "disable_ipv6_on_wifi";

const char kSSLKeyLogFile[] = "ssl_key_log_file";

const char kAllowPortMigration[] = "allow_port_migration";

const char kDisableTlsZeroRtt[] = "disable_tls_zero_rtt";

// Whether SPDY sessions should be closed or marked as going away upon relevant
// network changes. When not specified, /net behavior varies depending on the
// underlying OS.
const char kSpdyGoAwayOnIpChange[] = "spdy_go_away_on_ip_change";

// Whether the connection status of all bidirectional streams (created through
// the Cronet engine) should be monitored.
// The value must be an integer (> 0) and will be interpreted as a suggestion
// for the period of the heartbeat signal. See
// SpdySession#EnableBrokenConnectionDetection for more info.
const char kBidiStreamDetectBrokenConnection[] =
    "bidi_stream_detect_broken_connection";

const char kUseDnsHttpsSvcbFieldTrialName[] = "UseDnsHttpsSvcb";
const char kUseDnsHttpsSvcbUseAlpn[] = "use_alpn";

// Serializes a base::Value into a string that can be used as the value of
// JFV-encoded HTTP header [1].  If |value| is a list, we remove the outermost
// [] delimiters from the result.
//
// [1] https://tools.ietf.org/html/draft-reschke-http-jfv
std::string SerializeJFVHeader(const base::Value& value) {
  std::string result;
  if (!base::JSONWriter::Write(value, &result))
    return std::string();
  if (value.is_list()) {
    DCHECK(result.size() >= 2);
    return result.substr(1, result.size() - 2);
  }
  return result;
}

std::vector<URLRequestContextConfig::PreloadedNelAndReportingHeader>
ParseNetworkErrorLoggingHeaders(
    const base::Value::List& preloaded_headers_config) {
  std::vector<URLRequestContextConfig::PreloadedNelAndReportingHeader> result;
  for (const auto& preloaded_header_config : preloaded_headers_config) {
    if (!preloaded_header_config.is_dict())
      continue;

    const std::string* origin_config =
        preloaded_header_config.GetDict().FindString(
            kNetworkErrorLoggingOrigin);
    if (!origin_config)
      continue;
    GURL origin_url(*origin_config);
    if (!origin_url.is_valid())
      continue;
    auto origin = url::Origin::Create(origin_url);

    auto* value =
        preloaded_header_config.GetDict().Find(kNetworkErrorLoggingValue);
    if (!value)
      continue;

    result.push_back(URLRequestContextConfig::PreloadedNelAndReportingHeader(
        origin, SerializeJFVHeader(*value)));
  }
  return result;
}

// Applies |f| to the value contained by |maybe|, returns empty optional
// otherwise.
template <typename T, typename F>
auto map(std::optional<T> maybe, F&& f) {
  if (!maybe)
    return std::optional<std::invoke_result_t<F, T>>();
  return std::optional<std::invoke_result_t<F, T>>(f(maybe.value()));
}

}  // namespace

URLRequestContextConfig::QuicHint::QuicHint(const std::string& host,
                                            int port,
                                            int alternate_port)
    : host(host), port(port), alternate_port(alternate_port) {}

URLRequestContextConfig::QuicHint::~QuicHint() = default;

URLRequestContextConfig::Pkp::Pkp(const std::string& host,
                                  bool include_subdomains,
                                  const base::Time& expiration_date)
    : host(host),
      include_subdomains(include_subdomains),
      expiration_date(expiration_date) {}

URLRequestContextConfig::Pkp::~Pkp() = default;

URLRequestContextConfig::PreloadedNelAndReportingHeader::
    PreloadedNelAndReportingHeader(const url::Origin& origin, std::string value)
    : origin(origin), value(std::move(value)) {}

URLRequestContextConfig::PreloadedNelAndReportingHeader::
    ~PreloadedNelAndReportingHeader() = default;

URLRequestContextConfig::URLRequestContextConfig(
    bool enable_quic,
    bool enable_spdy,
    bool enable_brotli,
    HttpCacheType http_cache,
    int http_cache_max_size,
    bool load_disable_cache,
    const std::string& storage_path,
    const std::string& accept_language,
    const std::string& user_agent,
    base::Value::Dict experimental_options,
    std::unique_ptr<net::CertVerifier> mock_cert_verifier,
    bool enable_network_quality_estimator,
    bool bypass_public_key_pinning_for_local_trust_anchors,
    std::optional<int> network_thread_priority,
    std::optional<cronet::proto::ProxyOptions> proxy_options)
    : enable_quic(enable_quic),
      enable_spdy(enable_spdy),
      enable_brotli(enable_brotli),
      http_cache(http_cache),
      http_cache_max_size(http_cache_max_size),
      load_disable_cache(load_disable_cache),
      storage_path(storage_path),
      accept_language(accept_language),
      user_agent(user_agent),
      mock_cert_verifier(std::move(mock_cert_verifier)),
      enable_network_quality_estimator(enable_network_quality_estimator),
      bypass_public_key_pinning_for_local_trust_anchors(
          bypass_public_key_pinning_for_local_trust_anchors),
      effective_experimental_options(experimental_options.Clone()),
      experimental_options(std::move(experimental_options)),
      network_thread_priority(network_thread_priority),
      bidi_stream_detect_broken_connection(false),
      heartbeat_interval(base::Seconds(0)),
      proxy_options(std::move(proxy_options)) {
  SetContextConfigExperimentalOptions();
}

URLRequestContextConfig::~URLRequestContextConfig() = default;

// static
std::unique_ptr<URLRequestContextConfig>
URLRequestContextConfig::CreateURLRequestContextConfig(
    bool enable_quic,
    bool enable_spdy,
    bool enable_brotli,
    HttpCacheType http_cache,
    int http_cache_max_size,
    bool load_disable_cache,
    const std::string& storage_path,
    const std::string& accept_language,
    const std::string& user_agent,
    const std::string& unparsed_experimental_options,
    std::unique_ptr<net::CertVerifier> mock_cert_verifier,
    bool enable_network_quality_estimator,
    bool bypass_public_key_pinning_for_local_trust_anchors,
    std::optional<int> network_thread_priority,
    std::optional<cronet::proto::ProxyOptions> proxy_options) {
  std::optional<base::Value::Dict> experimental_options =
      ParseExperimentalOptions(unparsed_experimental_options);
  if (!experimental_options) {
    // For the time being maintain backward compatibility by only failing to
    // parse when DCHECKs are enabled.
    if (ExperimentalOptionsParsingIsAllowedToFail())
      return nullptr;
    else
      experimental_options = base::Value::Dict();
  }
  return base::WrapUnique(new URLRequestContextConfig(
      enable_quic, enable_spdy, enable_brotli, http_cache, http_cache_max_size,
      load_disable_cache, storage_path, accept_language, user_agent,
      std::move(experimental_options).value(), std::move(mock_cert_verifier),
      enable_network_quality_estimator,
      bypass_public_key_pinning_for_local_trust_anchors,
      network_thread_priority, std::move(proxy_options)));
}

// static
std::optional<base::Value::Dict>
URLRequestContextConfig::ParseExperimentalOptions(
    std::string unparsed_experimental_options) {
  // From a user perspective no experimental options means an empty string. The
  // underlying code instead expects and empty dictionary. Normalize this.
  if (unparsed_experimental_options.empty())
    unparsed_experimental_options = "{}";
  DVLOG(1) << "Experimental Options:" << unparsed_experimental_options;
  auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(
      unparsed_experimental_options, base::JSON_PARSE_CHROMIUM_EXTENSIONS);
  if (!parsed_json.has_value()) {
    LOG(ERROR) << "Parsing experimental options failed: '"
               << unparsed_experimental_options << "', error "
               << parsed_json.error().message;
    return std::nullopt;
  }

  base::Value::Dict* experimental_options_dict = parsed_json->GetIfDict();
  if (!experimental_options_dict) {
    LOG(ERROR) << "Experimental options string is not a dictionary: "
               << *parsed_json;
    return std::nullopt;
  }

  return std::move(*experimental_options_dict);
}

void URLRequestContextConfig::SetContextConfigExperimentalOptions() {
  const base::Value* heartbeat_interval_value =
      experimental_options.Find(kBidiStreamDetectBrokenConnection);
  if (heartbeat_interval_value) {
    if (!heartbeat_interval_value->is_int()) {
      LOG(ERROR) << "\"" << kBidiStreamDetectBrokenConnection
                 << "\" config params \"" << heartbeat_interval_value
                 << "\" is not an int";
      experimental_options.Remove(kBidiStreamDetectBrokenConnection);
      effective_experimental_options.Remove(kBidiStreamDetectBrokenConnection);
    } else {
      int heartbeat_interval_secs = heartbeat_interval_value->GetInt();
      heartbeat_interval = base::Seconds(heartbeat_interval_secs);
      bidi_stream_detect_broken_connection = heartbeat_interval_secs > 0;
      experimental_options.Remove(kBidiStreamDetectBrokenConnection);
    }
  }
}

void URLRequestContextConfig::SetContextBuilderExperimentalOptions(
    net::URLRequestContextBuilder* context_builder,
    net::HttpNetworkSessionParams* session_params,
    net::QuicParams* quic_params,
    net::handles::NetworkHandle bound_network) {
  bool async_dns_enable = false;
  bool stale_dns_enable = false;
  bool host_resolver_rules_enable = false;
  bool disable_ipv6_on_wifi = false;
  bool nel_enable = false;
  bool is_network_bound = bound_network != net::handles::kInvalidNetworkHandle;
  std::optional<net::HostResolver::HttpsSvcbOptions> https_svcb_options;

  net::StaleHostResolver::StaleOptions stale_dns_options;
  // TODO(crbug.com/399372859): Run an experiment to use the default
  // StaleOptions() values.
  stale_dns_options.allow_other_network = false;
  stale_dns_options.max_stale_uses = 0;
  stale_dns_options.use_stale_on_name_not_resolved = false;
  stale_dns_options.max_expired_time = base::Milliseconds(0);

  const std::string* host_resolver_rules_string;

  for (auto iter = experimental_options.begin();
       iter != experimental_options.end(); ++iter) {
    if (iter->first == kQuicFieldTrialName) {
      if (!iter->second.is_dict()) {
        LOG(ERROR) << "Quic config params \"" << iter->second
                   << "\" is not a dictionary value";
        effective_experimental_options.Remove(iter->first);
        continue;
      }

      const base::Value::Dict& quic_args = iter->second.GetDict();
      const std::string* quic_version_string =
          quic_args.FindString(kQuicVersion);
      if (quic_version_string) {
        quic::ParsedQuicVersionVector supported_versions =
            quic::ParseQuicVersionVectorString(*quic_version_string);
        quic::ParsedQuicVersionVector filtered_versions;
        quic::ParsedQuicVersionVector obsolete_versions =
            net::ObsoleteQuicVersions();
        for (const quic::ParsedQuicVersion& version : supported_versions) {
          if (!base::Contains(obsolete_versions, version)) {
            filtered_versions.push_back(version);
          }
        }
        if (!filtered_versions.empty()) {
          quic_params->supported_versions = filtered_versions;
        }
      }

      const std::string* quic_connection_options =
          quic_args.FindString(kQuicConnectionOptions);
      if (quic_connection_options) {
        quic_params->connection_options =
            quic::ParseQuicTagVector(*quic_connection_options);
      }

      const std::string* quic_client_connection_options =
          quic_args.FindString(kQuicClientConnectionOptions);
      if (quic_client_connection_options) {
        quic_params->client_connection_options =
            quic::ParseQuicTagVector(*quic_client_connection_options);
      }

      // TODO(rtenneti): Delete this option after apps stop using it.
      // Added this for backward compatibility.
      if (quic_args.FindBool(kQuicStoreServerConfigsInProperties)
              .value_or(false)) {
        quic_params->max_server_configs_stored_in_properties =
            net::kDefaultMaxQuicServerEntries;
      }

      quic_params->max_server_configs_stored_in_properties =
          static_cast<size_t>(
              quic_args.FindInt(kQuicMaxServerConfigsStoredInProperties)
                  .value_or(
                      quic_params->max_server_configs_stored_in_properties));

      quic_params->idle_connection_timeout =
          map(quic_args.FindInt(kQuicIdleConnectionTimeoutSeconds),
              base::Seconds<int>)
              .value_or(quic_params->idle_connection_timeout);

      quic_params->max_time_before_crypto_handshake =
          map(quic_args.FindInt(kQuicMaxTimeBeforeCryptoHandshakeSeconds),
              base::Seconds<int>)
              .value_or(quic_params->max_time_before_crypto_handshake);

      quic_params->max_idle_time_before_crypto_handshake =
          map(quic_args.FindInt(kQuicMaxIdleTimeBeforeCryptoHandshakeSeconds),
              base::Seconds<int>)
              .value_or(quic_params->max_idle_time_before_crypto_handshake);

      quic_params->close_sessions_on_ip_change =
          quic_args.FindBool(kQuicCloseSessionsOnIpChange)
              .value_or(quic_params->close_sessions_on_ip_change);

      quic_params->goaway_sessions_on_ip_change =
          quic_args.FindBool(kQuicGoAwaySessionsOnIpChange)
              .value_or(quic_params->goaway_sessions_on_ip_change);
      quic_params->allow_server_migration =
          quic_args.FindBool(kQuicAllowServerMigration)
              .value_or(quic_params->allow_server_migration);

      std::optional<bool> quic_migrate_sessions_on_network_change_v2_in =
          quic_args.FindBool(kQuicMigrateSessionsOnNetworkChangeV2);
      if (quic_migrate_sessions_on_network_change_v2_in.has_value()) {
        quic_params->migrate_sessions_on_network_change_v2 =
            quic_migrate_sessions_on_network_change_v2_in.value();
        quic_params->max_time_on_non_default_network =
            map(quic_args.FindInt(kQuicMaxTimeOnNonDefaultNetworkSeconds),
                base::Seconds<int>)
                .value_or(quic_params->max_time_on_non_default_network);
        quic_params->max_migrations_to_non_default_network_on_write_error =
            quic_args.FindInt(kQuicMaxMigrationsToNonDefaultNetworkOnWriteError)
                .value_or(
                    quic_params
                        ->max_migrations_to_non_default_network_on_write_error);
        quic_params->max_migrations_to_non_default_network_on_path_degrading =
            quic_args
                .FindInt(kQuicMaxMigrationsToNonDefaultNetworkOnPathDegrading)
                .value_or(
                    quic_params
                        ->max_migrations_to_non_default_network_on_path_degrading);
      }

      std::optional<bool> quic_migrate_idle_sessions_in =
          quic_args.FindBool(kQuicMigrateIdleSessions);
      if (quic_migrate_idle_sessions_in.has_value()) {
        quic_params->migrate_idle_sessions =
            quic_migrate_idle_sessions_in.value();
        quic_params->idle_session_migration_period =
            map(quic_args.FindInt(kQuicIdleSessionMigrationPeriodSeconds),
                base::Seconds<int>)
                .value_or(quic_params->idle_session_migration_period);
      }

      quic_params->migrate_sessions_early_v2 =
          quic_args.FindBool(kQuicMigrateSessionsEarlyV2)
              .value_or(quic_params->migrate_sessions_early_v2);

      quic_params->retransmittable_on_wire_timeout =
          map(quic_args.FindInt(kQuicRetransmittableOnWireTimeoutMilliseconds),
              base::Milliseconds<int>)
              .value_or(quic_params->retransmittable_on_wire_timeout);

      quic_params->retry_on_alternate_network_before_handshake =
          quic_args.FindBool(kQuicRetryOnAlternateNetworkBeforeHandshake)
              .value_or(
                  quic_params->retry_on_alternate_network_before_handshake);

      quic_params->allow_port_migration =
          quic_args.FindBool(kAllowPortMigration)
              .value_or(quic_params->allow_port_migration);

      quic_params->retry_without_alt_svc_on_quic_errors =
          quic_args.FindBool(kRetryWithoutAltSvcOnQuicErrors)
              .value_or(quic_params->retry_without_alt_svc_on_quic_errors);

      quic_params->initial_delay_for_broken_alternative_service = map(
          quic_args.FindInt(kInitialDelayForBrokenAlternativeServiceSeconds),
          base::Seconds<int>);

      quic_params->exponential_backoff_on_initial_delay =
          quic_args.FindBool(kExponentialBackoffOnInitialDelay);

      quic_params->delay_main_job_with_available_spdy_session =
          quic_args.FindBool(kDelayMainJobWithAvailableSpdySession)
              .value_or(
                  quic_params->delay_main_job_with_available_spdy_session);

      quic_params->disable_tls_zero_rtt =
          quic_args.FindBool(kDisableTlsZeroRtt)
              .value_or(quic_params->disable_tls_zero_rtt);

      const std::string* quic_host_allowlist =
          quic_args.FindString(kQuicHostWhitelist);
      if (quic_host_allowlist) {
        std::vector<std::string> host_vector =
            base::SplitString(*quic_host_allowlist, ",", base::TRIM_WHITESPACE,
                              base::SPLIT_WANT_ALL);
        session_params->quic_host_allowlist.clear();
        for (const std::string& host : host_vector) {
          session_params->quic_host_allowlist.insert(host);
        }
      }

      const std::string* quic_flags = quic_args.FindString(kQuicFlags);
      if (quic_flags) {
        for (const auto& flag :
             base::SplitString(*quic_flags, ",", base::TRIM_WHITESPACE,
                               base::SPLIT_WANT_ALL)) {
          std::vector<std::string> tokens = base::SplitString(
              flag, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
          if (tokens.size() != 2)
            continue;
          net::SetQuicFlagByName(tokens[0], tokens[1]);
        }
      }
    } else if (iter->first == kAsyncDnsFieldTrialName) {
      if (!iter->second.is_dict()) {
        LOG(ERROR) << "\"" << iter->first << "\" config params \""
                   << iter->second << "\" is not a dictionary value";
        effective_experimental_options.Remove(iter->first);
        continue;
      }
      const base::Value::Dict& async_dns_args = iter->second.GetDict();
      async_dns_enable =
          async_dns_args.FindBool(kAsyncDnsEnable).value_or(async_dns_enable);
    } else if (iter->first == kStaleDnsFieldTrialName) {
      if (!iter->second.is_dict()) {
        LOG(ERROR) << "\"" << iter->first << "\" config params \""
                   << iter->second << "\" is not a dictionary value";
        effective_experimental_options.Remove(iter->first);
        continue;
      }
      const base::Value::Dict& stale_dns_args = iter->second.GetDict();
      stale_dns_enable =
          stale_dns_args.FindBool(kStaleDnsEnable).value_or(false);

      if (stale_dns_enable) {
        stale_dns_options.delay = map(stale_dns_args.FindInt(kStaleDnsDelayMs),
                                      base::Milliseconds<int>)
                                      .value_or(stale_dns_options.delay);
        stale_dns_options.max_expired_time =
            map(stale_dns_args.FindInt(kStaleDnsMaxExpiredTimeMs),
                base::Milliseconds<int>)
                .value_or(stale_dns_options.max_expired_time);
        stale_dns_options.max_stale_uses =
            stale_dns_args.FindInt(kStaleDnsMaxStaleUses)
                .value_or(stale_dns_options.max_stale_uses);
        stale_dns_options.allow_other_network =
            stale_dns_args.FindBool(kStaleDnsAllowOtherNetwork)
                .value_or(stale_dns_options.allow_other_network);
        enable_host_cache_persistence =
            stale_dns_args.FindBool(kStaleDnsPersist)
                .value_or(enable_host_cache_persistence);
        host_cache_persistence_delay_ms =
            stale_dns_args.FindInt(kStaleDnsPersistTimer)
                .value_or(host_cache_persistence_delay_ms);
        stale_dns_options.use_stale_on_name_not_resolved =
            stale_dns_args.FindBool(kStaleDnsUseStaleOnNameNotResolved)
                .value_or(stale_dns_options.use_stale_on_name_not_resolved);
      }
    } else if (iter->first == kHostResolverRulesFieldTrialName) {
      if (!iter->second.is_dict()) {
        LOG(ERROR) << "\"" << iter->first << "\" config params \""
                   << iter->second << "\" is not a dictionary value";
        effective_experimental_options.Remove(iter->first);
        continue;
      }
      const base::Value::Dict& host_resolver_rules_args =
          iter->second.GetDict();
      host_resolver_rules_string =
          host_resolver_rules_args.FindString(kHostResolverRules);
      host_resolver_rules_enable = !!host_resolver_rules_string;
    } else if (iter->first == kUseDnsHttpsSvcbFieldTrialName) {
      if (!iter->second.is_dict()) {
        LOG(ERROR) << "\"" << iter->first << "\" config params \""
                   << iter->second << "\" is not a dictionary value";
        effective_experimental_options.Remove(iter->first);
        continue;
      }
      const base::Value::Dict& args = iter->second.GetDict();
      https_svcb_options = net::HostResolver::HttpsSvcbOptions::FromDict(args);
      session_params->use_dns_https_svcb_alpn =
          args.FindBool(kUseDnsHttpsSvcbUseAlpn)
              .value_or(session_params->use_dns_https_svcb_alpn);
    } else if (iter->first == kNetworkErrorLoggingFieldTrialName) {
      if (!iter->second.is_dict()) {
        LOG(ERROR) << "\"" << iter->first << "\" config params \""
                   << iter->second << "\" is not a dictionary value";
        effective_experimental_options.Remove(iter->first);
        continue;
      }
      const base::Value::Dict& nel_args = iter->second.GetDict();
      nel_enable =
          nel_args.FindBool(kNetworkErrorLoggingEnable).value_or(nel_enable);

      const auto* preloaded_report_to_headers_config =
          nel_args.FindList(kNetworkErrorLoggingPreloadedReportToHeaders);
      if (preloaded_report_to_headers_config) {
        preloaded_report_to_headers = ParseNetworkErrorLoggingHeaders(
            *preloaded_report_to_headers_config);
      }

      const auto* preloaded_nel_headers_config =
          nel_args.FindList(kNetworkErrorLoggingPreloadedNELHeaders);
      if (preloaded_nel_headers_config) {
        preloaded_nel_headers =
            ParseNetworkErrorLoggingHeaders(*preloaded_nel_headers_config);
      }
    } else if (iter->first == kDisableIPv6OnWifi) {
      if (!iter->second.is_bool()) {
        LOG(ERROR) << "\"" << iter->first << "\" config params \""
                   << iter->second << "\" is not a bool";
        effective_experimental_options.Remove(iter->first);
        continue;
      }
      disable_ipv6_on_wifi = iter->second.GetBool();
    } else if (iter->first == kSSLKeyLogFile) {
      if (iter->second.is_string()) {
        base::FilePath ssl_key_log_file(
            base::FilePath::FromUTF8Unsafe(iter->second.GetString()));
        if (!ssl_key_log_file.empty()) {
          // SetSSLKeyLogger is only safe to call before any SSLClientSockets
          // are created. This should not be used if there are multiple
          // CronetEngine.
          // TODO(xunjieli): Expose this as a stable API after crbug.com/458365
          // is resolved.
          net::SSLClientSocket::SetSSLKeyLogger(
              std::make_unique<net::SSLKeyLoggerImpl>(ssl_key_log_file));
        }
      }
    } else if (iter->first == kNetworkQualityEstimatorFieldTrialName) {
      if (!iter->second.is_dict()) {
        LOG(ERROR) << "\"" << iter->first << "\" config params \""
                   << iter->second << "\" is not a dictionary value";
        effective_experimental_options.Remove(iter->first);
        continue;
      }

      const base::Value::Dict& nqe_args = iter->second.GetDict();
      const std::string* nqe_option =
          nqe_args.FindString(net::kForceEffectiveConnectionType);
      if (nqe_option) {
        nqe_forced_effective_connection_type =
            net::GetEffectiveConnectionTypeForName(*nqe_option);
        if (!nqe_option->empty() && !nqe_forced_effective_connection_type) {
          LOG(ERROR) << "\"" << nqe_option
                     << "\" is not a valid effective connection type value";
        }
      }
    } else if (iter->first == kSpdyGoAwayOnIpChange) {
      if (!iter->second.is_bool()) {
        LOG(ERROR) << "\"" << iter->first << "\" config params \""
                   << iter->second << "\" is not a bool";
        effective_experimental_options.Remove(iter->first);
        continue;
      }
      session_params->spdy_go_away_on_ip_change = iter->second.GetBool();
    } else {
      LOG(WARNING) << "Unrecognized Cronet experimental option \""
                   << iter->first << "\" with params \"" << iter->second;
      effective_experimental_options.Remove(iter->first);
    }
  }

  auto reconcile_quic_tags = [](quic::QuicTagVector& quic_tags,
                                const quic::QuicTagVector& tags_to_force_on,
                                const quic::QuicTagVector& tags_to_force_off) {
    std::ranges::copy_if(tags_to_force_on, std::back_inserter(quic_tags),
                         [&](quic::QuicTag tag) {
                           return !quic::ContainsQuicTag(quic_tags, tag);
                         });
    std::erase_if(quic_tags, [&](quic::QuicTag tag) {
      return quic::ContainsQuicTag(tags_to_force_off, tag);
    });
  };

  if (base::FeatureList::IsEnabled(kOverrideClientConnectionOptions)) {
    reconcile_quic_tags(
        quic_params->client_connection_options,
        quic::ParseQuicTagVector(kClientConnectionOptionsForceOn.Get()),
        quic::ParseQuicTagVector(kClientConnectionOptionsForceOff.Get()));
  }

  if (base::FeatureList::IsEnabled(kOverrideConnectionOptions)) {
    reconcile_quic_tags(
        quic_params->connection_options,
        quic::ParseQuicTagVector(kConnectionOptionsForceOn.Get()),
        quic::ParseQuicTagVector(kConnectionOptionsForceOff.Get()));
  }

  if (async_dns_enable || stale_dns_enable || host_resolver_rules_enable ||
      disable_ipv6_on_wifi || is_network_bound || https_svcb_options) {
    net::HostResolver::ManagerOptions host_resolver_manager_options;
    host_resolver_manager_options.insecure_dns_client_enabled =
        async_dns_enable;
    host_resolver_manager_options.check_ipv6_on_wifi = !disable_ipv6_on_wifi;
    if (https_svcb_options) {
      host_resolver_manager_options.https_svcb_options = https_svcb_options;
    }

    if (!is_network_bound) {
      std::unique_ptr<net::HostResolver> host_resolver;
      // TODO(crbug.com/40614970): Consider using a shared HostResolverManager
      // for Cronet HostResolvers.
      if (stale_dns_enable) {
        DCHECK(!disable_ipv6_on_wifi);
        host_resolver = std::make_unique<net::StaleHostResolver>(
            net::HostResolver::CreateStandaloneContextResolver(
                net::NetLog::Get(), std::move(host_resolver_manager_options)),
            stale_dns_options);
      } else {
        host_resolver = net::HostResolver::CreateStandaloneResolver(
            net::NetLog::Get(), std::move(host_resolver_manager_options));
      }
      if (host_resolver_rules_enable) {
        std::unique_ptr<net::MappedHostResolver> remapped_resolver(
            new net::MappedHostResolver(std::move(host_resolver)));
        remapped_resolver->SetRulesFromString(*host_resolver_rules_string);
        host_resolver = std::move(remapped_resolver);
      }
      context_builder->set_host_resolver(std::move(host_resolver));
    } else {
      // `stale_dns_enable` and `host_resolver_rules_enable` are purposefully
      // ignored. Implementing them requires instantiating a special
      // HostResolver that wraps the real underlying resolver: that isn't
      // possible at the moment for network-bound contexts as they create a
      // special HostResolver internally and don't expose that.
      context_builder->BindToNetwork(bound_network,
                                     std::move(host_resolver_manager_options));
    }
  }

#if BUILDFLAG(ENABLE_REPORTING)
  if (nel_enable) {
    auto policy = net::ReportingPolicy::Create();

    // Apps (like Cronet embedders) are generally allowed to run in the
    // background, even across network changes, so use more relaxed privacy
    // settings than when Reporting is running in the browser.
    policy->persist_reports_across_restarts = true;
    policy->persist_clients_across_restarts = true;
    policy->persist_reports_across_network_changes = true;
    policy->persist_clients_across_network_changes = true;

    context_builder->set_reporting_policy(std::move(policy));
    context_builder->set_network_error_logging_enabled(true);
  }
#endif  // BUILDFLAG(ENABLE_REPORTING)
}

void URLRequestContextConfig::ConfigureURLRequestContextBuilder(
    net::URLRequestContextBuilder* context_builder,
    CronetContext::NetworkTasks* network_tasks,
    net::handles::NetworkHandle bound_network) {
  std::string config_cache;
  if (http_cache != DISABLED) {
    net::URLRequestContextBuilder::HttpCacheParams cache_params;
    if (http_cache == DISK && !storage_path.empty()) {
      cache_params.type = net::URLRequestContextBuilder::HttpCacheParams::DISK;
      cache_params.path = base::FilePath::FromUTF8Unsafe(storage_path)
                              .Append(kDiskCacheDirectoryName);
    } else {
      cache_params.type =
          net::URLRequestContextBuilder::HttpCacheParams::IN_MEMORY;
    }
    cache_params.max_size = http_cache_max_size;
    context_builder->EnableHttpCache(cache_params);
  } else {
    context_builder->DisableHttpCache();
  }
  context_builder->set_accept_language(accept_language);
  context_builder->set_user_agent(user_agent);
  net::HttpNetworkSessionParams session_params;
  session_params.enable_http2 = enable_spdy;
  session_params.enable_quic = enable_quic;
  auto quic_context = std::make_unique<net::QuicContext>();
  if (enable_quic) {
    quic_context->params()->goaway_sessions_on_ip_change = false;
    // Explicitly disable network-change migration on Cronet. This is tracked
    // at crbug.com/1430096.
    quic_context->params()->migrate_sessions_on_network_change_v2 = false;
  }

  SetContextBuilderExperimentalOptions(context_builder, &session_params,
                                       quic_context->params(), bound_network);

  context_builder->set_http_network_session_params(session_params);
  context_builder->set_quic_context(std::move(quic_context));

  if (proxy_options.has_value()) {
    context_builder->set_proxy_delegate(
        std::make_unique<CronetProxyDelegate>(*proxy_options, network_tasks));
  }

  if (mock_cert_verifier)
    context_builder->SetCertVerifier(std::move(mock_cert_verifier));
  // TODO(mef): Use |config| to set cookies.
}

URLRequestContextConfigBuilder::URLRequestContextConfigBuilder() = default;
URLRequestContextConfigBuilder::~URLRequestContextConfigBuilder() = default;

std::unique_ptr<URLRequestContextConfig>
URLRequestContextConfigBuilder::Build() {
  return URLRequestContextConfig::CreateURLRequestContextConfig(
      enable_quic, enable_spdy, enable_brotli, http_cache, http_cache_max_size,
      load_disable_cache, storage_path, accept_language, user_agent,
      experimental_options, std::move(mock_cert_verifier),
      enable_network_quality_estimator,
      bypass_public_key_pinning_for_local_trust_anchors,
      network_thread_priority, std::optional<cronet::proto::ProxyOptions>());
}

}  // namespace cronet