#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;
}
}
class HeadlessProxyConfigMonitor
: public net::ProxyConfigService::Observer,
public ::network::mojom::ProxyConfigPollerClient {
public:
HeadlessProxyConfigMonitor() {
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);
}
void AddToNetworkContextParams(
::network::mojom::NetworkContextParams* network_context_params) {
if (proxy_config_client_) {
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:
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();
}
}
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_;
};
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)
:
#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);
}
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)
context_params->file_paths->trigger_migration = true;
#else
context_params->file_paths->trigger_migration = false;
#endif
}
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);
}
}
}