| Index: ios/net/crn_http_protocol_handler.mm
|
| diff --git a/ios/net/crn_http_protocol_handler.mm b/ios/net/crn_http_protocol_handler.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..c8414678a73a28a1ba6d792eb49295168ed0f990
|
| --- /dev/null
|
| +++ b/ios/net/crn_http_protocol_handler.mm
|
| @@ -0,0 +1,952 @@
|
| +// 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 "base/command_line.h"
|
| +#include "base/logging.h"
|
| +#include "base/mac/bind_objc_block.h"
|
| +#include "base/mac/scoped_nsobject.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/net_util.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.
|
| + __weak net::HttpProtocolHandlerCore* _core;
|
| +}
|
| +- (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 is_hsts_host) 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.
|
| + ScopedVector<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(make_scoped_ptr(
|
| + new ElementsUploadDataStream(post_data_readers_.Pass(), 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(
|
| + 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 is_hsts_host) {
|
| + DCHECK(thread_checker_.CalledOnValidThread());
|
| +
|
| + if (is_hsts_host) {
|
| + 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, !is_hsts_host, 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_VERIFY_EV_CERT;
|
| + // TODO(droger) Support MAIN_FRAME and SUB_FRAME flags.
|
| + 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;
|
| + default:
|
| + // For the NSURLRequestUseProtocolCachePolicy case.
|
| + 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, nullptr).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);
|
| + scoped_ptr<UploadElementReader> reader(
|
| + new UploadOwnedBytesElementReader(&owned_data));
|
| + net_request_->set_upload(
|
| + ElementsUploadDataStream::CreateWithReader(reader.Pass(), 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 HttpProtocolHandler
|
| +
|
| +// 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;
|
| + 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;
|
| + }
|
| +
|
| + _protocolProxy.reset([[CRNHTTPProtocolHandlerProxyWithClientThread alloc]
|
| + initWithProtocol:self
|
| + clientThread:[NSThread currentThread]
|
| + runLoopMode:[[NSRunLoop currentRunLoop] currentMode]]);
|
| + g_protocol_handler_delegate->GetDefaultURLRequestContext()
|
| + ->GetNetworkTaskRunner()
|
| + ->PostTask(FROM_HERE, base::Bind(&net::HttpProtocolHandlerCore::Start,
|
| + _core, _protocolProxy));
|
| +}
|
| +
|
| +- (void)stopLoading {
|
| + g_protocol_handler_delegate->GetDefaultURLRequestContext()
|
| + ->GetNetworkTaskRunner()
|
| + ->PostTask(FROM_HERE,
|
| + base::Bind(&net::HttpProtocolHandlerCore::Cancel, _core));
|
| + [_protocolProxy invalidate];
|
| + _protocolProxy.reset();
|
| +}
|
| +
|
| +@end
|
|
|