1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ios/crnet/crnet_environment.h"
7 #import <Foundation/Foundation.h>
9 #include "base/at_exit.h"
10 #include "base/command_line.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/files/scoped_file.h"
14 #include "base/i18n/icu_util.h"
15 #include "base/json/json_writer.h"
16 #include "base/mac/bind_objc_block.h"
17 #include "base/mac/foundation_util.h"
18 #include "base/mac/scoped_block.h"
19 #include "base/metrics/statistics_recorder.h"
20 #include "base/path_service.h"
21 #include "base/threading/worker_pool.h"
22 #import "components/webp_transcode/webp_network_client_factory.h"
23 #include "crypto/nss_util.h"
24 #include "ios/net/cookies/cookie_store_ios.h"
25 #include "ios/net/crn_http_protocol_handler.h"
26 #include "ios/net/empty_nsurlcache.h"
27 #include "ios/net/request_tracker.h"
28 #include "ios/web/public/user_agent.h"
29 #include "net/base/net_errors.h"
30 #include "net/base/network_change_notifier.h"
31 #include "net/base/sdch_manager.h"
32 #include "net/cert/cert_verifier.h"
33 #include "net/cert_net/nss_ocsp.h"
34 #include "net/disk_cache/disk_cache.h"
35 #include "net/http/http_auth_handler_factory.h"
36 #include "net/http/http_cache.h"
37 #include "net/http/http_server_properties_impl.h"
38 #include "net/http/http_stream_factory.h"
39 #include "net/http/http_util.h"
40 #include "net/log/net_log.h"
41 #include "net/log/write_to_file_net_log_observer.h"
42 #include "net/proxy/proxy_service.h"
43 #include "net/socket/next_proto.h"
44 #include "net/ssl/channel_id_service.h"
45 #include "net/ssl/default_channel_id_store.h"
46 #include "net/ssl/ssl_config_service_defaults.h"
47 #include "net/url_request/data_protocol_handler.h"
48 #include "net/url_request/file_protocol_handler.h"
49 #include "net/url_request/sdch_dictionary_fetcher.h"
50 #include "net/url_request/static_http_user_agent_settings.h"
51 #include "net/url_request/url_request_context_getter.h"
52 #include "net/url_request/url_request_context_storage.h"
53 #include "net/url_request/url_request_job_factory_impl.h"
54 #include "url/url_util.h"
58 base::AtExitManager* g_at_exit_ = nullptr;
60 // Request context getter for CrNet.
61 class CrNetURLRequestContextGetter : public net::URLRequestContextGetter {
63 CrNetURLRequestContextGetter(
64 net::URLRequestContext* context,
65 const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
66 : context_(context), task_runner_(task_runner) {}
68 net::URLRequestContext* GetURLRequestContext() override { return context_; }
70 scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner()
75 // Must be called on the IO thread.
76 ~CrNetURLRequestContextGetter() override {}
78 net::URLRequestContext* context_;
79 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
80 DISALLOW_COPY_AND_ASSIGN(CrNetURLRequestContextGetter);
85 // net::HTTPProtocolHandlerDelegate for CrNet.
86 class CrNetHttpProtocolHandlerDelegate
87 : public net::HTTPProtocolHandlerDelegate {
89 CrNetHttpProtocolHandlerDelegate(net::URLRequestContextGetter* getter,
90 RequestFilterBlock filter)
91 : getter_(getter), filter_(filter, base::scoped_policy::RETAIN) {}
94 // net::HTTPProtocolHandlerDelegate implementation:
95 bool CanHandleRequest(NSURLRequest* request) override {
96 // Don't advertise support for file:// URLs for now.
97 // This broke some time ago but it's not clear how to fix it at the moment.
98 // http://crbug.com/480620
99 if ([[[request URL] scheme] caseInsensitiveCompare:@"file"] ==
104 RequestFilterBlock block = filter_.get();
105 return block(request);
110 bool IsRequestSupported(NSURLRequest* request) override {
111 NSString* scheme = [[request URL] scheme];
114 return [scheme caseInsensitiveCompare:@"data"] == NSOrderedSame ||
115 [scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
116 [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame;
119 net::URLRequestContextGetter* GetDefaultURLRequestContext() override {
120 return getter_.get();
123 scoped_refptr<net::URLRequestContextGetter> getter_;
124 base::mac::ScopedBlock<RequestFilterBlock> filter_;
127 void CrNetEnvironment::PostToNetworkThread(
128 const tracked_objects::Location& from_here,
129 const base::Closure& task) {
130 network_io_thread_->message_loop()->PostTask(from_here, task);
133 void CrNetEnvironment::PostToFileUserBlockingThread(
134 const tracked_objects::Location& from_here,
135 const base::Closure& task) {
136 file_user_blocking_thread_->message_loop()->PostTask(from_here, task);
140 void CrNetEnvironment::Initialize() {
141 DCHECK_EQ([NSThread currentThread], [NSThread mainThread]);
143 g_at_exit_ = new base::AtExitManager;
145 CHECK(base::i18n::InitializeICU());
147 base::CommandLine::Init(0, nullptr);
148 // This needs to happen on the main thread. NSPR's initialization sets up its
149 // memory allocator; if this is not done before other threads are created,
150 // this initialization can race to cause accidental free/allocation
152 crypto::EnsureNSPRInit();
153 // Without doing this, StatisticsRecorder::FactoryGet() leaks one histogram
154 // per call after the first for a given name.
155 base::StatisticsRecorder::Initialize();
157 // Create a message loop on the UI thread.
158 base::MessageLoop* main_message_loop =
159 new base::MessageLoop(base::MessageLoop::TYPE_UI);
160 #pragma unused(main_message_loop)
161 base::MessageLoopForUI::current()->Attach();
164 void CrNetEnvironment::StartNetLog(base::FilePath::StringType file_name,
166 DCHECK(file_name.length());
167 PostToFileUserBlockingThread(FROM_HERE,
168 base::Bind(&CrNetEnvironment::StartNetLogInternal,
169 base::Unretained(this), file_name, log_bytes));
172 void CrNetEnvironment::StartNetLogInternal(
173 base::FilePath::StringType file_name, bool log_bytes) {
174 DCHECK(base::MessageLoop::current() ==
175 file_user_blocking_thread_->message_loop());
176 DCHECK(file_name.length());
179 if (net_log_observer_)
182 base::FilePath temp_dir;
183 if (!base::GetTempDir(&temp_dir))
186 base::FilePath full_path = temp_dir.Append(file_name);
187 base::ScopedFILE file(base::OpenFile(full_path, "w"));
191 net::NetLogCaptureMode capture_mode = log_bytes ?
192 net::NetLogCaptureMode::IncludeSocketBytes() :
193 net::NetLogCaptureMode::Default();
195 net_log_observer_.reset(new net::WriteToFileNetLogObserver());
196 net_log_observer_->set_capture_mode(capture_mode);
197 net_log_observer_->StartObserving(net_log_.get(), file.Pass(), nullptr,
201 void CrNetEnvironment::StopNetLog() {
202 PostToFileUserBlockingThread(FROM_HERE,
203 base::Bind(&CrNetEnvironment::StopNetLogInternal,
204 base::Unretained(this)));
207 void CrNetEnvironment::StopNetLogInternal() {
208 DCHECK(base::MessageLoop::current() ==
209 file_user_blocking_thread_->message_loop());
210 if (net_log_observer_) {
211 net_log_observer_->StopObserving(nullptr);
212 net_log_observer_.reset();
216 void CrNetEnvironment::CloseAllSpdySessions() {
217 PostToNetworkThread(FROM_HERE,
218 base::Bind(&CrNetEnvironment::CloseAllSpdySessionsInternal,
219 base::Unretained(this)));
222 void CrNetEnvironment::SetRequestFilterBlock(RequestFilterBlock block) {
223 http_protocol_handler_delegate_.reset(
224 new CrNetHttpProtocolHandlerDelegate(main_context_getter_.get(), block));
225 net::HTTPProtocolHandlerDelegate::SetInstance(
226 http_protocol_handler_delegate_.get());
229 net::HttpNetworkSession* CrNetEnvironment::GetHttpNetworkSession(
230 net::URLRequestContext* context) {
232 if (!context->http_transaction_factory())
235 return context->http_transaction_factory()->GetSession();
238 void CrNetEnvironment::CloseAllSpdySessionsInternal() {
239 DCHECK(base::MessageLoop::current() ==
240 network_io_thread_->message_loop());
242 net::HttpNetworkSession* http_network_session =
243 GetHttpNetworkSession(GetMainContextGetter()->GetURLRequestContext());
245 if (http_network_session) {
246 net::SpdySessionPool *spdy_session_pool =
247 http_network_session->spdy_session_pool();
248 if (spdy_session_pool)
249 spdy_session_pool->CloseCurrentSessions(net::ERR_ABORTED);
253 CrNetEnvironment::CrNetEnvironment(std::string user_agent_product_name)
254 : main_context_(new net::URLRequestContext),
255 user_agent_product_name_(user_agent_product_name) {
259 void CrNetEnvironment::Install() {
261 network_cache_thread_.reset(new base::Thread("Chrome Network Cache Thread"));
262 network_cache_thread_->StartWithOptions(
263 base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
264 network_io_thread_.reset(new base::Thread("Chrome Network IO Thread"));
265 network_io_thread_->StartWithOptions(
266 base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
267 file_thread_.reset(new base::Thread("Chrome File Thread"));
268 file_thread_->StartWithOptions(
269 base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
270 file_user_blocking_thread_.reset(
271 new base::Thread("Chrome File User Blocking Thread"));
272 file_user_blocking_thread_->StartWithOptions(
273 base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
275 // The network change notifier must be initialized so that registered
276 // delegates will receive callbacks.
277 network_change_notifier_.reset(net::NetworkChangeNotifier::Create());
278 proxy_config_service_.reset(net::ProxyService::CreateSystemProxyConfigService(
279 network_io_thread_->task_runner(), nullptr));
281 PostToNetworkThread(FROM_HERE,
282 base::Bind(&CrNetEnvironment::InitializeOnNetworkThread,
283 base::Unretained(this)));
285 net::SetURLRequestContextForNSSHttpIO(main_context_.get());
286 main_context_getter_ = new CrNetURLRequestContextGetter(
287 main_context_.get(), network_io_thread_->task_runner());
288 SetRequestFilterBlock(nil);
291 void CrNetEnvironment::InstallIntoSessionConfiguration(
292 NSURLSessionConfiguration* config) {
293 config.protocolClasses = @[ [CRNPauseableHTTPProtocolHandler class] ];
296 CrNetEnvironment::~CrNetEnvironment() {
297 net::HTTPProtocolHandlerDelegate::SetInstance(nullptr);
298 net::SetURLRequestContextForNSSHttpIO(nullptr);
301 net::URLRequestContextGetter* CrNetEnvironment::GetMainContextGetter() {
302 return main_context_getter_.get();
305 void CrNetEnvironment::SetHTTPProtocolHandlerRegistered(bool registered) {
307 // Disable the default cache.
308 [NSURLCache setSharedURLCache:[EmptyNSURLCache emptyNSURLCache]];
309 // Register the chrome http protocol handler to replace the default one.
311 [NSURLProtocol registerClass:[CRNPauseableHTTPProtocolHandler class]];
314 // Set up an empty default cache, with default size.
315 // TODO(droger): If the NSURLCache is to be used, its size should most
316 // likely be changed. On an iPod2 with iOS4, the default size is 512k.
317 [NSURLCache setSharedURLCache:[[[NSURLCache alloc] init] autorelease]];
318 [NSURLProtocol unregisterClass:[CRNPauseableHTTPProtocolHandler class]];
322 void CrNetEnvironment::InitializeOnNetworkThread() {
323 DCHECK(base::MessageLoop::current() == network_io_thread_->message_loop());
325 // Register network clients.
326 net::RequestTracker::AddGlobalNetworkClientFactory(
327 [[[WebPNetworkClientFactory alloc]
328 initWithTaskRunner:file_user_blocking_thread_
329 ->task_runner()] autorelease]);
332 // TODO(huey): Re-enable this once SDCH supports SSL and dictionaries from
333 // previous sessions can be used on the first request after a fresh launch.
334 sdch_manager_.reset(new net::SdchManager());
335 sdch_manager_->set_sdch_fetcher(
336 new SdchDictionaryFetcher(main_context_getter_));
338 // Otherwise, explicitly disable SDCH to avoid a crash.
339 net::SdchManager::EnableSdchSupport(false);
342 NSString* bundlePath =
343 [[NSBundle mainBundle] pathForResource:@"crnet_resources"
345 NSBundle* bundle = [NSBundle bundleWithPath:bundlePath];
346 NSString* acceptableLanguages = NSLocalizedStringWithDefaultValue(
347 @"IDS_ACCEPT_LANGUAGES",
351 @"These values are copied from Chrome's .xtb files, so the same "
352 "values are used in the |Accept-Language| header. Key name matches "
354 DCHECK(acceptableLanguages);
355 std::string acceptable_languages =
356 [acceptableLanguages cStringUsingEncoding:NSUTF8StringEncoding];
357 std::string user_agent =
358 web::BuildUserAgentFromProduct(user_agent_product_name_);
359 // Set the user agent through NSUserDefaults. This sets it for both
360 // UIWebViews and WKWebViews, and javascript calls to navigator.userAgent
361 // return this value.
362 [[NSUserDefaults standardUserDefaults] registerDefaults:@{
363 @"UserAgent" : [NSString stringWithUTF8String:user_agent.c_str()]
365 main_context_->set_http_user_agent_settings(
366 new net::StaticHttpUserAgentSettings(acceptable_languages, user_agent));
368 main_context_->set_ssl_config_service(new net::SSLConfigServiceDefaults);
369 main_context_->set_transport_security_state(
370 new net::TransportSecurityState());
371 http_server_properties_.reset(new net::HttpServerPropertiesImpl());
372 main_context_->set_http_server_properties(
373 http_server_properties_->GetWeakPtr());
374 main_context_->set_host_resolver(
375 net::HostResolver::CreateDefaultResolver(nullptr).release());
376 main_context_->set_cert_verifier(net::CertVerifier::CreateDefault());
377 main_context_->set_http_auth_handler_factory(
378 net::HttpAuthHandlerRegistryFactory::CreateDefault(
379 main_context_->host_resolver()));
380 main_context_->set_proxy_service(
381 net::ProxyService::CreateUsingSystemProxyResolver(
382 proxy_config_service_.get(), 0, nullptr));
385 NSArray* dirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory,
388 base::FilePath cache_path =
389 base::mac::NSStringToFilePath([dirs objectAtIndex:0]);
390 cache_path = cache_path.Append(FILE_PATH_LITERAL("crnet"));
391 net::HttpCache::DefaultBackend* main_backend =
392 new net::HttpCache::DefaultBackend(net::DISK_CACHE,
393 net::CACHE_BACKEND_DEFAULT, cache_path,
394 0, // Default cache size.
395 network_cache_thread_->task_runner());
397 net::HttpNetworkSession::Params params;
398 params.host_resolver = main_context_->host_resolver();
399 params.cert_verifier = main_context_->cert_verifier();
400 params.channel_id_service = main_context_->channel_id_service();
401 params.transport_security_state = main_context_->transport_security_state();
402 params.proxy_service = main_context_->proxy_service();
403 params.ssl_session_cache_shard = "";
404 params.ssl_config_service = main_context_->ssl_config_service();
405 params.http_auth_handler_factory = main_context_->http_auth_handler_factory();
406 params.network_delegate = main_context_->network_delegate();
407 params.http_server_properties = main_context_->http_server_properties();
408 params.net_log = main_context_->net_log();
410 net::NextProtosWithSpdyAndQuic(spdy_enabled(), quic_enabled());
411 params.use_alternate_protocols = true;
412 params.enable_quic = quic_enabled();
413 params.alternative_service_probability_threshold =
414 alternate_protocol_threshold_;
416 if (!params.channel_id_service) {
417 // The main context may not have a ChannelIDService, since it is lazily
418 // constructed. If not, build an ephemeral ChannelIDService with no backing
420 // TODO(ellyjones): support persisting ChannelID.
421 params.channel_id_service = new net::ChannelIDService(
422 new net::DefaultChannelIDStore(NULL),
423 base::WorkerPool::GetTaskRunner(true));
426 net::HttpCache* main_cache = new net::HttpCache(params, main_backend);
427 main_context_->set_http_transaction_factory(main_cache);
430 scoped_refptr<net::CookieStore> cookie_store =
431 net::CookieStoreIOS::CreateCookieStoreFromNSHTTPCookieStorage();
432 main_context_->set_cookie_store(cookie_store.get());
434 net::URLRequestJobFactoryImpl* job_factory =
435 new net::URLRequestJobFactoryImpl;
436 job_factory->SetProtocolHandler("data", new net::DataProtocolHandler);
437 job_factory->SetProtocolHandler(
438 "file", new net::FileProtocolHandler(file_thread_->task_runner()));
439 main_context_->set_job_factory(job_factory);
441 net_log_.reset(new net::NetLog());
442 main_context_->set_net_log(net_log_.get());
445 std::string CrNetEnvironment::user_agent() {
446 const net::HttpUserAgentSettings* user_agent_settings =
447 main_context_->http_user_agent_settings();
448 if (!user_agent_settings) {
452 return user_agent_settings->GetUserAgent();
455 void CrNetEnvironment::ClearCache(ClearCacheCallback callback) {
456 PostToNetworkThread(FROM_HERE,
457 base::Bind(&CrNetEnvironment::ClearCacheOnNetworkThread,
458 base::Unretained(this),
462 void CrNetEnvironment::ClearCacheOnNetworkThread(ClearCacheCallback callback) {
463 DCHECK(base::MessageLoop::current() == network_io_thread_->message_loop());
464 __block disk_cache::Backend* backend = nullptr;
465 net::HttpCache* cache = main_context_->http_transaction_factory()->GetCache();
466 net::CompletionCallback client_callback = base::BindBlock(^(int error) {
467 if (callback != nil) {
471 net::CompletionCallback doom_callback = base::BindBlock(^(int error) {
473 backend->DoomAllEntries(client_callback);
475 int rc = cache->GetBackend(&backend, doom_callback);
476 if (rc != net::ERR_IO_PENDING) {
477 // GetBackend doesn't call the callback if it completes synchronously, so
478 // call it directly here.
479 doom_callback.Run(rc);