Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(267)

Unified Diff: ios/net/crn_http_protocol_handler.mm

Issue 994823004: [iOS] Upstream //ios/net (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698