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 |