| // 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/at_exit.h" |
| #include "base/atomicops.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/json/json_writer.h" |
| #include "base/mac/bind_objc_block.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/statistics_recorder.h" |
| #include "base/path_service.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/worker_pool.h" |
| #include "components/cronet/histogram_manager.h" |
| #include "components/cronet/ios/version.h" |
| #include "components/prefs/json_pref_store.h" |
| #include "components/prefs/pref_filter.h" |
| #include "ios/net/cookies/cookie_store_ios.h" |
| #include "ios/web/public/user_agent.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/cert/cert_verifier.h" |
| #include "net/cert/ct_known_logs.h" |
| #include "net/cert/ct_log_verifier.h" |
| #include "net/cert/ct_policy_enforcer.h" |
| #include "net/cert/ct_verifier.h" |
| #include "net/cert/multi_log_ct_verifier.h" |
| #include "net/cookies/cookie_store.h" |
| #include "net/dns/host_resolver.h" |
| #include "net/dns/mapped_host_resolver.h" |
| #include "net/http/http_auth_handler_factory.h" |
| #include "net/http/http_cache.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_server_properties_impl.h" |
| #include "net/http/http_stream_factory.h" |
| #include "net/http/http_util.h" |
| #include "net/log/net_log.h" |
| #include "net/log/net_log_capture_mode.h" |
| #include "net/log/write_to_file_net_log_observer.h" |
| #include "net/proxy/proxy_service.h" |
| #include "net/socket/ssl_client_socket.h" |
| #include "net/ssl/channel_id_service.h" |
| #include "net/ssl/default_channel_id_store.h" |
| #include "net/ssl/ssl_config_service_defaults.h" |
| #include "net/url_request/static_http_user_agent_settings.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" |
| |
| namespace { |
| |
| base::AtExitManager* g_at_exit_ = nullptr; |
| net::NetworkChangeNotifier* g_network_change_notifier = nullptr; |
| // MessageLoop on the main thread. |
| base::MessageLoop* g_main_message_loop = nullptr; |
| |
| // 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); |
| }; |
| |
| } // namespace |
| |
| namespace cronet { |
| |
| void CronetEnvironment::PostToNetworkThread( |
| const tracked_objects::Location& from_here, |
| const base::Closure& task) { |
| network_io_thread_->task_runner()->PostTask(from_here, task); |
| } |
| |
| void CronetEnvironment::PostToFileUserBlockingThread( |
| const tracked_objects::Location& from_here, |
| const base::Closure& task) { |
| file_user_blocking_thread_->task_runner()->PostTask(from_here, task); |
| } |
| |
| net::URLRequestContext* CronetEnvironment::GetURLRequestContext() const { |
| return main_context_.get(); |
| } |
| |
| net::URLRequestContextGetter* CronetEnvironment::GetURLRequestContextGetter() |
| const { |
| return main_context_getter_.get(); |
| } |
| |
| // static |
| void CronetEnvironment::Initialize() { |
| // DCHECK_EQ([NSThread currentThread], [NSThread mainThread]); |
| // This method must be called once from the main thread. |
| if (!g_at_exit_) |
| g_at_exit_ = new base::AtExitManager; |
| |
| url::Initialize(); |
| base::CommandLine::Init(0, nullptr); |
| |
| // Without doing this, StatisticsRecorder::FactoryGet() leaks one histogram |
| // per call after the first for a given name. |
| base::StatisticsRecorder::Initialize(); |
| |
| // Create a message loop on the UI thread. |
| DCHECK(!base::MessageLoop::current()); |
| DCHECK(!g_main_message_loop); |
| g_main_message_loop = new base::MessageLoopForUI(); |
| base::MessageLoopForUI::current()->Attach(); |
| // The network change notifier must be initialized so that registered |
| // delegates will receive callbacks. |
| DCHECK(!g_network_change_notifier); |
| g_network_change_notifier = net::NetworkChangeNotifier::Create(); |
| } |
| |
| void CronetEnvironment::StartNetLog(base::FilePath::StringType file_name, |
| bool log_bytes) { |
| DCHECK(file_name.length()); |
| PostToNetworkThread(FROM_HERE, |
| base::Bind(&CronetEnvironment::StartNetLogOnNetworkThread, |
| base::Unretained(this), file_name, log_bytes)); |
| } |
| |
| void CronetEnvironment::StartNetLogOnNetworkThread( |
| const base::FilePath::StringType& file_name, |
| bool log_bytes) { |
| DCHECK(file_name.length()); |
| DCHECK(net_log_); |
| |
| if (net_log_observer_) |
| return; |
| |
| base::FilePath files_root; |
| if (!PathService::Get(base::DIR_HOME, &files_root)) |
| return; |
| |
| base::FilePath full_path = files_root.Append(file_name); |
| base::ScopedFILE file(base::OpenFile(full_path, "w")); |
| if (!file) { |
| LOG(ERROR) << "Can not start NetLog to " << full_path.value(); |
| return; |
| } |
| |
| net::NetLogCaptureMode capture_mode = |
| log_bytes ? net::NetLogCaptureMode::IncludeSocketBytes() |
| : net::NetLogCaptureMode::Default(); |
| |
| net_log_observer_.reset(new net::WriteToFileNetLogObserver()); |
| net_log_observer_->set_capture_mode(capture_mode); |
| net_log_observer_->StartObserving(main_context_->net_log(), std::move(file), |
| nullptr, main_context_.get()); |
| LOG(WARNING) << "Started NetLog to " << full_path.value(); |
| } |
| |
| 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 (net_log_observer_) { |
| DLOG(WARNING) << "Stopped NetLog."; |
| net_log_observer_->StopObserving(main_context_.get()); |
| net_log_observer_.reset(); |
| } |
| log_stopped_event->Signal(); |
| } |
| |
| 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), |
| user_agent_(user_agent), |
| user_agent_partial_(user_agent_partial), |
| net_log_(new net::NetLog) {} |
| |
| void CronetEnvironment::Start() { |
| // Threads setup. |
| network_cache_thread_.reset(new base::Thread("Chrome Network Cache Thread")); |
| network_cache_thread_->StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| network_io_thread_.reset(new base::Thread("Chrome Network IO Thread")); |
| network_io_thread_->StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| file_thread_.reset(new base::Thread("Chrome File Thread")); |
| file_thread_->StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| file_user_blocking_thread_.reset( |
| new base::Thread("Chrome File User Blocking Thread")); |
| file_user_blocking_thread_->StartWithOptions( |
| base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| |
| 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; |
| if (!PathService::Get(base::DIR_HOME, &ssl_key_log_file)) |
| return; |
| net::SSLClientSocket::SetSSLKeyLogFile( |
| ssl_key_log_file.Append(ssl_key_log_file_name_), |
| file_thread_->task_runner()); |
| } |
| |
| proxy_config_service_ = net::ProxyService::CreateSystemProxyConfigService( |
| network_io_thread_->task_runner(), nullptr); |
| main_context_getter_ = new CronetURLRequestContextGetter( |
| this, network_io_thread_->task_runner()); |
| base::subtle::MemoryBarrier(); |
| PostToNetworkThread(FROM_HERE, |
| base::Bind(&CronetEnvironment::InitializeOnNetworkThread, |
| base::Unretained(this))); |
| } |
| |
| CronetEnvironment::~CronetEnvironment() { |
| // net::HTTPProtocolHandlerDelegate::SetInstance(nullptr); |
| } |
| |
| void CronetEnvironment::InitializeOnNetworkThread() { |
| DCHECK(network_io_thread_->task_runner()->BelongsToCurrentThread()); |
| base::FeatureList::InitializeInstance(std::string(), std::string()); |
| // TODO(mef): Use net:UrlRequestContextBuilder instead of manual build. |
| main_context_.reset(new net::URLRequestContext); |
| main_context_->set_net_log(net_log_.get()); |
| |
| if (user_agent_partial_) |
| user_agent_ = web::BuildUserAgentFromProduct(user_agent_); |
| |
| main_context_->set_http_user_agent_settings( |
| new net::StaticHttpUserAgentSettings(accept_language_, user_agent_)); |
| |
| main_context_->set_ssl_config_service(new net::SSLConfigServiceDefaults); |
| main_context_->set_transport_security_state( |
| new net::TransportSecurityState()); |
| http_server_properties_.reset(new net::HttpServerPropertiesImpl()); |
| main_context_->set_http_server_properties(http_server_properties_.get()); |
| |
| // TODO(rdsmith): Note that the ".release()" calls below are leaking |
| // the objects in question; this should be fixed by having an object |
| // corresponding to URLRequestContextStorage that actually owns those |
| // objects. See http://crbug.com/523858. |
| std::unique_ptr<net::MappedHostResolver> mapped_host_resolver( |
| new net::MappedHostResolver( |
| net::HostResolver::CreateDefaultResolver(nullptr))); |
| |
| main_context_->set_host_resolver(mapped_host_resolver.release()); |
| |
| if (!cert_verifier_) |
| cert_verifier_ = net::CertVerifier::CreateDefault(); |
| main_context_->set_cert_verifier(cert_verifier_.get()); |
| |
| std::unique_ptr<net::MultiLogCTVerifier> ct_verifier = |
| base::MakeUnique<net::MultiLogCTVerifier>(); |
| ct_verifier->AddLogs(net::ct::CreateLogVerifiersForKnownLogs()); |
| main_context_->set_cert_transparency_verifier(ct_verifier.release()); |
| main_context_->set_ct_policy_enforcer(new net::CTPolicyEnforcer()); |
| |
| main_context_->set_http_auth_handler_factory( |
| net::HttpAuthHandlerRegistryFactory::CreateDefault( |
| main_context_->host_resolver()) |
| .release()); |
| main_context_->set_proxy_service( |
| net::ProxyService::CreateUsingSystemProxyResolver( |
| std::move(proxy_config_service_), 0, nullptr) |
| .release()); |
| |
| // Cache |
| base::FilePath cache_path; |
| if (!PathService::Get(base::DIR_CACHE, &cache_path)) |
| return; |
| cache_path = cache_path.Append(FILE_PATH_LITERAL("cronet")); |
| std::unique_ptr<net::HttpCache::DefaultBackend> main_backend( |
| new net::HttpCache::DefaultBackend(net::DISK_CACHE, |
| net::CACHE_BACKEND_SIMPLE, cache_path, |
| 0, // Default cache size. |
| network_cache_thread_->task_runner())); |
| |
| net::HttpNetworkSession::Params params; |
| |
| params.host_resolver = main_context_->host_resolver(); |
| params.cert_verifier = main_context_->cert_verifier(); |
| params.cert_transparency_verifier = |
| main_context_->cert_transparency_verifier(); |
| params.ct_policy_enforcer = main_context_->ct_policy_enforcer(); |
| params.channel_id_service = main_context_->channel_id_service(); |
| params.transport_security_state = main_context_->transport_security_state(); |
| params.proxy_service = main_context_->proxy_service(); |
| params.ssl_config_service = main_context_->ssl_config_service(); |
| params.http_auth_handler_factory = main_context_->http_auth_handler_factory(); |
| params.http_server_properties = main_context_->http_server_properties(); |
| params.net_log = main_context_->net_log(); |
| params.enable_http2 = http2_enabled(); |
| params.enable_quic = quic_enabled(); |
| |
| for (const auto& quic_hint : quic_hints_) { |
| 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()->SetAlternativeService( |
| quic_hint_server, alternative_service, base::Time::Max()); |
| params.quic_host_whitelist.insert(quic_hint.host()); |
| } |
| |
| if (!params.channel_id_service) { |
| // The main context may not have a ChannelIDService, since it is lazily |
| // constructed. If not, build an ephemeral ChannelIDService with no backing |
| // disk store. |
| // TODO(ellyjones): support persisting ChannelID. |
| params.channel_id_service = |
| new net::ChannelIDService(new net::DefaultChannelIDStore(NULL), |
| base::WorkerPool::GetTaskRunner(true)); |
| } |
| |
| // TODO(mmenke): These really shouldn't be leaked. |
| // See https://crbug.com/523858. |
| net::HttpNetworkSession* http_network_session = |
| new net::HttpNetworkSession(params); |
| net::HttpCache* main_cache = |
| new net::HttpCache(http_network_session, std::move(main_backend), |
| true /* set_up_quic_server_info */); |
| main_context_->set_http_transaction_factory(main_cache); |
| // Cookies |
| cookie_store_ = net::CookieStoreIOS::CreateCookieStore( |
| [NSHTTPCookieStorage sharedHTTPCookieStorage]); |
| main_context_->set_cookie_store(cookie_store_.get()); |
| |
| net::URLRequestJobFactoryImpl* job_factory = |
| new net::URLRequestJobFactoryImpl; |
| main_context_->set_job_factory(job_factory); |
| main_context_->set_net_log(net_log_.get()); |
| } |
| |
| 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() { |
| base::StatisticsRecorder::Initialize(); |
| std::vector<uint8_t> data; |
| if (!HistogramManager::GetInstance()->GetDeltas(&data)) |
| return std::vector<uint8_t>(); |
| 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(); |
| } |
| |
| } // namespace cronet |