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

#include "headless/lib/browser/headless_request_context_manager.h"

#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "components/embedder_support/switches.h"
#include "components/os_crypt/async/browser/os_crypt_async.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/network_service_instance.h"
#include "headless/lib/browser/headless_browser_context_options.h"
#include "headless/public/switches.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "net/base/features.h"
#include "net/http/http_auth_preferences.h"
#include "net/proxy_resolution/proxy_config_service.h"
#include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h"
#include "services/network/network_service.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "services/network/url_request_context_builder_mojo.h"

namespace headless {

namespace {

net::NetworkTrafficAnnotationTag GetProxyConfigTrafficAnnotationTag() {
  static net::NetworkTrafficAnnotationTag traffic_annotation =
      net::DefineNetworkTrafficAnnotation("proxy_config_headless", R"(
    semantics {
      sender: "Proxy Config"
      description:
        "Creates a proxy based on configuration received from headless "
        "command prompt."
      trigger:
        "User starts headless with proxy config."
      data:
        "Proxy configurations."
      destination: OTHER
      destination_other:
        "The proxy server specified in the configuration."
    }
    policy {
      cookies_allowed: NO
      setting:
        "This config is only used for headless mode and provided by user."
      policy_exception_justification:
        "This config is only used for headless mode and provided by user."
    })");
  return traffic_annotation;
}

}  // namespace

// Tracks the ProxyConfig to use, and passes any updates to a NetworkContext's
// ProxyConfigClient.
class HeadlessProxyConfigMonitor
    : public net::ProxyConfigService::Observer,
      public ::network::mojom::ProxyConfigPollerClient {
 public:
  HeadlessProxyConfigMonitor() {
    // We must create the proxy config service on the UI loop on Linux because
    // it must synchronously run on the glib message loop.
    proxy_config_service_ =
        net::ProxyConfigService::CreateSystemProxyConfigService(
            base::SingleThreadTaskRunner::GetCurrentDefault());
    proxy_config_service_->AddObserver(this);
  }

  HeadlessProxyConfigMonitor(const HeadlessProxyConfigMonitor&) = delete;
  HeadlessProxyConfigMonitor& operator=(const HeadlessProxyConfigMonitor&) =
      delete;

  ~HeadlessProxyConfigMonitor() override {
    proxy_config_service_->RemoveObserver(this);
  }

  // Populates proxy-related fields of |network_context_params|. Updated
  // ProxyConfigs will be sent to a NetworkContext created with those params
  // whenever the configuration changes. Can be called more than once to inform
  // multiple NetworkContexts of proxy changes.
  void AddToNetworkContextParams(
      ::network::mojom::NetworkContextParams* network_context_params) {
    if (proxy_config_client_) {
      // This may be called in the course of re-connecting to a new instance
      // of network service following a restart, so the config client / poller
      // interfaces may have been previously bound.
      proxy_config_client_.reset();
      poller_receiver_.reset();
    }
    network_context_params->proxy_config_client_receiver =
        proxy_config_client_.BindNewPipeAndPassReceiver();
    poller_receiver_.Bind(network_context_params->proxy_config_poller_client
                              .InitWithNewPipeAndPassReceiver());
    net::ProxyConfigWithAnnotation proxy_config;
    net::ProxyConfigService::ConfigAvailability availability =
        proxy_config_service_->GetLatestProxyConfig(&proxy_config);
    if (availability != net::ProxyConfigService::CONFIG_PENDING)
      network_context_params->initial_proxy_config = proxy_config;
  }

 private:
  // net::ProxyConfigService::Observer implementation:
  void OnProxyConfigChanged(
      const net::ProxyConfigWithAnnotation& config,
      net::ProxyConfigService::ConfigAvailability availability) override {
    if (!proxy_config_client_)
      return;
    switch (availability) {
      case net::ProxyConfigService::CONFIG_VALID:
        proxy_config_client_->OnProxyConfigUpdated(config);
        break;
      case net::ProxyConfigService::CONFIG_UNSET:
        proxy_config_client_->OnProxyConfigUpdated(
            net::ProxyConfigWithAnnotation::CreateDirect());
        break;
      case net::ProxyConfigService::CONFIG_PENDING:
        NOTREACHED();
    }
  }

  // network::mojom::ProxyConfigPollerClient implementation:
  void OnLazyProxyConfigPoll() override { proxy_config_service_->OnLazyPoll(); }

  std::unique_ptr<net::ProxyConfigService> proxy_config_service_;
  mojo::Receiver<::network::mojom::ProxyConfigPollerClient> poller_receiver_{
      this};
  mojo::Remote<::network::mojom::ProxyConfigClient> proxy_config_client_;
};

// static
std::unique_ptr<HeadlessRequestContextManager>
HeadlessRequestContextManager::CreateSystemContext(
    const HeadlessBrowserContextOptions* options,
    os_crypt_async::OSCryptAsync* os_crypt_async) {
  auto manager = std::make_unique<HeadlessRequestContextManager>(
      options, base::FilePath(), os_crypt_async);

  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  auto auth_params = ::network::mojom::HttpAuthDynamicParams::New();

  if (command_line->HasSwitch(switches::kAuthServerAllowlist)) {
    auth_params->server_allowlist =
        command_line->GetSwitchValueASCII(switches::kAuthServerAllowlist);
  }

  auto* network_service = content::GetNetworkService();
  network_service->ConfigureHttpAuthPrefs(std::move(auth_params));

  ::network::mojom::NetworkContextParamsPtr network_context_params =
      ::network::mojom::NetworkContextParams::New();
  ::cert_verifier::mojom::CertVerifierCreationParamsPtr
      cert_verifier_creation_params =
          ::cert_verifier::mojom::CertVerifierCreationParams::New();
  manager->ConfigureNetworkContextParamsInternal(
      network_context_params.get(), cert_verifier_creation_params.get());
  network_context_params->cert_verifier_params =
      content::GetCertVerifierParams(std::move(cert_verifier_creation_params));
  content::CreateNetworkContextInNetworkService(
      manager->system_context_.InitWithNewPipeAndPassReceiver(),
      std::move(network_context_params));

  return manager;
}

HeadlessRequestContextManager::HeadlessRequestContextManager(
    const HeadlessBrowserContextOptions* options,
    base::FilePath user_data_path,
    os_crypt_async::OSCryptAsync* os_crypt_async)
    :
// On Windows, Cookie encryption requires access to local_state prefs.
#if BUILDFLAG(IS_WIN) && !defined(HEADLESS_USE_PREFS)
      cookie_encryption_enabled_(false),
#else
      cookie_encryption_enabled_(
          !base::CommandLine::ForCurrentProcess()->HasSwitch(
              switches::kDisableCookieEncryption)),
#endif
      os_crypt_async_(os_crypt_async),
      user_data_path_(std::move(user_data_path)),
      disk_cache_dir_(options->disk_cache_dir()),
      accept_language_(options->accept_language()),
      user_agent_(options->user_agent()),
      proxy_config_(
          options->proxy_config()
              ? std::make_unique<net::ProxyConfig>(*options->proxy_config())
              : nullptr) {
  if (cookie_encryption_enabled_) {
    cookie_encryption_provider_ =
        std::make_unique<CookieEncryptionProviderImpl>(os_crypt_async_.get());
  }
  if (!proxy_config_) {
    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
    if (command_line->HasSwitch(switches::kNoSystemProxyConfigService)) {
      proxy_config_ = std::make_unique<net::ProxyConfig>();
    } else {
      proxy_config_monitor_ = std::make_unique<HeadlessProxyConfigMonitor>();
    }
  }
}

HeadlessRequestContextManager::~HeadlessRequestContextManager() = default;

void HeadlessRequestContextManager::ConfigureNetworkContextParams(
    bool in_memory,
    const base::FilePath& relative_partition_path,
    ::network::mojom::NetworkContextParams* network_context_params,
    ::cert_verifier::mojom::CertVerifierCreationParams*
        cert_verifier_creation_params) {
  ConfigureNetworkContextParamsInternal(network_context_params,
                                        cert_verifier_creation_params);
}

void HeadlessRequestContextManager::ConfigureNetworkContextParamsInternal(
    ::network::mojom::NetworkContextParams* context_params,
    ::cert_verifier::mojom::CertVerifierCreationParams*
        cert_verifier_creation_params) {
  context_params->user_agent = user_agent_;
  context_params->accept_language = accept_language_;
  context_params->enable_zstd = true;

  const base::CommandLine& command_line =
      CHECK_DEREF(base::CommandLine::ForCurrentProcess());
  if (command_line.HasSwitch(embedder_support::kShortReportingDelay)) {
    context_params->reporting_delivery_interval = base::Milliseconds(100);
  }

  // TODO(crbug.com/40405715): Allow
  // context_params->http_auth_static_network_context_params->allow_default_credentials
  // to be controllable by a flag.
  context_params->http_auth_static_network_context_params =
      ::network::mojom::HttpAuthStaticNetworkContextParams::New();

  if (!user_data_path_.empty()) {
    context_params->enable_encrypted_cookies = cookie_encryption_enabled_;
    if (cookie_encryption_enabled_) {
      context_params->cookie_encryption_provider =
          cookie_encryption_provider_->BindNewRemote();
    }
    context_params->file_paths =
        ::network::mojom::NetworkContextFilePaths::New();
    context_params->file_paths->data_directory =
        user_data_path_.Append(FILE_PATH_LITERAL("Network"));
    context_params->file_paths->unsandboxed_data_path = user_data_path_;
    context_params->file_paths->cookie_database_name =
        base::FilePath(FILE_PATH_LITERAL("Cookies"));
#if BUILDFLAG(IS_WIN)
    // For the network sandbox to operate, the network data must be in the
    // 'Network' directory and not the `unsandboxed_data_path`.
    //
    // On Windows, the majority of data dir is already residing in the 'Network'
    // data dir. This is because there are three possible cases:
    // 1. A data dir from headful is being used, and headful has migrated any
    // data since M98 (Feb 2022). (data is in 'Network')
    // 2. Headless has been using the 'Network' data dir since M96, since
    // although headless never opted into migration, any new data was always in
    // the 'Network' dir since ba2eb47b. (data is in 'Network')
    // 3. Headless is using a data dir from before M96, and since migration was
    // never enabled, it has been continuing to use this directory up until now.
    // (data is in `unsandboxed_data_path` and sandbox will not function).
    //
    // The majority of users are in 1, or 2. For the small number of users in 3,
    // setting `trigger_migration` will migrate their data dirs to 'Network' but
    // this data will still interop between headless and headful as long as they
    // are running M96 or later that understands both directories, the only
    // noticeable difference will be that the files will move, as they have
    // already been doing in headful.
    context_params->file_paths->trigger_migration = true;
#else
    // On non-Windows platforms, trigger migration is not set, so there is no
    // point (but equally no harm) in doing a migration since headful does not
    // perform the migration. See
    // ProfileNetworkContextService::ConfigureNetworkContextParamsInternal in
    // src/chrome.
    context_params->file_paths->trigger_migration = false;
#endif  // BUILDFLAG(IS_WIN)
  }

  if (!disk_cache_dir_.empty()) {
    if (!context_params->file_paths) {
      context_params->file_paths =
          ::network::mojom::NetworkContextFilePaths::New();
    }
    context_params->file_paths->http_cache_directory = disk_cache_dir_;
  } else if (!user_data_path_.empty()) {
    context_params->file_paths->http_cache_directory =
        user_data_path_.Append(FILE_PATH_LITERAL("Cache"));
  }
  if (proxy_config_) {
    context_params->initial_proxy_config = net::ProxyConfigWithAnnotation(
        *proxy_config_, GetProxyConfigTrafficAnnotationTag());
  } else {
    proxy_config_monitor_->AddToNetworkContextParams(context_params);
  }
}

}  // namespace headless