blob: ea6839135f5c3df5776da89e6fd8599fb4d5ba71 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/cronet/ios/cronet_environment.h"
#include <utility>
#include "base/atomicops.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/mac/foundation_util.h"
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_restrictions.h"
#include "components/cronet/cronet_buildflags.h"
#include "components/cronet/cronet_global_state.h"
#include "components/cronet/cronet_prefs_manager.h"
#include "components/cronet/histogram_manager.h"
#include "components/prefs/pref_filter.h"
#include "ios/net/cookies/cookie_store_ios.h"
#include "ios/net/cookies/cookie_store_ios_client.h"
#include "ios/web/public/global_state/ios_global_state.h"
#include "ios/web/public/global_state/ios_global_state_configuration.h"
#include "ios/web/public/user_agent.h"
#include "net/base/http_user_agent_settings.h"
#include "net/base/network_change_notifier.h"
#include "net/base/url_util.h"
#include "net/cert/cert_verifier.h"
#include "net/dns/host_resolver.h"
#include "net/dns/mapped_host_resolver.h"
#include "net/http/http_server_properties_impl.h"
#include "net/http/http_stream_factory.h"
#include "net/http/http_transaction_factory.h"
#include "net/http/http_util.h"
#include "net/log/file_net_log_observer.h"
#include "net/log/net_log.h"
#include "net/log/net_log_capture_mode.h"
#include "net/log/net_log_util.h"
#include "net/proxy_resolution/proxy_resolution_service.h"
#include "net/socket/ssl_client_socket.h"
#include "net/ssl/channel_id_service.h"
#include "net/ssl/ssl_key_logger_impl.h"
#include "net/third_party/quiche/src/quic/core/quic_versions.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_context_storage.h"
#include "net/url_request/url_request_job_factory_impl.h"
#include "url/scheme_host_port.h"
#include "url/url_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Request context getter for Cronet.
class CronetURLRequestContextGetter : public net::URLRequestContextGetter {
public:
CronetURLRequestContextGetter(
cronet::CronetEnvironment* environment,
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
: environment_(environment), task_runner_(task_runner) {}
net::URLRequestContext* GetURLRequestContext() override {
DCHECK(environment_);
return environment_->GetURLRequestContext();
}
scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner()
const override {
return task_runner_;
}
private:
// Must be called on the IO thread.
~CronetURLRequestContextGetter() override {}
cronet::CronetEnvironment* environment_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(CronetURLRequestContextGetter);
};
// Cronet implementation of net::CookieStoreIOSClient.
// Used to provide Cronet Network IO TaskRunner.
class CronetCookieStoreIOSClient : public net::CookieStoreIOSClient {
public:
CronetCookieStoreIOSClient(
const scoped_refptr<base::SequencedTaskRunner>& task_runner)
: task_runner_(task_runner) {}
scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override {
return task_runner_;
}
private:
~CronetCookieStoreIOSClient() override {}
scoped_refptr<base::SequencedTaskRunner> task_runner_;
DISALLOW_COPY_AND_ASSIGN(CronetCookieStoreIOSClient);
};
void SignalEvent(base::WaitableEvent* event) {
event->Signal();
}
// TODO(eroman): Creating the file(s) for a netlog is an internal detail for
// FileNetLogObsever. This code assumes that the unbounded format is being used,
// which writes a single file at |path| (creating or overwriting it).
bool IsNetLogPathValid(const base::FilePath& path) {
base::ScopedFILE file(base::OpenFile(path, "w"));
return !!file;
}
} // namespace
namespace cronet {
const double CronetEnvironment::kKeepDefaultThreadPriority = -1;
base::SingleThreadTaskRunner* CronetEnvironment::GetNetworkThreadTaskRunner()
const {
if (network_io_thread_) {
return network_io_thread_->task_runner().get();
}
return ios_global_state::GetSharedNetworkIOThreadTaskRunner().get();
}
void CronetEnvironment::PostToNetworkThread(const base::Location& from_here,
const base::Closure& task) {
GetNetworkThreadTaskRunner()->PostTask(from_here, task);
}
net::URLRequestContext* CronetEnvironment::GetURLRequestContext() const {
return main_context_.get();
}
net::URLRequestContextGetter* CronetEnvironment::GetURLRequestContextGetter()
const {
return main_context_getter_.get();
}
bool CronetEnvironment::StartNetLog(base::FilePath::StringType file_name,
bool log_bytes) {
if (file_name.empty())
return false;
base::FilePath path(file_name);
if (!IsNetLogPathValid(path)) {
LOG(ERROR) << "Can not start NetLog to " << path.value() << ": "
<< strerror(errno);
return false;
}
LOG(WARNING) << "Starting NetLog to " << path.value();
PostToNetworkThread(FROM_HERE,
base::Bind(&CronetEnvironment::StartNetLogOnNetworkThread,
base::Unretained(this), path, log_bytes));
return true;
}
void CronetEnvironment::StartNetLogOnNetworkThread(const base::FilePath& path,
bool log_bytes) {
DCHECK(net_log_);
if (file_net_log_observer_)
return;
net::NetLogCaptureMode capture_mode =
log_bytes ? net::NetLogCaptureMode::IncludeSocketBytes()
: net::NetLogCaptureMode::Default();
file_net_log_observer_ =
net::FileNetLogObserver::CreateUnbounded(path, nullptr);
file_net_log_observer_->StartObserving(main_context_->net_log(),
capture_mode);
LOG(WARNING) << "Started NetLog";
}
void CronetEnvironment::StopNetLog() {
base::WaitableEvent log_stopped_event(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
PostToNetworkThread(FROM_HERE,
base::Bind(&CronetEnvironment::StopNetLogOnNetworkThread,
base::Unretained(this), &log_stopped_event));
log_stopped_event.Wait();
}
void CronetEnvironment::StopNetLogOnNetworkThread(
base::WaitableEvent* log_stopped_event) {
if (file_net_log_observer_) {
DLOG(WARNING) << "Stopped NetLog.";
file_net_log_observer_->StopObserving(
GetNetLogInfo(), base::BindOnce(&SignalEvent, log_stopped_event));
file_net_log_observer_.reset();
} else {
log_stopped_event->Signal();
}
}
std::unique_ptr<base::DictionaryValue> CronetEnvironment::GetNetLogInfo()
const {
std::unique_ptr<base::DictionaryValue> net_info =
net::GetNetInfo(main_context_.get(), net::NET_INFO_ALL_SOURCES);
if (effective_experimental_options_) {
net_info->Set("cronetExperimentalParams",
effective_experimental_options_->CreateDeepCopy());
}
return net_info;
}
net::HttpNetworkSession* CronetEnvironment::GetHttpNetworkSession(
net::URLRequestContext* context) {
DCHECK(context);
if (!context->http_transaction_factory())
return nullptr;
return context->http_transaction_factory()->GetSession();
}
void CronetEnvironment::AddQuicHint(const std::string& host,
int port,
int alternate_port) {
DCHECK(port == alternate_port);
quic_hints_.push_back(net::HostPortPair(host, port));
}
CronetEnvironment::CronetEnvironment(const std::string& user_agent,
bool user_agent_partial)
: http2_enabled_(false),
quic_enabled_(false),
brotli_enabled_(false),
http_cache_(URLRequestContextConfig::HttpCacheType::DISK),
user_agent_(user_agent),
user_agent_partial_(user_agent_partial),
net_log_(new net::NetLog),
enable_pkp_bypass_for_local_trust_anchors_(true),
network_thread_priority_(kKeepDefaultThreadPriority) {}
void CronetEnvironment::Start() {
// Threads setup.
file_thread_.reset(new base::Thread("Chrome File Thread"));
file_thread_->StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
// Fetching the task_runner will create the shared thread if necessary.
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
ios_global_state::GetSharedNetworkIOThreadTaskRunner();
if (!task_runner) {
network_io_thread_.reset(
new CronetNetworkThread("Chrome Network IO Thread", this));
network_io_thread_->StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
}
net::SetCookieStoreIOSClient(new CronetCookieStoreIOSClient(
CronetEnvironment::GetNetworkThreadTaskRunner()));
main_context_getter_ = new CronetURLRequestContextGetter(
this, CronetEnvironment::GetNetworkThreadTaskRunner());
base::subtle::MemoryBarrier();
PostToNetworkThread(FROM_HERE,
base::Bind(&CronetEnvironment::InitializeOnNetworkThread,
base::Unretained(this)));
}
void CronetEnvironment::CleanUpOnNetworkThread() {
// TODO(lilyhoughton) make unregistering of this work.
// net::HTTPProtocolHandlerDelegate::SetInstance(nullptr);
// TODO(lilyhoughton) this can only be run once, so right now leaking it.
// Should be be called when the _last_ CronetEnvironment is destroyed.
// base::TaskScheduler* ts = base::TaskScheduler::GetInstance();
// if (ts)
// ts->Shutdown();
if (cronet_prefs_manager_) {
cronet_prefs_manager_->PrepareForShutdown();
}
// TODO(lilyhoughton) this should be smarter about making sure there are no
// pending requests, etc.
main_context_.reset();
// cronet_prefs_manager_ should be deleted on the network thread.
cronet_prefs_manager_.reset();
}
CronetEnvironment::~CronetEnvironment() {
// Deleting a thread blocks the current thread and waits until all pending
// tasks are completed.
network_io_thread_.reset();
file_thread_.reset();
}
void CronetEnvironment::InitializeOnNetworkThread() {
DCHECK(GetNetworkThreadTaskRunner()->BelongsToCurrentThread());
base::DisallowBlocking();
static bool ssl_key_log_file_set = false;
if (!ssl_key_log_file_set && !ssl_key_log_file_name_.empty()) {
ssl_key_log_file_set = true;
base::FilePath ssl_key_log_file(ssl_key_log_file_name_);
net::SSLClientSocket::SetSSLKeyLogger(
std::make_unique<net::SSLKeyLoggerImpl>(ssl_key_log_file));
}
if (user_agent_partial_)
user_agent_ = web::BuildUserAgentFromProduct(user_agent_);
// Cache
base::FilePath storage_path;
if (!base::PathService::Get(base::DIR_CACHE, &storage_path))
return;
storage_path = storage_path.Append(FILE_PATH_LITERAL("cronet"));
URLRequestContextConfigBuilder context_config_builder;
context_config_builder.enable_quic = quic_enabled_; // Enable QUIC.
context_config_builder.quic_user_agent_id =
getDefaultQuicUserAgentId(); // QUIC User Agent ID.
context_config_builder.enable_spdy = http2_enabled_; // Enable HTTP/2.
context_config_builder.http_cache = http_cache_; // Set HTTP cache.
context_config_builder.storage_path =
storage_path.value(); // Storage path for http cache and prefs storage.
context_config_builder.accept_language =
accept_language_; // Accept-Language request header field.
context_config_builder.user_agent =
user_agent_; // User-Agent request header field.
context_config_builder.experimental_options =
experimental_options_; // Set experimental Cronet options.
context_config_builder.mock_cert_verifier = std::move(
mock_cert_verifier_); // MockCertVerifier to use for testing purposes.
if (network_thread_priority_ != kKeepDefaultThreadPriority)
context_config_builder.network_thread_priority = network_thread_priority_;
std::unique_ptr<URLRequestContextConfig> config =
context_config_builder.Build();
config->pkp_list = std::move(pkp_list_);
net::URLRequestContextBuilder context_builder;
// Explicitly disable the persister for Cronet to avoid persistence of dynamic
// HPKP. This is a safety measure ensuring that nobody enables the
// persistence of HPKP by specifying transport_security_persister_path in the
// future.
context_builder.set_transport_security_persister_path(base::FilePath());
config->ConfigureURLRequestContextBuilder(&context_builder, net_log_.get());
effective_experimental_options_ =
std::move(config->effective_experimental_options);
std::unique_ptr<net::MappedHostResolver> mapped_host_resolver(
new net::MappedHostResolver(
net::HostResolver::CreateDefaultResolver(nullptr)));
if (!config->storage_path.empty()) {
cronet_prefs_manager_ = std::make_unique<CronetPrefsManager>(
config->storage_path, GetNetworkThreadTaskRunner(),
file_thread_->task_runner(), false /* nqe */, false /* host_cache */,
net_log_.get(), &context_builder);
}
context_builder.set_host_resolver(std::move(mapped_host_resolver));
// TODO(690969): This behavior matches previous behavior of CookieStoreIOS in
// CrNet, but should change to adhere to App's Cookie Accept Policy instead
// of changing it.
[[NSHTTPCookieStorage sharedHTTPCookieStorage]
setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
auto cookie_store = std::make_unique<net::CookieStoreIOS>(
[NSHTTPCookieStorage sharedHTTPCookieStorage], nullptr /* net_log */);
context_builder.SetCookieStore(std::move(cookie_store));
context_builder.set_enable_brotli(brotli_enabled_);
main_context_ = context_builder.Build();
for (const auto& quic_hint : quic_hints_) {
url::CanonHostInfo host_info;
std::string canon_host(net::CanonicalizeHost(quic_hint.host(), &host_info));
if (!host_info.IsIPAddress() &&
!net::IsCanonicalizedHostCompliant(canon_host)) {
LOG(ERROR) << "Invalid QUIC hint host: " << quic_hint.host();
continue;
}
net::AlternativeService alternative_service(net::kProtoQUIC, "",
quic_hint.port());
url::SchemeHostPort quic_hint_server("https", quic_hint.host(),
quic_hint.port());
main_context_->http_server_properties()->SetQuicAlternativeService(
quic_hint_server, alternative_service, base::Time::Max(),
quic::QuicTransportVersionVector());
}
main_context_->transport_security_state()
->SetEnablePublicKeyPinningBypassForLocalTrustAnchors(
enable_pkp_bypass_for_local_trust_anchors_);
// Iterate trhough PKP configuration for every host.
for (const auto& pkp : config->pkp_list) {
// Add the host pinning.
main_context_->transport_security_state()->AddHPKP(
pkp->host, pkp->expiration_date, pkp->include_subdomains,
pkp->pin_hashes, GURL::EmptyGURL());
}
}
void CronetEnvironment::SetNetworkThreadPriority(double priority) {
DCHECK_LE(priority, 1.0);
DCHECK_GE(priority, 0.0);
network_thread_priority_ = priority;
if (network_io_thread_) {
PostToNetworkThread(
FROM_HERE,
base::BindRepeating(
&CronetEnvironment::SetNetworkThreadPriorityOnNetworkThread,
base::Unretained(this), priority));
}
}
std::string CronetEnvironment::user_agent() {
const net::HttpUserAgentSettings* user_agent_settings =
main_context_->http_user_agent_settings();
if (!user_agent_settings) {
return nullptr;
}
return user_agent_settings->GetUserAgent();
}
std::vector<uint8_t> CronetEnvironment::GetHistogramDeltas() {
std::vector<uint8_t> data;
#if BUILDFLAG(DISABLE_HISTOGRAM_SUPPORT)
NOTREACHED() << "Histogram support is disabled";
#else // BUILDFLAG(DISABLE_HISTOGRAM_SUPPORT)
if (!HistogramManager::GetInstance()->GetDeltas(&data))
return std::vector<uint8_t>();
#endif // BUILDFLAG(DISABLE_HISTOGRAM_SUPPORT)
return data;
}
void CronetEnvironment::SetHostResolverRules(const std::string& rules) {
base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
PostToNetworkThread(
FROM_HERE,
base::Bind(&CronetEnvironment::SetHostResolverRulesOnNetworkThread,
base::Unretained(this), rules, &event));
event.Wait();
}
void CronetEnvironment::SetHostResolverRulesOnNetworkThread(
const std::string& rules,
base::WaitableEvent* event) {
static_cast<net::MappedHostResolver*>(main_context_->host_resolver())
->SetRulesFromString(rules);
event->Signal();
}
void CronetEnvironment::SetNetworkThreadPriorityOnNetworkThread(
double priority) {
DCHECK(GetNetworkThreadTaskRunner()->BelongsToCurrentThread());
cronet::SetNetworkThreadPriorityOnNetworkThread(priority);
}
std::string CronetEnvironment::getDefaultQuicUserAgentId() const {
return base::SysNSStringToUTF8([[NSBundle mainBundle]
objectForInfoDictionaryKey:@"CFBundleDisplayName"]) +
" Cronet/" + CRONET_VERSION;
}
base::SingleThreadTaskRunner* CronetEnvironment::GetFileThreadRunnerForTesting()
const {
return file_thread_->task_runner().get();
}
base::SingleThreadTaskRunner*
CronetEnvironment::GetNetworkThreadRunnerForTesting() const {
return GetNetworkThreadTaskRunner();
}
CronetEnvironment::CronetNetworkThread::CronetNetworkThread(
const std::string& name,
cronet::CronetEnvironment* cronet_environment)
: base::Thread(name), cronet_environment_(cronet_environment) {}
CronetEnvironment::CronetNetworkThread::~CronetNetworkThread() {
Stop();
}
void CronetEnvironment::CronetNetworkThread::CleanUp() {
cronet_environment_->CleanUpOnNetworkThread();
}
} // namespace cronet