blob: d413f97de704039f29790589dff8d5ff0b7e70d9 [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.
#import "components/cronet/ios/Cronet.h"
#include <memory>
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/mac/bundle_locations.h"
#include "base/mac/scoped_block.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_vector.h"
#include "base/strings/sys_string_conversions.h"
#include "base/synchronization/lock.h"
#include "components/cronet/ios/cronet_environment.h"
#include "components/cronet/url_request_context_config.h"
#include "ios/net/crn_http_protocol_handler.h"
#include "ios/net/empty_nsurlcache.h"
#include "net/cert/cert_verifier.h"
#include "net/url_request/url_request_context_getter.h"
namespace {
class CronetHttpProtocolHandlerDelegate;
// Currently there is one and only one instance of CronetEnvironment,
// which is leaked at the shutdown. We should consider allowing multiple
// instances if that makes sense in the future.
base::LazyInstance<std::unique_ptr<cronet::CronetEnvironment>>::Leaky
gChromeNet = LAZY_INSTANCE_INITIALIZER;
BOOL gHttp2Enabled = YES;
BOOL gQuicEnabled = NO;
ScopedVector<cronet::URLRequestContextConfig::QuicHint> gQuicHints;
NSString* gUserAgent = nil;
BOOL gUserAgentPartial = NO;
NSString* gSslKeyLogFileName = nil;
RequestFilterBlock gRequestFilterBlock = nil;
std::unique_ptr<CronetHttpProtocolHandlerDelegate> gHttpProtocolHandlerDelegate;
NSURLCache* gPreservedSharedURLCache = nil;
BOOL gEnableTestCertVerifierForTesting = FALSE;
// CertVerifier, which allows any certificates for testing.
class TestCertVerifier : public net::CertVerifier {
int Verify(const RequestParams& params,
net::CRLSet* crl_set,
net::CertVerifyResult* verify_result,
const net::CompletionCallback& callback,
std::unique_ptr<Request>* out_req,
const net::NetLogWithSource& net_log) override {
net::Error result = net::OK;
verify_result->verified_cert = params.certificate();
verify_result->cert_status = net::MapNetErrorToCertStatus(result);
return result;
}
};
// net::HTTPProtocolHandlerDelegate for Cronet.
class CronetHttpProtocolHandlerDelegate
: public net::HTTPProtocolHandlerDelegate {
public:
CronetHttpProtocolHandlerDelegate(net::URLRequestContextGetter* getter,
RequestFilterBlock filter)
: getter_(getter), filter_(filter, base::scoped_policy::RETAIN) {}
void SetRequestFilterBlock(RequestFilterBlock filter) {
base::AutoLock auto_lock(lock_);
filter_.reset(filter);
}
private:
// net::HTTPProtocolHandlerDelegate implementation:
bool CanHandleRequest(NSURLRequest* request) override {
base::AutoLock auto_lock(lock_);
if (filter_) {
RequestFilterBlock block = filter_.get();
return block(request);
}
return true;
}
bool IsRequestSupported(NSURLRequest* request) override {
NSString* scheme = [[request URL] scheme];
if (!scheme)
return false;
return [scheme caseInsensitiveCompare:@"data"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame;
}
net::URLRequestContextGetter* GetDefaultURLRequestContext() override {
return getter_.get();
}
scoped_refptr<net::URLRequestContextGetter> getter_;
base::mac::ScopedBlock<RequestFilterBlock> filter_;
base::Lock lock_;
};
} // namespace
@implementation Cronet
+ (void)configureCronetEnvironmentForTesting:
(cronet::CronetEnvironment*)cronetEnvironment {
if (gEnableTestCertVerifierForTesting) {
std::unique_ptr<TestCertVerifier> test_cert_verifier =
base::MakeUnique<TestCertVerifier>();
cronetEnvironment->set_cert_verifier(std::move(test_cert_verifier));
}
}
+ (NSString*)getAcceptLanguages {
// Use the framework bundle to search for resources.
NSBundle* frameworkBundle = [NSBundle bundleForClass:self];
NSString* bundlePath =
[frameworkBundle pathForResource:@"cronet_resources" ofType:@"bundle"];
NSBundle* bundle = [NSBundle bundleWithPath:bundlePath];
NSString* acceptLanguages = NSLocalizedStringWithDefaultValue(
@"IDS_ACCEPT_LANGUAGES", @"Localizable", bundle, @"en-US,en",
@"These values are copied from Chrome's .xtb files, so the same "
"values are used in the |Accept-Language| header. Key name matches "
"Chrome's.");
if (acceptLanguages == Nil)
acceptLanguages = @"";
return acceptLanguages;
}
+ (void)checkNotStarted {
CHECK(gChromeNet == NULL) << "Cronet is already started.";
}
+ (void)setHttp2Enabled:(BOOL)http2Enabled {
[self checkNotStarted];
gHttp2Enabled = http2Enabled;
}
+ (void)setQuicEnabled:(BOOL)quicEnabled {
[self checkNotStarted];
gQuicEnabled = quicEnabled;
}
+ (void)addQuicHint:(NSString*)host port:(int)port altPort:(int)altPort {
[self checkNotStarted];
gQuicHints.push_back(new cronet::URLRequestContextConfig::QuicHint(
base::SysNSStringToUTF8(host), port, altPort));
}
+ (void)setUserAgent:(NSString*)userAgent partial:(BOOL)partial {
[self checkNotStarted];
gUserAgent = userAgent;
gUserAgentPartial = partial;
}
+ (void)setSslKeyLogFileName:(NSString*)sslKeyLogFileName {
[self checkNotStarted];
gSslKeyLogFileName = sslKeyLogFileName;
}
+ (void)setRequestFilterBlock:(RequestFilterBlock)block {
if (gHttpProtocolHandlerDelegate.get())
gHttpProtocolHandlerDelegate.get()->SetRequestFilterBlock(block);
else
gRequestFilterBlock = block;
}
+ (void)startInternal {
cronet::CronetEnvironment::Initialize();
std::string user_agent = base::SysNSStringToUTF8(gUserAgent);
gChromeNet.Get().reset(
new cronet::CronetEnvironment(user_agent, gUserAgentPartial));
gChromeNet.Get()->set_accept_language(
base::SysNSStringToUTF8([self getAcceptLanguages]));
gChromeNet.Get()->set_http2_enabled(gHttp2Enabled);
gChromeNet.Get()->set_quic_enabled(gQuicEnabled);
gChromeNet.Get()->set_ssl_key_log_file_name(
base::SysNSStringToUTF8(gSslKeyLogFileName));
for (const auto* quicHint : gQuicHints) {
gChromeNet.Get()->AddQuicHint(quicHint->host, quicHint->port,
quicHint->alternate_port);
}
[self configureCronetEnvironmentForTesting:gChromeNet.Get().get()];
gChromeNet.Get()->Start();
gHttpProtocolHandlerDelegate.reset(new CronetHttpProtocolHandlerDelegate(
gChromeNet.Get()->GetURLRequestContextGetter(), gRequestFilterBlock));
net::HTTPProtocolHandlerDelegate::SetInstance(
gHttpProtocolHandlerDelegate.get());
gRequestFilterBlock = nil;
}
+ (void)start {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (![NSThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self startInternal];
});
} else {
[self startInternal];
}
});
}
+ (void)registerHttpProtocolHandler {
if (gPreservedSharedURLCache == nil) {
gPreservedSharedURLCache = [NSURLCache sharedURLCache];
}
// Disable the default cache.
[NSURLCache setSharedURLCache:[EmptyNSURLCache emptyNSURLCache]];
// Register the chrome http protocol handler to replace the default one.
BOOL success =
[NSURLProtocol registerClass:[CRNPauseableHTTPProtocolHandler class]];
DCHECK(success);
}
+ (void)unregisterHttpProtocolHandler {
// Set up SharedURLCache preserved in registerHttpProtocolHandler.
if (gPreservedSharedURLCache != nil) {
[NSURLCache setSharedURLCache:gPreservedSharedURLCache];
gPreservedSharedURLCache = nil;
}
[NSURLProtocol unregisterClass:[CRNPauseableHTTPProtocolHandler class]];
}
+ (void)installIntoSessionConfiguration:(NSURLSessionConfiguration*)config {
config.protocolClasses = @[ [CRNPauseableHTTPProtocolHandler class] ];
}
+ (void)startNetLogToFile:(NSString*)fileName logBytes:(BOOL)logBytes {
if (gChromeNet.Get().get() && [fileName length]) {
gChromeNet.Get()->StartNetLog([fileName UTF8String], logBytes);
}
}
+ (void)stopNetLog {
if (gChromeNet.Get().get()) {
gChromeNet.Get()->StopNetLog();
}
}
+ (NSString*)getUserAgent {
if (!gChromeNet.Get().get()) {
return nil;
}
return [NSString stringWithCString:gChromeNet.Get()->user_agent().c_str()
encoding:[NSString defaultCStringEncoding]];
}
+ (stream_engine*)getGlobalEngine {
DCHECK(gChromeNet.Get().get());
if (gChromeNet.Get().get()) {
static stream_engine engine;
engine.obj = gChromeNet.Get()->GetURLRequestContextGetter();
return &engine;
}
return nil;
}
+ (NSData*)getGlobalMetricsDeltas {
if (!gChromeNet.Get().get()) {
return nil;
}
std::vector<uint8_t> deltas(gChromeNet.Get()->GetHistogramDeltas());
return [NSData dataWithBytes:deltas.data() length:deltas.size()];
}
+ (void)enableTestCertVerifierForTesting {
gEnableTestCertVerifierForTesting = YES;
}
+ (void)setHostResolverRulesForTesting:(NSString*)hostResolverRulesForTesting {
DCHECK(gChromeNet.Get().get());
gChromeNet.Get()->SetHostResolverRules(
base::SysNSStringToUTF8(hostResolverRulesForTesting));
}
// This is a non-public dummy method that prevents the linker from stripping out
// the otherwise non-referenced methods from 'bidirectional_stream.cc'.
+ (void)preventStrippingCronetBidirectionalStream {
bidirectional_stream_create(NULL, 0, 0);
}
@end