diff options
Diffstat (limited to 'net/http/http_transaction_winhttp.cc')
-rw-r--r-- | net/http/http_transaction_winhttp.cc | 1807 |
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 |