blob: 845d19c2a7e3405b70b16ed4c1de57e5be010c1a [file] [log] [blame]
// Copyright 2012 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 "ios/net/crn_http_protocol_handler.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/mac/bind_objc_block.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#import "ios/net/clients/crn_network_client_protocol.h"
#import "ios/net/crn_http_protocol_handler_proxy_with_client_thread.h"
#import "ios/net/http_protocol_logging.h"
#include "ios/net/nsurlrequest_util.h"
#import "ios/net/protocol_handler_util.h"
#include "ios/net/request_tracker.h"
#include "net/base/auth.h"
#include "net/base/elements_upload_data_stream.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#import "net/base/mac/url_conversions.h"
#include "net/base/net_errors.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/http/http_request_headers.h"
#include "net/url_request/redirect_info.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
namespace net {
class HttpProtocolHandlerCore;
}
namespace {
// Size of the buffer used to read the net::URLRequest.
const int kIOBufferSize = 4096;
// Global instance of the HTTPProtocolHandlerDelegate.
net::HTTPProtocolHandlerDelegate* g_protocol_handler_delegate = nullptr;
// Empty callback.
void DoNothing(bool flag) {}
} // namespace
// Bridge class to forward NSStream events to the HttpProtocolHandlerCore.
// Lives on the IO thread.
@interface CRWHTTPStreamDelegate : NSObject<NSStreamDelegate> {
@private
// The object is owned by |_core| and has a weak reference to it.
net::HttpProtocolHandlerCore* _core; // weak
}
- (instancetype)initWithHttpProtocolHandlerCore:
(net::HttpProtocolHandlerCore*)core;
// NSStreamDelegate method.
- (void)stream:(NSStream*)theStream handleEvent:(NSStreamEvent)streamEvent;
@end
#pragma mark -
#pragma mark HttpProtocolHandlerCore
namespace net {
// static
void HTTPProtocolHandlerDelegate::SetInstance(
HTTPProtocolHandlerDelegate* delegate) {
g_protocol_handler_delegate = delegate;
}
// The HttpProtocolHandlerCore class is the bridge between the URLRequest
// and the NSURLProtocolClient.
// Threading and ownership details:
// - The HttpProtocolHandlerCore is owned by the HttpProtocolHandler
// - The HttpProtocolHandler is owned by the system and can be deleted anytime
// - All the methods of HttpProtocolHandlerCore must be called on the IO thread,
// except its constructor that can be called from any thread.
// Implementation notes from Apple's "Read Me About CustomHttpProtocolHandler":
//
// An NSURLProtocol subclass is expected to call the various methods of the
// NSURLProtocolClient from the loading thread, including all of the following:
// -URLProtocol:wasRedirectedToRequest:redirectResponse:
// -URLProtocol:didReceiveResponse:cacheStoragePolicy:
// -URLProtocol:didLoadData:
// -URLProtocol:didFinishLoading:
// -URLProtocol:didFailWithError:
// -URLProtocol:didReceiveAuthenticationChallenge:
// -URLProtocol:didCancelAuthenticationChallenge:
//
// The NSURLProtocol subclass must call the client callbacks in the expected
// order. This breaks down into three phases:
// o pre-response -- In the initial phase the NSURLProtocol can make any number
// of -URLProtocol:wasRedirectedToRequest:redirectResponse: and
// -URLProtocol:didReceiveAuthenticationChallenge: callbacks.
// o response -- It must then call
// -URLProtocol:didReceiveResponse:cacheStoragePolicy: to indicate the
// arrival of a definitive response.
// o post-response -- After receive a response it may then make any number of
// -URLProtocol:didLoadData: callbacks, followed by a
// -URLProtocolDidFinishLoading: callback.
//
// The -URLProtocol:didFailWithError: callback can be made at any time
// (although keep in mind the following point).
//
// The NSProtocol subclass must only send one authentication challenge to the
// client at a time. After calling
// -URLProtocol:didReceiveAuthenticationChallenge:, it must wait for the client
// to resolve the challenge before calling any callbacks other than
// -URLProtocol:didCancelAuthenticationChallenge:. This means that, if the
// connection fails while there is an outstanding authentication challenge, the
// NSURLProtocol subclass must call
// -URLProtocol:didCancelAuthenticationChallenge: before calling
// -URLProtocol:didFailWithError:.
class HttpProtocolHandlerCore
: public base::RefCountedThreadSafe<HttpProtocolHandlerCore,
HttpProtocolHandlerCore>,
public URLRequest::Delegate {
public:
HttpProtocolHandlerCore(NSURLRequest* request);
// Starts the network request, and forwards the data downloaded from the
// network to |base_client|.
void Start(id<CRNNetworkClientProtocol> base_client);
// Cancels the request.
void Cancel();
// Called by NSStreamDelegate. Used for POST requests having a HTTPBodyStream.
void HandleStreamEvent(NSStream* stream, NSStreamEvent event);
// URLRequest::Delegate methods:
void OnReceivedRedirect(URLRequest* request,
const RedirectInfo& new_url,
bool* defer_redirect) override;
void OnAuthRequired(URLRequest* request,
AuthChallengeInfo* auth_info) override;
void OnCertificateRequested(URLRequest* request,
SSLCertRequestInfo* cert_request_info) override;
void OnSSLCertificateError(URLRequest* request,
const SSLInfo& ssl_info,
bool fatal) override;
void OnResponseStarted(URLRequest* request) override;
void OnReadCompleted(URLRequest* request, int bytes_read) override;
private:
friend class base::RefCountedThreadSafe<HttpProtocolHandlerCore,
HttpProtocolHandlerCore>;
friend class base::DeleteHelper<HttpProtocolHandlerCore>;
~HttpProtocolHandlerCore() override;
// RefCountedThreadSafe traits implementation:
static void Destruct(const HttpProtocolHandlerCore* x);
void SetLoadFlags();
void StopNetRequest();
// Stop listening the delegate on the IO run loop.
void StopListeningStream(NSStream* stream);
NSInteger IOSErrorCode(int os_error);
void StopRequestWithError(NSInteger ns_error_code, int net_error_code);
// Pass an authentication result provided by a client down to the network
// request. |auth_ok| is true if the authentication was successful, false
// otherwise. |username| and |password| should be populated with the correct
// credentials if |auth_ok| is true.
void CompleteAuthentication(bool auth_ok,
const base::string16& username,
const base::string16& password);
void StripPostSpecificHeaders(NSMutableURLRequest* request);
void CancelAfterSSLError();
void ContinueAfterSSLError();
void SSLErrorCallback(bool carryOn);
void HostStateCallback(bool carryOn);
void StartReading();
// Pushes |client| at the end of the |clients_| array and sets it as the top
// level client.
void PushClient(id<CRNNetworkClientProtocol> client);
// Pushes all of the clients in |clients|, calling PushClient() on each one.
void PushClients(NSArray* clients);
base::ThreadChecker thread_checker_;
// Contains CRNNetworkClientProtocol objects. The first client is the original
// NSURLProtocol client, and the following clients are ordered such as the
// ith client is responsible for managing the (i-1)th client.
base::scoped_nsobject<NSMutableArray> clients_;
// Weak. This is the last client in |clients_|.
id<CRNNetworkClientProtocol> top_level_client_;
scoped_refptr<IOBuffer> buffer_;
base::scoped_nsobject<NSMutableURLRequest> request_;
// Stream delegate to read the HTTPBodyStream.
base::scoped_nsobject<CRWHTTPStreamDelegate> stream_delegate_;
// Vector of readers used to accumulate a POST data stream.
std::vector<std::unique_ptr<UploadElementReader>> post_data_readers_;
// This cannot be a scoped pointer because it must be deleted on the IO
// thread.
URLRequest* net_request_;
base::WeakPtr<RequestTracker> tracker_;
DISALLOW_COPY_AND_ASSIGN(HttpProtocolHandlerCore);
};
HttpProtocolHandlerCore::HttpProtocolHandlerCore(NSURLRequest* request)
: clients_([[NSMutableArray alloc] init]),
top_level_client_(nil),
buffer_(new IOBuffer(kIOBufferSize)),
net_request_(nullptr) {
// The request will be accessed from another thread. It is safer to make a
// copy to avoid conflicts.
// The copy is mutable, because that request will be given to the client in
// case of a redirect, but with a different URL. The URL must be created
// from the absoluteString of the original URL, because mutableCopy only
// shallowly copies the request, and just retains the non-threadsafe NSURL.
thread_checker_.DetachFromThread();
request_.reset([request mutableCopy]);
[request_ setURL:[NSURL URLWithString:[[request URL] absoluteString]]];
}
void HttpProtocolHandlerCore::HandleStreamEvent(NSStream* stream,
NSStreamEvent event) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(stream_delegate_);
switch (event) {
case NSStreamEventErrorOccurred:
DLOG(ERROR)
<< "Failed to read POST data: "
<< base::SysNSStringToUTF8([[stream streamError] description]);
StopListeningStream(stream);
StopRequestWithError(NSURLErrorUnknown, ERR_UNEXPECTED);
break;
case NSStreamEventEndEncountered:
StopListeningStream(stream);
if (!post_data_readers_.empty()) {
// NOTE: This call will result in |post_data_readers_| being cleared,
// which is the desired behavior.
net_request_->set_upload(base::WrapUnique(
new ElementsUploadDataStream(std::move(post_data_readers_), 0)));
DCHECK(post_data_readers_.empty());
}
net_request_->Start();
if (tracker_)
tracker_->StartRequest(net_request_);
break;
case NSStreamEventHasBytesAvailable: {
NSUInteger length;
DCHECK([stream isKindOfClass:[NSInputStream class]]);
length = [(NSInputStream*)stream read:(unsigned char*)buffer_->data()
maxLength:kIOBufferSize];
if (length) {
std::vector<char> owned_data(buffer_->data(), buffer_->data() + length);
post_data_readers_.push_back(
base::WrapUnique(new UploadOwnedBytesElementReader(&owned_data)));
}
break;
}
case NSStreamEventNone:
case NSStreamEventOpenCompleted:
case NSStreamEventHasSpaceAvailable:
break;
default:
NOTREACHED() << "Unexpected stream event: " << event;
break;
}
}
#pragma mark URLRequest::Delegate methods
void HttpProtocolHandlerCore::OnReceivedRedirect(
URLRequest* request,
const RedirectInfo& redirect_info,
bool* /* defer_redirect */) {
DCHECK(thread_checker_.CalledOnValidThread());
// Cancel the request and notify UIWebView.
// If we did nothing, the network stack would follow the redirect
// automatically, however we do not want this because we need the UIWebView to
// be notified. The UIWebView will then issue a new request following the
// redirect.
DCHECK(request_);
GURL new_url = redirect_info.new_url;
if (!new_url.is_valid()) {
StopRequestWithError(NSURLErrorBadURL, ERR_INVALID_URL);
return;
}
DCHECK(new_url.is_valid());
NSURL* new_nsurl = NSURLWithGURL(new_url);
// Stash the original URL in case we need to report it in an error.
[request_ setURL:new_nsurl];
if (stream_delegate_.get())
StopListeningStream([request_ HTTPBodyStream]);
// TODO(droger): See if we can share some code with URLRequest::Redirect() in
// net/net_url_request/url_request.cc.
// For 303 redirects, all request methods except HEAD are converted to GET,
// as per the latest httpbis draft. The draft also allows POST requests to
// be converted to GETs when following 301/302 redirects, for historical
// reasons. Most major browsers do this and so shall we.
// See:
// https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-17#section-7.3
const int http_status_code = request->GetResponseCode();
NSString* method = [request_ HTTPMethod];
const bool was_post = [method isEqualToString:@"POST"];
if ((http_status_code == 303 && ![method isEqualToString:@"HEAD"]) ||
((http_status_code == 301 || http_status_code == 302) && was_post)) {
[request_ setHTTPMethod:@"GET"];
[request_ setHTTPBody:nil];
[request_ setHTTPBodyStream:nil];
if (was_post) {
// If being switched from POST to GET, must remove headers that were
// specific to the POST and don't have meaning in GET. For example
// the inclusion of a multipart Content-Type header in GET can cause
// problems with some servers:
// http://code.google.com/p/chromium/issues/detail?id=843
StripPostSpecificHeaders(request_.get());
}
}
NSURLResponse* response = GetNSURLResponseForRequest(request);
#if !defined(NDEBUG)
DVLOG(2) << "Redirect, to client:";
LogNSURLResponse(response);
DVLOG(2) << "Redirect, to client:";
LogNSURLRequest(request_);
#endif // !defined(NDEBUG)
if (tracker_) {
tracker_->StopRedirectedRequest(request);
// Add clients from tracker that depend on redirect data.
PushClients(tracker_->ClientsHandlingRedirect(*request, new_url, response));
}
[top_level_client_ wasRedirectedToRequest:request_
nativeRequest:request
redirectResponse:response];
// Don't use |request_| or |response| anymore, as the client may be using them
// on another thread and they are not re-entrant. As |request_| is mutable, it
// is also important that it is not modified.
request_.reset(nil);
request->Cancel();
DCHECK_EQ(net_request_, request);
StopNetRequest();
}
void HttpProtocolHandlerCore::OnAuthRequired(URLRequest* request,
AuthChallengeInfo* auth_info) {
DCHECK(thread_checker_.CalledOnValidThread());
// A request with no tab ID should not hit HTTP authentication.
if (tracker_) {
// UIWebView does not handle authentication, so there is no point in calling
// the protocol method didReceiveAuthenticationChallenge.
// Instead, clients may handle proxy auth or display a UI to handle the
// challenge.
// Pass a weak reference, to avoid retain cycles.
network_client::AuthCallback callback =
base::Bind(&HttpProtocolHandlerCore::CompleteAuthentication,
base::Unretained(this));
[top_level_client_ didRecieveAuthChallenge:auth_info
nativeRequest:*request
callback:callback];
} else if (net_request_ != nullptr) {
net_request_->CancelAuth();
}
}
void HttpProtocolHandlerCore::OnCertificateRequested(
URLRequest* request,
SSLCertRequestInfo* cert_request_info) {
DCHECK(thread_checker_.CalledOnValidThread());
// TODO(ios): The network stack does not support SSL client authentication
// on iOS yet. The request has to be canceled for now.
request->Cancel();
StopRequestWithError(NSURLErrorClientCertificateRequired,
ERR_SSL_PROTOCOL_ERROR);
}
void HttpProtocolHandlerCore::ContinueAfterSSLError(void) {
DCHECK(thread_checker_.CalledOnValidThread());
if (net_request_ != nullptr) {
// Continue the request and load the data.
net_request_->ContinueDespiteLastError();
}
}
void HttpProtocolHandlerCore::CancelAfterSSLError(void) {
DCHECK(thread_checker_.CalledOnValidThread());
if (net_request_ != nullptr) {
// Cancel the request.
net_request_->Cancel();
// The request is signalled simply cancelled to the consumer, the
// presentation of the SSL error will be done via the tracker.
StopRequestWithError(NSURLErrorCancelled, ERR_BLOCKED_BY_CLIENT);
}
}
void HttpProtocolHandlerCore::SSLErrorCallback(bool carryOn) {
DCHECK(thread_checker_.CalledOnValidThread());
if (carryOn)
ContinueAfterSSLError();
else
CancelAfterSSLError();
}
void HttpProtocolHandlerCore::HostStateCallback(bool carryOn) {
DCHECK(thread_checker_.CalledOnValidThread());
if (carryOn)
StartReading();
else
CancelAfterSSLError();
}
void HttpProtocolHandlerCore::OnSSLCertificateError(URLRequest* request,
const SSLInfo& ssl_info,
bool fatal) {
DCHECK(thread_checker_.CalledOnValidThread());
if (fatal) {
if (tracker_) {
tracker_->OnSSLCertificateError(request, ssl_info, false,
base::Bind(&DoNothing));
}
CancelAfterSSLError(); // High security host do not tolerate any issue.
} else if (!tracker_) {
// No tracker, this is a request outside the context of a tab. There is no
// way to present anything to the user so only allow trivial errors.
// See ssl_cert_error_handler upstream.
if (IsCertStatusMinorError(ssl_info.cert_status))
ContinueAfterSSLError();
else
CancelAfterSSLError();
} else {
// The tracker will decide, eventually asking the user, and will invoke the
// callback.
RequestTracker::SSLCallback callback =
base::Bind(&HttpProtocolHandlerCore::SSLErrorCallback, this);
DCHECK(tracker_);
tracker_->OnSSLCertificateError(request, ssl_info, !fatal, callback);
}
}
void HttpProtocolHandlerCore::OnResponseStarted(URLRequest* request) {
DCHECK(thread_checker_.CalledOnValidThread());
if (net_request_ == nullptr)
return;
const URLRequestStatus& status = request->status();
if (!status.is_success()) {
int error = status.error();
StopRequestWithError(IOSErrorCode(error), error);
return;
}
if (tracker_ && IsCertStatusError(request->ssl_info().cert_status) &&
!request->context()->GetNetworkSessionParams()->
ignore_certificate_errors) {
// The certificate policy cache is captured here because SSL errors do not
// always trigger OnSSLCertificateError (this is the case when a page comes
// from the HTTP cache).
RequestTracker::SSLCallback callback =
base::Bind(&HttpProtocolHandlerCore::HostStateCallback, this);
tracker_->CaptureCertificatePolicyCache(request, callback);
return;
}
StartReading();
}
void HttpProtocolHandlerCore::StartReading() {
DCHECK(thread_checker_.CalledOnValidThread());
if (net_request_ == nullptr)
return;
NSURLResponse* response = GetNSURLResponseForRequest(net_request_);
#if !defined(NDEBUG)
DVLOG(2) << "To client:";
LogNSURLResponse(response);
#endif // !defined(NDEBUG)
if (tracker_) {
tracker_->CaptureHeaders(net_request_);
long long expectedContentLength = [response expectedContentLength];
if (expectedContentLength > 0)
tracker_->CaptureExpectedLength(net_request_, expectedContentLength);
// Add clients from tracker.
PushClients(
tracker_->ClientsHandlingRequestAndResponse(*net_request_, response));
}
// Don't call any function on the response from now on, as the client may be
// using it and the object is not re-entrant.
[top_level_client_ didReceiveResponse:response];
int bytes_read = 0;
if (net_request_->Read(buffer_.get(), kIOBufferSize, &bytes_read)) {
OnReadCompleted(net_request_, bytes_read);
} else if (!net_request_->status().is_success()) {
int error = net_request_->status().error();
StopRequestWithError(IOSErrorCode(error), error);
}
}
void HttpProtocolHandlerCore::OnReadCompleted(URLRequest* request,
int bytes_read) {
DCHECK(thread_checker_.CalledOnValidThread());
if (net_request_ == nullptr)
return;
base::scoped_nsobject<NSMutableData> data([[NSMutableData alloc] init]);
// Read all we can from the socket and put it into data.
// TODO(droger): It may be possible to avoid some of the copies (using
// WrappedIOBuffer for example).
NSUInteger data_length;
bool loop = (bytes_read > 0);
bool io_pending = false;
uint64_t total_byte_read = loop ? bytes_read : 0;
while (loop) {
data_length = [data length]; // Assumes that getting the length is fast.
[data increaseLengthBy:bytes_read];
memcpy(reinterpret_cast<char*>([data mutableBytes]) + data_length,
buffer_->data(), bytes_read);
io_pending = !request->Read(buffer_.get(), kIOBufferSize, &bytes_read);
loop = !io_pending && (bytes_read > 0);
total_byte_read += bytes_read;
}
if (tracker_)
tracker_->CaptureReceivedBytes(request, total_byte_read);
// Notify the client.
const URLRequestStatus& status = request->status();
if (status.is_success()) {
if ([data length] > 0) {
// If the data is not encoded in UTF8, the NSString is nil.
DVLOG(3) << "To client:" << std::endl
<< base::SysNSStringToUTF8([[[NSString alloc]
initWithData:data
encoding:NSUTF8StringEncoding] autorelease]);
[top_level_client_ didLoadData:data];
}
if (bytes_read == 0 && !io_pending) {
DCHECK_EQ(net_request_, request);
// There is nothing more to read.
StopNetRequest();
[top_level_client_ didFinishLoading];
}
} else {
// Request failed (not canceled).
int error = status.error();
StopRequestWithError(IOSErrorCode(error), error);
}
}
HttpProtocolHandlerCore::~HttpProtocolHandlerCore() {
DCHECK(thread_checker_.CalledOnValidThread());
[top_level_client_ cancelAuthRequest];
DCHECK(!net_request_);
DCHECK(!stream_delegate_);
}
// static
void HttpProtocolHandlerCore::Destruct(const HttpProtocolHandlerCore* x) {
scoped_refptr<base::SingleThreadTaskRunner> task_runner =
g_protocol_handler_delegate->GetDefaultURLRequestContext()
->GetNetworkTaskRunner();
if (task_runner->BelongsToCurrentThread())
delete x;
else
task_runner->DeleteSoon(FROM_HERE, x);
}
void HttpProtocolHandlerCore::SetLoadFlags() {
DCHECK(thread_checker_.CalledOnValidThread());
int load_flags = LOAD_NORMAL;
if (![request_ HTTPShouldHandleCookies])
load_flags |= LOAD_DO_NOT_SEND_COOKIES | LOAD_DO_NOT_SAVE_COOKIES;
// Cache flags.
if (tracker_) {
RequestTracker::CacheMode cache_mode = tracker_->GetCacheMode();
switch (cache_mode) {
case RequestTracker::CACHE_RELOAD:
load_flags |= LOAD_VALIDATE_CACHE;
break;
case RequestTracker::CACHE_HISTORY:
load_flags |= LOAD_PREFERRING_CACHE;
break;
case RequestTracker::CACHE_BYPASS:
load_flags |= LOAD_DISABLE_CACHE | LOAD_BYPASS_CACHE;
break;
case RequestTracker::CACHE_ONLY:
load_flags |= LOAD_ONLY_FROM_CACHE;
break;
case RequestTracker::CACHE_NORMAL:
// Do nothing, normal load.
break;
}
} else {
switch ([request_ cachePolicy]) {
case NSURLRequestReloadIgnoringLocalAndRemoteCacheData:
load_flags |= LOAD_BYPASS_CACHE;
case NSURLRequestReloadIgnoringLocalCacheData:
load_flags |= LOAD_DISABLE_CACHE;
break;
case NSURLRequestReturnCacheDataElseLoad:
load_flags |= LOAD_PREFERRING_CACHE;
break;
case NSURLRequestReturnCacheDataDontLoad:
load_flags |= LOAD_ONLY_FROM_CACHE;
break;
case NSURLRequestReloadRevalidatingCacheData:
load_flags |= LOAD_VALIDATE_CACHE;
break;
case NSURLRequestUseProtocolCachePolicy:
// Do nothing, normal load.
break;
}
}
net_request_->SetLoadFlags(load_flags);
}
void HttpProtocolHandlerCore::Start(id<CRNNetworkClientProtocol> base_client) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(!top_level_client_);
DCHECK_EQ(0u, [clients_ count]);
DCHECK(base_client);
top_level_client_ = base_client;
[clients_ addObject:base_client];
GURL url = GURLWithNSURL([request_ URL]);
bool valid_tracker = RequestTracker::GetRequestTracker(request_, &tracker_);
if (!valid_tracker) {
// The request is associated with a tracker that does not exist, cancel it.
// NSURLErrorCancelled avoids triggering any error page.
[top_level_client_ didFailWithNSErrorCode:NSURLErrorCancelled
netErrorCode:ERR_ABORTED];
return;
}
if (tracker_) {
// Set up any clients that can operate regardless of the request
PushClients(tracker_->ClientsHandlingAnyRequest());
} else {
// There was no request_group_id, so the request was from something like a
// data: or file: URL.
// Attach any global clients to the request.
PushClients(RequestTracker::GlobalClientsHandlingAnyRequest());
}
// Now that all of the network clients are set up, if there was an error with
// the URL, it can be raised and all of the clients will have a chance to
// handle it.
if (!url.is_valid()) {
DLOG(ERROR) << "Trying to load an invalid URL: "
<< base::SysNSStringToUTF8([[request_ URL] absoluteString]);
[top_level_client_ didFailWithNSErrorCode:NSURLErrorBadURL
netErrorCode:ERR_INVALID_URL];
return;
}
const URLRequestContext* context =
tracker_ ? tracker_->GetRequestContext()
: g_protocol_handler_delegate->GetDefaultURLRequestContext()
->GetURLRequestContext();
DCHECK(context);
net_request_ =
context->CreateRequest(url, DEFAULT_PRIORITY, this).release();
net_request_->set_method(base::SysNSStringToUTF8([request_ HTTPMethod]));
net_request_->set_first_party_for_cookies(
GURLWithNSURL([request_ mainDocumentURL]));
#if !defined(NDEBUG)
DVLOG(2) << "From client:";
LogNSURLRequest(request_);
#endif // !defined(NDEBUG)
CopyHttpHeaders(request_, net_request_);
// Add network clients.
if (tracker_)
PushClients(tracker_->ClientsHandlingRequest(*net_request_));
[top_level_client_ didCreateNativeRequest:net_request_];
SetLoadFlags();
if ([request_ HTTPBodyStream]) {
NSInputStream* input_stream = [request_ HTTPBodyStream];
stream_delegate_.reset(
[[CRWHTTPStreamDelegate alloc] initWithHttpProtocolHandlerCore:this]);
[input_stream setDelegate:stream_delegate_];
[input_stream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[input_stream open];
// The request will be started when the stream is fully read.
return;
}
NSData* body = [request_ HTTPBody];
const NSUInteger body_length = [body length];
if (body_length > 0) {
const char* source_bytes = reinterpret_cast<const char*>([body bytes]);
std::vector<char> owned_data(source_bytes, source_bytes + body_length);
std::unique_ptr<UploadElementReader> reader(
new UploadOwnedBytesElementReader(&owned_data));
net_request_->set_upload(
ElementsUploadDataStream::CreateWithReader(std::move(reader), 0));
}
net_request_->Start();
if (tracker_)
tracker_->StartRequest(net_request_);
}
void HttpProtocolHandlerCore::Cancel() {
DCHECK(thread_checker_.CalledOnValidThread());
if (net_request_ == nullptr)
return;
DVLOG(2) << "Client canceling request: " << net_request_->url().spec();
net_request_->Cancel();
StopNetRequest();
}
void HttpProtocolHandlerCore::StopNetRequest() {
DCHECK(thread_checker_.CalledOnValidThread());
if (tracker_)
tracker_->StopRequest(net_request_);
delete net_request_;
net_request_ = nullptr;
if (stream_delegate_.get())
StopListeningStream([request_ HTTPBodyStream]);
}
void HttpProtocolHandlerCore::StopListeningStream(NSStream* stream) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(stream);
DCHECK(stream_delegate_);
DCHECK([stream delegate] == stream_delegate_.get());
[stream setDelegate:nil];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
stream_delegate_.reset(nil);
// Close the stream if needed.
switch ([stream streamStatus]) {
case NSStreamStatusOpening:
case NSStreamStatusOpen:
case NSStreamStatusReading:
case NSStreamStatusWriting:
case NSStreamStatusAtEnd:
[stream close];
break;
case NSStreamStatusNotOpen:
case NSStreamStatusClosed:
case NSStreamStatusError:
break;
default:
NOTREACHED() << "Unexpected stream status: " << [stream streamStatus];
break;
}
}
NSInteger HttpProtocolHandlerCore::IOSErrorCode(int os_error) {
DCHECK(thread_checker_.CalledOnValidThread());
switch (os_error) {
case ERR_SSL_PROTOCOL_ERROR:
return NSURLErrorClientCertificateRequired;
case ERR_CONNECTION_RESET:
case ERR_NETWORK_CHANGED:
return NSURLErrorNetworkConnectionLost;
case ERR_UNEXPECTED:
return NSURLErrorUnknown;
default:
return NSURLErrorCannotConnectToHost;
}
}
void HttpProtocolHandlerCore::StopRequestWithError(NSInteger ns_error_code,
int net_error_code) {
DCHECK(net_request_ != nullptr);
DCHECK(thread_checker_.CalledOnValidThread());
// Don't show an error message on ERR_ABORTED because this is error is often
// fired when switching profiles (see RequestTracker::CancelRequests()).
DLOG_IF(ERROR, net_error_code != ERR_ABORTED)
<< "HttpProtocolHandlerCore - Network error: "
<< ErrorToString(net_error_code) << " (" << net_error_code << ")";
[top_level_client_ didFailWithNSErrorCode:ns_error_code
netErrorCode:net_error_code];
StopNetRequest();
}
void HttpProtocolHandlerCore::CompleteAuthentication(
bool auth_ok,
const base::string16& username,
const base::string16& password) {
DCHECK(thread_checker_.CalledOnValidThread());
if (net_request_ == nullptr)
return;
if (auth_ok) {
net_request_->SetAuth(AuthCredentials(username, password));
} else {
net_request_->CancelAuth();
}
}
void HttpProtocolHandlerCore::StripPostSpecificHeaders(
NSMutableURLRequest* request) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(request);
[request setValue:nil forHTTPHeaderField:base::SysUTF8ToNSString(
HttpRequestHeaders::kContentLength)];
[request setValue:nil forHTTPHeaderField:base::SysUTF8ToNSString(
HttpRequestHeaders::kContentType)];
[request setValue:nil forHTTPHeaderField:base::SysUTF8ToNSString(
HttpRequestHeaders::kOrigin)];
}
void HttpProtocolHandlerCore::PushClient(id<CRNNetworkClientProtocol> client) {
DCHECK(thread_checker_.CalledOnValidThread());
[client setUnderlyingClient:top_level_client_];
[clients_ addObject:client];
top_level_client_ = client;
}
void HttpProtocolHandlerCore::PushClients(NSArray* clients) {
DCHECK(thread_checker_.CalledOnValidThread());
for (id<CRNNetworkClientProtocol> client in clients)
PushClient(client);
}
} // namespace net
#pragma mark -
#pragma mark CRWHTTPStreamDelegate
@implementation CRWHTTPStreamDelegate
- (instancetype)initWithHttpProtocolHandlerCore:
(net::HttpProtocolHandlerCore*)core {
DCHECK(core);
self = [super init];
if (self)
_core = core;
return self;
}
- (void)stream:(NSStream*)theStream handleEvent:(NSStreamEvent)streamEvent {
_core->HandleStreamEvent(theStream, streamEvent);
}
@end
#pragma mark -
#pragma mark DeferredCancellation
// An object of class |DeferredCancellation| represents a deferred cancellation
// of a request. In principle this is a block posted to a thread's runloop, but
// since there is no performBlock:onThread:, this class wraps the desired
// behavior in an object.
@interface DeferredCancellation : NSObject
- (instancetype)initWithCore:(scoped_refptr<net::HttpProtocolHandlerCore>)core;
- (void)cancel;
@end
@implementation DeferredCancellation {
scoped_refptr<net::HttpProtocolHandlerCore> _core;
}
- (instancetype)initWithCore:(scoped_refptr<net::HttpProtocolHandlerCore>)core {
if ((self = [super init])) {
_core = core;
}
return self;
}
- (void)cancel {
g_protocol_handler_delegate->GetDefaultURLRequestContext()
->GetNetworkTaskRunner()
->PostTask(FROM_HERE,
base::Bind(&net::HttpProtocolHandlerCore::Cancel, _core));
}
@end
#pragma mark -
#pragma mark HttpProtocolHandler
@interface CRNHTTPProtocolHandler (Private)
- (id<CRNHTTPProtocolHandlerProxy>)getProtocolHandlerProxy;
- (scoped_refptr<net::HttpProtocolHandlerCore>)getCore;
- (NSThread*)getClientThread;
- (void)cancelRequest;
@end
// The HttpProtocolHandler is called by the iOS system to handle the
// NSURLRequest.
@implementation CRNHTTPProtocolHandler {
scoped_refptr<net::HttpProtocolHandlerCore> _core;
base::scoped_nsprotocol<id<CRNHTTPProtocolHandlerProxy>> _protocolProxy;
NSThread* _clientThread;
NSString* _clientRunLoopMode;
BOOL _supportedURL;
}
#pragma mark NSURLProtocol methods
+ (BOOL)canInitWithRequest:(NSURLRequest*)request {
DVLOG(5) << "canInitWithRequest " << net::FormatUrlRequestForLogging(request);
return g_protocol_handler_delegate->CanHandleRequest(request);
}
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request {
// TODO(droger): Is this used if we disable the cache of UIWebView? If it is,
// then we need a real implementation, even though Chrome network stack does
// not need it (GURLs are automatically canonized).
return request;
}
- (instancetype)initWithRequest:(NSURLRequest*)request
cachedResponse:(NSCachedURLResponse*)cachedResponse
client:(id<NSURLProtocolClient>)client {
DCHECK(!cachedResponse);
self = [super initWithRequest:request
cachedResponse:cachedResponse
client:client];
if (self) {
_supportedURL = g_protocol_handler_delegate->IsRequestSupported(request);
_core = new net::HttpProtocolHandlerCore(request);
}
return self;
}
#pragma mark NSURLProtocol overrides.
- (NSCachedURLResponse*)cachedResponse {
// We do not use the UIWebView cache.
// TODO(droger): Disable the UIWebView cache.
return nil;
}
- (void)startLoading {
// If the scheme is not valid, just return an error right away.
if (!_supportedURL) {
NSMutableDictionary* dictionary = [NSMutableDictionary dictionary];
// It is possible for URL to be nil, so check for that
// before creating the error object. See http://crbug/349051
NSURL* url = [[self request] URL];
if (url)
[dictionary setObject:url forKey:NSURLErrorKey];
NSError* error = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorUnsupportedURL
userInfo:dictionary];
[[self client] URLProtocol:self didFailWithError:error];
return;
}
_clientThread = [NSThread currentThread];
// The closure passed to PostTask must to retain the _protocolProxy
// scoped_nsobject. A call to getProtocolHandlerProxy before passing
// _protocolProxy ensure that _protocolProxy is instanciated before passing
// it.
[self getProtocolHandlerProxy];
DCHECK(_protocolProxy);
g_protocol_handler_delegate->GetDefaultURLRequestContext()
->GetNetworkTaskRunner()
->PostTask(FROM_HERE, base::Bind(&net::HttpProtocolHandlerCore::Start,
_core, _protocolProxy));
}
- (id<CRNHTTPProtocolHandlerProxy>)getProtocolHandlerProxy {
DCHECK_EQ([NSThread currentThread], _clientThread);
if (!_protocolProxy.get()) {
_protocolProxy.reset([[CRNHTTPProtocolHandlerProxyWithClientThread alloc]
initWithProtocol:self
clientThread:_clientThread
runLoopMode:[[NSRunLoop currentRunLoop] currentMode]]);
}
return _protocolProxy.get();
}
- (scoped_refptr<net::HttpProtocolHandlerCore>)getCore {
return _core;
}
- (NSThread*)getClientThread {
return _clientThread;
}
- (void)cancelRequest {
g_protocol_handler_delegate->GetDefaultURLRequestContext()
->GetNetworkTaskRunner()
->PostTask(FROM_HERE,
base::Bind(&net::HttpProtocolHandlerCore::Cancel, _core));
[_protocolProxy invalidate];
}
- (void)stopLoading {
[self cancelRequest];
_protocolProxy.reset();
}
@end
#pragma mark -
#pragma mark PauseableHttpProtocolHandler
// The HttpProtocolHandler is called by the iOS system to handle the
// NSURLRequest. This HttpProtocolHandler conforms to the observed semantics of
// NSURLProtocol when used with NSURLSession on iOS 8 - i.e., |-startLoading|
// means "start or resume request" and |-stopLoading| means "pause request".
// Since there is no way to actually pause a request in the network stack, this
// is implemented using a subclass of CRNHTTPProtocolHandlerProxy that knows how
// to defer callbacks.
//
// Note that this class conforms to somewhat complex threading rules:
// 1) |initWithRequest:cachedResponse:client:| and |dealloc| can be called on
// any thread.
// 2) |startLoading| and |stopLoading| are always called on the client thread.
// 3) |stopLoading| is called before |dealloc| is called.
//
// The main wrinkle is that |dealloc|, which may be called on any thread, needs
// to clean up a running network request. To do this, |dealloc| needs to run
// |cancelRequest|, which needs to be run on the client thread. Since it is
// guaranteed that |startLoading| is called before |dealloc| is called, the
// |startLoading| method stores a pointer to the client thread, then |dealloc|
// asks that client thread to perform the |cancelRequest| selector via
// |scheduleCancelRequest|.
//
// Some of the above logic is implemented in the parent class
// (CRNHTTPProtocolHandler) because it is convenient.
@implementation CRNPauseableHTTPProtocolHandler {
BOOL _started;
dispatch_queue_t _queue;
}
#pragma mark NSURLProtocol methods
- (void)dealloc {
[self scheduleCancelRequest];
[super dealloc];
}
#pragma mark NSURLProtocol overrides.
- (void)startLoading {
if (_started) {
[[self getProtocolHandlerProxy] resume];
return;
}
_started = YES;
[super startLoading];
}
- (void)stopLoading {
[[self getProtocolHandlerProxy] pause];
}
// This method has unusual concurrency properties. It can be called on any
// thread, but it must be called from |-dealloc|, which guarantees that no other
// method of this object is running concurrently (since |-dealloc| is only
// called when the last reference to the object drops).
//
// This method takes a reference to _core to ensure that _core lives long enough
// to have the request cleanly cancelled.
- (void)scheduleCancelRequest {
DeferredCancellation* cancellation =
[[DeferredCancellation alloc] initWithCore:[self getCore]];
NSArray* modes = @[ [[NSRunLoop currentRunLoop] currentMode] ];
[cancellation performSelector:@selector(cancel)
onThread:[self getClientThread]
withObject:nil
waitUntilDone:NO
modes:modes];
}
@end