OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #include "ios/crnet/crnet_environment.h" |
| 6 |
| 7 #import <Foundation/Foundation.h> |
| 8 |
| 9 #include "base/at_exit.h" |
| 10 #include "base/command_line.h" |
| 11 #include "base/i18n/icu_util.h" |
| 12 #include "base/json/json_writer.h" |
| 13 #include "base/mac/bind_objc_block.h" |
| 14 #include "base/mac/foundation_util.h" |
| 15 #include "base/mac/scoped_block.h" |
| 16 #include "base/path_service.h" |
| 17 #include "base/threading/worker_pool.h" |
| 18 #import "components/webp_transcode/webp_network_client_factory.h" |
| 19 #include "crypto/nss_util.h" |
| 20 #include "ios/net/cookies/cookie_store_ios.h" |
| 21 #include "ios/net/crn_http_protocol_handler.h" |
| 22 #include "ios/net/request_tracker.h" |
| 23 #include "ios/web/public/user_agent.h" |
| 24 #include "net/base/net_errors.h" |
| 25 #include "net/base/network_change_notifier.h" |
| 26 #include "net/base/sdch_manager.h" |
| 27 #include "net/cert/cert_verifier.h" |
| 28 #include "net/cert_net/nss_ocsp.h" |
| 29 #include "net/disk_cache/disk_cache.h" |
| 30 #include "net/http/http_auth_handler_factory.h" |
| 31 #include "net/http/http_cache.h" |
| 32 #include "net/http/http_server_properties_impl.h" |
| 33 #include "net/http/http_stream_factory.h" |
| 34 #include "net/http/http_util.h" |
| 35 #include "net/proxy/proxy_service.h" |
| 36 #include "net/socket/next_proto.h" |
| 37 #include "net/ssl/channel_id_service.h" |
| 38 #include "net/ssl/default_channel_id_store.h" |
| 39 #include "net/ssl/ssl_config_service_defaults.h" |
| 40 #include "net/url_request/data_protocol_handler.h" |
| 41 #include "net/url_request/file_protocol_handler.h" |
| 42 #include "net/url_request/sdch_dictionary_fetcher.h" |
| 43 #include "net/url_request/static_http_user_agent_settings.h" |
| 44 #include "net/url_request/url_request_context_getter.h" |
| 45 #include "net/url_request/url_request_context_storage.h" |
| 46 #include "net/url_request/url_request_job_factory_impl.h" |
| 47 #include "url/url_util.h" |
| 48 |
| 49 namespace { |
| 50 |
| 51 base::AtExitManager* g_at_exit_ = nullptr; |
| 52 |
| 53 // Request context getter for CrNet. |
| 54 class CrNetURLRequestContextGetter : public net::URLRequestContextGetter { |
| 55 public: |
| 56 CrNetURLRequestContextGetter( |
| 57 net::URLRequestContext* context, |
| 58 const scoped_refptr<base::MessageLoopProxy>& loop) |
| 59 : context_(context), loop_(loop) {} |
| 60 |
| 61 net::URLRequestContext* GetURLRequestContext() override { return context_; } |
| 62 |
| 63 scoped_refptr<base::SingleThreadTaskRunner> GetNetworkTaskRunner() |
| 64 const override { |
| 65 return loop_; |
| 66 } |
| 67 private: |
| 68 // Must be called on the IO thread. |
| 69 ~CrNetURLRequestContextGetter() override {} |
| 70 |
| 71 net::URLRequestContext* context_; |
| 72 scoped_refptr<base::MessageLoopProxy> loop_; |
| 73 DISALLOW_COPY_AND_ASSIGN(CrNetURLRequestContextGetter); |
| 74 }; |
| 75 |
| 76 } // namespace |
| 77 |
| 78 // net::HTTPProtocolHandlerDelegate for CrNet. |
| 79 class CrNetHttpProtocolHandlerDelegate |
| 80 : public net::HTTPProtocolHandlerDelegate { |
| 81 public: |
| 82 CrNetHttpProtocolHandlerDelegate(net::URLRequestContextGetter* getter, |
| 83 RequestFilterBlock filter) |
| 84 : getter_(getter), filter_(filter, base::scoped_policy::RETAIN) {} |
| 85 |
| 86 private: |
| 87 // net::HTTPProtocolHandlerDelegate implementation: |
| 88 bool CanHandleRequest(NSURLRequest* request) override { |
| 89 // Don't advertise support for file:// URLs for now. |
| 90 // This broke some time ago but it's not clear how to fix it at the moment. |
| 91 // http://crbug.com/480620 |
| 92 if ([[[request URL] scheme] caseInsensitiveCompare:@"file"] == |
| 93 NSOrderedSame) { |
| 94 return false; |
| 95 } |
| 96 if (filter_) { |
| 97 RequestFilterBlock block = filter_.get(); |
| 98 return block(request); |
| 99 } |
| 100 return true; |
| 101 } |
| 102 |
| 103 bool IsRequestSupported(NSURLRequest* request) override { |
| 104 NSString* scheme = [[request URL] scheme]; |
| 105 if (!scheme) |
| 106 return false; |
| 107 return [scheme caseInsensitiveCompare:@"data"] == NSOrderedSame || |
| 108 [scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || |
| 109 [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame; |
| 110 } |
| 111 |
| 112 net::URLRequestContextGetter* GetDefaultURLRequestContext() override { |
| 113 return getter_.get(); |
| 114 } |
| 115 |
| 116 scoped_refptr<net::URLRequestContextGetter> getter_; |
| 117 base::mac::ScopedBlock<RequestFilterBlock> filter_; |
| 118 }; |
| 119 |
| 120 void CrNetEnvironment::PostToNetworkThread( |
| 121 const tracked_objects::Location& from_here, |
| 122 const base::Closure& task) { |
| 123 network_io_thread_->message_loop()->PostTask(from_here, task); |
| 124 } |
| 125 |
| 126 void CrNetEnvironment::PostToFileUserBlockingThread( |
| 127 const tracked_objects::Location& from_here, |
| 128 const base::Closure& task) { |
| 129 file_user_blocking_thread_->message_loop()->PostTask(from_here, task); |
| 130 } |
| 131 |
| 132 // static |
| 133 void CrNetEnvironment::Initialize() { |
| 134 DCHECK_EQ([NSThread currentThread], [NSThread mainThread]); |
| 135 if (!g_at_exit_) |
| 136 g_at_exit_ = new base::AtExitManager; |
| 137 |
| 138 CHECK(base::i18n::InitializeICU()); |
| 139 url::Initialize(); |
| 140 base::CommandLine::Init(0, nullptr); |
| 141 // This needs to happen on the main thread. NSPR's initialization sets up its |
| 142 // memory allocator; if this is not done before other threads are created, |
| 143 // this initialization can race to cause accidental free/allocation |
| 144 // mismatches. |
| 145 crypto::EnsureNSPRInit(); |
| 146 |
| 147 // Create a message loop on the UI thread. |
| 148 base::MessageLoop* main_message_loop = |
| 149 new base::MessageLoop(base::MessageLoop::TYPE_UI); |
| 150 #pragma unused(main_message_loop) |
| 151 base::MessageLoopForUI::current()->Attach(); |
| 152 } |
| 153 |
| 154 void CrNetEnvironment::StartNetLog(base::FilePath::StringType file_name, |
| 155 bool log_bytes) { |
| 156 DCHECK(file_name.length()); |
| 157 base::AutoLock lock(net_log_lock_); |
| 158 if (net_log_started_) { |
| 159 return; |
| 160 } |
| 161 net_log_started_ = true; |
| 162 PostToFileUserBlockingThread(FROM_HERE, |
| 163 base::Bind(&CrNetEnvironment::StartNetLogInternal, |
| 164 base::Unretained(this), file_name, log_bytes)); |
| 165 } |
| 166 |
| 167 void CrNetEnvironment::StartNetLogInternal( |
| 168 base::FilePath::StringType file_name, bool log_bytes) { |
| 169 DCHECK(base::MessageLoop::current() == |
| 170 file_user_blocking_thread_->message_loop()); |
| 171 DCHECK(file_name.length()); |
| 172 if (!net_log_.get()) { |
| 173 net_log_.reset(new CrNetNetLog()); |
| 174 main_context_.get()->set_net_log(net_log_.get()); |
| 175 } |
| 176 CrNetNetLog::Mode mode = log_bytes ? CrNetNetLog::LOG_ALL_BYTES : |
| 177 CrNetNetLog::LOG_STRIP_PRIVATE_DATA; |
| 178 net_log_->Start(base::FilePath(file_name), mode); |
| 179 } |
| 180 |
| 181 void CrNetEnvironment::StopNetLog() { |
| 182 base::AutoLock lock(net_log_lock_); |
| 183 if (!net_log_started_) { |
| 184 return; |
| 185 } |
| 186 net_log_started_ = false; |
| 187 PostToFileUserBlockingThread(FROM_HERE, |
| 188 base::Bind(&CrNetEnvironment::StopNetLogInternal, |
| 189 base::Unretained(this))); |
| 190 } |
| 191 |
| 192 void CrNetEnvironment::StopNetLogInternal() { |
| 193 DCHECK(base::MessageLoop::current() == |
| 194 file_user_blocking_thread_->message_loop()); |
| 195 if (net_log_.get()) { |
| 196 net_log_->Stop(); |
| 197 } |
| 198 } |
| 199 |
| 200 void CrNetEnvironment::CloseAllSpdySessions() { |
| 201 PostToNetworkThread(FROM_HERE, |
| 202 base::Bind(&CrNetEnvironment::CloseAllSpdySessionsInternal, |
| 203 base::Unretained(this))); |
| 204 } |
| 205 |
| 206 void CrNetEnvironment::SetRequestFilterBlock(RequestFilterBlock block) { |
| 207 http_protocol_handler_delegate_.reset( |
| 208 new CrNetHttpProtocolHandlerDelegate(main_context_getter_.get(), block)); |
| 209 net::HTTPProtocolHandlerDelegate::SetInstance( |
| 210 http_protocol_handler_delegate_.get()); |
| 211 } |
| 212 |
| 213 net::HttpNetworkSession* CrNetEnvironment::GetHttpNetworkSession( |
| 214 net::URLRequestContext* context) { |
| 215 DCHECK(context); |
| 216 if (!context->http_transaction_factory()) |
| 217 return nullptr; |
| 218 |
| 219 return context->http_transaction_factory()->GetSession(); |
| 220 } |
| 221 |
| 222 void CrNetEnvironment::CloseAllSpdySessionsInternal() { |
| 223 DCHECK(base::MessageLoop::current() == |
| 224 network_io_thread_->message_loop()); |
| 225 |
| 226 net::HttpNetworkSession* http_network_session = |
| 227 GetHttpNetworkSession(GetMainContextGetter()->GetURLRequestContext()); |
| 228 |
| 229 if (http_network_session) { |
| 230 net::SpdySessionPool *spdy_session_pool = |
| 231 http_network_session->spdy_session_pool(); |
| 232 if (spdy_session_pool) |
| 233 spdy_session_pool->CloseCurrentSessions(net::ERR_ABORTED); |
| 234 } |
| 235 } |
| 236 |
| 237 CrNetEnvironment::CrNetEnvironment(std::string user_agent_product_name) |
| 238 : main_context_(new net::URLRequestContext), |
| 239 user_agent_product_name_(user_agent_product_name) { |
| 240 |
| 241 } |
| 242 |
| 243 void CrNetEnvironment::Install() { |
| 244 // Threads setup. |
| 245 network_cache_thread_.reset(new base::Thread("Chrome Network Cache Thread")); |
| 246 network_cache_thread_->StartWithOptions( |
| 247 base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| 248 network_io_thread_.reset(new base::Thread("Chrome Network IO Thread")); |
| 249 network_io_thread_->StartWithOptions( |
| 250 base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| 251 file_thread_.reset(new base::Thread("Chrome File Thread")); |
| 252 file_thread_->StartWithOptions( |
| 253 base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| 254 file_user_blocking_thread_.reset( |
| 255 new base::Thread("Chrome File User Blocking Thread")); |
| 256 file_user_blocking_thread_->StartWithOptions( |
| 257 base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); |
| 258 |
| 259 // The network change notifier must be initialized so that registered |
| 260 // delegates will receive callbacks. |
| 261 network_change_notifier_.reset(net::NetworkChangeNotifier::Create()); |
| 262 proxy_config_service_.reset(net::ProxyService::CreateSystemProxyConfigService( |
| 263 network_io_thread_->message_loop_proxy(), nullptr)); |
| 264 |
| 265 PostToNetworkThread(FROM_HERE, |
| 266 base::Bind(&CrNetEnvironment::InitializeOnNetworkThread, |
| 267 base::Unretained(this))); |
| 268 |
| 269 net::SetURLRequestContextForNSSHttpIO(main_context_.get()); |
| 270 main_context_getter_ = new CrNetURLRequestContextGetter( |
| 271 main_context_.get(), network_io_thread_->message_loop_proxy()); |
| 272 SetRequestFilterBlock(nil); |
| 273 net_log_started_ = false; |
| 274 } |
| 275 |
| 276 void CrNetEnvironment::InstallIntoSessionConfiguration( |
| 277 NSURLSessionConfiguration* config) { |
| 278 config.protocolClasses = @[ [CRNHTTPProtocolHandler class] ]; |
| 279 } |
| 280 |
| 281 CrNetEnvironment::~CrNetEnvironment() { |
| 282 net::HTTPProtocolHandlerDelegate::SetInstance(nullptr); |
| 283 net::SetURLRequestContextForNSSHttpIO(nullptr); |
| 284 } |
| 285 |
| 286 net::URLRequestContextGetter* CrNetEnvironment::GetMainContextGetter() { |
| 287 return main_context_getter_.get(); |
| 288 } |
| 289 |
| 290 void CrNetEnvironment::SetHTTPProtocolHandlerRegistered(bool registered) { |
| 291 if (registered) { |
| 292 // Disable the default cache. |
| 293 [NSURLCache setSharedURLCache:nil]; |
| 294 // Register the chrome http protocol handler to replace the default one. |
| 295 BOOL success = [NSURLProtocol registerClass:[CRNHTTPProtocolHandler class]]; |
| 296 DCHECK(success); |
| 297 } else { |
| 298 // Set up an empty default cache, with default size. |
| 299 // TODO(droger): If the NSURLCache is to be used, its size should most |
| 300 // likely be changed. On an iPod2 with iOS4, the default size is 512k. |
| 301 [NSURLCache setSharedURLCache:[[[NSURLCache alloc] init] autorelease]]; |
| 302 [NSURLProtocol unregisterClass:[CRNHTTPProtocolHandler class]]; |
| 303 } |
| 304 } |
| 305 |
| 306 void CrNetEnvironment::InitializeOnNetworkThread() { |
| 307 DCHECK(base::MessageLoop::current() == network_io_thread_->message_loop()); |
| 308 |
| 309 // Register network clients. |
| 310 net::RequestTracker::AddGlobalNetworkClientFactory( |
| 311 [[[WebPNetworkClientFactory alloc] |
| 312 initWithTaskRunner:file_user_blocking_thread_ |
| 313 ->message_loop_proxy()] autorelease]); |
| 314 |
| 315 #if 0 |
| 316 // TODO(huey): Re-enable this once SDCH supports SSL and dictionaries from |
| 317 // previous sessions can be used on the first request after a fresh launch. |
| 318 sdch_manager_.reset(new net::SdchManager()); |
| 319 sdch_manager_->set_sdch_fetcher( |
| 320 new SdchDictionaryFetcher(main_context_getter_)); |
| 321 #else |
| 322 // Otherwise, explicitly disable SDCH to avoid a crash. |
| 323 net::SdchManager::EnableSdchSupport(false); |
| 324 #endif |
| 325 |
| 326 NSString* bundlePath = |
| 327 [[NSBundle mainBundle] pathForResource:@"crnet_resources" |
| 328 ofType:@"bundle"]; |
| 329 NSBundle* bundle = [NSBundle bundleWithPath:bundlePath]; |
| 330 NSString* acceptableLanguages = NSLocalizedStringWithDefaultValue( |
| 331 @"IDS_ACCEPT_LANGUAGES", |
| 332 @"Localizable", |
| 333 bundle, |
| 334 @"en-US,en", |
| 335 @"These values are copied from Chrome's .xtb files, so the same " |
| 336 "values are used in the |Accept-Language| header. Key name matches " |
| 337 "Chrome's."); |
| 338 DCHECK(acceptableLanguages); |
| 339 std::string acceptable_languages = |
| 340 [acceptableLanguages cStringUsingEncoding:NSUTF8StringEncoding]; |
| 341 std::string user_agent = |
| 342 web::BuildUserAgentFromProduct(user_agent_product_name_); |
| 343 // Set the user agent through NSUserDefaults. This sets it for both |
| 344 // UIWebViews and WKWebViews, and javascript calls to navigator.userAgent |
| 345 // return this value. |
| 346 [[NSUserDefaults standardUserDefaults] registerDefaults:@{ |
| 347 @"UserAgent" : [NSString stringWithUTF8String:user_agent.c_str()] |
| 348 }]; |
| 349 main_context_->set_http_user_agent_settings( |
| 350 new net::StaticHttpUserAgentSettings(acceptable_languages, user_agent)); |
| 351 |
| 352 main_context_->set_ssl_config_service(new net::SSLConfigServiceDefaults); |
| 353 main_context_->set_transport_security_state( |
| 354 new net::TransportSecurityState()); |
| 355 http_server_properties_.reset(new net::HttpServerPropertiesImpl()); |
| 356 main_context_->set_http_server_properties( |
| 357 http_server_properties_->GetWeakPtr()); |
| 358 main_context_->set_host_resolver( |
| 359 net::HostResolver::CreateDefaultResolver(nullptr).release()); |
| 360 main_context_->set_cert_verifier(net::CertVerifier::CreateDefault()); |
| 361 main_context_->set_http_auth_handler_factory( |
| 362 net::HttpAuthHandlerRegistryFactory::CreateDefault( |
| 363 main_context_->host_resolver())); |
| 364 main_context_->set_proxy_service( |
| 365 net::ProxyService::CreateUsingSystemProxyResolver( |
| 366 proxy_config_service_.get(), 0, nullptr)); |
| 367 |
| 368 // Cache |
| 369 NSArray* dirs = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, |
| 370 NSUserDomainMask, |
| 371 YES); |
| 372 base::FilePath cache_path = |
| 373 base::mac::NSStringToFilePath([dirs objectAtIndex:0]); |
| 374 cache_path = cache_path.Append(FILE_PATH_LITERAL("crnet")); |
| 375 net::HttpCache::DefaultBackend* main_backend = |
| 376 new net::HttpCache::DefaultBackend( |
| 377 net::DISK_CACHE, |
| 378 net::CACHE_BACKEND_DEFAULT, |
| 379 cache_path, |
| 380 0, // Default cache size. |
| 381 network_cache_thread_->message_loop_proxy()); |
| 382 |
| 383 net::HttpNetworkSession::Params params; |
| 384 params.host_resolver = main_context_->host_resolver(); |
| 385 params.cert_verifier = main_context_->cert_verifier(); |
| 386 params.channel_id_service = main_context_->channel_id_service(); |
| 387 params.transport_security_state = main_context_->transport_security_state(); |
| 388 params.proxy_service = main_context_->proxy_service(); |
| 389 params.ssl_session_cache_shard = ""; |
| 390 params.ssl_config_service = main_context_->ssl_config_service(); |
| 391 params.http_auth_handler_factory = main_context_->http_auth_handler_factory(); |
| 392 params.network_delegate = main_context_->network_delegate(); |
| 393 params.http_server_properties = main_context_->http_server_properties(); |
| 394 params.net_log = main_context_->net_log(); |
| 395 params.next_protos = |
| 396 net::NextProtosWithSpdyAndQuic(spdy_enabled(), quic_enabled()); |
| 397 params.use_alternate_protocols = true; |
| 398 params.enable_quic = quic_enabled(); |
| 399 params.alternative_service_probability_threshold = |
| 400 alternate_protocol_threshold_; |
| 401 |
| 402 if (!params.channel_id_service) { |
| 403 // The main context may not have a ChannelIDService, since it is lazily |
| 404 // constructed. If not, build an ephemeral ChannelIDService with no backing |
| 405 // disk store. |
| 406 // TODO(ellyjones): support persisting ChannelID. |
| 407 params.channel_id_service = new net::ChannelIDService( |
| 408 new net::DefaultChannelIDStore(NULL), |
| 409 base::WorkerPool::GetTaskRunner(true)); |
| 410 } |
| 411 |
| 412 net::HttpCache* main_cache = new net::HttpCache(params, main_backend); |
| 413 main_context_->set_http_transaction_factory(main_cache); |
| 414 |
| 415 // Cookies |
| 416 scoped_refptr<net::CookieStore> cookie_store = |
| 417 net::CookieStoreIOS::CreateCookieStoreFromNSHTTPCookieStorage(); |
| 418 main_context_->set_cookie_store(cookie_store.get()); |
| 419 |
| 420 net::URLRequestJobFactoryImpl* job_factory = |
| 421 new net::URLRequestJobFactoryImpl; |
| 422 job_factory->SetProtocolHandler("data", new net::DataProtocolHandler); |
| 423 job_factory->SetProtocolHandler( |
| 424 "file", new net::FileProtocolHandler(file_thread_->message_loop_proxy())); |
| 425 main_context_->set_job_factory(job_factory); |
| 426 } |
| 427 |
| 428 std::string CrNetEnvironment::user_agent() { |
| 429 const net::HttpUserAgentSettings* user_agent_settings = |
| 430 main_context_->http_user_agent_settings(); |
| 431 if (!user_agent_settings) { |
| 432 return nullptr; |
| 433 } |
| 434 |
| 435 return user_agent_settings->GetUserAgent(); |
| 436 } |
| 437 |
| 438 void CrNetEnvironment::ClearCache(ClearCacheCallback callback) { |
| 439 PostToNetworkThread(FROM_HERE, |
| 440 base::Bind(&CrNetEnvironment::ClearCacheOnNetworkThread, |
| 441 base::Unretained(this), |
| 442 callback)); |
| 443 } |
| 444 |
| 445 void CrNetEnvironment::ClearCacheOnNetworkThread(ClearCacheCallback callback) { |
| 446 DCHECK(base::MessageLoop::current() == network_io_thread_->message_loop()); |
| 447 __block disk_cache::Backend* backend = nullptr; |
| 448 net::HttpCache* cache = main_context_->http_transaction_factory()->GetCache(); |
| 449 net::CompletionCallback client_callback = base::BindBlock(^(int error) { |
| 450 if (callback != nil) { |
| 451 callback(error); |
| 452 } |
| 453 }); |
| 454 net::CompletionCallback doom_callback = base::BindBlock(^(int error) { |
| 455 if (backend) |
| 456 backend->DoomAllEntries(client_callback); |
| 457 }); |
| 458 int rc = cache->GetBackend(&backend, doom_callback); |
| 459 if (rc != net::ERR_IO_PENDING) { |
| 460 // GetBackend doesn't call the callback if it completes synchronously, so |
| 461 // call it directly here. |
| 462 doom_callback.Run(rc); |
| 463 } |
| 464 } |
OLD | NEW |