|  | // 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/network_change_notifier.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/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/url_request/http_user_agent_settings.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" | 
|  |  | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | 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()); | 
|  |  | 
|  | if (user_agent_partial_) | 
|  | user_agent_ = web::BuildUserAgentFromProduct(user_agent_); | 
|  |  | 
|  | // 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<URLRequestContextConfig> config(new URLRequestContextConfig( | 
|  | quic_enabled_,  // Enable QUIC. | 
|  | quic_enabled_ && quic_user_agent_id_.empty() | 
|  | ? getDefaultQuicUserAgentId() | 
|  | : quic_user_agent_id_,      // QUIC User Agent ID. | 
|  | http2_enabled_,                 // Enable SPDY. | 
|  | false,                          // Enable SDCH | 
|  | URLRequestContextConfig::DISK,  // Type of http cache. | 
|  | 0,                              // Max size of http cache in bytes. | 
|  | false,                          // Disable caching for HTTP responses. | 
|  | cache_path.value(),  // Storage path for http cache and cookie storage. | 
|  | user_agent_,         // User-Agent request header field. | 
|  | "{}",                // JSON encoded experimental options. | 
|  | "",                  // Data reduction proxy key. | 
|  | "",                  // Data reduction proxy. | 
|  | "",                  // Fallback data reduction proxy. | 
|  | "",                  // Data reduction proxy secure proxy check URL. | 
|  | std::move(mock_cert_verifier_),  // MockCertVerifier to use for testing | 
|  | // purposes. | 
|  | false,                           // Enable network quality estimator. | 
|  | true,  // Enable bypassing of public key pinning for local trust anchors | 
|  | ""));  // Certificate verifier cache data. | 
|  |  | 
|  | net::URLRequestContextBuilder context_builder; | 
|  |  | 
|  | context_builder.set_accept_language(accept_language_); | 
|  |  | 
|  | config->ConfigureURLRequestContextBuilder(&context_builder, net_log_.get(), | 
|  | file_thread_.get()->task_runner()); | 
|  |  | 
|  | std::unique_ptr<net::MappedHostResolver> mapped_host_resolver( | 
|  | new net::MappedHostResolver( | 
|  | net::HostResolver::CreateDefaultResolver(nullptr))); | 
|  |  | 
|  | context_builder.set_host_resolver(std::move(mapped_host_resolver)); | 
|  |  | 
|  | std::unique_ptr<net::CookieStore> cookie_store( | 
|  | net::CookieStoreIOS::CreateCookieStore( | 
|  | [NSHTTPCookieStorage sharedHTTPCookieStorage])); | 
|  | context_builder.SetCookieAndChannelIdStores(std::move(cookie_store), nullptr); | 
|  |  | 
|  | std::unordered_set<std::string> quic_host_whitelist; | 
|  | std::unique_ptr<net::HttpServerProperties> http_server_properties( | 
|  | new net::HttpServerPropertiesImpl()); | 
|  | 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()); | 
|  | http_server_properties->SetAlternativeService( | 
|  | quic_hint_server, alternative_service, base::Time::Max()); | 
|  | quic_host_whitelist.insert(quic_hint.host()); | 
|  | } | 
|  |  | 
|  | context_builder.SetHttpServerProperties(std::move(http_server_properties)); | 
|  | context_builder.set_quic_host_whitelist(quic_host_whitelist); | 
|  |  | 
|  | main_context_ = context_builder.Build(); | 
|  | } | 
|  |  | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | std::string CronetEnvironment::getDefaultQuicUserAgentId() const { | 
|  | return base::SysNSStringToUTF8([[NSBundle mainBundle] | 
|  | objectForInfoDictionaryKey:@"CFBundleDisplayName"]) + | 
|  | " Cronet/" + CRONET_VERSION; | 
|  | } | 
|  |  | 
|  | }  // namespace cronet |