|  | // 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, int net_error) 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::MakeUnique<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::MakeUnique<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, | 
|  | int net_error) { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | DCHECK_NE(net::ERR_IO_PENDING, net_error); | 
|  |  | 
|  | if (net_request_ == nullptr) | 
|  | return; | 
|  |  | 
|  | if (net_error != net::OK) { | 
|  | StopRequestWithError(IOSErrorCode(net_error), net_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 = net_request_->Read(buffer_.get(), kIOBufferSize); | 
|  | if (bytes_read == net::ERR_IO_PENDING) | 
|  | return; | 
|  |  | 
|  | if (bytes_read >= 0) { | 
|  | OnReadCompleted(net_request_, bytes_read); | 
|  | } else { | 
|  | int error = bytes_read; | 
|  | StopRequestWithError(IOSErrorCode(error), error); | 
|  | } | 
|  | } | 
|  |  | 
|  | void HttpProtocolHandlerCore::OnReadCompleted(URLRequest* request, | 
|  | int bytes_read) { | 
|  | DCHECK_NE(net::ERR_IO_PENDING, 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; | 
|  | uint64_t total_byte_read = 0; | 
|  | while (bytes_read > 0) { | 
|  | total_byte_read += bytes_read; | 
|  | 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); | 
|  | bytes_read = request->Read(buffer_.get(), kIOBufferSize); | 
|  | } | 
|  |  | 
|  | if (tracker_) | 
|  | tracker_->CaptureReceivedBytes(request, total_byte_read); | 
|  |  | 
|  | // Notify the client. | 
|  | if (bytes_read == net::OK || bytes_read == net::ERR_IO_PENDING) { | 
|  | 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) { | 
|  | DCHECK_EQ(net_request_, request); | 
|  | // There is nothing more to read. | 
|  | StopNetRequest(); | 
|  | [top_level_client_ didFinishLoading]; | 
|  | } | 
|  | } else { | 
|  | // Request failed (not canceled). | 
|  | int error = bytes_read; | 
|  | 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 |