summaryrefslogtreecommitdiffstats
path: root/net/http/http_transaction_winhttp.cc
diff options
context:
space:
mode:
Diffstat (limited to 'net/http/http_transaction_winhttp.cc')
-rw-r--r--net/http/http_transaction_winhttp.cc1807
1 files changed, 1807 insertions, 0 deletions
diff --git a/net/http/http_transaction_winhttp.cc b/net/http/http_transaction_winhttp.cc
new file mode 100644
index 0000000..6570b0d
--- /dev/null
+++ b/net/http/http_transaction_winhttp.cc
@@ -0,0 +1,1807 @@
+// Copyright 2008, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "net/http/http_transaction_winhttp.h"
+
+#include <winhttp.h>
+
+#include "base/lock.h"
+#include "base/memory_debug.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/auth_cache.h"
+#include "net/base/cert_status_flags.h"
+#include "net/base/dns_resolution_observer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/ssl_config_service.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/cert_status_cache.h"
+#include "net/http/http_proxy_resolver_winhttp.h"
+#include "net/http/http_request_info.h"
+#include "net/http/winhttp_request_throttle.h"
+
+#pragma comment(lib, "winhttp.lib")
+#pragma warning(disable: 4355)
+
+namespace net {
+
+static int TranslateOSError(DWORD error) {
+ switch (error) {
+ case ERROR_SUCCESS:
+ return OK;
+ case ERROR_FILE_NOT_FOUND:
+ return ERR_FILE_NOT_FOUND;
+ case ERROR_HANDLE_EOF: // TODO(wtc): return OK?
+ return ERR_CONNECTION_CLOSED;
+ case ERROR_INVALID_HANDLE:
+ return ERR_INVALID_HANDLE;
+ case ERROR_INVALID_PARAMETER:
+ return ERR_INVALID_ARGUMENT;
+
+ case ERROR_WINHTTP_CANNOT_CONNECT:
+ return ERR_CONNECTION_FAILED;
+ case ERROR_WINHTTP_TIMEOUT:
+ return ERR_TIMED_OUT;
+ case ERROR_WINHTTP_INVALID_URL:
+ return ERR_INVALID_URL;
+ case ERROR_WINHTTP_NAME_NOT_RESOLVED:
+ return ERR_NAME_NOT_RESOLVED;
+ case ERROR_WINHTTP_OPERATION_CANCELLED:
+ return ERR_ABORTED;
+ case ERROR_WINHTTP_SECURE_CHANNEL_ERROR:
+ case ERROR_WINHTTP_SECURE_FAILURE:
+ return ERR_SSL_PROTOCOL_ERROR;
+ case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED:
+ return ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ case ERROR_WINHTTP_UNRECOGNIZED_SCHEME:
+ return ERR_UNKNOWN_URL_SCHEME;
+ case ERROR_WINHTTP_INVALID_SERVER_RESPONSE:
+ return ERR_INVALID_RESPONSE;
+
+ // SSL certificate errors
+ case ERROR_WINHTTP_SECURE_CERT_CN_INVALID:
+ return ERR_CERT_COMMON_NAME_INVALID;
+ case ERROR_WINHTTP_SECURE_CERT_DATE_INVALID:
+ return ERR_CERT_DATE_INVALID;
+ case ERROR_WINHTTP_SECURE_INVALID_CA:
+ return ERR_CERT_AUTHORITY_INVALID;
+ case ERROR_WINHTTP_SECURE_CERT_REV_FAILED:
+ return ERR_CERT_UNABLE_TO_CHECK_REVOCATION;
+ case ERROR_WINHTTP_SECURE_CERT_REVOKED:
+ return ERR_CERT_REVOKED;
+ case ERROR_WINHTTP_SECURE_INVALID_CERT:
+ return ERR_CERT_INVALID;
+
+ default:
+ DCHECK(error != ERROR_IO_PENDING); // WinHTTP doesn't use this error.
+ return ERR_FAILED;
+ }
+}
+
+static int TranslateLastOSError() {
+ return TranslateOSError(GetLastError());
+}
+
+// Clear certificate errors that we want to ignore.
+static DWORD FilterSecureFailure(DWORD status, int load_flags) {
+ if (load_flags & LOAD_IGNORE_CERT_COMMON_NAME_INVALID)
+ status &= ~WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID;
+ if (load_flags & LOAD_IGNORE_CERT_DATE_INVALID)
+ status &= ~WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID;
+ if (load_flags & LOAD_IGNORE_CERT_AUTHORITY_INVALID)
+ status &= ~WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA;
+ if (load_flags & LOAD_IGNORE_CERT_WRONG_USAGE)
+ status &= ~WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE;
+ return status;
+}
+
+static DWORD MapSecureFailureToError(DWORD status) {
+ // A certificate may have multiple errors. We report the most
+ // serious error.
+
+ // Unrecoverable errors
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR)
+ return ERROR_WINHTTP_SECURE_CHANNEL_ERROR;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)
+ return ERROR_WINHTTP_SECURE_INVALID_CERT;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)
+ return ERROR_WINHTTP_SECURE_CERT_REVOKED;
+
+ // Recoverable errors
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)
+ return ERROR_WINHTTP_SECURE_INVALID_CA;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)
+ return ERROR_WINHTTP_SECURE_CERT_CN_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)
+ return ERROR_WINHTTP_SECURE_CERT_DATE_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_WRONG_USAGE)
+ return ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE;
+
+ // Unknown status. Give it the benefit of the doubt.
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)
+ return ERROR_WINHTTP_SECURE_CERT_REV_FAILED;
+
+ // Map a status of 0 to the generic secure failure error. We have seen a
+ // case where WinHttp doesn't notify us of a secure failure (so status is 0)
+ // before notifying us of a request error with ERROR_WINHTTP_SECURE_FAILURE.
+ // (WinInet fails with ERROR_INTERNET_SECURITY_CHANNEL_ERROR in that case.)
+ return ERROR_WINHTTP_SECURE_FAILURE;
+}
+
+static int MapSecureFailureToCertStatus(DWORD status) {
+ int cert_status = 0;
+
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT)
+ cert_status |= CERT_STATUS_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED)
+ cert_status |= CERT_STATUS_REVOKED;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA)
+ cert_status |= CERT_STATUS_AUTHORITY_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID)
+ cert_status |= CERT_STATUS_COMMON_NAME_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID)
+ cert_status |= CERT_STATUS_DATE_INVALID;
+ if (status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)
+ cert_status |= CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+
+ return cert_status;
+ // TODO(jcampan): what about ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE?
+}
+
+// Session --------------------------------------------------------------------
+
+class HttpTransactionWinHttp::Session
+ : public base::RefCounted<HttpTransactionWinHttp::Session> {
+ public:
+ enum {
+ // By default WinHTTP enables only SSL3 and TLS1.
+ SECURE_PROTOCOLS_SSL3_TLS1 = WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 |
+ WINHTTP_FLAG_SECURE_PROTOCOL_TLS1
+ };
+
+ Session()
+ : internet_(NULL),
+ internet_no_tls_(NULL),
+ message_loop_(NULL),
+ handle_closing_event_(NULL),
+ quit_event_(NULL),
+ session_callback_ref_count_(0),
+ quitting_(false),
+ rev_checking_enabled_(false),
+ secure_protocols_(SECURE_PROTOCOLS_SSL3_TLS1) {
+ }
+
+ bool Init();
+
+ // Opens the alternative WinHttp session handle for TLS-intolerant servers.
+ bool InitNoTLS();
+
+ void AddRefBySessionCallback();
+
+ void ReleaseBySessionCallback();
+
+ // The primary WinHttp session handle.
+ HINTERNET internet() { return internet_; }
+
+ // An alternative WinHttp session handle. It is not opened until we have
+ // encountered a TLS-intolerant server and used for those servers only.
+ // TLS is disabled in this session.
+ HINTERNET internet_no_tls() { return internet_no_tls_; }
+
+ // The message loop of the thread where the session was created.
+ MessageLoop* message_loop() { return message_loop_; }
+
+ HttpProxyService* proxy_service() { return proxy_service_.get(); }
+
+ // Gets the HTTP authentication cache for the session.
+ AuthCache* auth_cache() { return &auth_cache_; }
+
+ HANDLE handle_closing_event() const { return handle_closing_event_; }
+
+ CertStatusCache* cert_status_cache() { return &cert_status_cache_; }
+
+ bool rev_checking_enabled() const { return rev_checking_enabled_; }
+
+ bool tls_enabled() const {
+ return (secure_protocols_ & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1) != 0;
+ }
+
+ bool ShouldIgnoreCertRev(const std::string& origin) const {
+ OriginSet::const_iterator pos = ignore_cert_rev_servers_.find(origin);
+ return pos != ignore_cert_rev_servers_.end();
+ }
+
+ void IgnoreCertRev(const std::string& origin) {
+ ignore_cert_rev_servers_.insert(origin);
+ }
+
+ WinHttpRequestThrottle* request_throttle() {
+ return &request_throttle_;
+ }
+
+ private:
+ friend class base::RefCounted<HttpTransactionWinHttp::Session>;
+
+ ~Session();
+
+ // Called by the destructor only.
+ void WaitUntilCallbacksAllDone();
+
+ HINTERNET OpenWinHttpSession();
+
+ // Get the SSL configuration settings and apply them to the session handle.
+ void ConfigureSSL();
+
+ HINTERNET internet_;
+ HINTERNET internet_no_tls_;
+ MessageLoop* message_loop_;
+ scoped_ptr<HttpProxyService> proxy_service_;
+ scoped_ptr<HttpProxyResolver> proxy_resolver_;
+ AuthCache auth_cache_;
+
+ // This event object is used when destroying a transaction. It is given
+ // to the transaction's session callback if WinHTTP still has the caller's
+ // data (request info or read buffer) and we need to wait until WinHTTP is
+ // done with the data.
+ HANDLE handle_closing_event_;
+
+ // The following members ensure a clean destruction of the Session object.
+ // The Session destructor waits until all the request handles have been
+ // terminated by WinHTTP, at which point no more status callbacks will
+ // reference the MessageLoop of the Session.
+ //
+ // quit_event_ is the event object used for this wait.
+ //
+ // lock_ protects session_callback_ref_count_ and quitting_.
+ //
+ // session_callback_ref_count_ is the number of SessionCallback objects
+ // that may reference the MessageLoop of the Session.
+ //
+ // The boolean quitting_ is true when the Session object is being
+ // destructed.
+ HANDLE quit_event_;
+ Lock lock_;
+ int session_callback_ref_count_;
+ bool quitting_;
+
+ // We use a cache to store the certificate error as we cannot always rely on
+ // WinHTTP to provide us the SSL error once we restarted a connection asking
+ // to ignored errors.
+ CertStatusCache cert_status_cache_;
+
+ // SSL settings
+ bool rev_checking_enabled_;
+ DWORD secure_protocols_;
+
+ // The servers for which certificate revocation should be ignored.
+ //
+ // WinHTTP verifies each certificate only once and caches the certificate
+ // verification results, so if we ever ignore certificate revocation for a
+ // server, we cannot enable revocation checking again for that server for
+ // the rest of the session.
+ //
+ // If we honor changes to the rev_checking_enabled system setting during
+ // the session, we will have to remember all the servers we have visited
+ // while the rev_checking_enabled setting is false. This will consume a
+ // lot of memory. So we now require the users to restart Chrome for a
+ // rev_checking_enabled change to take effect, just like IE does.
+ typedef std::set<std::string> OriginSet;
+ OriginSet ignore_cert_rev_servers_;
+
+ WinHttpRequestThrottle request_throttle_;
+};
+
+HttpTransactionWinHttp::Session::~Session() {
+ // It is important to shutdown the proxy service before closing the WinHTTP
+ // session handle since the proxy service uses the WinHTTP session handle.
+ proxy_service_.reset();
+
+ // Next, the resolver which also references our session handle.
+ proxy_resolver_.reset();
+
+ if (internet_) {
+ WinHttpCloseHandle(internet_);
+ if (internet_no_tls_)
+ WinHttpCloseHandle(internet_no_tls_);
+
+ // Ensure that all status callbacks that may reference the MessageLoop
+ // of this thread are done before we can allow the current thread to exit.
+ WaitUntilCallbacksAllDone();
+ }
+
+ if (handle_closing_event_)
+ CloseHandle(handle_closing_event_);
+ if (quit_event_)
+ CloseHandle(quit_event_);
+}
+
+bool HttpTransactionWinHttp::Session::Init() {
+ DCHECK(!internet_);
+
+ internet_ = OpenWinHttpSession();
+
+ if (!internet_)
+ return false;
+
+ proxy_resolver_.reset(new HttpProxyResolverWinHttp());
+ proxy_service_.reset(new HttpProxyService(proxy_resolver_.get()));
+
+ ConfigureSSL();
+
+ // Save the current message loop for callback notifications.
+ message_loop_ = MessageLoop::current();
+
+ handle_closing_event_ = CreateEvent(NULL,
+ FALSE, // auto-reset
+ FALSE, // initially nonsignaled
+ NULL); // unnamed
+
+ quit_event_ = CreateEvent(NULL,
+ FALSE, // auto-reset
+ FALSE, // initially nonsignaled
+ NULL); // unnamed
+
+ return true;
+}
+
+bool HttpTransactionWinHttp::Session::InitNoTLS() {
+ DCHECK(tls_enabled());
+ DCHECK(internet_);
+ DCHECK(!internet_no_tls_);
+
+ internet_no_tls_ = OpenWinHttpSession();
+
+ if (!internet_no_tls_)
+ return false;
+
+ DWORD protocols = secure_protocols_ & ~WINHTTP_FLAG_SECURE_PROTOCOL_TLS1;
+ BOOL rv = WinHttpSetOption(internet_no_tls_,
+ WINHTTP_OPTION_SECURE_PROTOCOLS,
+ &protocols, sizeof(protocols));
+ DCHECK(rv);
+
+ return true;
+}
+
+void HttpTransactionWinHttp::Session::AddRefBySessionCallback() {
+ AutoLock lock(lock_);
+ session_callback_ref_count_++;
+}
+
+void HttpTransactionWinHttp::Session::ReleaseBySessionCallback() {
+ bool need_to_signal;
+ {
+ AutoLock lock(lock_);
+ session_callback_ref_count_--;
+ need_to_signal = (quitting_ && session_callback_ref_count_ == 0);
+ }
+ if (need_to_signal)
+ SetEvent(quit_event_);
+}
+
+// This is called by the Session destructor only. By now the transaction
+// factory and all the transactions have been destructed. This means that
+// new transactions can't be created, and existing transactions can't be
+// started, which in turn implies that session_callback_ref_count_ cannot
+// increase. We wait until session_callback_ref_count_ drops to 0.
+void HttpTransactionWinHttp::Session::WaitUntilCallbacksAllDone() {
+ bool need_to_wait;
+ {
+ AutoLock lock(lock_);
+ quitting_ = true;
+ need_to_wait = (session_callback_ref_count_ != 0);
+ }
+ if (need_to_wait)
+ WaitForSingleObject(quit_event_, INFINITE);
+ DCHECK(session_callback_ref_count_ == 0);
+}
+
+HINTERNET HttpTransactionWinHttp::Session::OpenWinHttpSession() {
+ // UA string and proxy config will be set explicitly for each request
+ HINTERNET internet = WinHttpOpen(NULL,
+ WINHTTP_ACCESS_TYPE_NO_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ WINHTTP_FLAG_ASYNC);
+ if (!internet)
+ return internet;
+
+ // Use a 90-second timeout (1.5 times the default) for connect. Disable
+ // name resolution, send, and receive timeouts. We expect our consumer to
+ // apply timeouts or provide controls for users to stop requests that are
+ // taking too long.
+ BOOL rv = WinHttpSetTimeouts(internet, 0, 90000, 0, 0);
+ DCHECK(rv);
+
+ return internet;
+}
+
+void HttpTransactionWinHttp::Session::ConfigureSSL() {
+ DCHECK(internet_);
+
+ SSLConfig ssl_config;
+ SSLConfigService::GetSSLConfigNow(&ssl_config);
+ rev_checking_enabled_ = ssl_config.rev_checking_enabled;
+ DWORD protocols = 0;
+ if (ssl_config.ssl2_enabled)
+ protocols |= WINHTTP_FLAG_SECURE_PROTOCOL_SSL2;
+ if (ssl_config.ssl3_enabled)
+ protocols |= WINHTTP_FLAG_SECURE_PROTOCOL_SSL3;
+ if (ssl_config.tls1_enabled)
+ protocols |= WINHTTP_FLAG_SECURE_PROTOCOL_TLS1;
+ if (protocols != secure_protocols_) {
+ BOOL rv = WinHttpSetOption(internet_, WINHTTP_OPTION_SECURE_PROTOCOLS,
+ &protocols, sizeof(protocols));
+ DCHECK(rv);
+ secure_protocols_ = protocols;
+ }
+}
+
+// SessionCallback ------------------------------------------------------------
+
+class HttpTransactionWinHttp::SessionCallback
+ : public base::RefCountedThreadSafe<HttpTransactionWinHttp::SessionCallback> {
+ public:
+ SessionCallback(HttpTransactionWinHttp* trans, Session* session)
+ : trans_(trans),
+ session_(session),
+ load_state_(LOAD_STATE_IDLE),
+ handle_closing_event_(NULL),
+ bytes_available_(0),
+ read_buf_(NULL),
+ read_buf_len_(0),
+ secure_failure_(0),
+ connection_was_opened_(false),
+ request_was_probably_sent_(false),
+ response_was_received_(false),
+ response_is_empty_(true) {
+ }
+
+ // Called when the associated trans_ has to reopen its connection and
+ // request handles to recover from certain SSL errors. Resets the members
+ // that may have been modified at that point.
+ void ResetForNewRequest() {
+ secure_failure_ = 0;
+ connection_was_opened_ = false;
+ }
+
+ void DropTransaction() {
+ trans_ = NULL;
+ }
+
+ void Notify(DWORD status, DWORD_PTR result, DWORD error) {
+ DWORD secure_failure = 0;
+ if (status == WINHTTP_CALLBACK_STATUS_REQUEST_ERROR) {
+ switch (error) {
+ // WinHttp sends this error code in two interesting cases: 1) when a
+ // response header is malformed, and 2) when a response is empty. In
+ // the latter case, we want to actually resend the request if the
+ // request was sent over a reused "keep-alive" connection. This is a
+ // risky thing to do since it is possible that the server did receive
+ // our request, but it is unfortunately required to support HTTP keep-
+ // alive connections properly, and other browsers all do this too.
+ case ERROR_WINHTTP_INVALID_SERVER_RESPONSE:
+ if (empty_response_was_received() && !connection_was_opened_)
+ error = ERROR_WINHTTP_RESEND_REQUEST;
+ break;
+ case ERROR_WINHTTP_SECURE_FAILURE:
+ secure_failure = secure_failure_;
+ break;
+ }
+ } else if (status == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE) {
+ secure_failure = secure_failure_;
+ }
+ session_->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(this,
+ &HttpTransactionWinHttp::SessionCallback::OnNotify,
+ status, result, error, secure_failure));
+ }
+
+ // Calls WinHttpReadData and returns its return value.
+ BOOL ReadData(HINTERNET request_handle);
+
+ void OnHandleClosing() {
+ if (handle_closing_event_)
+ SetEvent(handle_closing_event_);
+ session_->ReleaseBySessionCallback();
+ Release();
+ }
+
+ // Modified from any thread.
+ void set_load_state(LoadState state) {
+ load_state_ = state;
+ }
+ LoadState load_state() const {
+ return load_state_;
+ }
+
+ int bytes_available() const { return bytes_available_; }
+ void set_bytes_available(int n) { bytes_available_ = n; }
+ void reduce_bytes_available(int n) { bytes_available_ -= n; }
+
+ char* read_buf() const { return read_buf_; }
+ void set_read_buf(char* buf) { read_buf_ = buf; }
+
+ int read_buf_len() const { return read_buf_len_; }
+ void set_read_buf_len(int buf_len) { read_buf_len_ = buf_len; }
+
+ // Tells this SessionCallback to signal this event when receiving the
+ // handle closing status callback.
+ void set_handle_closing_event(HANDLE event) {
+ handle_closing_event_ = event;
+ }
+
+ void set_secure_failure(DWORD flags) { secure_failure_ = flags; }
+
+ void did_open_connection() {
+ connection_was_opened_ = true;
+ }
+
+ void did_start_sending_request() {
+ request_was_probably_sent_ = true;
+ }
+ bool request_was_probably_sent() const {
+ return request_was_probably_sent_;
+ }
+
+ void did_receive_bytes(DWORD count) {
+ response_was_received_ = true;
+ if (count)
+ response_is_empty_ = false;
+ }
+
+ private:
+ friend base::RefCountedThreadSafe<HttpTransactionWinHttp::SessionCallback>;
+ ~SessionCallback() {}
+
+ void OnNotify(DWORD status,
+ DWORD_PTR result,
+ DWORD error,
+ DWORD secure_failure) {
+ if (trans_)
+ trans_->HandleStatusCallback(status, result, error, secure_failure);
+
+ // Balances the AddRefs made by the transaction object after an async
+ // WinHTTP call.
+ Release();
+ }
+
+ bool empty_response_was_received() const {
+ return response_was_received_ && response_is_empty_;
+ }
+
+ HttpTransactionWinHttp* trans_;
+
+ // Session is reference-counted, but this is a plain pointer. The
+ // reference on the Session owned by SessionCallback is managed using
+ // Session::AddRefBySessionCallback and Session::ReleaseBySessionCallback.
+ Session* session_;
+
+ // Modified from any thread.
+ volatile LoadState load_state_;
+
+ // Amount of data available reported by WinHttpQueryDataAvailable that
+ // haven't been consumed by WinHttpReadData.
+ int bytes_available_;
+
+ // Caller's read buffer and buffer size, to be passed to WinHttpReadData.
+ // These are used by the IO thread and the thread WinHTTP uses to make
+ // status callbacks, but not at the same time.
+ char* read_buf_;
+ int read_buf_len_;
+
+ // If not null, we set this event on receiving the handle closing callback.
+ HANDLE handle_closing_event_;
+
+ // The secure connection failure flags reported by the
+ // WINHTTP_CALLBACK_STATUS_SECURE_FAILURE status callback.
+ DWORD secure_failure_;
+
+ // True if a connection was opened for this request.
+ bool connection_was_opened_;
+
+ // True if the request may have been sent to the server (and therefore we
+ // should not restart the request).
+ bool request_was_probably_sent_;
+
+ // True if any response was received.
+ bool response_was_received_;
+
+ // True if we have an empty response (no headers, no status line, nothing).
+ bool response_is_empty_;
+};
+
+BOOL HttpTransactionWinHttp::SessionCallback::ReadData(
+ HINTERNET request_handle) {
+ DCHECK(bytes_available_ >= 0);
+ char* buf = read_buf_;
+ read_buf_ = NULL;
+ int bytes_to_read = std::min(bytes_available_, read_buf_len_);
+ read_buf_len_ = 0;
+ if (!bytes_to_read)
+ bytes_to_read = 1;
+
+ // Because of how WinHTTP fills memory when used asynchronously, Purify isn't
+ // able to detect that it's been initialized, so it scans for 0xcd in the
+ // buffer and reports UMRs (uninitialized memory reads) for those individual
+ // bytes. We override that to avoid the false error reports.
+ // See http://b/issue?id=1173916.
+ base::MemoryDebug::MarkAsInitialized(buf, bytes_to_read);
+ return WinHttpReadData(request_handle, buf, bytes_to_read, NULL);
+}
+
+// static
+void HttpTransactionWinHttp::StatusCallback(HINTERNET handle,
+ DWORD_PTR context,
+ DWORD status,
+ LPVOID status_info,
+ DWORD status_info_len) {
+ SessionCallback* callback = reinterpret_cast<SessionCallback*>(context);
+
+ switch (status) {
+ case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING:
+ if (callback)
+ callback->OnHandleClosing();
+ break;
+ case WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER:
+ callback->set_load_state(LOAD_STATE_CONNECTING);
+ break;
+ case WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER:
+ callback->did_open_connection();
+ break;
+ case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
+ callback->set_load_state(LOAD_STATE_SENDING_REQUEST);
+ callback->did_start_sending_request();
+ break;
+ case WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE:
+ callback->set_load_state(LOAD_STATE_WAITING_FOR_RESPONSE);
+ break;
+ case WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED:
+ callback->did_receive_bytes(*static_cast<DWORD*>(status_info));
+ break;
+ case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE:
+ DCHECK(callback->bytes_available() == 0);
+ DCHECK(status_info_len == sizeof(DWORD));
+ callback->set_bytes_available(static_cast<DWORD*>(status_info)[0]);
+ if (!callback->ReadData(handle))
+ callback->Notify(WINHTTP_CALLBACK_STATUS_REQUEST_ERROR,
+ API_READ_DATA, GetLastError());
+ break;
+ case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
+ callback->Notify(status, status_info_len, 0);
+ break;
+ case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
+ DCHECK(status_info_len == sizeof(DWORD));
+ callback->Notify(status, static_cast<DWORD*>(status_info)[0], 0);
+ break;
+ case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
+ callback->Notify(status, TRUE, 0);
+ break;
+ case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
+ callback->Notify(status, TRUE, 0);
+ break;
+ case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: {
+ WINHTTP_ASYNC_RESULT* result =
+ static_cast<WINHTTP_ASYNC_RESULT*>(status_info);
+ callback->Notify(status, result->dwResult, result->dwError);
+ if (API_SEND_REQUEST == result->dwResult &&
+ ERROR_WINHTTP_NAME_NOT_RESOLVED == result->dwError)
+ DidFinishDnsResolutionWithStatus(false,
+ reinterpret_cast<void*>(context));
+ break;
+ }
+ // This status callback provides the detailed reason for a secure
+ // failure. We map that to an error code and save it for later use.
+ case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE: {
+ DCHECK(status_info_len == sizeof(DWORD));
+ DWORD* status_ptr = static_cast<DWORD*>(status_info);
+ callback->set_secure_failure(*status_ptr);
+ break;
+ }
+ // Looking up the IP address of a server name. The status_info
+ // parameter contains a pointer to the server name being resolved.
+ case WINHTTP_CALLBACK_STATUS_RESOLVING_NAME: {
+ callback->set_load_state(LOAD_STATE_RESOLVING_HOST);
+ std::wstring wname(static_cast<wchar_t*>(status_info),
+ status_info_len - 1);
+ DidStartDnsResolution(WideToASCII(wname),
+ reinterpret_cast<void*>(context));
+ break;
+ }
+ // Successfully found the IP address of the server.
+ case WINHTTP_CALLBACK_STATUS_NAME_RESOLVED:
+ DidFinishDnsResolutionWithStatus(true, reinterpret_cast<void*>(context));
+ break;
+ }
+}
+
+// Factory --------------------------------------------------------------------
+
+HttpTransactionWinHttp::Factory::~Factory() {
+ if (session_)
+ session_->Release();
+}
+
+HttpTransaction* HttpTransactionWinHttp::Factory::CreateTransaction() {
+ if (is_suspended_)
+ return NULL;
+
+ if (!session_) {
+ session_ = new Session();
+ session_->AddRef();
+ if (!session_->Init()) {
+ DLOG(ERROR) << "unable to create the internet";
+ session_->Release();
+ session_ = NULL;
+ return NULL;
+ }
+ }
+ return new HttpTransactionWinHttp(session_, proxy_info_.get());
+}
+
+HttpCache* HttpTransactionWinHttp::Factory::GetCache() {
+ return NULL;
+}
+
+AuthCache* HttpTransactionWinHttp::Factory::GetAuthCache() {
+ return session_->auth_cache();
+}
+
+void HttpTransactionWinHttp::Factory::Suspend(bool suspend) {
+ is_suspended_ = suspend;
+
+ if (is_suspended_ && session_) {
+ session_->Release();
+ session_ = NULL;
+ }
+}
+
+// Transaction ----------------------------------------------------------------
+
+HttpTransactionWinHttp::HttpTransactionWinHttp(Session* session,
+ const HttpProxyInfo* info)
+ : session_(session),
+ request_(NULL),
+ load_flags_(0),
+ last_error_(ERROR_SUCCESS),
+ content_length_remaining_(-1),
+ pac_request_(NULL),
+ proxy_callback_(this, &HttpTransactionWinHttp::OnProxyInfoAvailable),
+ callback_(NULL),
+ upload_progress_(0),
+ connect_handle_(NULL),
+ request_handle_(NULL),
+ is_https_(false),
+ is_tls_intolerant_(false),
+ rev_checking_enabled_(false),
+ request_submitted_(false),
+ have_proxy_info_(false),
+ need_to_wait_for_handle_closing_(false) {
+ session->AddRef();
+ session_callback_ = new SessionCallback(this, session);
+ if (info) {
+ proxy_info_.Use(*info);
+ have_proxy_info_ = true;
+ }
+}
+
+HttpTransactionWinHttp::~HttpTransactionWinHttp() {
+ if (pac_request_)
+ session_->proxy_service()->CancelPacRequest(pac_request_);
+
+ if (request_handle_) {
+ if (need_to_wait_for_handle_closing_) {
+ session_callback_->set_handle_closing_event(
+ session_->handle_closing_event());
+ }
+ WinHttpCloseHandle(request_handle_);
+ if (need_to_wait_for_handle_closing_)
+ WaitForSingleObject(session_->handle_closing_event(), INFINITE);
+ }
+ if (connect_handle_)
+ WinHttpCloseHandle(connect_handle_);
+
+ if (request_submitted_) {
+ session_->request_throttle()->RemoveRequest(connect_peer_,
+ request_handle_);
+ }
+
+ if (session_callback_) {
+ session_callback_->DropTransaction();
+ session_callback_ = NULL; // Release() reference as side effect.
+ }
+ if (session_)
+ session_->Release();
+}
+
+void HttpTransactionWinHttp::Destroy() {
+ delete this;
+}
+
+int HttpTransactionWinHttp::Start(const HttpRequestInfo* request_info,
+ CompletionCallback* callback) {
+ DCHECK(request_info);
+ DCHECK(callback);
+
+ // ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+
+ LOG(INFO) << request_info->method << ": " << request_info->url;
+
+ request_ = request_info;
+ load_flags_ = request_info->load_flags;
+
+ int rv = OK;
+ if (!have_proxy_info_) {
+ // Resolve proxy info.
+ rv = session_->proxy_service()->ResolveProxy(request_->url,
+ &proxy_info_,
+ &proxy_callback_,
+ &pac_request_);
+ if (rv == ERR_IO_PENDING) {
+ session_callback_->set_load_state(
+ LOAD_STATE_RESOLVING_PROXY_FOR_URL);
+ }
+ }
+
+ if (rv == OK)
+ rv = DidResolveProxy(); // calls OpenRequest and SendRequest
+
+ if (rv == ERR_IO_PENDING) {
+ session_callback_->AddRef(); // balanced when callback runs or from
+ // OnProxyInfoAvailable.
+ callback_ = callback;
+ }
+
+ return rv;
+}
+
+int HttpTransactionWinHttp::RestartIgnoringLastError(
+ CompletionCallback* callback) {
+ int flags = load_flags_;
+
+ // Depending on the error, we make different adjustments to our load flags.
+ // We DCHECK that we shouldn't already have ignored this error.
+ switch (last_error_) {
+ case ERROR_WINHTTP_SECURE_CERT_CN_INVALID:
+ DCHECK(!(flags & LOAD_IGNORE_CERT_COMMON_NAME_INVALID));
+ flags |= LOAD_IGNORE_CERT_COMMON_NAME_INVALID;
+ break;
+ case ERROR_WINHTTP_SECURE_CERT_DATE_INVALID:
+ DCHECK(!(flags & LOAD_IGNORE_CERT_DATE_INVALID));
+ flags |= LOAD_IGNORE_CERT_DATE_INVALID;
+ break;
+ case ERROR_WINHTTP_SECURE_INVALID_CA:
+ DCHECK(!(flags & LOAD_IGNORE_CERT_AUTHORITY_INVALID));
+ flags |= LOAD_IGNORE_CERT_AUTHORITY_INVALID;
+ break;
+ case ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE:
+ DCHECK(!(flags & LOAD_IGNORE_CERT_WRONG_USAGE));
+ flags |= LOAD_IGNORE_CERT_WRONG_USAGE;
+ break;
+ case ERROR_WINHTTP_SECURE_CERT_REV_FAILED: {
+ DCHECK(!(flags & LOAD_IGNORE_CERT_REVOCATION));
+ flags |= LOAD_IGNORE_CERT_REVOCATION;
+ // WinHTTP doesn't have a SECURITY_FLAG_IGNORE_CERT_REV_FAILED flag
+ // and doesn't let us undo WINHTTP_ENABLE_SSL_REVOCATION. The only
+ // way to ignore this error is to open a new request without enabling
+ // WINHTTP_ENABLE_SSL_REVOCATION.
+ if (!ReopenRequest())
+ return TranslateLastOSError();
+ break;
+ }
+ // We can't instruct WinHttp to recover from these errors. No choice
+ // but to cancel the request.
+ case ERROR_WINHTTP_SECURE_CHANNEL_ERROR:
+ case ERROR_WINHTTP_SECURE_INVALID_CERT:
+ case ERROR_WINHTTP_SECURE_CERT_REVOKED:
+ // We don't knows how to continue from here.
+ default:
+ LOG(ERROR) << "Unable to restart the HTTP transaction ignoring "
+ "the error " << last_error_;
+ return ERR_ABORTED;
+ }
+
+ // Update the load flags to ignore the specified error.
+ load_flags_ = flags;
+
+ return Restart(callback);
+}
+
+int HttpTransactionWinHttp::RestartWithAuth(
+ const std::wstring& username,
+ const std::wstring& password,
+ CompletionCallback* callback) {
+ DCHECK(proxy_auth_ && proxy_auth_->state == AUTH_STATE_NEED_AUTH ||
+ server_auth_ && server_auth_->state == AUTH_STATE_NEED_AUTH);
+
+ // Proxy gets set first, then WWW.
+ AuthData* auth =
+ proxy_auth_ && proxy_auth_->state == AUTH_STATE_NEED_AUTH ?
+ proxy_auth_ : server_auth_;
+
+ if (auth) {
+ auth->state = AUTH_STATE_HAVE_AUTH;
+ auth->username = username;
+ auth->password = password;
+ }
+
+ return Restart(callback);
+}
+
+// The code common to RestartIgnoringLastError and RestartWithAuth.
+int HttpTransactionWinHttp::Restart(CompletionCallback* callback) {
+ DCHECK(callback);
+
+ // ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+
+ int rv = SendRequest();
+ if (rv != ERR_IO_PENDING)
+ return rv;
+
+ session_callback_->AddRef(); // balanced when callback runs.
+
+ callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+// We use WinHttpQueryDataAvailable rather than pure async read to trade
+// a better latency for a decreased throughput. We'll make more IO calls,
+// and thus use more CPU for a given transaction by using
+// WinHttpQueryDataAvailable, but it allows us to get a faster response
+// time to the app for data, which is more important.
+int HttpTransactionWinHttp::Read(char* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK(buf_len > 0);
+ DCHECK(callback);
+
+ DCHECK(!callback_);
+ DCHECK(request_handle_);
+
+ // If we have already received the full response, then we know we are done.
+ if (content_length_remaining_ == 0)
+ return 0;
+
+ session_callback_->set_read_buf(buf);
+ session_callback_->set_read_buf_len(buf_len);
+
+ // We must consume all the available data reported by the previous
+ // WinHttpQueryDataAvailable call before we can call
+ // WinHttpQueryDataAvailable again.
+ BOOL ok;
+ if (session_callback_->bytes_available()) {
+ ok = session_callback_->ReadData(request_handle_);
+ } else {
+ ok = WinHttpQueryDataAvailable(request_handle_, NULL);
+ }
+ if (!ok)
+ return TranslateLastOSError();
+
+ session_callback_->set_load_state(LOAD_STATE_READING_RESPONSE);
+ session_callback_->AddRef(); // balanced when callback runs.
+ need_to_wait_for_handle_closing_ = true;
+
+ callback_ = callback;
+ return ERR_IO_PENDING;
+}
+
+const HttpResponseInfo* HttpTransactionWinHttp::GetResponseInfo() const {
+ return (response_.headers || response_.ssl_info.cert) ? &response_ : NULL;
+}
+
+LoadState HttpTransactionWinHttp::GetLoadState() const {
+ return session_callback_->load_state();
+}
+
+uint64 HttpTransactionWinHttp::GetUploadProgress() const {
+ return upload_progress_;
+}
+
+void HttpTransactionWinHttp::DoCallback(int rv) {
+ DCHECK(rv != ERR_IO_PENDING);
+ DCHECK(callback_);
+
+ // since Run may result in Read being called, clear callback_ up front.
+ CompletionCallback* c = callback_;
+ callback_ = NULL;
+ c->Run(rv);
+}
+
+bool HttpTransactionWinHttp::OpenRequest() {
+ DCHECK(!connect_handle_);
+ DCHECK(!request_handle_);
+
+ const GURL& url = request_->url;
+ const std::string& scheme = url.scheme();
+
+ // Flags passed to WinHttpOpenRequest. Disable any conversion WinHttp
+ // might perform on our URL string. We handle the escaping ourselves.
+ DWORD open_flags = WINHTTP_FLAG_ESCAPE_DISABLE |
+ WINHTTP_FLAG_ESCAPE_DISABLE_QUERY |
+ WINHTTP_FLAG_NULL_CODEPAGE;
+
+ // We should only be dealing with HTTP at this point:
+ DCHECK(LowerCaseEqualsASCII(scheme, "http") ||
+ LowerCaseEqualsASCII(scheme, "https"));
+
+ int in_port = url.IntPort();
+ DCHECK(in_port != url_parse::PORT_INVALID) <<
+ "Valid URLs should have valid ports";
+
+ // Map to port numbers that Windows expects.
+ INTERNET_PORT port = in_port;
+ if (LowerCaseEqualsASCII(scheme, "https")) {
+ is_https_ = true;
+ open_flags |= WINHTTP_FLAG_SECURE;
+ if (in_port == url_parse::PORT_UNSPECIFIED)
+ port = INTERNET_DEFAULT_HTTPS_PORT;
+ } else {
+ if (in_port == url_parse::PORT_UNSPECIFIED)
+ port = INTERNET_DEFAULT_HTTP_PORT;
+ }
+
+ const std::string& host = url.host();
+
+ // Use the primary session handle unless we are talking to a TLS-intolerant
+ // server.
+ //
+ // Since the SSL protocol versions enabled are an option of a session
+ // handle, supporting TLS-intolerant servers unfortunately requires opening
+ // an alternative session in which TLS 1.0 is disabled.
+ HINTERNET internet = session_->internet();
+ if (is_tls_intolerant_) {
+ if (!session_->internet_no_tls() && !session_->InitNoTLS()) {
+ DLOG(ERROR) << "unable to create the no-TLS alternative internet";
+ return false;
+ }
+ internet = session_->internet_no_tls();
+ }
+
+ // This function operates synchronously.
+ connect_handle_ =
+ WinHttpConnect(internet, ASCIIToWide(host).c_str(), port, 0);
+ if (!connect_handle_) {
+ DLOG(ERROR) << "WinHttpConnect failed: " << GetLastError();
+ return false;
+ }
+
+ std::string request_path = url.PathForRequest();
+
+ // This function operates synchronously.
+ request_handle_ =
+ WinHttpOpenRequest(connect_handle_,
+ ASCIIToWide(request_->method).c_str(),
+ ASCIIToWide(request_path).c_str(),
+ NULL, // use HTTP/1.1
+ WINHTTP_NO_REFERER, // none
+ WINHTTP_DEFAULT_ACCEPT_TYPES, // none
+ open_flags);
+ if (!request_handle_) {
+ DLOG(ERROR) << "WinHttpOpenRequest failed: " << GetLastError();
+ return false;
+ }
+
+ // TODO(darin): we may wish to prune-back the set of notifications we receive
+ WINHTTP_STATUS_CALLBACK old_callback = WinHttpSetStatusCallback(
+ request_handle_, StatusCallback,
+ WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, NULL);
+ DCHECK(old_callback == NULL);
+ if (old_callback == WINHTTP_INVALID_STATUS_CALLBACK) {
+ DLOG(ERROR) << "WinHttpSetStatusCallback failed:" << GetLastError();
+ return false;
+ }
+
+ DWORD_PTR ctx = reinterpret_cast<DWORD_PTR>(session_callback_.get());
+ if (!WinHttpSetOption(request_handle_, WINHTTP_OPTION_CONTEXT_VALUE,
+ &ctx, sizeof(ctx))) {
+ DLOG(ERROR) << "WinHttpSetOption context value failed:" << GetLastError();
+ return false;
+ }
+
+ // We just associated a status callback context value with the request
+ // handle.
+ session_callback_->AddRef(); // balanced in OnHandleClosing
+ session_->AddRefBySessionCallback();
+
+ // We have our own cookie and redirect management.
+ DWORD options = WINHTTP_DISABLE_COOKIES |
+ WINHTTP_DISABLE_REDIRECTS;
+
+ if (!WinHttpSetOption(request_handle_, WINHTTP_OPTION_DISABLE_FEATURE,
+ &options, sizeof(options))) {
+ DLOG(ERROR) << "WinHttpSetOption disable feature failed:" << GetLastError();
+ return false;
+ }
+
+ // Disable auto-login for Negotiate and NTLM auth methods.
+ DWORD security_level = WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;
+ if (!WinHttpSetOption(request_handle_, WINHTTP_OPTION_AUTOLOGON_POLICY,
+ &security_level, sizeof(security_level))) {
+ DLOG(ERROR) << "WinHttpSetOption autologon failed: " << GetLastError();
+ return false;
+ }
+
+ // Add request headers. WinHttp is known to convert the headers to bytes
+ // using the system charset converter, so we use the same converter to map
+ // our request headers to UTF-16 before handing the data to WinHttp.
+ std::wstring request_headers = NativeMBToWide(GetRequestHeaders());
+
+ DWORD len = static_cast<DWORD>(request_headers.size());
+ if (!WinHttpAddRequestHeaders(request_handle_,
+ request_headers.c_str(),
+ len,
+ WINHTTP_ADDREQ_FLAG_ADD |
+ WINHTTP_ADDREQ_FLAG_REPLACE)) {
+ DLOG(ERROR) << "WinHttpAddRequestHeaders failed: " << GetLastError();
+ return false;
+ }
+
+ return true;
+}
+
+int HttpTransactionWinHttp::SendRequest() {
+ DCHECK(request_handle_);
+
+ // Apply any authentication (username/password) we might have.
+ ApplyAuth();
+
+ // Apply any proxy info.
+ proxy_info_.Apply(request_handle_);
+
+ // Check SSL server certificate revocation.
+ if (is_https_) {
+ bool ignore_cert_rev = (load_flags_ & LOAD_IGNORE_CERT_REVOCATION) != 0;
+ GURL origin = request_->url.GetOrigin();
+ const std::string& origin_spec = origin.spec();
+ if (ignore_cert_rev)
+ session_->IgnoreCertRev(origin_spec);
+ else if (session_->ShouldIgnoreCertRev(origin_spec))
+ ignore_cert_rev = true;
+
+ if (session_->rev_checking_enabled() && !ignore_cert_rev) {
+ DWORD options = WINHTTP_ENABLE_SSL_REVOCATION;
+ if (!WinHttpSetOption(request_handle_, WINHTTP_OPTION_ENABLE_FEATURE,
+ &options, sizeof(options))) {
+ DLOG(ERROR) << "WinHttpSetOption failed: " << GetLastError();
+ return TranslateLastOSError();
+ }
+ rev_checking_enabled_ = true;
+ }
+ }
+
+ const int kCertFlags = LOAD_IGNORE_CERT_COMMON_NAME_INVALID |
+ LOAD_IGNORE_CERT_DATE_INVALID |
+ LOAD_IGNORE_CERT_AUTHORITY_INVALID |
+ LOAD_IGNORE_CERT_WRONG_USAGE;
+
+ if (load_flags_ & kCertFlags) {
+ DWORD security_flags;
+ DWORD length = sizeof(security_flags);
+
+ if (!WinHttpQueryOption(request_handle_,
+ WINHTTP_OPTION_SECURITY_FLAGS,
+ &security_flags,
+ &length)) {
+ NOTREACHED() << "WinHttpQueryOption failed.";
+ return TranslateLastOSError();
+ }
+
+ // On Vista, WinHttpSetOption() fails with an incorrect parameter error.
+ // WinHttpQueryOption() sets an undocumented flag (0x01000000, which seems
+ // to be a query-only flag) in security_flags that causes this error. To
+ // work-around it, we only keep the documented error flags.
+ security_flags &= (SECURITY_FLAG_IGNORE_UNKNOWN_CA |
+ SECURITY_FLAG_IGNORE_CERT_DATE_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_CN_INVALID |
+ SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE);
+
+ if (load_flags_ & LOAD_IGNORE_CERT_COMMON_NAME_INVALID)
+ security_flags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
+
+ if (load_flags_ & LOAD_IGNORE_CERT_DATE_INVALID)
+ security_flags |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;
+
+ if (load_flags_ & LOAD_IGNORE_CERT_AUTHORITY_INVALID)
+ security_flags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
+
+ if (load_flags_ & LOAD_IGNORE_CERT_WRONG_USAGE)
+ security_flags |= SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE;
+
+ if (!WinHttpSetOption(request_handle_,
+ WINHTTP_OPTION_SECURITY_FLAGS,
+ &security_flags,
+ sizeof(security_flags))) {
+ NOTREACHED() << "WinHttpSetOption failed.";
+ return TranslateLastOSError();
+ }
+ }
+
+ response_.request_time = Time::Now();
+
+ DWORD total_size = 0;
+ if (request_->upload_data) {
+ upload_stream_.reset(new UploadDataStream(request_->upload_data));
+ uint64 upload_len = upload_stream_->size();
+ if (upload_len == 0) {
+ upload_stream_.reset();
+ } else {
+ // TODO(darin): no way to support >4GB uploads w/ WinHttp?
+ if (upload_len > static_cast<uint64>(DWORD(-1))) {
+ NOTREACHED() << "upload length is too large";
+ return ERR_FAILED;
+ }
+
+ total_size = static_cast<DWORD>(upload_len);
+ }
+ }
+
+ if (request_submitted_) {
+ request_submitted_ = false;
+ session_->request_throttle()->NotifyRequestDone(connect_peer_);
+ }
+ if (proxy_info_.is_direct())
+ connect_peer_ = request_->url.GetOrigin().spec();
+ else
+ connect_peer_ = WideToASCII(proxy_info_.proxy_server());
+ DWORD_PTR ctx = reinterpret_cast<DWORD_PTR>(session_callback_.get());
+ if (!session_->request_throttle()->SubmitRequest(connect_peer_,
+ request_handle_,
+ total_size, ctx)) {
+ last_error_ = GetLastError();
+ DLOG(ERROR) << "WinHttpSendRequest failed: " << last_error_;
+ return TranslateOSError(last_error_);
+ }
+
+ request_submitted_ = true;
+ return ERR_IO_PENDING;
+}
+
+// Called after certain failures of SendRequest to reset the members opened
+// or modified in OpenRequest and SendRequest and call OpenRequest again.
+bool HttpTransactionWinHttp::ReopenRequest() {
+ DCHECK(connect_handle_);
+ DCHECK(request_handle_);
+
+ session_callback_->set_handle_closing_event(
+ session_->handle_closing_event());
+ WinHttpCloseHandle(request_handle_);
+ WaitForSingleObject(session_->handle_closing_event(), INFINITE);
+ request_handle_ = NULL;
+ WinHttpCloseHandle(connect_handle_);
+ connect_handle_ = NULL;
+ session_callback_->ResetForNewRequest();
+
+ // Don't need to reset is_https_, rev_checking_enabled_, and
+ // response_.request_time.
+
+ return OpenRequest();
+}
+
+int HttpTransactionWinHttp::DidResolveProxy() {
+ // We may already have a request handle if we are changing proxy config.
+ if (!(request_handle_ ? ReopenRequest() : OpenRequest()))
+ return TranslateLastOSError();
+
+ return SendRequest();
+}
+
+int HttpTransactionWinHttp::DidReceiveError(DWORD error,
+ DWORD secure_failure) {
+ DCHECK(error != ERROR_SUCCESS);
+
+ session_callback_->set_load_state(LOAD_STATE_IDLE);
+ need_to_wait_for_handle_closing_ = false;
+
+ int rv;
+
+ if (error == ERROR_WINHTTP_RESEND_REQUEST) {
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ return Restart(callback);
+ }
+
+ if (error == ERROR_WINHTTP_NAME_NOT_RESOLVED ||
+ error == ERROR_WINHTTP_CANNOT_CONNECT ||
+ error == ERROR_WINHTTP_TIMEOUT) {
+ // These errors may have been caused by a proxy configuration error, or
+ // rather they may go away by trying a different proxy config! If we have
+ // an explicit proxy config, then we just have to report an error.
+ if (!have_proxy_info_) {
+ rv = session_->proxy_service()->ReconsiderProxyAfterError(
+ request_->url, &proxy_info_, &proxy_callback_, &pac_request_);
+ if (rv == OK) // got new proxy info to try
+ return DidResolveProxy();
+ if (rv == ERR_IO_PENDING) // waiting to resolve proxy info
+ return rv;
+ // else, fall through and just report an error.
+ }
+ }
+
+ if (error == ERROR_WINHTTP_SECURE_FAILURE) {
+ DWORD filtered_secure_failure = FilterSecureFailure(secure_failure,
+ load_flags_);
+ // If load_flags_ ignores all the errors in secure_failure, we shouldn't
+ // get the ERROR_WINHTTP_SECURE_FAILURE error.
+ DCHECK(filtered_secure_failure || !secure_failure);
+ error = MapSecureFailureToError(filtered_secure_failure);
+ }
+
+ last_error_ = error;
+ rv = TranslateOSError(error);
+
+ if (rv == ERR_SSL_PROTOCOL_ERROR &&
+ !session_callback_->request_was_probably_sent() &&
+ session_->tls_enabled() && !is_tls_intolerant_) {
+ // The server might be TLS intolerant. Downgrade to SSL 3.0 and retry.
+ is_tls_intolerant_ = true;
+ if (!ReopenRequest())
+ return TranslateLastOSError();
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ return Restart(callback);
+ }
+ if (rv == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ // TODO(wtc): Bug 1230409: We don't support SSL client authentication yet.
+ // For now we set a null client certificate, which works on Vista and
+ // later. On XP, this fails with ERROR_INVALID_PARAMETER (87). This
+ // allows us to access servers that request but do not require client
+ // certificates.
+ if (WinHttpSetOption(request_handle_,
+ WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
+ WINHTTP_NO_CLIENT_CERT_CONTEXT, 0)) {
+ CompletionCallback* callback = callback_;
+ callback_ = NULL;
+ return Restart(callback);
+ }
+ }
+ if (IsCertificateError(rv)) {
+ response_.ssl_info.cert = GetServerCertificate();
+ response_.ssl_info.cert_status =
+ MapSecureFailureToCertStatus(secure_failure);
+ CertStatusCache* cert_status_cache = session_->cert_status_cache();
+ cert_status_cache->SetCertStatus(*response_.ssl_info.cert,
+ request_->url.host(),
+ response_.ssl_info.cert_status);
+ }
+
+ return rv;
+}
+
+int HttpTransactionWinHttp::DidSendRequest() {
+ BOOL ok;
+ if (upload_stream_.get() && upload_stream_->buf_len() > 0) {
+ // write upload data
+ DWORD buf_len = static_cast<DWORD>(upload_stream_->buf_len());
+ ok = WinHttpWriteData(request_handle_,
+ upload_stream_->buf(),
+ buf_len,
+ NULL);
+ if (ok)
+ need_to_wait_for_handle_closing_ = true;
+ } else {
+ upload_stream_.reset();
+ need_to_wait_for_handle_closing_ = false;
+
+ // begin receiving the response
+ ok = WinHttpReceiveResponse(request_handle_, NULL);
+ }
+ return ok ? ERR_IO_PENDING : TranslateLastOSError();
+}
+
+int HttpTransactionWinHttp::DidWriteData(DWORD num_bytes) {
+ DCHECK(upload_stream_.get());
+ DCHECK(num_bytes > 0);
+
+ upload_stream_->DidConsume(num_bytes);
+ upload_progress_ = upload_stream_->position();
+
+ // OK, we are ready to start receiving the response. The code in
+ // DidSendRequest does exactly what we want!
+ return DidSendRequest();
+}
+
+int HttpTransactionWinHttp::DidReadData(DWORD num_bytes) {
+ int rv = static_cast<int>(num_bytes);
+ DCHECK(rv >= 0);
+
+ session_callback_->set_load_state(LOAD_STATE_IDLE);
+ session_callback_->reduce_bytes_available(rv);
+ need_to_wait_for_handle_closing_ = false;
+
+ if (content_length_remaining_ > 0) {
+ content_length_remaining_ -= rv;
+
+ // HTTP/1.0 servers are known to send more data than they report in their
+ // Content-Length header (in the non-keepalive case). IE and Moz both
+ // tolerate this situation, and therefore so must we.
+ if (content_length_remaining_ < 0)
+ content_length_remaining_ = 0;
+ }
+
+ return rv;
+}
+
+int HttpTransactionWinHttp::DidReceiveHeaders() {
+ session_callback_->set_load_state(LOAD_STATE_IDLE);
+
+ DWORD size = 0;
+ if (!WinHttpQueryHeaders(request_handle_,
+ WINHTTP_QUERY_RAW_HEADERS,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ NULL,
+ &size,
+ WINHTTP_NO_HEADER_INDEX)) {
+ DWORD error = GetLastError();
+ if (error != ERROR_INSUFFICIENT_BUFFER) {
+ DLOG(ERROR) << "WinHttpQueryHeaders failed: " << GetLastError();
+ return TranslateLastOSError();
+ }
+ // OK, size should tell us how much to allocate...
+ DCHECK(size > 0);
+ }
+
+ std::wstring raw_headers;
+
+ // 'size' is the number of bytes rather than the number of characters.
+ DCHECK(size % 2 == 0);
+ if (!WinHttpQueryHeaders(request_handle_,
+ WINHTTP_QUERY_RAW_HEADERS,
+ WINHTTP_HEADER_NAME_BY_INDEX,
+ WriteInto(&raw_headers, size/2 + 1),
+ &size,
+ WINHTTP_NO_HEADER_INDEX)) {
+ DLOG(ERROR) << "WinHttpQueryHeaders failed: " << GetLastError();
+ return TranslateLastOSError();
+ }
+
+ response_.response_time = Time::Now();
+
+ // From experimentation, it appears that WinHttp translates non-ASCII bytes
+ // found in the response headers to UTF-16 assuming that they are encoded
+ // using the default system charset. We attempt to undo that here.
+ response_.headers = new HttpResponseHeaders(WideToNativeMB(raw_headers));
+
+ // WinHTTP truncates a response longer than 2GB. Perhaps it stores the
+ // response's content length in a signed 32-bit integer. We fail rather
+ // than reading a truncated response.
+ if (response_.headers->GetContentLength() > 0x80000000)
+ return ERR_FILE_TOO_BIG;
+
+ response_.vary_data.Init(*request_, *response_.headers);
+ PopulateAuthChallenge();
+
+ // Unfortunately, WinHttp does not close the connection when a non-keepalive
+ // response is _not_ followed by the server closing the connection. So, we
+ // attempt to hack around this bug.
+ if (!response_.headers->IsKeepAlive())
+ content_length_remaining_ = response_.headers->GetContentLength();
+
+ return OK;
+}
+
+// Populates response_.auth_challenge with the authentication challenge info.
+void HttpTransactionWinHttp::PopulateAuthChallenge() {
+ DCHECK(response_.headers);
+
+ int status = response_.headers->response_code();
+ if (status != 401 && status != 407)
+ return;
+
+ scoped_refptr<AuthChallengeInfo> auth_info = new AuthChallengeInfo;
+
+ auth_info->is_proxy = (status == 407);
+
+ if (auth_info->is_proxy) {
+ // TODO(wtc): get the proxy server host from proxy_info_.
+ // TODO(wtc): internationalize?
+ auth_info->host = L"proxy";
+ } else {
+ auth_info->host = ASCIIToWide(request_->url.host());
+ }
+
+ // Here we're checking only the first *-Authenticate header. When a server
+ // responds with multiple methods, we use the first.
+ // TODO(wtc): Bug 1124614: look at all the authentication methods and pick
+ // the best one that we support. failover to other authentication methods.
+ std::string header_value;
+ std::string header_name = auth_info->is_proxy ?
+ "Proxy-Authenticate" : "WWW-Authenticate";
+ if (!response_.headers->EnumerateHeader(NULL, header_name, &header_value))
+ return;
+
+ // TODO(darin): Need to support RFC 2047 encoded realm strings. For now, we
+ // just match Mozilla and limit our support to ASCII realm strings.
+ std::wstring auth_header = ASCIIToWide(header_value);
+
+ // auth_header is a string which looks like:
+ // Digest realm="The Awesome Site", domain="/page.html", ...
+ std::wstring::const_iterator space = find(auth_header.begin(),
+ auth_header.end(), ' ');
+ auth_info->scheme.assign(auth_header.begin(), space);
+ auth_info->realm = net_util::GetHeaderParamValue(auth_header, L"realm");
+
+ // Now auth_info has been fully populated. Before we swap it with
+ // response_.auth_challenge, update the auth cache key and remove any
+ // presumably incorrect auth data in the auth cache.
+ std::string* auth_cache_key;
+ AuthData* auth;
+ if (auth_info->is_proxy) {
+ if (!proxy_auth_)
+ proxy_auth_ = new AuthData;
+ auth = proxy_auth_;
+ auth_cache_key = &proxy_auth_cache_key_;
+ } else {
+ if (!server_auth_)
+ server_auth_ = new AuthData;
+ auth = server_auth_;
+ auth_cache_key = &server_auth_cache_key_;
+ }
+ *auth_cache_key = AuthCache::HttpKey(request_->url, *auth_info);
+ DCHECK(!auth_cache_key->empty());
+ auth->scheme = auth_info->scheme;
+ if (auth->state == AUTH_STATE_HAVE_AUTH) {
+ session_->auth_cache()->Remove(*auth_cache_key);
+ auth->state = AUTH_STATE_NEED_AUTH;
+ }
+
+ response_.auth_challenge.swap(auth_info);
+}
+
+static DWORD StringToAuthScheme(const std::wstring& scheme) {
+ if (LowerCaseEqualsASCII(scheme, "basic"))
+ return WINHTTP_AUTH_SCHEME_BASIC;
+ if (LowerCaseEqualsASCII(scheme, "digest"))
+ return WINHTTP_AUTH_SCHEME_DIGEST;
+ if (LowerCaseEqualsASCII(scheme, "ntlm"))
+ return WINHTTP_AUTH_SCHEME_NTLM;
+ if (LowerCaseEqualsASCII(scheme, "negotiate"))
+ return WINHTTP_AUTH_SCHEME_NEGOTIATE;
+ if (LowerCaseEqualsASCII(scheme, "passport1.4"))
+ return WINHTTP_AUTH_SCHEME_PASSPORT;
+ return 0;
+}
+
+// Applies authentication credentials to request_handle_.
+void HttpTransactionWinHttp::ApplyAuth() {
+ DWORD auth_scheme;
+ BOOL rv;
+ if (proxy_auth_ && proxy_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ // Add auth data to cache.
+ DCHECK(!proxy_auth_cache_key_.empty());
+ session_->auth_cache()->Add(proxy_auth_cache_key_, proxy_auth_);
+ auth_scheme = StringToAuthScheme(proxy_auth_->scheme);
+ if (auth_scheme == 0)
+ return;
+
+ rv = WinHttpSetCredentials(request_handle_,
+ WINHTTP_AUTH_TARGET_PROXY,
+ auth_scheme,
+ proxy_auth_->username.c_str(),
+ proxy_auth_->password.c_str(),
+ NULL);
+ }
+
+ // First, check if we have auth, then check URL.
+ // That way a user can re-enter credentials, and we'll try with their
+ // latest input rather than always trying what they specified
+ // in the URL (if anything).
+ if (server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ // Add auth data to cache.
+ DCHECK(!server_auth_cache_key_.empty());
+ session_->auth_cache()->Add(server_auth_cache_key_, server_auth_);
+ auth_scheme = StringToAuthScheme(server_auth_->scheme);
+ if (auth_scheme == 0)
+ return;
+
+ rv = WinHttpSetCredentials(request_handle_,
+ WINHTTP_AUTH_TARGET_SERVER,
+ auth_scheme,
+ server_auth_->username.c_str(),
+ server_auth_->password.c_str(),
+ NULL);
+ } else if (request_->url.has_username()) {
+ // TODO(wtc) It may be necessary to unescape the username and password
+ // after extracting them from the URL. We should be careful about
+ // embedded nulls in that case.
+ rv = WinHttpSetCredentials(request_handle_,
+ WINHTTP_AUTH_TARGET_SERVER,
+ WINHTTP_AUTH_SCHEME_BASIC, // TODO(wtc)
+ ASCIIToWide(request_->url.username()).c_str(),
+ ASCIIToWide(request_->url.password()).c_str(),
+ NULL);
+ }
+}
+
+void HttpTransactionWinHttp::OnProxyInfoAvailable(int result) {
+ if (result != OK) {
+ DLOG(WARNING) << "failed to get proxy info: " << result;
+ proxy_info_.UseDirect();
+ }
+
+ // Balances extra reference taken when proxy resolution was initiated.
+ session_callback_->Release();
+
+ pac_request_ = NULL;
+
+ // Since OnProxyInfoAvailable is always called asynchronously (via the
+ // message loop), we need to trap any errors and pass them to the consumer
+ // via their completion callback.
+
+ int rv = DidResolveProxy();
+ if (rv == ERR_IO_PENDING) {
+ session_callback_->AddRef(); // balanced when callback runs.
+ } else {
+ DoCallback(rv);
+ }
+}
+
+std::string HttpTransactionWinHttp::GetRequestHeaders() const {
+ std::string headers;
+
+ if (!request_->user_agent.empty())
+ headers += "User-Agent: " + request_->user_agent + "\r\n";
+
+ // Our consumer should have made sure that this is a safe referrer. See for
+ // instance WebCore::FrameLoader::HideReferrer.
+ if (request_->referrer.is_valid())
+ headers += "Referer: " + request_->referrer.spec() + "\r\n";
+
+ // IE and Safari do this. Presumably it is to support sending a HEAD request
+ // to an URL that only expects to be sent a POST or some other method that
+ // normally would have a message body.
+ if (request_->method == "HEAD")
+ headers += "Content-Length: 0\r\n";
+
+ // Honor load flags that impact proxy caches.
+ if (request_->load_flags & LOAD_BYPASS_CACHE) {
+ headers += "Pragma: no-cache\r\nCache-Control: no-cache\r\n";
+ } else if (request_->load_flags & LOAD_VALIDATE_CACHE) {
+ headers += "Cache-Control: max-age=0\r\n";
+ }
+
+ // TODO(darin): Prune out duplicate headers?
+ headers += request_->extra_headers;
+
+ return headers;
+}
+
+// Retrieves the SSL server certificate associated with the transaction.
+// The caller is responsible for freeing the certificate.
+X509Certificate* HttpTransactionWinHttp::GetServerCertificate() const {
+ DCHECK(is_https_);
+ PCCERT_CONTEXT cert_context = NULL;
+ DWORD length = sizeof(cert_context);
+ if (!WinHttpQueryOption(request_handle_,
+ WINHTTP_OPTION_SERVER_CERT_CONTEXT,
+ &cert_context,
+ &length)) {
+ return NULL;
+ }
+ // cert_context may be NULL here even though WinHttpQueryOption succeeded.
+ // For example, a proxy server may return a 404 error page to report the
+ // DNS resolution failure of the server's hostname.
+ if (!cert_context)
+ return NULL;
+ return X509Certificate::CreateFromHandle(cert_context);
+}
+
+// Retrieves the security strength, in bits, of the SSL cipher suite
+// associated with the transaction.
+int HttpTransactionWinHttp::GetSecurityBits() const {
+ DCHECK(is_https_);
+ DWORD key_bits = 0;
+ DWORD length = sizeof(key_bits);
+ if (!WinHttpQueryOption(request_handle_,
+ WINHTTP_OPTION_SECURITY_KEY_BITNESS,
+ &key_bits,
+ &length)) {
+ return -1;
+ }
+ return key_bits;
+}
+
+void HttpTransactionWinHttp::PopulateSSLInfo(DWORD secure_failure) {
+ if (is_https_) {
+ response_.ssl_info.cert = GetServerCertificate();
+ response_.ssl_info.security_bits = GetSecurityBits();
+ // If there is no cert (such as when the proxy server makes up a
+ // 404 response to report a server name resolution error), don't set
+ // the cert status.
+ if (!response_.ssl_info.cert)
+ return;
+ response_.ssl_info.cert_status =
+ MapSecureFailureToCertStatus(secure_failure);
+ // WinHTTP does not always return a cert status once we ignored errors
+ // for a cert. (Our experiments showed that WinHTTP reliably returns a
+ // cert status only when there are unignored errors or when we resend a
+ // request with the errors ignored.) So we have to remember what the
+ // last status was for a cert. Note that if the cert status changes
+ // from error to OK, we won't know that. If we have never stored our
+ // status in the CertStatusCache (meaning no errors so far), then it is
+ // OK (0).
+ CertStatusCache* cert_status_cache = session_->cert_status_cache();
+ if (net::IsCertStatusError(response_.ssl_info.cert_status)) {
+ cert_status_cache->SetCertStatus(*response_.ssl_info.cert,
+ request_->url.host(),
+ response_.ssl_info.cert_status);
+ } else {
+ response_.ssl_info.cert_status |=
+ cert_status_cache->GetCertStatus(*response_.ssl_info.cert,
+ request_->url.host()) &
+ net::CERT_STATUS_ALL_ERRORS;
+ }
+
+ if (rev_checking_enabled_)
+ response_.ssl_info.cert_status |= CERT_STATUS_REV_CHECKING_ENABLED;
+ } else {
+ // If this is not https, we should not get a cert status.
+ DCHECK(!secure_failure);
+ }
+}
+
+void HttpTransactionWinHttp::HandleStatusCallback(DWORD status,
+ DWORD_PTR result,
+ DWORD error,
+ DWORD secure_failure) {
+ int rv = ERR_FAILED;
+
+ switch (status) {
+ case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
+ rv = DidReceiveError(error, secure_failure);
+ break;
+ case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
+ PopulateSSLInfo(secure_failure);
+ rv = DidSendRequest();
+ break;
+ case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
+ rv = DidWriteData(static_cast<DWORD>(result));
+ break;
+ case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
+ rv = DidReceiveHeaders();
+ break;
+ case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
+ rv = DidReadData(static_cast<DWORD>(result));
+ break;
+ default:
+ NOTREACHED() << "unexpected status code";
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ session_callback_->AddRef(); // balanced when callback runs.
+ } else if (callback_) {
+ DoCallback(rv);
+ }
+}
+
+} // namespace net