diff options
Diffstat (limited to 'net/http')
49 files changed, 13709 insertions, 0 deletions
diff --git a/net/http/cert_status_cache.cc b/net/http/cert_status_cache.cc new file mode 100644 index 0000000..82741f7 --- /dev/null +++ b/net/http/cert_status_cache.cc @@ -0,0 +1,89 @@ +// 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/base/cert_status_flags.h" +#include "net/http/cert_status_cache.h" + +namespace net { + +CertStatusCache::CertStatusCache() { +} + +CertStatusCache::~CertStatusCache() { + for (HostMap::iterator iter = fingerprint_to_bad_hosts_.begin(); + iter != fingerprint_to_bad_hosts_.end(); ++iter) { + delete iter->second; + } +} + +int CertStatusCache::GetCertStatus(const X509Certificate& cert, + const std::string& host) const { + StatusMap::const_iterator iter = + fingerprint_to_cert_status_.find(cert.fingerprint()); + if (iter != fingerprint_to_cert_status_.end()) { + int cert_status = iter->second; + + // We get the CERT_STATUS_COMMON_NAME_INVALID error based on the host. + HostMap::const_iterator fp_iter = + fingerprint_to_bad_hosts_.find(cert.fingerprint()); + if (fp_iter != fingerprint_to_bad_hosts_.end()) { + StringSet* bad_hosts = fp_iter->second; + StringSet::const_iterator host_iter = bad_hosts->find(host); + if (host_iter != bad_hosts->end()) + cert_status |= net::CERT_STATUS_COMMON_NAME_INVALID; + } + + return cert_status; + } + return 0; // The cert has never had errors. +} + +void CertStatusCache::SetCertStatus(const X509Certificate& cert, + const std::string& host, + int status) { + // We store the CERT_STATUS_COMMON_NAME_INVALID status separately as it is + // host related. + fingerprint_to_cert_status_[cert.fingerprint()] = + status & ~net::CERT_STATUS_COMMON_NAME_INVALID; + + if ((status & net::CERT_STATUS_COMMON_NAME_INVALID) != 0) { + StringSet* bad_hosts; + HostMap::const_iterator iter = + fingerprint_to_bad_hosts_.find(cert.fingerprint()); + if (iter == fingerprint_to_bad_hosts_.end()) { + bad_hosts = new StringSet; + fingerprint_to_bad_hosts_[cert.fingerprint()] = bad_hosts; + } else { + bad_hosts = iter->second; + } + bad_hosts->insert(host); + } +} + +}
\ No newline at end of file diff --git a/net/http/cert_status_cache.h b/net/http/cert_status_cache.h new file mode 100644 index 0000000..4f5f7b9 --- /dev/null +++ b/net/http/cert_status_cache.h @@ -0,0 +1,74 @@ +// 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. + +#ifndef NET_HTTP_CERT_STATUS_CACHE_H +#define NET_HTTP_CERT_STATUS_CACHE_H + +#include <vector> +#include <map> + +#include "net/base/x509_certificate.h" + +// This class is used to remember the status of certificates, as WinHTTP +// does not report errors once it has been told to ignore them. +// It only exists because of the WinHTTP bug. +// IMPORTANT: this class is not thread-safe. + +namespace net { + +class CertStatusCache { + public: + CertStatusCache(); + ~CertStatusCache(); + + int GetCertStatus(const X509Certificate& cert, + const std::string& host_name) const; + void SetCertStatus(const X509Certificate& cert, + const std::string& host_name, + int status); + + private: + typedef std::map<X509Certificate::Fingerprint, int, + X509Certificate::FingerprintLessThan> StatusMap; + typedef std::set<std::string> StringSet; + typedef std::map<X509Certificate::Fingerprint, StringSet*, + X509Certificate::FingerprintLessThan> HostMap; + + StatusMap fingerprint_to_cert_status_; + + // We keep a map for each cert to the list of host names that have been marked + // with the CN invalid error, as that error is host name specific. + HostMap fingerprint_to_bad_hosts_; + + DISALLOW_EVIL_CONSTRUCTORS(CertStatusCache); +}; + +} // namespace net + +#endif // NET_HTTP_CERT_STATUS_CACHE_H diff --git a/net/http/http_atom_list.h b/net/http/http_atom_list.h new file mode 100644 index 0000000..7ac67c5 --- /dev/null +++ b/net/http/http_atom_list.h @@ -0,0 +1,86 @@ +// 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. + +HTTP_ATOM(ACCEPT) +HTTP_ATOM(ACCEPT_CHARSET) +HTTP_ATOM(ACCEPT_ENCODING) +HTTP_ATOM(ACCEPT_LANGUAGE) +HTTP_ATOM(ACCEPT_RANGES) +HTTP_ATOM(AGE) +HTTP_ATOM(ALLOW) +HTTP_ATOM(AUTHORIZATION) +HTTP_ATOM(CACHE_CONTROL) +HTTP_ATOM(CONNECTION) +HTTP_ATOM(CONTENT_BASE) +HTTP_ATOM(CONTENT_DISPOSITION) +HTTP_ATOM(CONTENT_ENCODING) +HTTP_ATOM(CONTENT_LANGUAGE) +HTTP_ATOM(CONTENT_LENGTH) +HTTP_ATOM(CONTENT_LOCATION) +HTTP_ATOM(CONTENT_MD5) +HTTP_ATOM(CONTENT_RANGE) +HTTP_ATOM(CONTENT_TRANSFER_ENCODING) +HTTP_ATOM(CONTENT_TYPE) +HTTP_ATOM(COOKIE) +HTTP_ATOM(DATE) +HTTP_ATOM(DERIVED_FROM) +HTTP_ATOM(ETAG) +HTTP_ATOM(EXPECT) +HTTP_ATOM(EXPIRES) +HTTP_ATOM(FORWARDED) +HTTP_ATOM(FROM) +HTTP_ATOM(HOST) +HTTP_ATOM(IF_MATCH) +HTTP_ATOM(IF_MODIFIED_SINCE) +HTTP_ATOM(IF_NONE_MATCH) +HTTP_ATOM(IF_RANGE) +HTTP_ATOM(IF_UNMODIFIED_SINCE) +HTTP_ATOM(LAST_MODIFIED) +HTTP_ATOM(LINK) +HTTP_ATOM(LOCATION) +HTTP_ATOM(MAX_FORWARDS) +HTTP_ATOM(MESSAGE_ID) +HTTP_ATOM(PRAGMA) +HTTP_ATOM(PROXY_AUTHENTICATE) +HTTP_ATOM(PROXY_AUTHORIZATION) +HTTP_ATOM(PROXY_CONNECTION) +HTTP_ATOM(RANGE) +HTTP_ATOM(REFERER) +HTTP_ATOM(REFRESH) +HTTP_ATOM(RETRY_AFTER) +HTTP_ATOM(SERVER) +HTTP_ATOM(SET_COOKIE) +HTTP_ATOM(TITLE) +HTTP_ATOM(TRANSFER_ENCODING) +HTTP_ATOM(UPGRADE) +HTTP_ATOM(USER_AGENT) +HTTP_ATOM(VARY) +HTTP_ATOM(VIA) +HTTP_ATOM(WARNING) +HTTP_ATOM(WWW_AUTHENTICATE) diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc new file mode 100644 index 0000000..50e6de0 --- /dev/null +++ b/net/http/http_cache.cc @@ -0,0 +1,1359 @@ +// 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_cache.h" + +#include <algorithm> + +#include "base/message_loop.h" +#include "base/pickle.h" +#include "base/ref_counted.h" +#include "base/string_util.h" +#include "base/time.h" +#include "net/base/load_flags.h" +#include "net/base/net_errors.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_network_layer.h" +#include "net/http/http_proxy_service.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" +#include "net/http/http_transaction.h" +#include "net/http/http_util.h" + +#pragma warning(disable: 4355) + +namespace net { + +// disk cache entry data indices. +enum { + kResponseInfoIndex, + kResponseContentIndex +}; + +// These values can be bit-wise combined to form the flags field of the +// serialized HttpResponseInfo. +enum { + // The version of the response info used when persisting response info. + RESPONSE_INFO_VERSION = 1, + + // We reserve up to 8 bits for the version number. + RESPONSE_INFO_VERSION_MASK = 0xFF, + + // This bit is set if the response info has a cert at the end. + RESPONSE_INFO_HAS_CERT = 1 << 8, + + // This bit is set if the response info has a security-bits field (security + // strength, in bits, of the SSL connection) at the end. + RESPONSE_INFO_HAS_SECURITY_BITS = 1 << 9, + + // This bit is set if the response info has a cert status at the end. + RESPONSE_INFO_HAS_CERT_STATUS = 1 << 10, + + // This bit is set if the response info has vary header data. + RESPONSE_INFO_HAS_VARY_DATA = 1 << 11, + + // TODO(darin): Add other bits to indicate alternate request methods and + // whether or not we are storing a partial document. For now, we don't + // support storing those. +}; + +//----------------------------------------------------------------------------- + +struct HeaderNameAndValue { + const char* name; + const char* value; +}; + +// If the request includes one of these request headers, then avoid caching +// to avoid getting confused. +static const HeaderNameAndValue kPassThroughHeaders[] = { + { "range", NULL }, // causes unexpected 206s + { "if-modified-since", NULL }, // causes unexpected 304s + { "if-none-match", NULL }, // causes unexpected 304s + { "if-unmodified-since", NULL }, // causes unexpected 412s + { "if-match", NULL }, // causes unexpected 412s + { NULL, NULL } +}; + +// If the request includes one of these request headers, then avoid reusing +// our cached copy if any. +static const HeaderNameAndValue kForceFetchHeaders[] = { + { "cache-control", "no-cache" }, + { "pragma", "no-cache" }, + { NULL, NULL } +}; + +// If the request includes one of these request headers, then force our +// cached copy (if any) to be revalidated before reusing it. +static const HeaderNameAndValue kForceValidateHeaders[] = { + { "cache-control", "max-age=0" }, + { NULL, NULL } +}; + +static bool HeaderMatches(const HttpUtil::HeadersIterator& h, + const HeaderNameAndValue* search) { + for (; search->name; ++search) { + if (!LowerCaseEqualsASCII(h.name_begin(), h.name_end(), search->name)) + continue; + + if (!search->value) + return true; + + HttpUtil::ValuesIterator v(h.values_begin(), h.values_end(), ','); + while (v.GetNext()) { + if (LowerCaseEqualsASCII(v.value_begin(), v.value_end(), search->value)) + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- + +std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request) { + std::string url = request->url.spec(); + if (request->url.has_ref()) + url.erase(url.find_last_of('#')); + + if (mode_ == NORMAL) { + return url; + } + + // In playback and record mode, we cache everything. + + // Lazily initialize. + if (playback_cache_map_ == NULL) + playback_cache_map_.reset(new PlaybackCacheMap()); + + // Each time we request an item from the cache, we tag it with a + // generation number. During playback, multiple fetches for the same + // item will use the same generation number and pull the proper + // instance of an URL from the cache. + int generation = 0; + DCHECK(playback_cache_map_ != NULL); + if (playback_cache_map_->find(url) != playback_cache_map_->end()) + generation = (*playback_cache_map_)[url]; + (*playback_cache_map_)[url] = generation + 1; + + // The key into the cache is GENERATION # + METHOD + URL. + std::string result = IntToString(generation); + result.append(request->method); + result.append(url); + return result; +} + +//----------------------------------------------------------------------------- + +HttpCache::ActiveEntry::ActiveEntry(disk_cache::Entry *e) + : disk_entry(e), + will_process_pending_queue(false), + doomed(false) { +} + +HttpCache::ActiveEntry::~ActiveEntry() { + disk_entry->Close(); +} + +//----------------------------------------------------------------------------- + +class HttpCache::Transaction : public HttpTransaction, + public base::RefCounted<HttpCache::Transaction> { + public: + explicit Transaction(HttpCache* cache) + : request_(NULL), + cache_(cache), + entry_(NULL), + network_trans_(NULL), + callback_(NULL), + mode_(NONE), + read_buf_(NULL), + read_offset_(0), + effective_load_flags_(0), + final_upload_progress_(0), + network_info_callback_(this, &Transaction::OnNetworkInfoAvailable), + network_read_callback_(this, &Transaction::OnNetworkReadCompleted), + cache_read_callback_(this, &Transaction::OnCacheReadCompleted) { + AddRef(); // Balanced in Destroy + } + + // HttpTransaction methods: + virtual void Destroy(); + virtual int Start(const HttpRequestInfo*, CompletionCallback*); + virtual int RestartIgnoringLastError(CompletionCallback*); + virtual int RestartWithAuth(const std::wstring& username, + const std::wstring& password, + CompletionCallback* callback); + virtual int Read(char* buf, int buf_len, CompletionCallback*); + virtual const HttpResponseInfo* GetResponseInfo() const; + virtual LoadState GetLoadState() const; + virtual uint64 GetUploadProgress(void) const; + + // The transaction has the following modes, which apply to how it may access + // its cache entry. + // + // o If the mode of the transaction is NONE, then it is in "pass through" + // mode and all methods just forward to the inner network transaction. + // + // o If the mode of the transaction is only READ, then it may only read from + // the cache entry. + // + // o If the mode of the transaction is only WRITE, then it may only write to + // the cache entry. + // + // o If the mode of the transaction is READ_WRITE, then the transaction may + // optionally modify the cache entry (e.g., possibly corresponding to + // cache validation). + // + enum Mode { + NONE = 0x0, + READ = 0x1, + WRITE = 0x2, + READ_WRITE = READ | WRITE + }; + + Mode mode() const { return mode_; } + + const std::string& key() const { return cache_key_; } + + // Associates this transaction with a cache entry. + int AddToEntry(); + + // Called by the HttpCache when the given disk cache entry becomes accessible + // to the transaction. Returns network error code. + int EntryAvailable(ActiveEntry* entry); + + private: + // This is a helper function used to trigger a completion callback. It may + // only be called if callback_ is non-null. + void DoCallback(int rv); + + // This will trigger the completion callback if appropriate. + int HandleResult(int rv); + + // Set request_ and fields derived from it. + void SetRequest(const HttpRequestInfo* request); + + // Returns true if the request should be handled exclusively by the network + // layer (skipping the cache entirely). + bool ShouldPassThrough(); + + // Returns true if we should force an end-to-end fetch. + bool ShouldBypassCache(); + + // Called to begin reading from the cache. Returns network error code. + int BeginCacheRead(); + + // Called to begin validating the cache entry. Returns network error code. + int BeginCacheValidation(); + + // Called to begin a network transaction. Returns network error code. + int BeginNetworkRequest(); + + // Called to restart a network transaction after an error. Returns network + // error code. + int RestartNetworkRequest(); + + // Called to restart a network transaction with authentication credentials. + // Returns network error code. + int RestartNetworkRequestWithAuth(const std::wstring& username, + const std::wstring& password); + + // Called to determine if we need to validate the cache entry before using it. + bool RequiresValidation(); + + // Called to make the request conditional (to ask the server if the cached + // copy is valid). Returns true if able to make the request conditional. + bool ConditionalizeRequest(); + + // Called to populate response_ from the cache entry. + int ReadResponseInfoFromEntry(); + + // Called to write data to the cache entry. If the write fails, then the + // cache entry is destroyed. Future calls to this function will just do + // nothing without side-effect. + void WriteToEntry(int index, int offset, const char* data, int data_len); + + // Called to write response_ to the cache entry. + void WriteResponseInfoToEntry(); + + // Called to append response data to the cache entry. + void AppendResponseDataToEntry(const char* data, int data_len); + + // Called when we are done writing to the cache entry. + void DoneWritingToEntry(bool success); + + // Called to signal completion of the network transaction's Start method: + void OnNetworkInfoAvailable(int result); + + // Called to signal completion of the network transaction's Read method: + void OnNetworkReadCompleted(int result); + + // Called to signal completion of the cache's ReadData method: + void OnCacheReadCompleted(int result); + + const HttpRequestInfo* request_; + scoped_ptr<HttpRequestInfo> custom_request_; + HttpCache* cache_; + HttpCache::ActiveEntry* entry_; + HttpTransaction* network_trans_; + CompletionCallback* callback_; // consumer's callback + HttpResponseInfo response_; + HttpResponseInfo auth_response_; + std::string cache_key_; + Mode mode_; + char* read_buf_; + int read_offset_; + int effective_load_flags_; + uint64 final_upload_progress_; + CompletionCallbackImpl<Transaction> network_info_callback_; + CompletionCallbackImpl<Transaction> network_read_callback_; + CompletionCallbackImpl<Transaction> cache_read_callback_; +}; + +void HttpCache::Transaction::Destroy() { + if (entry_) { + if (mode_ & WRITE) { + // Assume that this is not a successful write. + cache_->DoneWritingToEntry(entry_, false); + } else { + cache_->DoneReadingFromEntry(entry_, this); + } + } else { + cache_->RemovePendingTransaction(this); + } + + if (network_trans_) + network_trans_->Destroy(); + + // We could still have a cache read in progress, so we just null the cache_ + // pointer to signal that we are dead. See OnCacheReadCompleted. + cache_ = NULL; + + Release(); +} + +int HttpCache::Transaction::Start(const HttpRequestInfo* request, + CompletionCallback* callback) { + DCHECK(request); + DCHECK(callback); + + // ensure that we only have one asynchronous call at a time. + DCHECK(!callback_); + + SetRequest(request); + + int rv; + + if (ShouldPassThrough()) { + // if must use cache, then we must fail. this can happen for back/forward + // navigations to a page generated via a form post. + if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) + return ERR_CACHE_MISS; + + rv = BeginNetworkRequest(); + } else { + cache_key_ = cache_->GenerateCacheKey(request); + + // requested cache access mode + if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) { + mode_ = READ; + } else if (effective_load_flags_ & LOAD_BYPASS_CACHE) { + mode_ = WRITE; + } else { + mode_ = READ_WRITE; + } + + rv = AddToEntry(); + } + + // setting this here allows us to check for the existance of a callback_ to + // determine if we are still inside Start. + if (rv == ERR_IO_PENDING) + callback_ = callback; + + return rv; +} + +int HttpCache::Transaction::RestartIgnoringLastError( + CompletionCallback* callback) { + DCHECK(callback); + + // ensure that we only have one asynchronous call at a time. + DCHECK(!callback_); + + int rv = RestartNetworkRequest(); + + if (rv == ERR_IO_PENDING) + callback_ = callback; + + return rv; +} + +int HttpCache::Transaction::RestartWithAuth( + const std::wstring& username, + const std::wstring& password, + CompletionCallback* callback) { + DCHECK(auth_response_.headers); + DCHECK(callback); + + // Ensure that we only have one asynchronous call at a time. + DCHECK(!callback_); + + // Clear the intermediate response since we are going to start over. + auth_response_ = HttpResponseInfo(); + + int rv = RestartNetworkRequestWithAuth(username, password); + + if (rv == ERR_IO_PENDING) + callback_ = callback; + + return rv; +} + +int HttpCache::Transaction::Read(char* buf, int buf_len, + CompletionCallback* callback) { + DCHECK(buf); + DCHECK(buf_len > 0); + DCHECK(callback); + + DCHECK(!callback_); + + // If we have an intermediate auth response at this point, then it means the + // user wishes to read the network response (the error page). If there is a + // previous response in the cache then we should leave it intact. + if (auth_response_.headers && mode_ != NONE) { + DCHECK(mode_ & WRITE); + DoneWritingToEntry(mode_ == READ_WRITE); + mode_ = NONE; + } + + int rv; + + switch (mode_) { + case NONE: + case WRITE: + DCHECK(network_trans_); + rv = network_trans_->Read(buf, buf_len, &network_read_callback_); + read_buf_ = buf; + if (rv >= 0) + OnNetworkReadCompleted(rv); + break; + case READ: + DCHECK(entry_); + AddRef(); // Balanced in OnCacheReadCompleted + rv = entry_->disk_entry->ReadData(kResponseContentIndex, read_offset_, + buf, buf_len, &cache_read_callback_); + read_buf_ = buf; + if (rv >= 0) { + OnCacheReadCompleted(rv); + } else if (rv != ERR_IO_PENDING) { + Release(); + } + break; + default: + NOTREACHED(); + rv = ERR_FAILED; + } + + if (rv == ERR_IO_PENDING) + callback_ = callback; + return rv; +} + +const HttpResponseInfo* HttpCache::Transaction::GetResponseInfo() const { + // Null headers means we encountered an error or haven't a response yet + if (auth_response_.headers) + return &auth_response_; + return (response_.headers || response_.ssl_info.cert) ? &response_ : NULL; +} + +LoadState HttpCache::Transaction::GetLoadState() const { + if (network_trans_) + return network_trans_->GetLoadState(); + if (entry_ || !request_) + return LOAD_STATE_IDLE; + return LOAD_STATE_WAITING_FOR_CACHE; +} + +uint64 HttpCache::Transaction::GetUploadProgress() const { + if (network_trans_) + return network_trans_->GetUploadProgress(); + return final_upload_progress_; +} + +int HttpCache::Transaction::AddToEntry() { + ActiveEntry* entry = NULL; + + if (mode_ == WRITE) { + cache_->DoomEntry(cache_key_); + } else { + entry = cache_->FindActiveEntry(cache_key_); + if (!entry) { + entry = cache_->OpenEntry(cache_key_); + if (!entry) { + if (mode_ & WRITE) { + mode_ = WRITE; + } else { + if (cache_->mode() == PLAYBACK) + DLOG(INFO) << "Playback Cache Miss: " << request_->url; + + // entry does not exist, and not permitted to create a new entry, so + // we must fail. + return HandleResult(ERR_CACHE_MISS); + } + } + } + } + + if (mode_ == WRITE) { + DCHECK(!entry); + entry = cache_->CreateEntry(cache_key_); + if (!entry) { + DLOG(WARNING) << "unable to create cache entry"; + mode_ = NONE; + return BeginNetworkRequest(); + } + } + + return cache_->AddTransactionToEntry(entry, this); +} + +int HttpCache::Transaction::EntryAvailable(ActiveEntry* entry) { + // We now have access to the cache entry. + // + // o if we are the writer for the transaction, then we can start the network + // transaction. + // + // o if we are a reader for the transaction, then we can start reading the + // cache entry. + // + // o if we can read or write, then we should check if the cache entry needs + // to be validated and then issue a network request if needed or just read + // from the cache if the cache entry is already valid. + // + int rv; + entry_ = entry; + switch (mode_) { + case READ: + rv = BeginCacheRead(); + break; + case WRITE: + rv = BeginNetworkRequest(); + break; + case READ_WRITE: + rv = BeginCacheValidation(); + break; + default: + NOTREACHED(); + rv = ERR_FAILED; + } + return rv; +} + +void HttpCache::Transaction::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); +} + +int HttpCache::Transaction::HandleResult(int rv) { + DCHECK(rv != ERR_IO_PENDING); + if (callback_) + DoCallback(rv); + return rv; +} + +void HttpCache::Transaction::SetRequest(const HttpRequestInfo* request) { + request_ = request; + effective_load_flags_ = request_->load_flags; + + // When in playback mode, we want to load exclusively from the cache. + if (cache_->mode() == PLAYBACK) + effective_load_flags_ |= LOAD_ONLY_FROM_CACHE; + + // When in record mode, we want to NEVER load from the cache. + // The reason for this is beacuse we save the Set-Cookie headers + // (intentionally). If we read from the cache, we replay them + // prematurely. + if (cache_->mode() == RECORD) + effective_load_flags_ |= LOAD_BYPASS_CACHE; + + // Some headers imply load flags. The order here is significant. + // + // LOAD_DISABLE_CACHE : no cache read or write + // LOAD_BYPASS_CACHE : no cache read + // LOAD_VALIDATE_CACHE : no cache read unless validation + // + // The former modes trump latter modes, so if we find a matching header we + // can stop iterating kSpecialHeaders. + // + static const struct { + const HeaderNameAndValue* search; + int load_flag; + } kSpecialHeaders[] = { + { kPassThroughHeaders, LOAD_DISABLE_CACHE }, + { kForceFetchHeaders, LOAD_BYPASS_CACHE }, + { kForceValidateHeaders, LOAD_VALIDATE_CACHE }, + }; + + // scan request headers to see if we have any that would impact our load flags + HttpUtil::HeadersIterator it(request_->extra_headers.begin(), + request_->extra_headers.end(), + "\r\n"); + while (it.GetNext()) { + for (size_t i = 0; i < arraysize(kSpecialHeaders); ++i) { + if (HeaderMatches(it, kSpecialHeaders[i].search)) { + effective_load_flags_ |= kSpecialHeaders[i].load_flag; + break; + } + } + } +} + +bool HttpCache::Transaction::ShouldPassThrough() { + // We may have a null disk_cache if there is an error we cannot recover from, + // like not enough disk space, or sharing violations. + if (!cache_->disk_cache()) + return true; + + // When using the record/playback modes, we always use the cache + // and we never pass through. + if (cache_->mode() == RECORD || cache_->mode() == PLAYBACK) + return false; + + if (effective_load_flags_ & LOAD_DISABLE_CACHE) + return true; + + // TODO(darin): add support for caching HEAD and POST responses + if (request_->method != "GET") + return true; + + return false; +} + +int HttpCache::Transaction::BeginCacheRead() { + DCHECK(mode_ == READ); + + // read response headers + return HandleResult(ReadResponseInfoFromEntry()); +} + +int HttpCache::Transaction::BeginCacheValidation() { + DCHECK(mode_ == READ_WRITE); + + int rv = ReadResponseInfoFromEntry(); + if (rv != OK) { + DCHECK(rv != ERR_IO_PENDING); + } else if (effective_load_flags_ & LOAD_PREFERRING_CACHE || + !RequiresValidation()) { + cache_->ConvertWriterToReader(entry_); + mode_ = READ; + } else { + // Make the network request conditional, to see if we may reuse our cached + // response. If we cannot do so, then we just resort to a normal fetch. + // Our mode remains READ_WRITE for a conditional request. We'll switch to + // either READ or WRITE mode once we hear back from the server. + if (!ConditionalizeRequest()) + mode_ = WRITE; + return BeginNetworkRequest(); + } + return HandleResult(rv); +} + +int HttpCache::Transaction::BeginNetworkRequest() { + DCHECK(mode_ & WRITE || mode_ == NONE); + DCHECK(!network_trans_); + + network_trans_ = cache_->network_layer_->CreateTransaction(); + if (!network_trans_) + return net::ERR_FAILED; + + int rv = network_trans_->Start(request_, &network_info_callback_); + if (rv != ERR_IO_PENDING) + OnNetworkInfoAvailable(rv); + return rv; +} + +int HttpCache::Transaction::RestartNetworkRequest() { + DCHECK(mode_ & WRITE || mode_ == NONE); + DCHECK(network_trans_); + + int rv = network_trans_->RestartIgnoringLastError(&network_info_callback_); + if (rv != ERR_IO_PENDING) + OnNetworkInfoAvailable(rv); + return rv; +} + +int HttpCache::Transaction::RestartNetworkRequestWithAuth( + const std::wstring& username, + const std::wstring& password) { + DCHECK(mode_ & WRITE || mode_ == NONE); + DCHECK(network_trans_); + + int rv = network_trans_->RestartWithAuth(username, password, + &network_info_callback_); + if (rv != ERR_IO_PENDING) + OnNetworkInfoAvailable(rv); + return rv; +} + +bool HttpCache::Transaction::RequiresValidation() { + // TODO(darin): need to do more work here: + // - make sure we have a matching request method + // - watch out for cached responses that depend on authentication + // In playback mode, nothing requires validation. + if (mode_ == PLAYBACK) + return false; + + if (effective_load_flags_ & LOAD_VALIDATE_CACHE) + return true; + + if (response_.headers->RequiresValidation( + response_.request_time, response_.response_time, Time::Now())) + return true; + + // Since Vary header computation is fairly expensive, we save it for last. + if (response_.vary_data.is_valid() && + !response_.vary_data.MatchesRequest(*request_, *response_.headers)) + return true; + + return false; +} + +bool HttpCache::Transaction::ConditionalizeRequest() { + DCHECK(response_.headers); + + // This only makes sense for cached 200 responses. + if (response_.headers->response_code() != 200) + return false; + + // Just use the first available ETag and/or Last-Modified header value. + // TODO(darin): Or should we use the last? + + std::string etag_value; + response_.headers->EnumerateHeader(NULL, "etag", &etag_value); + + std::string last_modified_value; + response_.headers->EnumerateHeader(NULL, "last-modified", + &last_modified_value); + + if (etag_value.empty() && last_modified_value.empty()) + return false; + + // Need to customize the request, so this forces us to allocate :( + custom_request_.reset(new HttpRequestInfo(*request_)); + request_ = custom_request_.get(); + + if (!etag_value.empty()) { + custom_request_->extra_headers.append("If-None-Match: "); + custom_request_->extra_headers.append(etag_value); + custom_request_->extra_headers.append("\r\n"); + } + + if (!last_modified_value.empty()) { + custom_request_->extra_headers.append("If-Modified-Since: "); + custom_request_->extra_headers.append(last_modified_value); + custom_request_->extra_headers.append("\r\n"); + } + + return true; +} + +int HttpCache::Transaction::ReadResponseInfoFromEntry() { + DCHECK(entry_); + + if (!HttpCache::ReadResponseInfo(entry_->disk_entry, &response_)) + return ERR_FAILED; + + return OK; +} + +void HttpCache::Transaction::WriteToEntry(int index, int offset, + const char* data, int data_len) { + if (!entry_) + return; + + int rv = entry_->disk_entry->WriteData(index, offset, data, data_len, NULL, + true); + if (rv != data_len) { + DLOG(ERROR) << "failed to write response data to cache"; + DoneWritingToEntry(false); + } +} + +void HttpCache::Transaction::WriteResponseInfoToEntry() { + if (!entry_) + return; + + // Do not cache no-store content (unless we are record mode). Do not cache + // content with cert errors either. This is to prevent not reporting net + // errors when loading a resource from the cache. When we load a page over + // HTTPS with a cert error we show an SSL blocking page. If the user clicks + // proceed we reload the resource ignoring the errors. The loaded resource + // is then cached. If that resource is subsequently loaded from the cache, + // no net error is reported (even though the cert status contains the actual + // errors) and no SSL blocking page is shown. An alternative would be to + // reverse-map the cert status to a net error and replay the net error. + if ((cache_->mode() != RECORD && + response_.headers->HasHeaderValue("cache-control", "no-store")) || + net::IsCertStatusError(response_.ssl_info.cert_status)) { + DoneWritingToEntry(false); + return; + } + + // When writing headers, we normally only write the non-transient + // headers; when in record mode, record everything. + bool skip_transient_headers = (cache_->mode() != RECORD); + + if (!HttpCache::WriteResponseInfo(entry_->disk_entry, &response_, + skip_transient_headers)) { + DLOG(ERROR) << "failed to write response info to cache"; + DoneWritingToEntry(false); + } +} + +void HttpCache::Transaction::AppendResponseDataToEntry(const char* data, + int data_len) { + if (!entry_) + return; + + int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex); + WriteToEntry(kResponseContentIndex, current_size, data, data_len); +} + +void HttpCache::Transaction::DoneWritingToEntry(bool success) { + if (!entry_) + return; + + if (cache_->mode() == RECORD) + DLOG(INFO) << "Recorded: " << request_->method << request_->url + << " status: " << response_.headers->response_code(); + + cache_->DoneWritingToEntry(entry_, success); + entry_ = NULL; + mode_ = NONE; // switch to 'pass through' mode +} + +void HttpCache::Transaction::OnNetworkInfoAvailable(int result) { + DCHECK(result != ERR_IO_PENDING); + + if (result == OK) { + const HttpResponseInfo* new_response = network_trans_->GetResponseInfo(); + if (new_response->headers->response_code() == 401 || + new_response->headers->response_code() == 407) { + auth_response_ = *new_response; + } else { + // Are we expecting a response to a conditional query? + if (mode_ == READ_WRITE) { + if (new_response->headers->response_code() == 304) { + // Update cached response based on headers in new_response + response_.headers->Update(*new_response->headers); + WriteResponseInfoToEntry(); + + if (entry_) { + cache_->ConvertWriterToReader(entry_); + // We no longer need the network transaction, so destroy it. + final_upload_progress_ = network_trans_->GetUploadProgress(); + network_trans_->Destroy(); + network_trans_ = NULL; + mode_ = READ; + } + } else { + mode_ = WRITE; + } + } + + if (!(mode_ & READ)) { + response_ = *new_response; + WriteResponseInfoToEntry(); + + // Truncate the response data + WriteToEntry(kResponseContentIndex, 0, NULL, 0); + + // If this response is a redirect, then we can stop writing now. (We + // don't need to cache the response body of a redirect.) + if (response_.headers->IsRedirect(NULL)) + DoneWritingToEntry(true); + } + } + } else if (IsCertificateError(result)) { + response_.ssl_info = network_trans_->GetResponseInfo()->ssl_info; + } + HandleResult(result); +} + +void HttpCache::Transaction::OnNetworkReadCompleted(int result) { + DCHECK(mode_ & WRITE || mode_ == NONE); + + if (result > 0) { + AppendResponseDataToEntry(read_buf_, result); + } else if (result == 0) { // end of file + DoneWritingToEntry(true); + } + HandleResult(result); +} + +void HttpCache::Transaction::OnCacheReadCompleted(int result) { + // If Destroy was called while waiting for this callback, then cache_ will be + // NULL. In that case, we don't want to do anything but cleanup. + if (cache_) { + if (result > 0) { + read_offset_ += result; + } else if (result == 0) { // end of file + cache_->DoneReadingFromEntry(entry_, this); + entry_ = NULL; + } + HandleResult(result); + } + Release(); +} + +//----------------------------------------------------------------------------- + +HttpCache::HttpCache(const HttpProxyInfo* proxy_info, + const std::wstring& cache_dir, + int cache_size) + : disk_cache_dir_(cache_dir), + mode_(NORMAL), + network_layer_(HttpNetworkLayer::CreateFactory(proxy_info)), + task_factory_(this), + in_memory_cache_(false), + cache_size_(cache_size) { +} + +HttpCache::HttpCache(const HttpProxyInfo* proxy_info, int cache_size) + : mode_(NORMAL), + network_layer_(HttpNetworkLayer::CreateFactory(proxy_info)), + task_factory_(this), + in_memory_cache_(true), + cache_size_(cache_size) { +} + +HttpCache::HttpCache(HttpTransactionFactory* network_layer, + disk_cache::Backend* disk_cache) + : mode_(NORMAL), + network_layer_(network_layer), + disk_cache_(disk_cache), + task_factory_(this), + in_memory_cache_(false), + cache_size_(0) { +} + +HttpCache::~HttpCache() { + // If we have any active entries remaining, then we need to deactivate them. + // We may have some pending calls to OnProcessPendingQueue, but since those + // won't run (due to our destruction), we can simply ignore the corresponding + // will_process_pending_queue flag. + while (!active_entries_.empty()) { + ActiveEntry* entry = active_entries_.begin()->second; + entry->will_process_pending_queue = false; + DeactivateEntry(entry); + } + + ActiveEntriesSet::iterator it = doomed_entries_.begin(); + for (; it != doomed_entries_.end(); ++it) + delete *it; +} + +HttpTransaction* HttpCache::CreateTransaction() { + // Do lazy initialization of disk cache if needed. + if (!disk_cache_.get()) { + DCHECK(cache_size_ >= 0); + if (in_memory_cache_) { + // We may end up with no folder name and no cache if the initialization + // of the disk cache fails. We want to be sure that what we wanted to have + // was an in-memory cache. + disk_cache_.reset(disk_cache::CreateInMemoryCacheBackend(cache_size_)); + } else if (!disk_cache_dir_.empty()) { + disk_cache_.reset(disk_cache::CreateCacheBackend(disk_cache_dir_, true, + cache_size_)); + disk_cache_dir_.clear(); // Reclaim memory. + } + } + return new HttpCache::Transaction(this); +} + +HttpCache* HttpCache::GetCache() { + return this; +} + +AuthCache* HttpCache::GetAuthCache() { + return network_layer_->GetAuthCache(); +} + +void HttpCache::Suspend(bool suspend) { + network_layer_->Suspend(suspend); +} + +// static +bool HttpCache::ReadResponseInfo(disk_cache::Entry* disk_entry, + HttpResponseInfo* response_info) { + int size = disk_entry->GetDataSize(kResponseInfoIndex); + + std::string data; + int rv = disk_entry->ReadData(kResponseInfoIndex, 0, + WriteInto(&data, size + 1), + size, NULL); + if (rv != size) { + DLOG(ERROR) << "ReadData failed: " << rv; + return false; + } + + Pickle pickle(data.data(), static_cast<int>(data.size())); + void* iter = NULL; + + // read flags and verify version + int flags; + if (!pickle.ReadInt(&iter, &flags)) + return false; + int version = flags & RESPONSE_INFO_VERSION_MASK; + if (version != RESPONSE_INFO_VERSION) { + DLOG(ERROR) << "unexpected response info version: " << version; + return false; + } + + // read request-time + int64 time_val; + if (!pickle.ReadInt64(&iter, &time_val)) + return false; + response_info->request_time = Time::FromInternalValue(time_val); + + // read response-time + if (!pickle.ReadInt64(&iter, &time_val)) + return false; + response_info->response_time = Time::FromInternalValue(time_val); + + // read response-headers + response_info->headers = new HttpResponseHeaders(pickle, &iter); + DCHECK(response_info->headers->response_code() != -1); + + // read ssl-info + if (flags & RESPONSE_INFO_HAS_CERT) { + response_info->ssl_info.cert = + X509Certificate::CreateFromPickle(pickle, &iter); + } + if (flags & RESPONSE_INFO_HAS_CERT_STATUS) { + int cert_status; + if (!pickle.ReadInt(&iter, &cert_status)) + return false; + response_info->ssl_info.cert_status = cert_status; + } + if (flags & RESPONSE_INFO_HAS_SECURITY_BITS) { + int security_bits; + if (!pickle.ReadInt(&iter, &security_bits)) + return false; + response_info->ssl_info.security_bits = security_bits; + } + + // read vary-data + if (flags & RESPONSE_INFO_HAS_VARY_DATA) { + if (!response_info->vary_data.InitFromPickle(pickle, &iter)) + return false; + } + + return true; +} + +// static +bool HttpCache::WriteResponseInfo(disk_cache::Entry* disk_entry, + const HttpResponseInfo* response_info, + bool skip_transient_headers) { + int flags = RESPONSE_INFO_VERSION; + if (response_info->ssl_info.cert) { + flags |= RESPONSE_INFO_HAS_CERT; + flags |= RESPONSE_INFO_HAS_CERT_STATUS; + } + if (response_info->ssl_info.security_bits != -1) + flags |= RESPONSE_INFO_HAS_SECURITY_BITS; + if (response_info->vary_data.is_valid()) + flags |= RESPONSE_INFO_HAS_VARY_DATA; + + Pickle pickle; + pickle.WriteInt(flags); + pickle.WriteInt64(response_info->request_time.ToInternalValue()); + pickle.WriteInt64(response_info->response_time.ToInternalValue()); + + response_info->headers->Persist(&pickle, skip_transient_headers); + + if (response_info->ssl_info.cert) { + response_info->ssl_info.cert->Persist(&pickle); + pickle.WriteInt(response_info->ssl_info.cert_status); + } + if (response_info->ssl_info.security_bits != -1) + pickle.WriteInt(response_info->ssl_info.security_bits); + + if (response_info->vary_data.is_valid()) + response_info->vary_data.Persist(&pickle); + + const char* data = static_cast<const char*>(pickle.data()); + int len = pickle.size(); + + return disk_entry->WriteData(kResponseInfoIndex, 0, data, len, NULL, + true) == len; +} + +void HttpCache::DoomEntry(const std::string& key) { + // Need to abandon the ActiveEntry, but any transaction attached to the entry + // should not be impacted. Dooming an entry only means that it will no + // longer be returned by FindActiveEntry (and it will also be destroyed once + // all consumers are finished with the entry). + ActiveEntriesMap::iterator it = active_entries_.find(key); + if (it == active_entries_.end()) { + disk_cache_->DoomEntry(key); + } else { + ActiveEntry* entry = it->second; + active_entries_.erase(it); + + // We keep track of doomed entries so that we can ensure that they are + // cleaned up properly when the cache is destroyed. + doomed_entries_.insert(entry); + + entry->disk_entry->Doom(); + entry->doomed = true; + + DCHECK(entry->writer || !entry->readers.empty()); + } +} + +void HttpCache::FinalizeDoomedEntry(ActiveEntry* entry) { + DCHECK(entry->doomed); + DCHECK(!entry->writer); + DCHECK(entry->readers.empty()); + DCHECK(entry->pending_queue.empty()); + + ActiveEntriesSet::iterator it = doomed_entries_.find(entry); + DCHECK(it != doomed_entries_.end()); + doomed_entries_.erase(it); + + delete entry; +} + +HttpCache::ActiveEntry* HttpCache::FindActiveEntry(const std::string& key) { + ActiveEntriesMap::const_iterator it = active_entries_.find(key); + return it != active_entries_.end() ? it->second : NULL; +} + +HttpCache::ActiveEntry* HttpCache::OpenEntry(const std::string& key) { + DCHECK(!FindActiveEntry(key)); + + disk_cache::Entry* disk_entry; + if (!disk_cache_->OpenEntry(key, &disk_entry)) + return NULL; + + return ActivateEntry(key, disk_entry); +} + +HttpCache::ActiveEntry* HttpCache::CreateEntry(const std::string& key) { + DCHECK(!FindActiveEntry(key)); + + disk_cache::Entry* disk_entry; + if (!disk_cache_->CreateEntry(key, &disk_entry)) + return NULL; + + return ActivateEntry(key, disk_entry); +} + +void HttpCache::DestroyEntry(ActiveEntry* entry) { + if (entry->doomed) { + FinalizeDoomedEntry(entry); + } else { + DeactivateEntry(entry); + } +} + +HttpCache::ActiveEntry* HttpCache::ActivateEntry( + const std::string& key, + disk_cache::Entry* disk_entry) { + ActiveEntry* entry = new ActiveEntry(disk_entry); + entry->writer = NULL; + active_entries_[key] = entry; + return entry; +} + +void HttpCache::DeactivateEntry(ActiveEntry* entry) { + DCHECK(!entry->will_process_pending_queue); + DCHECK(!entry->doomed); + DCHECK(!entry->writer); + DCHECK(entry->readers.empty()); + DCHECK(entry->pending_queue.empty()); + + ActiveEntriesMap::iterator it = + active_entries_.find(entry->disk_entry->GetKey()); + DCHECK(it != active_entries_.end()); + DCHECK(it->second == entry); + + active_entries_.erase(it); + delete entry; +} + +int HttpCache::AddTransactionToEntry(ActiveEntry* entry, Transaction* trans) { + DCHECK(entry); + + // We implement a basic reader/writer lock for the disk cache entry. If + // there is already a writer, then everyone has to wait for the writer to + // finish before they can access the cache entry. There can be multiple + // readers. + // + // NOTE: If the transaction can only write, then the entry should not be in + // use (since any existing entry should have already been doomed). + + if (entry->writer || entry->will_process_pending_queue) { + entry->pending_queue.push_back(trans); + return ERR_IO_PENDING; + } + + if (trans->mode() & Transaction::WRITE) { + // transaction needs exclusive access to the entry + if (entry->readers.empty()) { + entry->writer = trans; + } else { + entry->pending_queue.push_back(trans); + return ERR_IO_PENDING; + } + } else { + // transaction needs read access to the entry + entry->readers.push_back(trans); + } + + // We do this before calling EntryAvailable to force any further calls to + // AddTransactionToEntry to add their transaction to the pending queue, which + // ensures FIFO ordering. + if (!entry->pending_queue.empty()) + ProcessPendingQueue(entry); + + return trans->EntryAvailable(entry); +} + +void HttpCache::DoneWritingToEntry(ActiveEntry* entry, bool success) { + DCHECK(entry->readers.empty()); + + entry->writer = NULL; + + if (success) { + ProcessPendingQueue(entry); + } else { + // We failed to create this entry. + TransactionList pending_queue; + pending_queue.swap(entry->pending_queue); + + entry->disk_entry->Doom(); + DestroyEntry(entry); + + // We need to do something about these pending entries, which now need to + // be added to a new entry. + while (!pending_queue.empty()) { + pending_queue.front()->AddToEntry(); + pending_queue.pop_front(); + } + } +} + +void HttpCache::DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans) { + DCHECK(!entry->writer); + + TransactionList::iterator it = + std::find(entry->readers.begin(), entry->readers.end(), trans); + DCHECK(it != entry->readers.end()); + + entry->readers.erase(it); + + ProcessPendingQueue(entry); +} + +void HttpCache::ConvertWriterToReader(ActiveEntry* entry) { + DCHECK(entry->writer); + DCHECK(entry->writer->mode() == Transaction::READ_WRITE); + DCHECK(entry->readers.empty()); + + Transaction* trans = entry->writer; + + entry->writer = NULL; + entry->readers.push_back(trans); + + ProcessPendingQueue(entry); +} + +void HttpCache::RemovePendingTransaction(Transaction* trans) { + ActiveEntriesMap::const_iterator i = active_entries_.find(trans->key()); + if (i == active_entries_.end()) + return; + + TransactionList& pending_queue = i->second->pending_queue; + + TransactionList::iterator j = + find(pending_queue.begin(), pending_queue.end(), trans); + if (j == pending_queue.end()) + return; + + pending_queue.erase(j); +} + +void HttpCache::ProcessPendingQueue(ActiveEntry* entry) { + // Multiple readers may finish with an entry at once, so we want to batch up + // calls to OnProcessPendingQueue. This flag also tells us that we should + // not delete the entry before OnProcessPendingQueue runs. + if (entry->will_process_pending_queue) + return; + entry->will_process_pending_queue = true; + + MessageLoop::current()->PostTask(FROM_HERE, + task_factory_.NewRunnableMethod(&HttpCache::OnProcessPendingQueue, + entry)); +} + +void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) { + entry->will_process_pending_queue = false; + + if (entry->writer) + return; + + // If no one is interested in this entry, then we can de-activate it. + if (entry->pending_queue.empty()) { + if (entry->readers.empty()) + DestroyEntry(entry); + return; + } + + // Promote next transaction from the pending queue. + Transaction* next = entry->pending_queue.front(); + if ((next->mode() & Transaction::WRITE) && !entry->readers.empty()) + return; // have to wait + + entry->pending_queue.erase(entry->pending_queue.begin()); + + AddTransactionToEntry(entry, next); +} + +//----------------------------------------------------------------------------- + +} // namespace net diff --git a/net/http/http_cache.h b/net/http/http_cache.h new file mode 100644 index 0000000..42fbfe0 --- /dev/null +++ b/net/http/http_cache.h @@ -0,0 +1,200 @@ +// 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. + +// This file declares a HttpTransactionFactory implementation that can be +// layered on top of another HttpTransactionFactory to add HTTP caching. The +// caching logic follows RFC 2616 (any exceptions are called out in the code). +// +// The HttpCache takes a disk_cache::Backend as a parameter, and uses that for +// the cache storage. +// +// See HttpTransactionFactory and HttpTransaction for more details. + +#ifndef NET_HTTP_HTTP_CACHE_H__ +#define NET_HTTP_HTTP_CACHE_H__ + +#include <hash_map> +#include <hash_set> +#include <list> +#include <string> + +#include "base/basictypes.h" +#include "base/task.h" +#include "net/http/http_transaction_factory.h" + +namespace disk_cache { +class Backend; +class Entry; +} + +namespace net { + +class HttpProxyInfo; +class HttpRequestInfo; +class HttpResponseInfo; + +class HttpCache : public HttpTransactionFactory { + public: + ~HttpCache(); + + // The cache mode of operation. + enum Mode { + // Normal mode just behaves like a standard web cache. + NORMAL = 0, + // Record mode caches everything for purposes of offline playback. + RECORD, + // Playback mode replays from a cache without considering any + // standard invalidations. + PLAYBACK + }; + + // Initialize the cache from the directory where its data is stored. The + // disk cache is initialized lazily (by CreateTransaction) in this case. If + // |cache_size| is zero, a default value will be calculated automatically. + // If the proxy information is null, then the system settings will be used. + HttpCache(const HttpProxyInfo* proxy_info, + const std::wstring& cache_dir, + int cache_size); + + // Initialize using an in-memory cache. The cache is initialized lazily + // (by CreateTransaction) in this case. If |cache_size| is zero, a default + // value will be calculated automatically. If the proxy information is null, + // then the system settings will be used. + HttpCache(const HttpProxyInfo* proxy_info, int cache_size); + + // Initialize the cache from its component parts, which is useful for + // testing. The lifetime of the network_layer and disk_cache are managed by + // the HttpCache and will be destroyed using |delete| when the HttpCache is + // destroyed. + HttpCache(HttpTransactionFactory* network_layer, + disk_cache::Backend* disk_cache); + + HttpTransactionFactory* network_layer() { return network_layer_.get(); } + disk_cache::Backend* disk_cache() { return disk_cache_.get(); } + + // HttpTransactionFactory implementation: + virtual HttpTransaction* CreateTransaction(); + virtual HttpCache* GetCache(); + virtual AuthCache* GetAuthCache(); + virtual void Suspend(bool suspend); + + // Helper function for reading response info from the disk cache. + static bool ReadResponseInfo(disk_cache::Entry* disk_entry, + HttpResponseInfo* response_info); + + // Helper function for writing response info into the disk cache. + static bool WriteResponseInfo(disk_cache::Entry* disk_entry, + const HttpResponseInfo* response_info, + bool skip_transient_headers); + + // Generate a key that can be used inside the cache. + std::string GenerateCacheKey(const HttpRequestInfo* request); + + // Get/Set the cache's mode. + void set_mode(Mode value) { mode_ = value; } + Mode mode() { return mode_; } + + private: + + // Types -------------------------------------------------------------------- + + class Transaction; + friend Transaction; + + typedef std::list<Transaction*> TransactionList; + + struct ActiveEntry { + disk_cache::Entry* disk_entry; + Transaction* writer; + TransactionList readers; + TransactionList pending_queue; + bool will_process_pending_queue; + bool doomed; + + explicit ActiveEntry(disk_cache::Entry*); + ~ActiveEntry(); + }; + + typedef stdext::hash_map<std::string, ActiveEntry*> ActiveEntriesMap; + typedef stdext::hash_set<ActiveEntry*> ActiveEntriesSet; + + + // Methods ------------------------------------------------------------------ + + void DoomEntry(const std::string& key); + void FinalizeDoomedEntry(ActiveEntry* entry); + ActiveEntry* FindActiveEntry(const std::string& key); + ActiveEntry* ActivateEntry(const std::string& key, disk_cache::Entry*); + void DeactivateEntry(ActiveEntry* entry); + ActiveEntry* OpenEntry(const std::string& key); + ActiveEntry* CreateEntry(const std::string& cache_key); + void DestroyEntry(ActiveEntry* entry); + int AddTransactionToEntry(ActiveEntry* entry, Transaction* trans); + void DoneWritingToEntry(ActiveEntry* entry, bool success); + void DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans); + void ConvertWriterToReader(ActiveEntry* entry); + void RemovePendingTransaction(Transaction* trans); + void ProcessPendingQueue(ActiveEntry* entry); + + + // Events (called via PostTask) --------------------------------------------- + + void OnProcessPendingQueue(ActiveEntry* entry); + + + // Variables ---------------------------------------------------------------- + + // used when lazily constructing the disk_cache_ + std::wstring disk_cache_dir_; + + Mode mode_; + + scoped_ptr<HttpTransactionFactory> network_layer_; + scoped_ptr<disk_cache::Backend> disk_cache_; + + // The set of active entries indexed by cache key + ActiveEntriesMap active_entries_; + + // The set of doomed entries + ActiveEntriesSet doomed_entries_; + + ScopedRunnableMethodFactory<HttpCache> task_factory_; + + bool in_memory_cache_; + int cache_size_; + + typedef stdext::hash_map<std::string, int> PlaybackCacheMap; + scoped_ptr<PlaybackCacheMap> playback_cache_map_; + + DISALLOW_EVIL_CONSTRUCTORS(HttpCache); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_CACHE_H__ diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc new file mode 100644 index 0000000..8f5db81 --- /dev/null +++ b/net/http/http_cache_unittest.cc @@ -0,0 +1,1012 @@ +// 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_cache.h" + +#include <windows.h> + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "net/base/load_flags.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" +#include "net/http/http_transaction.h" +#include "net/http/http_transaction_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +#pragma warning(disable: 4355) + +namespace { + +//----------------------------------------------------------------------------- +// mock disk cache (a very basic memory cache implementation) + +class MockDiskEntry : public disk_cache::Entry, + public base::RefCounted<MockDiskEntry> { + public: + MockDiskEntry() : test_mode_(0), doomed_(false) { + } + + MockDiskEntry(const std::string& key) : key_(key), doomed_(false) { + const MockTransaction* t = FindMockTransaction(GURL(key)); + DCHECK(t); + test_mode_ = t->test_mode; + } + + ~MockDiskEntry() { + } + + bool is_doomed() const { return doomed_; } + + virtual void Doom() { + doomed_ = true; + } + + virtual void Close() { + Release(); + } + + virtual std::string GetKey() const { + return key_; + } + + virtual Time GetLastUsed() const { + return Time::FromInternalValue(0); + } + + virtual Time GetLastModified() const { + return Time::FromInternalValue(0); + } + + virtual int32 GetDataSize(int index) const { + DCHECK(index >= 0 && index < 2); + return static_cast<int32>(data_[index].size()); + } + + virtual int ReadData(int index, int offset, char* buf, int buf_len, + net::CompletionCallback* callback) { + DCHECK(index >= 0 && index < 2); + + if (offset < 0 || offset > static_cast<int>(data_[index].size())) + return net::ERR_FAILED; + if (offset == data_[index].size()) + return 0; + + int num = std::min(buf_len, static_cast<int>(data_[index].size()) - offset); + memcpy(buf, &data_[index][offset], num); + + if (!callback || (test_mode_ & TEST_MODE_SYNC_CACHE_READ)) + return num; + + CallbackLater(callback, num); + return net::ERR_IO_PENDING; + } + + virtual int WriteData(int index, int offset, const char* buf, int buf_len, + net::CompletionCallback* callback, bool truncate) { + DCHECK(index >= 0 && index < 2); + DCHECK(truncate); + + if (offset < 0 || offset > static_cast<int>(data_[index].size())) + return net::ERR_FAILED; + + data_[index].resize(offset + buf_len); + if (buf_len) + memcpy(&data_[index][offset], buf, buf_len); + return buf_len; + } + + private: + // Unlike the callbacks for MockHttpTransaction, we want this one to run even + // if the consumer called Close on the MockDiskEntry. We achieve that by + // leveraging the fact that this class is reference counted. + void CallbackLater(net::CompletionCallback* callback, int result) { + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this, + &MockDiskEntry::RunCallback, callback, result)); + } + void RunCallback(net::CompletionCallback* callback, int result) { + callback->Run(result); + } + + std::string key_; + std::vector<char> data_[2]; + int test_mode_; + bool doomed_; +}; + +class MockDiskCache : public disk_cache::Backend { + public: + MockDiskCache() : open_count_(0), create_count_(0), fail_requests_(0) { + } + + ~MockDiskCache() { + EntryMap::iterator it = entries_.begin(); + for (; it != entries_.end(); ++it) + it->second->Release(); + } + + virtual int32 GetEntryCount() const { + return static_cast<int32>(entries_.size()); + } + + virtual bool OpenEntry(const std::string& key, disk_cache::Entry** entry) { + if (fail_requests_) + return false; + + EntryMap::iterator it = entries_.find(key); + if (it == entries_.end()) + return false; + + if (it->second->is_doomed()) { + it->second->Release(); + entries_.erase(it); + return false; + } + + open_count_++; + + it->second->AddRef(); + *entry = it->second; + + return true; + } + + virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry) { + if (fail_requests_) + return false; + + EntryMap::iterator it = entries_.find(key); + DCHECK(it == entries_.end()); + + create_count_++; + + MockDiskEntry* new_entry = new MockDiskEntry(key); + + new_entry->AddRef(); + entries_[key] = new_entry; + + new_entry->AddRef(); + *entry = new_entry; + + return true; + } + + virtual bool DoomEntry(const std::string& key) { + EntryMap::iterator it = entries_.find(key); + if (it != entries_.end()) { + it->second->Release(); + entries_.erase(it); + } + return true; + } + + virtual bool DoomAllEntries() { + return false; + } + + virtual bool DoomEntriesBetween(const Time initial_time, + const Time end_time) { + return true; + } + + virtual bool DoomEntriesSince(const Time initial_time) { + return true; + } + + virtual bool OpenNextEntry(void** iter, disk_cache::Entry** next_entry) { + return false; + } + + virtual void EndEnumeration(void** iter) {} + + virtual void GetStats( + std::vector<std::pair<std::string, std::string> >* stats) { + } + + // returns number of times a cache entry was successfully opened + int open_count() const { return open_count_; } + + // returns number of times a cache entry was successfully created + int create_count() const { return create_count_; } + + // Fail any subsequent CreateEntry and OpenEntry. + void set_fail_requests() { fail_requests_ = true; } + + private: + typedef stdext::hash_map<std::string, MockDiskEntry*> EntryMap; + EntryMap entries_; + int open_count_; + int create_count_; + bool fail_requests_; +}; + +class MockHttpCache { + public: + MockHttpCache() : http_cache_(new MockNetworkLayer(), new MockDiskCache()) { + } + + net::HttpCache* http_cache() { return &http_cache_; } + + MockNetworkLayer* network_layer() { + return static_cast<MockNetworkLayer*>(http_cache_.network_layer()); + } + MockDiskCache* disk_cache() { + return static_cast<MockDiskCache*>(http_cache_.disk_cache()); + } + + private: + net::HttpCache http_cache_; +}; + + +//----------------------------------------------------------------------------- +// helpers + +void ReadAndVerifyTransaction(net::HttpTransaction* trans, + const MockTransaction& trans_info) { + std::string content; + int rv = ReadTransaction(trans, &content); + + EXPECT_EQ(net::OK, rv); + EXPECT_EQ(strlen(trans_info.data), content.size()); + EXPECT_EQ(0, memcmp(trans_info.data, content.data(), content.size())); +} + +void RunTransactionTest(net::HttpCache* cache, + const MockTransaction& trans_info) { + MockHttpRequest request(trans_info); + TestCompletionCallback callback; + + // write to the cache + + net::HttpTransaction* trans = cache->CreateTransaction(); + ASSERT_TRUE(trans); + + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::OK, rv); + + const net::HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response); + + ReadAndVerifyTransaction(trans, trans_info); + + trans->Destroy(); +} + +} // namespace + + +//----------------------------------------------------------------------------- +// tests + + +TEST(HttpCache, CreateThenDestroy) { + MockHttpCache cache; + + net::HttpTransaction* trans = cache.http_cache()->CreateTransaction(); + ASSERT_TRUE(trans); + + trans->Destroy(); +} + +TEST(HttpCache, SimpleGET) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGETNoDiskCache) { + MockHttpCache cache; + + cache.disk_cache()->set_fail_requests(); + + // Read from the network, and don't use the cache. + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(0, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // force this transaction to read from the cache + MockTransaction transaction(kSimpleGET_Transaction); + transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE; + + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Miss) { + MockHttpCache cache; + + // force this transaction to read from the cache + MockTransaction transaction(kSimpleGET_Transaction); + transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE; + + MockHttpRequest request(transaction); + TestCompletionCallback callback; + + net::HttpTransaction* trans = cache.http_cache()->CreateTransaction(); + ASSERT_TRUE(trans); + + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_CACHE_MISS, rv); + + trans->Destroy(); + + EXPECT_EQ(0, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(0, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadPreferringCache_Hit) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // force this transaction to read from the cache if valid + MockTransaction transaction(kSimpleGET_Transaction); + transaction.load_flags |= net::LOAD_PREFERRING_CACHE; + + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadPreferringCache_Miss) { + MockHttpCache cache; + + // force this transaction to read from the cache if valid + MockTransaction transaction(kSimpleGET_Transaction); + transaction.load_flags |= net::LOAD_PREFERRING_CACHE; + + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadBypassCache) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // force this transaction to write to the cache again + MockTransaction transaction(kSimpleGET_Transaction); + transaction.load_flags |= net::LOAD_BYPASS_CACHE; + + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadBypassCache_Implicit) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // force this transaction to write to the cache again + MockTransaction transaction(kSimpleGET_Transaction); + transaction.request_headers = "pragma: no-cache"; + + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadBypassCache_Implicit2) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // force this transaction to write to the cache again + MockTransaction transaction(kSimpleGET_Transaction); + transaction.request_headers = "cache-control: no-cache"; + + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadValidateCache) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // read from the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // force this transaction to validate the cache + MockTransaction transaction(kSimpleGET_Transaction); + transaction.load_flags |= net::LOAD_VALIDATE_CACHE; + + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_LoadValidateCache_Implicit) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // read from the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + // force this transaction to validate the cache + MockTransaction transaction(kSimpleGET_Transaction); + transaction.request_headers = "cache-control: max-age=0"; + + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimpleGET_ManyReaders) { + MockHttpCache cache; + + MockHttpRequest request(kSimpleGET_Transaction); + + struct Context { + int result; + TestCompletionCallback callback; + net::HttpTransaction* trans; + + Context(net::HttpTransaction* t) : result(net::ERR_IO_PENDING), trans(t) { + } + }; + + std::vector<Context*> context_list; + const int kNumTransactions = 5; + + for (int i = 0; i < kNumTransactions; ++i) { + context_list.push_back( + new Context(cache.http_cache()->CreateTransaction())); + + Context* c = context_list[i]; + int rv = c->trans->Start(&request, &c->callback); + if (rv != net::ERR_IO_PENDING) + c->result = rv; + } + + // the first request should be a writer at this point, and the subsequent + // requests should be pending. + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + for (int i = 0; i < kNumTransactions; ++i) { + Context* c = context_list[i]; + if (c->result == net::ERR_IO_PENDING) + c->result = c->callback.WaitForResult(); + ReadAndVerifyTransaction(c->trans, kSimpleGET_Transaction); + } + + // we should not have had to re-open the disk entry + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + for (int i = 0; i < kNumTransactions; ++i) { + Context* c = context_list[i]; + c->trans->Destroy(); + delete c; + } +} + +TEST(HttpCache, SimpleGET_ManyWriters_CancelFirst) { + MockHttpCache cache; + + MockHttpRequest request(kSimpleGET_Transaction); + + struct Context { + int result; + TestCompletionCallback callback; + net::HttpTransaction* trans; + + Context(net::HttpTransaction* t) : result(net::ERR_IO_PENDING), trans(t) { + } + }; + + std::vector<Context*> context_list; + const int kNumTransactions = 2; + + for (int i = 0; i < kNumTransactions; ++i) { + context_list.push_back( + new Context(cache.http_cache()->CreateTransaction())); + + Context* c = context_list[i]; + int rv = c->trans->Start(&request, &c->callback); + if (rv != net::ERR_IO_PENDING) + c->result = rv; + } + + // the first request should be a writer at this point, and the subsequent + // requests should be pending. + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + for (int i = 0; i < kNumTransactions; ++i) { + Context* c = context_list[i]; + if (c->result == net::ERR_IO_PENDING) + c->result = c->callback.WaitForResult(); + // destroy only the first transaction + if (i == 0) { + c->trans->Destroy(); + delete c; + context_list[i] = NULL; + } + } + + // complete the rest of the transactions + for (int i = 1; i < kNumTransactions; ++i) { + Context* c = context_list[i]; + ReadAndVerifyTransaction(c->trans, kSimpleGET_Transaction); + } + + // we should have had to re-open the disk entry + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); + + for (int i = 1; i < kNumTransactions; ++i) { + Context* c = context_list[i]; + c->trans->Destroy(); + delete c; + } +} + +TEST(HttpCache, SimpleGET_AbandonedCacheRead) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + MockHttpRequest request(kSimpleGET_Transaction); + TestCompletionCallback callback; + + net::HttpTransaction* trans = cache.http_cache()->CreateTransaction(); + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::OK, rv); + + char buf[256]; + rv = trans->Read(buf, sizeof(buf), &callback); + EXPECT_EQ(net::ERR_IO_PENDING, rv); + + // Test that destroying the transaction while it is reading from the cache + // works properly. + trans->Destroy(); + + // Make sure we pump any pending events, which should include a call to + // HttpCache::Transaction::OnCacheReadCompleted. + MessageLoop::current()->Quit(); + MessageLoop::current()->Run(); +} + +TEST(HttpCache, TypicalGET_ConditionalRequest) { + MockHttpCache cache; + + // write to the cache + RunTransactionTest(cache.http_cache(), kTypicalGET_Transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + // get the same URL again, but this time we expect it to result + // in a conditional request. + RunTransactionTest(cache.http_cache(), kTypicalGET_Transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +static void ETagGet_ConditionalRequest_Handler( + const net::HttpRequestInfo* request, + std::string* response_status, + std::string* response_headers, + std::string* response_data) { + EXPECT_TRUE(request->extra_headers.find("If-None-Match") != std::string::npos); + response_status->assign("HTTP/1.1 304 Not Modified"); + response_headers->assign(kETagGET_Transaction.response_headers); + response_data->clear(); +} + +TEST(HttpCache, ETagGET_ConditionalRequest_304) { + MockHttpCache cache; + + ScopedMockTransaction transaction(kETagGET_Transaction); + + // write to the cache + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + // get the same URL again, but this time we expect it to result + // in a conditional request. + transaction.load_flags = net::LOAD_VALIDATE_CACHE; + transaction.handler = ETagGet_ConditionalRequest_Handler; + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimplePOST_SkipsCache) { + MockHttpCache cache; + + // Test that we skip the cache for POST requests. Eventually, we will want + // to cache these, but we'll still have cases where skipping the cache makes + // sense, so we want to make sure that it works properly. + + RunTransactionTest(cache.http_cache(), kSimplePOST_Transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(0, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SimplePOST_LoadOnlyFromCache_Miss) { + MockHttpCache cache; + + // Test that we skip the cache for POST requests. Eventually, we will want + // to cache these, but we'll still have cases where skipping the cache makes + // sense, so we want to make sure that it works properly. + + MockTransaction transaction(kSimplePOST_Transaction); + transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE; + + MockHttpRequest request(transaction); + TestCompletionCallback callback; + + net::HttpTransaction* trans = cache.http_cache()->CreateTransaction(); + ASSERT_TRUE(trans); + + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_CACHE_MISS, rv); + + trans->Destroy(); + + EXPECT_EQ(0, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(0, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, RangeGET_SkipsCache) { + MockHttpCache cache; + + // Test that we skip the cache for POST requests. Eventually, we will want + // to cache these, but we'll still have cases where skipping the cache makes + // sense, so we want to make sure that it works properly. + + RunTransactionTest(cache.http_cache(), kRangeGET_Transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(0, cache.disk_cache()->create_count()); + + MockTransaction transaction(kSimpleGET_Transaction); + transaction.request_headers = "If-None-Match: foo"; + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(0, cache.disk_cache()->create_count()); + + transaction.request_headers = "If-Modified-Since: Wed, 28 Nov 2007 00:45:20 GMT"; + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(3, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(0, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, SyncRead) { + MockHttpCache cache; + + // This test ensures that a read that completes synchronously does not cause + // any problems. + + ScopedMockTransaction transaction(kSimpleGET_Transaction); + transaction.test_mode |= (TEST_MODE_SYNC_CACHE_START | + TEST_MODE_SYNC_CACHE_READ); + + MockHttpRequest r1(transaction), + r2(transaction), + r3(transaction); + + TestTransactionConsumer c1(cache.http_cache()), + c2(cache.http_cache()), + c3(cache.http_cache()); + + c1.Start(&r1); + + r2.load_flags |= net::LOAD_ONLY_FROM_CACHE; + c2.Start(&r2); + + r3.load_flags |= net::LOAD_ONLY_FROM_CACHE; + c3.Start(&r3); + + MessageLoop::current()->Run(); + + EXPECT_TRUE(c1.is_done()); + EXPECT_TRUE(c2.is_done()); + EXPECT_TRUE(c3.is_done()); + + EXPECT_EQ(net::OK, c1.error()); + EXPECT_EQ(net::OK, c2.error()); + EXPECT_EQ(net::OK, c3.error()); +} + +TEST(HttpCache, ValidationResultsIn200) { + MockHttpCache cache; + + // This test ensures that a conditional request, which results in a 200 + // instead of a 304, properly truncates the existing response data. + + // write to the cache + RunTransactionTest(cache.http_cache(), kETagGET_Transaction); + + // force this transaction to validate the cache + MockTransaction transaction(kETagGET_Transaction); + transaction.load_flags |= net::LOAD_VALIDATE_CACHE; + RunTransactionTest(cache.http_cache(), transaction); + + // read from the cache + RunTransactionTest(cache.http_cache(), kETagGET_Transaction); +} + +TEST(HttpCache, CachedRedirect) { + MockHttpCache cache; + + ScopedMockTransaction kTestTransaction(kSimpleGET_Transaction); + kTestTransaction.status = "HTTP/1.1 301 Moved Permanently"; + kTestTransaction.response_headers = "Location: http://www.bar.com/\n"; + + MockHttpRequest request(kTestTransaction); + TestCompletionCallback callback; + + // write to the cache + { + net::HttpTransaction* trans = cache.http_cache()->CreateTransaction(); + ASSERT_TRUE(trans); + + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::OK, rv); + + const net::HttpResponseInfo* info = trans->GetResponseInfo(); + ASSERT_TRUE(info); + + EXPECT_EQ(info->headers->response_code(), 301); + + std::string location; + info->headers->EnumerateHeader(NULL, "Location", &location); + EXPECT_EQ(location, "http://www.bar.com/"); + + // now, destroy the transaction without actually reading the response body. + // we want to test that it is still getting cached. + trans->Destroy(); + } + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + // read from the cache + { + net::HttpTransaction* trans = cache.http_cache()->CreateTransaction(); + ASSERT_TRUE(trans); + + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::OK, rv); + + const net::HttpResponseInfo* info = trans->GetResponseInfo(); + ASSERT_TRUE(info); + + EXPECT_EQ(info->headers->response_code(), 301); + + std::string location; + info->headers->EnumerateHeader(NULL, "Location", &location); + EXPECT_EQ(location, "http://www.bar.com/"); + + // now, destroy the transaction without actually reading the response body. + // we want to test that it is still getting cached. + trans->Destroy(); + } + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); +} + +TEST(HttpCache, CacheControlNoStore) { + MockHttpCache cache; + + ScopedMockTransaction transaction(kSimpleGET_Transaction); + transaction.response_headers = "cache-control: no-store\n"; + + // initial load + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + // try loading again; it should result in a network fetch + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(2, cache.disk_cache()->create_count()); + + disk_cache::Entry* entry; + bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry); + EXPECT_FALSE(exists); +} + +TEST(HttpCache, CacheControlNoStore2) { + // this test is similar to the above test, except that the initial response + // is cachable, but when it is validated, no-store is received causing the + // cached document to be deleted. + MockHttpCache cache; + + ScopedMockTransaction transaction(kETagGET_Transaction); + + // initial load + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + // try loading again; it should result in a network fetch + transaction.load_flags = net::LOAD_VALIDATE_CACHE; + transaction.response_headers = "cache-control: no-store\n"; + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + disk_cache::Entry* entry; + bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry); + EXPECT_FALSE(exists); +} + +TEST(HttpCache, CacheControlNoStore3) { + // this test is similar to the above test, except that the response is a 304 + // instead of a 200. this should never happen in practice, but it seems like + // a good thing to verify that we still destroy the cache entry. + MockHttpCache cache; + + ScopedMockTransaction transaction(kETagGET_Transaction); + + // initial load + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(0, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + // try loading again; it should result in a network fetch + transaction.load_flags = net::LOAD_VALIDATE_CACHE; + transaction.response_headers = "cache-control: no-store\n"; + transaction.status = "HTTP/1.1 304 Not Modified"; + RunTransactionTest(cache.http_cache(), transaction); + + EXPECT_EQ(2, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->open_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + disk_cache::Entry* entry; + bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry); + EXPECT_FALSE(exists); +} + +// Ensure that we don't cache requests served over bad HTTPS. +TEST(HttpCache, SimpleGET_SSLError) { + MockHttpCache cache; + + MockTransaction transaction = kSimpleGET_Transaction; + transaction.cert_status = net::CERT_STATUS_REVOKED; + ScopedMockTransaction scoped_transaction(transaction); + + // write to the cache + RunTransactionTest(cache.http_cache(), transaction); + + // Test that it was not cached. + transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE; + + MockHttpRequest request(transaction); + TestCompletionCallback callback; + + net::HttpTransaction* trans = cache.http_cache()->CreateTransaction(); + ASSERT_TRUE(trans); + + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::ERR_CACHE_MISS, rv); + + trans->Destroy(); +} diff --git a/net/http/http_chunked_decoder.cc b/net/http/http_chunked_decoder.cc new file mode 100644 index 0000000..059d792 --- /dev/null +++ b/net/http/http_chunked_decoder.cc @@ -0,0 +1,136 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Netscape Communications. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher <darin@netscape.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Derived from: +// mozilla/netwerk/protocol/http/src/nsHttpChunkedDecoder.cpp + +#include "net/http/http_chunked_decoder.h" + +#include "base/logging.h" +#include "net/base/net_errors.h" + +namespace net { + +HttpChunkedDecoder::HttpChunkedDecoder() + : chunk_remaining_(0), + reached_last_chunk_(false), + reached_eof_(false) { +} + +int HttpChunkedDecoder::FilterBuf(char* buf, int buf_len) { + int result = 0; + + while (buf_len) { + if (chunk_remaining_) { + int num = std::min(chunk_remaining_, buf_len); + + buf_len -= num; + chunk_remaining_ -= num; + + result += num; + buf += num; + continue; + } else if (reached_eof_) { + break; // Done! + } + + // Returns bytes consumed or an error code. + int rv = ScanForChunkRemaining(buf, buf_len); + if (rv < 0) + return rv; + + buf_len -= rv; + if (buf_len) + memmove(buf, buf + rv, buf_len); + } + + return result; +} + +int HttpChunkedDecoder::ScanForChunkRemaining(char* buf, int buf_len) { + DCHECK(chunk_remaining_ == 0); + DCHECK(buf_len > 0); + + int result = 0; + + char *p = static_cast<char*>(memchr(buf, '\n', buf_len)); + if (p) { + *p = 0; + if ((p > buf) && (*(p - 1) == '\r')) // Eliminate a preceding CR. + *(p - 1) = 0; + result = static_cast<int>(p - buf) + 1; + + // Make buf point to the full line buffer to parse. + if (!line_buf_.empty()) { + line_buf_.append(buf); + buf = const_cast<char*>(line_buf_.data()); + } + + if (reached_last_chunk_) { + if (*buf) { + DLOG(INFO) << "ignoring http trailer"; + } else { + reached_eof_ = true; + } + } else if (*buf) { + // Ignore any chunk-extensions. + if ((p = strchr(buf, ';')) != NULL) + *p = 0; + + if (!sscanf_s(buf, "%x", &chunk_remaining_)) { + DLOG(ERROR) << "sscanf failed parsing HEX from: " << buf; + return ERR_FAILED; + } + + if (chunk_remaining_ == 0) + reached_last_chunk_ = true; + } + line_buf_.clear(); + } else { + // Save the partial line; wait for more data. + result = buf_len; + + // Ignore a trailing CR + if (buf[buf_len - 1] == '\r') + buf_len--; + + line_buf_.append(buf, buf_len); + } + return result; +} + +} // namespace net diff --git a/net/http/http_chunked_decoder.h b/net/http/http_chunked_decoder.h new file mode 100644 index 0000000..799ebfd --- /dev/null +++ b/net/http/http_chunked_decoder.h @@ -0,0 +1,107 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla. + * + * The Initial Developer of the Original Code is + * Netscape Communications. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Darin Fisher <darin@netscape.com> (original author) + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// Derived from: +// mozilla/netwerk/protocol/http/src/nsHttpChunkedDecoder.h + +#ifndef NET_HTTP_HTTP_CHUNKED_DECODER_H_ +#define NET_HTTP_HTTP_CHUNKED_DECODER_H_ + +#include <string> + +namespace net { + +// From RFC2617 section 3.6.1, the chunked transfer coding is defined as: +// +// Chunked-Body = *chunk +// last-chunk +// trailer +// CRLF +// chunk = chunk-size [ chunk-extension ] CRLF +// chunk-data CRLF +// chunk-size = 1*HEX +// last-chunk = 1*("0") [ chunk-extension ] CRLF +// +// chunk-extension = *( ";" chunk-ext-name [ "=" chunk-ext-val ] ) +// chunk-ext-name = token +// chunk-ext-val = token | quoted-string +// chunk-data = chunk-size(OCTET) +// trailer = *(entity-header CRLF) +// +// The chunk-size field is a string of hex digits indicating the size of the +// chunk. The chunked encoding is ended by any chunk whose size is zero, +// followed by the trailer, which is terminated by an empty line. +// +// NOTE: This implementation does not bother to parse trailers since they are +// not used on the web. +// +class HttpChunkedDecoder { + public: + HttpChunkedDecoder(); + + // Indicates that a previous call to FilterBuf encountered the final CRLF. + bool reached_eof() const { return reached_eof_; } + + // Called to filter out the chunk markers from buf and to check for end-of- + // file. This method modifies |buf| inline if necessary to remove chunk + // markers. The return value indicates the final size of decoded data stored + // in |buf|. Call reached_eof() after this method to check if end-of-file + // was encountered. + int FilterBuf(char* buf, int buf_len); + + private: + // Scan |buf| for the next chunk delimiter. This method returns the number + // of bytes consumed from |buf|. If found, |chunk_remaining_| holds the + // value for the next chunk size. + int ScanForChunkRemaining(char* buf, int buf_len); + + // Indicates the number of bytes remaining for the current chunk. + int chunk_remaining_; + + // A small buffer used to store a partial chunk marker. + std::string line_buf_; + + // Set to true when FilterBuf encounters the last-chunk. + bool reached_last_chunk_; + + // Set to true when FilterBuf encounters the final CRLF. + bool reached_eof_; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_CHUNKED_DECODER_H_ diff --git a/net/http/http_chunked_decoder_unittest.cc b/net/http/http_chunked_decoder_unittest.cc new file mode 100644 index 0000000..602ba6a --- /dev/null +++ b/net/http/http_chunked_decoder_unittest.cc @@ -0,0 +1,111 @@ +// 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 "base/basictypes.h" +#include "net/http/http_chunked_decoder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +typedef testing::Test HttpChunkedDecoderTest; + +void RunTest(const char* inputs[], size_t num_inputs, + const char* expected_output, + bool expected_eof) { + net::HttpChunkedDecoder decoder; + EXPECT_FALSE(decoder.reached_eof()); + + std::string result; + + for (size_t i = 0; i < num_inputs; ++i) { + std::string input = inputs[i]; + int n = decoder.FilterBuf(&input[0], static_cast<int>(input.size())); + EXPECT_TRUE(n >= 0); + if (n > 0) + result.append(input.data(), n); + } + + EXPECT_TRUE(result == expected_output); + EXPECT_TRUE(decoder.reached_eof() == expected_eof); +} + +} // namespace + +TEST(HttpChunkedDecoderTest, Basic) { + const char* inputs[] = { + "5\r\nhello\r\n0\r\n\r\n" + }; + RunTest(inputs, arraysize(inputs), "hello", true); +} + +TEST(HttpChunkedDecoderTest, Typical) { + const char* inputs[] = { + "5\r\nhello\r\n", + "1\r\n \r\n", + "5\r\nworld\r\n", + "0\r\n\r\n" + }; + RunTest(inputs, arraysize(inputs), "hello world", true); +} + +TEST(HttpChunkedDecoderTest, Incremental) { + const char* inputs[] = { + "5", + "\r", + "\n", + "hello", + "\r", + "\n", + "0", + "\r", + "\n", + "\r", + "\n" + }; + RunTest(inputs, arraysize(inputs), "hello", true); +} + +TEST(HttpChunkedDecoderTest, Extensions) { + const char* inputs[] = { + "5;x=0\r\nhello\r\n", + "0;y=\"2 \"\r\n\r\n" + }; + RunTest(inputs, arraysize(inputs), "hello", true); +} + +TEST(HttpChunkedDecoderTest, Trailers) { + const char* inputs[] = { + "5\r\nhello\r\n", + "0\r\n", + "Foo: 1\r\n", + "Bar: 2\r\n", + "\r\n" + }; + RunTest(inputs, arraysize(inputs), "hello", true); +} diff --git a/net/http/http_connection.cc b/net/http/http_connection.cc new file mode 100644 index 0000000..0ca8a3d --- /dev/null +++ b/net/http/http_connection.cc @@ -0,0 +1,64 @@ +// 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_connection.h" + +#include "net/base/client_socket.h" +#include "net/http/http_connection_manager.h" + +namespace net { + +HttpConnection::HttpConnection(HttpConnectionManager* mgr) + : mgr_(mgr), socket_(NULL) { +} + +HttpConnection::~HttpConnection() { + Reset(); +} + +int HttpConnection::Init(const std::string& group_name, + CompletionCallback* callback) { + Reset(); + group_name_ = group_name; + return mgr_->RequestSocket(group_name_, &socket_, callback); +} + +void HttpConnection::Reset() { + if (group_name_.empty()) // Was Init called? + return; + if (socket_) { + mgr_->ReleaseSocket(group_name_, socket_); + socket_ = NULL; + } else { + mgr_->CancelRequest(group_name_, &socket_); + } + group_name_.clear(); +} + +} // namespace net diff --git a/net/http/http_connection.h b/net/http/http_connection.h new file mode 100644 index 0000000..ada8468 --- /dev/null +++ b/net/http/http_connection.h @@ -0,0 +1,94 @@ +// 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. + +#ifndef NET_HTTP_HTTP_CONNECTION_H_ +#define NET_HTTP_HTTP_CONNECTION_H_ + +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "net/base/client_socket.h" +#include "net/base/completion_callback.h" + +namespace net { + +class HttpConnectionManager; + +// A container for a ClientSocket, representing a HTTP connection. +// +// The connection's |group_name| uniquely identifies the origin and type of the +// connection. It is used by the HttpConnectionManager to group similar http +// connection objects. +// +// A connection object is initialized with a null socket. It is the consumer's +// job to initialize a ClientSocket object and set it on the connection. +// +class HttpConnection { + public: + HttpConnection(HttpConnectionManager* mgr); + ~HttpConnection(); + + // Initializes a HttpConnection object, which involves talking to the + // HttpConnectionManager to locate a socket to possibly reuse. + // + // If this method succeeds, then the socket member will be set to an existing + // socket if an existing socket was available to reuse. Otherwise, the + // consumer should set the socket member of this connection object. + // + // This method returns ERR_IO_PENDING if it cannot complete synchronously, in + // which case the consumer should wait for the completion callback to run. + // + // Init may be called multiple times. + // + int Init(const std::string& group_name, CompletionCallback* callback); + + // An initialized connection can be reset, which causes it to return to the + // un-initialized state. This releases the underlying socket, which in the + // case of a socket that is not closed, indicates that the socket may be kept + // alive for use by a subsequent HttpConnection. NOTE: To prevent the socket + // from being kept alive, be sure to call its Close method. + void Reset(); + + // Returns true when Init has completed successfully. + bool is_initialized() const { return socket_ != NULL; } + + // These may only be used if is_initialized() is true. + ClientSocket* socket() { return socket_->get(); } + void set_socket(ClientSocket* s) { socket_->reset(s); } + + private: + scoped_refptr<HttpConnectionManager> mgr_; + scoped_ptr<ClientSocket>* socket_; + std::string group_name_; + + DISALLOW_EVIL_CONSTRUCTORS(HttpConnection); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_CONNECTION_H_ diff --git a/net/http/http_connection_manager.cc b/net/http/http_connection_manager.cc new file mode 100644 index 0000000..a7edd92 --- /dev/null +++ b/net/http/http_connection_manager.cc @@ -0,0 +1,199 @@ +// 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_connection_manager.h" + +#include "base/message_loop.h" +#include "net/base/client_socket.h" +#include "net/base/net_errors.h" + +namespace net { + +HttpConnectionManager::HttpConnectionManager() + : timer_(TimeDelta::FromSeconds(5)), + idle_count_(0) { + timer_.set_task(this); +} + +HttpConnectionManager::~HttpConnectionManager() { + timer_.set_task(NULL); + + // Cleanup any idle sockets. Assert that we have no remaining active sockets + // or pending requests. They should have all been cleaned up prior to the + // manager being destroyed. + + CloseIdleSockets(); + DCHECK(group_map_.empty()); +} + +int HttpConnectionManager::RequestSocket(const std::string& group_name, + SocketHandle** handle, + CompletionCallback* callback) { + Group& group = + group_map_.insert(std::make_pair(group_name, Group())).first->second; + + // Can we make another active socket now? + if (group.active_socket_count == kMaxSocketsPerGroup) { + Request r; + r.result = handle; + r.callback = callback; + group.pending_requests.push_back(r); + return ERR_IO_PENDING; + } + + // OK, we are going to activate one. + group.active_socket_count++; + + // Use idle sockets in LIFO order. + while (!group.idle_sockets.empty()) { + SocketHandle* h = group.idle_sockets.back(); + group.idle_sockets.pop_back(); + DecrementIdleCount(); + if (!h->get()->IsConnected()) { + delete h; + } else { + // We found one we can reuse! + *handle = h; + return OK; + } + } + + *handle = new SocketHandle(); + return OK; +} + +void HttpConnectionManager::CancelRequest(const std::string& group_name, + SocketHandle** handle) { + Group& group = group_map_[group_name]; + + // In order for us to be canceling a pending request, we must have active + // sockets equaling the limit. + DCHECK(group.active_socket_count == kMaxSocketsPerGroup); + + // Search pending_requests for matching handle. + std::deque<Request>::iterator it = group.pending_requests.begin(); + for (; it != group.pending_requests.end(); ++it) { + if (it->result == handle) { + group.pending_requests.erase(it); + break; + } + } +} + +void HttpConnectionManager::ReleaseSocket(const std::string& group_name, + SocketHandle* handle) { + // Run this asynchronously to allow the caller to finish before we let + // another to begin doing work. This also avoids nasty recursion issues. + MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod( + this, &HttpConnectionManager::DoReleaseSocket, group_name, handle)); +} + +void HttpConnectionManager::CloseIdleSockets() { + MaybeCloseIdleSockets(false); +} + +void HttpConnectionManager::MaybeCloseIdleSockets( + bool only_if_disconnected) { + if (idle_count_ == 0) + return; + + GroupMap::iterator i = group_map_.begin(); + while (i != group_map_.end()) { + Group& group = i->second; + + std::deque<SocketHandle*>::iterator j = group.idle_sockets.begin(); + while (j != group.idle_sockets.end()) { + if (!only_if_disconnected || !(*j)->get()->IsConnected()) { + delete *j; + j = group.idle_sockets.erase(j); + DecrementIdleCount(); + } else { + ++j; + } + } + + // Delete group if no longer needed. + if (group.active_socket_count == 0 && group.idle_sockets.empty()) { + DCHECK(group.pending_requests.empty()); + group_map_.erase(i++); + } else { + ++i; + } + } +} + +void HttpConnectionManager::IncrementIdleCount() { + if (++idle_count_ == 1) + timer_.Start(); +} + +void HttpConnectionManager::DecrementIdleCount() { + if (--idle_count_ == 0) + timer_.Stop(); +} + +void HttpConnectionManager::DoReleaseSocket(const std::string& group_name, + SocketHandle* handle) { + GroupMap::iterator i = group_map_.find(group_name); + DCHECK(i != group_map_.end()); + + Group& group = i->second; + + DCHECK(group.active_socket_count > 0); + group.active_socket_count--; + + bool can_reuse = handle->get() && handle->get()->IsConnected(); + if (can_reuse) { + group.idle_sockets.push_back(handle); + IncrementIdleCount(); + } else { + delete handle; + } + + // Process one pending request. + if (!group.pending_requests.empty()) { + Request r = group.pending_requests.front(); + group.pending_requests.pop_front(); + RequestSocket(i->first, r.result, NULL); + r.callback->Run(OK); + return; + } + + // Delete group if no longer needed. + if (group.active_socket_count == 0 && group.idle_sockets.empty()) { + DCHECK(group.pending_requests.empty()); + group_map_.erase(i); + } +} + +void HttpConnectionManager::Run() { + MaybeCloseIdleSockets(true); +} + +} // namespace net diff --git a/net/http/http_connection_manager.h b/net/http/http_connection_manager.h new file mode 100644 index 0000000..22080a3 --- /dev/null +++ b/net/http/http_connection_manager.h @@ -0,0 +1,136 @@ +// 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. + +#ifndef NET_HTTP_HTTP_CONNECTION_MANAGER_H_ +#define NET_HTTP_HTTP_CONNECTION_MANAGER_H_ + +#include <deque> +#include <map> + +#include "base/ref_counted.h" +#include "base/timer.h" +#include "net/base/completion_callback.h" + +namespace net { + +class ClientSocket; + +// A HttpConnectionManager is used to restrict the number of HTTP sockets +// open at a time. It also maintains a list of idle persistent sockets. +// +// The HttpConnectionManager allocates SocketHandle objects, but it is not +// responsible for allocating the associated ClientSocket object. The +// consumer must do so if it gets a SocketHandle with a null ClientSocket. +// +class HttpConnectionManager : public base::RefCounted<HttpConnectionManager>, + public Task { + public: + HttpConnectionManager(); + + // The maximum number of simultaneous sockets per group. + enum { kMaxSocketsPerGroup = 6 }; + + typedef scoped_ptr<ClientSocket> SocketHandle; + + // Called to get access to a SocketHandle object for the given group name. + // + // If this function returns OK, then |*handle| will reference a SocketHandle + // object. If ERR_IO_PENDING is returned, then the completion callback will + // be called when |*handle| has been populated. Otherwise, an error code is + // returned. + // + // If the resultant SocketHandle object has a null member, then it is the + // callers job to create a ClientSocket and associate it with the handle. + // + int RequestSocket(const std::string& group_name, SocketHandle** handle, + CompletionCallback* callback); + + // Called to cancel a RequestSocket call that returned ERR_IO_PENDING. The + // same group_name and handle parameters must be passed to this method as + // were passed to the RequestSocket call being cancelled. The associated + // CompletionCallback is not run. + void CancelRequest(const std::string& group_name, SocketHandle** handle); + + // Called to release a SocketHandle object that is no longer in use. If the + // handle has a ClientSocket that is still connected, then this handle may be + // added to the keep-alive set of sockets. + void ReleaseSocket(const std::string& group_name, SocketHandle* handle); + + // Called to close any idle connections held by the connection manager. + void CloseIdleSockets(); + + private: + friend class base::RefCounted<HttpConnectionManager>; + + ~HttpConnectionManager(); + + // Closes all idle sockets if |only_if_disconnected| is false. Else, only + // idle sockets that are disconnected get closed. + void MaybeCloseIdleSockets(bool only_if_disconnected); + + // Called when the number of idle sockets changes. + void IncrementIdleCount(); + void DecrementIdleCount(); + + // Called via PostTask by ReleaseSocket. + void DoReleaseSocket(const std::string& group_name, SocketHandle* handle); + + // Task implementation. This method scans the idle sockets checking to see + // if any have been disconnected. + virtual void Run(); + + // A Request is allocated per call to RequestSocket that results in + // ERR_IO_PENDING. + struct Request { + SocketHandle** result; + CompletionCallback* callback; + }; + + // A Group is allocated per group_name when there are idle sockets or pending + // requests. Otherwise, the Group object is removed from the map. + struct Group { + std::deque<SocketHandle*> idle_sockets; + std::deque<Request> pending_requests; + int active_socket_count; + Group() : active_socket_count(0) {} + }; + + typedef std::map<std::string, Group> GroupMap; + GroupMap group_map_; + + // Timer used to periodically prune sockets that have been disconnected. + RepeatingTimer timer_; + + // The total number of idle sockets in the system. + int idle_count_; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_CONNECTION_MANAGER_H_ diff --git a/net/http/http_connection_manager_unittest.cc b/net/http/http_connection_manager_unittest.cc new file mode 100644 index 0000000..9eade18 --- /dev/null +++ b/net/http/http_connection_manager_unittest.cc @@ -0,0 +1,277 @@ +// 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 "base/message_loop.h" +#include "net/base/client_socket.h" +#include "net/base/net_errors.h" +#include "net/http/http_connection_manager.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +typedef testing::Test HttpConnectionManagerTest; + +class MockClientSocket : public net::ClientSocket { + public: + MockClientSocket() : connected_(false) { + allocation_count++; + } + + // ClientSocket methods: + virtual int Connect(net::CompletionCallback* callback) { + connected_ = true; + return net::OK; + } + virtual int ReconnectIgnoringLastError(net::CompletionCallback* callback) { + return net::ERR_FAILED; + } + virtual void Disconnect() { + connected_ = false; + } + virtual bool IsConnected() const { + return connected_; + } + + // Socket methods: + virtual int Read(char* buf, int buf_len, + net::CompletionCallback* callback) { + return net::ERR_FAILED; + } + virtual int Write(const char* buf, int buf_len, + net::CompletionCallback* callback) { + return net::ERR_FAILED; + } + virtual int GetProperty(int property, void* buf, int buf_len) const { + return net::ERR_FAILED; + } + virtual int SetProperty(int property, const void* buf, int buf_len) { + return net::ERR_FAILED; + } + + static int allocation_count; + + private: + bool connected_; +}; + +int MockClientSocket::allocation_count = 0; + +class TestSocketRequest : public CallbackRunner< Tuple1<int> > { + public: + TestSocketRequest() : handle(NULL) {} + + net::HttpConnectionManager::SocketHandle* handle; + + void InitHandle() { + DCHECK(handle); + if (!handle->get()) { + handle->reset(new MockClientSocket()); + handle->get()->Connect(NULL); + } + } + + virtual void RunWithParams(const Tuple1<int>& params) { + DCHECK(params.a == net::OK); + completion_count++; + InitHandle(); + } + + static int completion_count; +}; + +int TestSocketRequest::completion_count = 0; + +// Call ReleaseSocket and wait for it to complete. It runs via PostTask, so we +// can just empty the MessageLoop to ensure that ReleaseSocket finished. +void CallReleaseSocket(net::HttpConnectionManager* connection_mgr, + const std::string& group_name, + net::HttpConnectionManager::SocketHandle* handle) { + connection_mgr->ReleaseSocket(group_name, handle); + MessageLoop::current()->Quit(); + MessageLoop::current()->Run(); +} + +} // namespace + +TEST(HttpConnectionManagerTest, Basic) { + scoped_refptr<net::HttpConnectionManager> mgr = + new net::HttpConnectionManager(); + + TestSocketRequest r; + int rv; + + rv = mgr->RequestSocket("a", &r.handle, &r); + EXPECT_EQ(net::OK, rv); + EXPECT_TRUE(r.handle != NULL); + + CallReleaseSocket(mgr, "a", r.handle); +} + +TEST(HttpConnectionManagerTest, WithIdleConnection) { + scoped_refptr<net::HttpConnectionManager> mgr = + new net::HttpConnectionManager(); + + TestSocketRequest r; + int rv; + + rv = mgr->RequestSocket("a", &r.handle, &r); + EXPECT_EQ(net::OK, rv); + EXPECT_TRUE(r.handle != NULL); + + r.InitHandle(); + + CallReleaseSocket(mgr, "a", r.handle); +} + +TEST(HttpConnectionManagerTest, PendingRequests) { + scoped_refptr<net::HttpConnectionManager> mgr = + new net::HttpConnectionManager(); + + int rv; + + TestSocketRequest reqs[ + net::HttpConnectionManager::kMaxSocketsPerGroup + 10]; + + // Reset + MockClientSocket::allocation_count = 0; + TestSocketRequest::completion_count = 0; + + // Create connections or queue up requests. + for (size_t i = 0; i < arraysize(reqs); ++i) { + rv = mgr->RequestSocket("a", &reqs[i].handle, &reqs[i]); + if (rv != net::ERR_IO_PENDING) { + EXPECT_EQ(net::OK, rv); + reqs[i].InitHandle(); + } + } + + // Release any connections until we have no connections. + bool released_one; + do { + released_one = false; + for (size_t i = 0; i < arraysize(reqs); ++i) { + if (reqs[i].handle) { + CallReleaseSocket(mgr, "a", reqs[i].handle); + reqs[i].handle = NULL; + released_one = true; + } + } + } while (released_one); + + EXPECT_EQ(net::HttpConnectionManager::kMaxSocketsPerGroup, + MockClientSocket::allocation_count); + EXPECT_EQ(10, TestSocketRequest::completion_count); +} + +TEST(HttpConnectionManagerTest, PendingRequests_NoKeepAlive) { + scoped_refptr<net::HttpConnectionManager> mgr = + new net::HttpConnectionManager(); + + int rv; + + TestSocketRequest reqs[ + net::HttpConnectionManager::kMaxSocketsPerGroup + 10]; + + // Reset + MockClientSocket::allocation_count = 0; + TestSocketRequest::completion_count = 0; + + // Create connections or queue up requests. + for (size_t i = 0; i < arraysize(reqs); ++i) { + rv = mgr->RequestSocket("a", &reqs[i].handle, &reqs[i]); + if (rv != net::ERR_IO_PENDING) { + EXPECT_EQ(net::OK, rv); + reqs[i].InitHandle(); + } + } + + // Release any connections until we have no connections. + bool released_one; + do { + released_one = false; + for (size_t i = 0; i < arraysize(reqs); ++i) { + if (reqs[i].handle) { + reqs[i].handle->get()->Disconnect(); + CallReleaseSocket(mgr, "a", reqs[i].handle); + reqs[i].handle = NULL; + released_one = true; + } + } + } while (released_one); + + EXPECT_EQ(net::HttpConnectionManager::kMaxSocketsPerGroup + 10, + MockClientSocket::allocation_count); + EXPECT_EQ(10, TestSocketRequest::completion_count); +} + +TEST(HttpConnectionManagerTest, CancelRequest) { + scoped_refptr<net::HttpConnectionManager> mgr = + new net::HttpConnectionManager(); + + int rv; + + TestSocketRequest reqs[ + net::HttpConnectionManager::kMaxSocketsPerGroup + 10]; + + // Reset + MockClientSocket::allocation_count = 0; + TestSocketRequest::completion_count = 0; + + // Create connections or queue up requests. + for (size_t i = 0; i < arraysize(reqs); ++i) { + rv = mgr->RequestSocket("a", &reqs[i].handle, &reqs[i]); + if (rv != net::ERR_IO_PENDING) { + EXPECT_EQ(net::OK, rv); + reqs[i].InitHandle(); + } + } + + // Cancel a request. + size_t index_to_cancel = + net::HttpConnectionManager::kMaxSocketsPerGroup + 2; + EXPECT_TRUE(reqs[index_to_cancel].handle == NULL); + mgr->CancelRequest("a", &reqs[index_to_cancel].handle); + + // Release any connections until we have no connections. + bool released_one; + do { + released_one = false; + for (size_t i = 0; i < arraysize(reqs); ++i) { + if (reqs[i].handle) { + CallReleaseSocket(mgr, "a", reqs[i].handle); + reqs[i].handle = NULL; + released_one = true; + } + } + } while (released_one); + + EXPECT_EQ(net::HttpConnectionManager::kMaxSocketsPerGroup, + MockClientSocket::allocation_count); + EXPECT_EQ(9, TestSocketRequest::completion_count); +} diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc new file mode 100644 index 0000000..2fd846c --- /dev/null +++ b/net/http/http_network_layer.cc @@ -0,0 +1,99 @@ +// 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_network_layer.h" + +#include "net/base/client_socket_factory.h" +#include "net/http/http_network_session.h" +#include "net/http/http_network_transaction.h" +#include "net/http/http_proxy_resolver_fixed.h" +#include "net/http/http_proxy_resolver_winhttp.h" +#include "net/http/http_transaction_winhttp.h" + +namespace net { + +//----------------------------------------------------------------------------- + +// static +bool HttpNetworkLayer::use_winhttp_ = true; + +// static +HttpTransactionFactory* HttpNetworkLayer::CreateFactory( + const HttpProxyInfo* pi) { + if (use_winhttp_) + return new HttpTransactionWinHttp::Factory(pi); + + return new HttpNetworkLayer(pi); +} + +// static +void HttpNetworkLayer::UseWinHttp(bool value) { + use_winhttp_ = value; +} + +//----------------------------------------------------------------------------- + +HttpNetworkLayer::HttpNetworkLayer(const HttpProxyInfo* pi) + : suspended_(false) { + HttpProxyResolver* proxy_resolver; + if (pi) { + proxy_resolver = new HttpProxyResolverFixed(*pi); + } else { + proxy_resolver = new HttpProxyResolverWinHttp(); + } + session_ = new HttpNetworkSession(proxy_resolver); +} + +HttpNetworkLayer::~HttpNetworkLayer() { +} + +HttpTransaction* HttpNetworkLayer::CreateTransaction() { + if (suspended_) + return NULL; + + return new HttpNetworkTransaction( + session_, ClientSocketFactory::GetDefaultFactory()); +} + +HttpCache* HttpNetworkLayer::GetCache() { + return NULL; +} + +AuthCache* HttpNetworkLayer::GetAuthCache() { + return session_->auth_cache(); +} + +void HttpNetworkLayer::Suspend(bool suspend) { + suspended_ = suspend; + + if (suspend) + session_->connection_manager()->CloseIdleSockets(); +} + +} // namespace net diff --git a/net/http/http_network_layer.h b/net/http/http_network_layer.h new file mode 100644 index 0000000..72fbca3 --- /dev/null +++ b/net/http/http_network_layer.h @@ -0,0 +1,68 @@ +// 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. + +#ifndef NET_HTTP_HTTP_NETWORK_LAYER_H_ +#define NET_HTTP_HTTP_NETWORK_LAYER_H_ + +#include "base/ref_counted.h" +#include "net/http/http_transaction_factory.h" + +namespace net { + +class HttpNetworkSession; +class HttpProxyInfo; + +class HttpNetworkLayer : public HttpTransactionFactory { + public: + // This function hides the details of how a network layer gets instantiated + // and allows other implementations to be substituted. + static HttpTransactionFactory* CreateFactory(const HttpProxyInfo* pi); + + // If value is true, then WinHTTP will be used. + static void UseWinHttp(bool value); + + HttpNetworkLayer(const HttpProxyInfo* pi); + ~HttpNetworkLayer(); + + // HttpTransactionFactory methods: + virtual HttpTransaction* CreateTransaction(); + virtual HttpCache* GetCache(); + virtual AuthCache* GetAuthCache(); + virtual void Suspend(bool suspend); + + private: + static bool use_winhttp_; + + scoped_refptr<HttpNetworkSession> session_; + bool suspended_; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_NETWORK_LAYER_H_ diff --git a/net/http/http_network_layer_unittest.cc b/net/http/http_network_layer_unittest.cc new file mode 100644 index 0000000..dae47ae --- /dev/null +++ b/net/http/http_network_layer_unittest.cc @@ -0,0 +1,87 @@ +// 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_network_layer.h" +#include "net/http/http_transaction_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class HttpNetworkLayerTest : public testing::Test { +}; + +} // namespace + +TEST_F(HttpNetworkLayerTest, CreateAndDestroy) { + net::HttpNetworkLayer factory(NULL); + + net::HttpTransaction* trans = factory.CreateTransaction(); + trans->Destroy(); +} + +TEST_F(HttpNetworkLayerTest, Suspend) { + net::HttpNetworkLayer factory(NULL); + + net::HttpTransaction* trans = factory.CreateTransaction(); + trans->Destroy(); + + factory.Suspend(true); + + trans = factory.CreateTransaction(); + ASSERT_TRUE(trans == NULL); + + factory.Suspend(false); + + trans = factory.CreateTransaction(); + trans->Destroy(); +} + +TEST_F(HttpNetworkLayerTest, GoogleGET) { + net::HttpNetworkLayer factory(NULL); + TestCompletionCallback callback; + + net::HttpTransaction* trans = factory.CreateTransaction(); + + net::HttpRequestInfo request_info; + request_info.url = GURL("http://www.google.com/"); + request_info.method = "GET"; + request_info.user_agent = "Foo/1.0"; + request_info.load_flags = net::LOAD_NORMAL; + + int rv = trans->Start(&request_info, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(net::OK, rv); + + std::string contents; + rv = ReadTransaction(trans, &contents); + EXPECT_EQ(net::OK, rv); + + trans->Destroy(); +} diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h new file mode 100644 index 0000000..dc31ed1 --- /dev/null +++ b/net/http/http_network_session.h @@ -0,0 +1,62 @@ +// 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. + +#ifndef NET_HTTP_HTTP_NETWORK_SESSION_H_ +#define NET_HTTP_HTTP_NETWORK_SESSION_H_ + +#include "base/ref_counted.h" +#include "net/base/auth_cache.h" +#include "net/http/http_connection_manager.h" +#include "net/http/http_proxy_service.h" + +namespace net { + +// This class holds session objects used by HttpNetworkTransaction objects. +class HttpNetworkSession : public base::RefCounted<HttpNetworkSession> { + public: + HttpNetworkSession(HttpProxyResolver* proxy_resolver) + : connection_manager_(new HttpConnectionManager()), + proxy_resolver_(proxy_resolver), + proxy_service_(proxy_resolver) { + } + + AuthCache* auth_cache() { return &auth_cache_; } + HttpConnectionManager* connection_manager() { return connection_manager_; } + HttpProxyService* proxy_service() { return &proxy_service_; } + + private: + AuthCache auth_cache_; + scoped_refptr<HttpConnectionManager> connection_manager_; + scoped_ptr<HttpProxyResolver> proxy_resolver_; + HttpProxyService proxy_service_; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_NETWORK_SESSION_H_ diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc new file mode 100644 index 0000000..5ae927f --- /dev/null +++ b/net/http/http_network_transaction.cc @@ -0,0 +1,673 @@ +// 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_network_transaction.h" + +#include "base/string_util.h" +#include "net/base/client_socket_factory.h" +#include "net/base/host_resolver.h" +#include "net/base/load_flags.h" +#include "net/base/upload_data_stream.h" +#include "net/http/http_chunked_decoder.h" +#include "net/http/http_network_session.h" +#include "net/http/http_request_info.h" +#include "net/http/http_util.h" + +// TODO(darin): +// - authentication +// - proxies (need to call ReconsiderProxyAfterError and handle SSL tunnel) +// - ssl +// - http/0.9 +// - header line continuations (i.e., lines that start with LWS) +// - tolerate some junk (up to 4 bytes) in front of the HTTP/1.x status line + +namespace net { + +//----------------------------------------------------------------------------- + +// TODO(darin): Move this onto HttpProxyInfo +static std::string GetProxyHostPort(const HttpProxyInfo& pi) { + return WideToASCII(pi.proxy_server()); +} + +//----------------------------------------------------------------------------- + +HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session, + ClientSocketFactory* csf) +#pragma warning(suppress: 4355) + : io_callback_(this, &HttpNetworkTransaction::OnIOComplete), + user_callback_(NULL), + session_(session), + request_(NULL), + pac_request_(NULL), + socket_factory_(csf), + connection_(session->connection_manager()), + reused_socket_(false), + using_ssl_(false), + using_proxy_(false), + using_tunnel_(false), + bytes_sent_(0), + header_buf_capacity_(0), + header_buf_len_(0), + header_buf_body_offset_(-1), + content_length_(-1), // -1 means unspecified. + content_read_(0), + read_buf_(NULL), + read_buf_len_(0), + next_state_(STATE_NONE) { +} + +HttpNetworkTransaction::~HttpNetworkTransaction() { + // If we still have an open socket, then make sure to close it so we don't + // try to reuse it later on. + if (connection_.is_initialized()) + connection_.set_socket(NULL); + + if (pac_request_) + session_->proxy_service()->CancelPacRequest(pac_request_); +} + +void HttpNetworkTransaction::BuildRequestHeaders() { + std::string path; + if (using_proxy_) { + // TODO(darin): GURL should have a method for this. + path = request_->url.spec(); + size_t ref_pos = path.rfind('#'); + if (ref_pos != std::string::npos) + path.erase(ref_pos); + } else { + path = request_->url.PathForRequest(); + } + + request_headers_ = request_->method + " " + path + " HTTP/1.1\r\n" + + "Host: " + request_->url.host(); + if (request_->url.IntPort() != -1) + request_headers_ += ":" + request_->url.port(); + request_headers_ += "\r\n"; + + // For compat with HTTP/1.0 servers and proxies: + if (using_proxy_) + request_headers_ += "Proxy-"; + request_headers_ += "Connection: keep-alive\r\n"; + + if (!request_->user_agent.empty()) + request_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()) + request_headers_ += "Referer: " + request_->referrer.spec() + "\r\n"; + + // Add a content length header? + if (request_->upload_data) { + request_body_stream_.reset(new UploadDataStream(request_->upload_data)); + request_headers_ += + "Content-Length: " + Uint64ToString(request_body_stream_->size()) + + "\r\n"; + } else if (request_->method == "POST" || request_->method == "PUT" || + request_->method == "HEAD") { + // An empty POST/PUT request still needs a content length. As for HEAD, + // IE and Safari also add a content length header. 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. + request_headers_ += "Content-Length: 0\r\n"; + } + + // Honor load flags that impact proxy caches. + if (request_->load_flags & LOAD_BYPASS_CACHE) { + request_headers_ += "Pragma: no-cache\r\nCache-Control: no-cache\r\n"; + } else if (request_->load_flags & LOAD_VALIDATE_CACHE) { + request_headers_ += "Cache-Control: max-age=0\r\n"; + } + + // TODO(darin): Need to prune out duplicate headers. + + request_headers_ += request_->extra_headers; + request_headers_ += "\r\n"; +} + +void HttpNetworkTransaction::DoCallback(int rv) { + DCHECK(rv != ERR_IO_PENDING); + DCHECK(user_callback_); + + // Since Run may result in Read being called, clear callback_ up front. + CompletionCallback* c = user_callback_; + user_callback_ = NULL; + c->Run(rv); +} + +void HttpNetworkTransaction::OnIOComplete(int result) { + int rv = DoLoop(result); + if (rv != ERR_IO_PENDING) + DoCallback(rv); +} + +int HttpNetworkTransaction::DoLoop(int result) { + DCHECK(next_state_ != STATE_NONE); + + int rv = result; + do { + State state = next_state_; + next_state_ = STATE_NONE; + switch (state) { + case STATE_RESOLVE_PROXY: + rv = DoResolveProxy(); + break; + case STATE_RESOLVE_PROXY_COMPLETE: + rv = DoResolveProxyComplete(rv); + break; + case STATE_INIT_CONNECTION: + rv = DoInitConnection(); + break; + case STATE_INIT_CONNECTION_COMPLETE: + rv = DoInitConnectionComplete(rv); + break; + case STATE_RESOLVE_HOST: + rv = DoResolveHost(); + break; + case STATE_RESOLVE_HOST_COMPLETE: + rv = DoResolveHostComplete(rv); + break; + case STATE_CONNECT: + rv = DoConnect(); + break; + case STATE_CONNECT_COMPLETE: + rv = DoConnectComplete(rv); + break; + case STATE_WRITE_HEADERS: + rv = DoWriteHeaders(); + break; + case STATE_WRITE_HEADERS_COMPLETE: + rv = DoWriteHeadersComplete(rv); + break; + case STATE_WRITE_BODY: + rv = DoWriteBody(); + break; + case STATE_WRITE_BODY_COMPLETE: + rv = DoWriteBodyComplete(rv); + break; + case STATE_READ_HEADERS: + rv = DoReadHeaders(); + break; + case STATE_READ_HEADERS_COMPLETE: + rv = DoReadHeadersComplete(rv); + break; + case STATE_READ_BODY: + rv = DoReadBody(); + break; + case STATE_READ_BODY_COMPLETE: + rv = DoReadBodyComplete(rv); + break; + default: + NOTREACHED() << "bad state"; + rv = ERR_FAILED; + } + } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); + + return rv; +} + +int HttpNetworkTransaction::DoResolveProxy() { + DCHECK(!pac_request_); + + next_state_ = STATE_RESOLVE_PROXY_COMPLETE; + + return session_->proxy_service()->ResolveProxy( + request_->url, &proxy_info_, &io_callback_, &pac_request_); +} + +int HttpNetworkTransaction::DoResolveProxyComplete(int result) { + next_state_ = STATE_INIT_CONNECTION; + + pac_request_ = NULL; + + if (result != OK) { + DLOG(ERROR) << "Failed to resolve proxy: " << result; + proxy_info_.UseDirect(); + } + return OK; +} + +int HttpNetworkTransaction::DoInitConnection() { + DCHECK(!connection_.is_initialized()); + + next_state_ = STATE_INIT_CONNECTION_COMPLETE; + + using_ssl_ = request_->url.SchemeIs("https"); + using_proxy_ = !proxy_info_.is_direct() && !using_ssl_; + using_tunnel_ = !proxy_info_.is_direct() && using_ssl_; + + // Build the string used to uniquely identify connections of this type. + std::string connection_group; + if (using_proxy_ || using_tunnel_) + connection_group = "proxy/" + GetProxyHostPort(proxy_info_) + "/"; + if (!using_proxy_) + connection_group.append(request_->url.GetOrigin().spec()); + + return connection_.Init(connection_group, &io_callback_); +} + +int HttpNetworkTransaction::DoInitConnectionComplete(int result) { + if (result < 0) + return result; + + DCHECK(connection_.is_initialized()); + + // Set the reused_socket_ flag to indicate that we are using a keep-alive + // connection. This flag is used to handle errors that occur while we are + // trying to reuse a keep-alive connection. + if (reused_socket_ = (connection_.socket() != NULL)) { + next_state_ = STATE_WRITE_HEADERS; + } else { + next_state_ = STATE_RESOLVE_HOST; + } + return OK; +} + +int HttpNetworkTransaction::DoResolveHost() { + next_state_ = STATE_RESOLVE_HOST_COMPLETE; + + DCHECK(!resolver_.get()); + + std::string host; + int port; + + // Determine the host and port to connect to. + if (using_proxy_ || using_tunnel_) { + const std::string& proxy = GetProxyHostPort(proxy_info_); + StringTokenizer t(proxy, ":"); + // TODO(darin): Handle errors here. Perhaps HttpProxyInfo should do this + // before claiming a proxy server configuration. + t.GetNext(); + host = t.token(); + t.GetNext(); + port = static_cast<int>(StringToInt64(t.token())); + } else { + host = request_->url.host(); + port = request_->url.IntPort(); + if (port == -1) { + if (using_ssl_) { + port = 443; // Default HTTPS port + } else { + port = 80; // Default HTTP port + } + } + } + + resolver_.reset(new HostResolver()); + return resolver_->Resolve(host, port, &addresses_, &io_callback_); +} + +int HttpNetworkTransaction::DoResolveHostComplete(int result) { + resolver_.reset(); + if (result == OK) + next_state_ = STATE_CONNECT; + return result; +} + +int HttpNetworkTransaction::DoConnect() { + next_state_ = STATE_CONNECT_COMPLETE; + + DCHECK(!connection_.socket()); + + ClientSocket* s = socket_factory_->CreateTCPClientSocket(addresses_); + + // If we are using a direct SSL connection, then go ahead and create the SSL + // wrapper socket now. Otherwise, we need to first issue a CONNECT request. + if (using_ssl_ && !using_tunnel_) + s = socket_factory_->CreateSSLClientSocket(s, request_->url.host()); + + connection_.set_socket(s); + return connection_.socket()->Connect(&io_callback_); +} + +int HttpNetworkTransaction::DoConnectComplete(int result) { + if (result == OK) + next_state_ = STATE_WRITE_HEADERS; + return result; +} + +int HttpNetworkTransaction::DoWriteHeaders() { + next_state_ = STATE_WRITE_HEADERS_COMPLETE; + + // This is constructed lazily (instead of within our Start method), so that + // we have proxy info available. + if (request_headers_.empty()) + BuildRequestHeaders(); + + // Record our best estimate of the 'request time' as the time when we send + // out the first bytes of the request headers. + if (bytes_sent_ == 0) + response_.request_time = Time::Now(); + + const char* buf = request_headers_.data() + bytes_sent_; + int buf_len = static_cast<int>(request_headers_.size() - bytes_sent_); + DCHECK(buf_len > 0); + + return connection_.socket()->Write(buf, buf_len, &io_callback_); +} + +int HttpNetworkTransaction::DoWriteHeadersComplete(int result) { + if (result < 0) + return HandleIOError(result); + + bytes_sent_ += result; + if (bytes_sent_ < request_headers_.size()) { + next_state_ = STATE_WRITE_HEADERS; + } else if (request_->upload_data) { + next_state_ = STATE_WRITE_BODY; + } else { + next_state_ = STATE_READ_HEADERS; + } + return OK; +} + +int HttpNetworkTransaction::DoWriteBody() { + next_state_ = STATE_WRITE_BODY_COMPLETE; + + DCHECK(request_->upload_data); + DCHECK(request_body_stream_.get()); + + const char* buf = request_body_stream_->buf(); + int buf_len = static_cast<int>(request_body_stream_->buf_len()); + + return connection_.socket()->Write(buf, buf_len, &io_callback_); +} + +int HttpNetworkTransaction::DoWriteBodyComplete(int result) { + if (result < 0) + return HandleIOError(result); + + request_body_stream_->DidConsume(result); + + if (request_body_stream_->position() < request_body_stream_->size()) { + next_state_ = STATE_WRITE_BODY; + } else { + next_state_ = STATE_READ_HEADERS; + } + return OK; +} + +int HttpNetworkTransaction::DoReadHeaders() { + next_state_ = STATE_READ_HEADERS_COMPLETE; + + // Grow the read buffer if necessary. + if (header_buf_len_ == header_buf_capacity_) { + header_buf_capacity_ += kHeaderBufInitialSize; + header_buf_.reset(static_cast<char*>( + realloc(header_buf_.release(), header_buf_capacity_))); + } + + char* buf = header_buf_.get() + header_buf_len_; + int buf_len = header_buf_capacity_ - header_buf_len_; + + return connection_.socket()->Read(buf, buf_len, &io_callback_); +} + +int HttpNetworkTransaction::DoReadHeadersComplete(int result) { + if (result < 0) + return HandleIOError(result); + + // Record our best estimate of the 'response time' as the time when we read + // the first bytes of the response headers. + if (header_buf_len_ == 0) + response_.response_time = Time::Now(); + + if (result == 0) { + // The socket was closed before we found end-of-headers. Assume that EOF + // is end-of-headers. + header_buf_body_offset_ = header_buf_len_; + } else { + header_buf_len_ += result; + DCHECK(header_buf_len_ <= header_buf_capacity_); + + // TODO(darin): Check for a HTTP/0.9 response. + + int eoh = HttpUtil::LocateEndOfHeaders(header_buf_.get(), header_buf_len_); + if (eoh != -1) { + header_buf_body_offset_ = eoh; + } else { + next_state_ = STATE_READ_HEADERS; // Read more. + return OK; + } + } + + // And, we are done with the Start sequence. + next_state_ = STATE_NONE; + return DidReadResponseHeaders(); +} + +int HttpNetworkTransaction::DoReadBody() { + DCHECK(read_buf_); + DCHECK(read_buf_len_ > 0); + DCHECK(connection_.is_initialized()); + + next_state_ = STATE_READ_BODY_COMPLETE; + + // We may have some data remaining in the read buffer. + if (header_buf_.get() && header_buf_body_offset_ < header_buf_len_) { + int n = std::min(read_buf_len_, header_buf_len_ - header_buf_body_offset_); + memcpy(read_buf_, header_buf_.get() + header_buf_body_offset_, n); + header_buf_body_offset_ += n; + if (header_buf_body_offset_ == header_buf_len_) + header_buf_.reset(); + return n; + } + + return connection_.socket()->Read(read_buf_, read_buf_len_, &io_callback_); +} + +int HttpNetworkTransaction::DoReadBodyComplete(int result) { + // We are done with the Read call. + + // Filter incoming data if appropriate. FilterBuf may return an error. + if (result > 0 && chunked_decoder_.get()) { + result = chunked_decoder_->FilterBuf(read_buf_, result); + if (result == 0) { + // Don't signal completion of the Read call yet or else it'll look like + // we received end-of-file. Wait for more data. + next_state_ = STATE_READ_BODY; + return OK; + } + } + + bool done = false, keep_alive = false; + if (result < 0) { + // Error while reading the socket. + done = true; + } else { + content_read_ += result; + if ((content_length_ != -1 && content_read_ >= content_length_) || + (chunked_decoder_.get() && chunked_decoder_->reached_eof())) { + done = true; + keep_alive = response_.headers->IsKeepAlive(); + } + } + + // Cleanup the HttpConnection if we are done. + if (done) { + if (!keep_alive) + connection_.set_socket(NULL); + connection_.Reset(); + } + + // Clear these to avoid leaving around old state. + read_buf_ = NULL; + read_buf_len_ = 0; + + return result; +} + +int HttpNetworkTransaction::DidReadResponseHeaders() { + scoped_refptr<HttpResponseHeaders> headers = new HttpResponseHeaders( + HttpUtil::AssembleRawHeaders(header_buf_.get(), header_buf_body_offset_)); + + // Check for an intermediate 100 Continue response. An origin server is + // allowed to send this response even if we didn't ask for it, so we just + // need to skip over it. + if (headers->response_code() == 100) { + header_buf_len_ = 0; + header_buf_body_offset_ = -1; + next_state_ = STATE_READ_HEADERS; + return OK; + } + + response_.headers = headers; + response_.vary_data.Init(*request_, *response_.headers); + + // Figure how to determine EOF: + + // For certain responses, we know the content length is always 0. + switch (response_.headers->response_code()) { + case 204: + case 205: + case 304: + content_length_ = 0; + break; + } + + if (content_length_ == -1) { + // Ignore spurious chunked responses from HTTP/1.0 servers and proxies. + // Otherwise "Transfer-Encoding: chunked" trumps "Content-Length: N" + const std::string& status_line = response_.headers->GetStatusLine(); + if (!StartsWithASCII(status_line, "HTTP/1.0 ", true) && + response_.headers->HasHeaderValue("Transfer-Encoding", "chunked")) { + chunked_decoder_.reset(new HttpChunkedDecoder()); + } else { + content_length_ = response_.headers->GetContentLength(); + // If content_length_ is still -1, then we have to wait for the server to + // close the connection. + } + } + + return OK; +} + +int HttpNetworkTransaction::HandleIOError(int error) { + switch (error) { + // If we try to reuse a connection that the server is in the process of + // closing, we may end up successfully writing out our request (or a + // portion of our request) only to find a connection error when we try to + // read from (or finish writing to) the socket. + case ERR_CONNECTION_RESET: + case ERR_CONNECTION_CLOSED: + case ERR_CONNECTION_ABORTED: + if (reused_socket_ && // We reused a keep-alive connection. + !header_buf_len_) { // We have not received any response data yet. + connection_.set_socket(NULL); + connection_.Reset(); + bytes_sent_ = 0; + if (request_body_stream_.get()) + request_body_stream_->Reset(); + next_state_ = STATE_INIT_CONNECTION; + error = OK; + } + break; + } + return error; +} + +void HttpNetworkTransaction::Destroy() { + delete this; +} + +int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info, + CompletionCallback* callback) { + request_ = request_info; + + next_state_ = STATE_RESOLVE_PROXY; + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + user_callback_ = callback; + return rv; +} + +int HttpNetworkTransaction::RestartIgnoringLastError( + CompletionCallback* callback) { + return ERR_FAILED; // TODO(darin): implement me! +} + +int HttpNetworkTransaction::RestartWithAuth( + const std::wstring& username, + const std::wstring& password, + CompletionCallback* callback) { + return ERR_FAILED; // TODO(darin): implement me! +} + +int HttpNetworkTransaction::Read(char* buf, int buf_len, + CompletionCallback* callback) { + DCHECK(response_.headers); + DCHECK(buf); + DCHECK(buf_len > 0); + + if (!connection_.is_initialized()) + return 0; // Treat like EOF. + + read_buf_ = buf; + read_buf_len_ = buf_len; + + next_state_ = STATE_READ_BODY; + int rv = DoLoop(OK); + if (rv == ERR_IO_PENDING) + user_callback_ = callback; + return rv; +} + +const HttpResponseInfo* HttpNetworkTransaction::GetResponseInfo() const { + return response_.headers ? &response_ : NULL; +} + +LoadState HttpNetworkTransaction::GetLoadState() const { + switch (next_state_) { + case STATE_RESOLVE_PROXY_COMPLETE: + return LOAD_STATE_RESOLVING_PROXY_FOR_URL; + case STATE_RESOLVE_HOST_COMPLETE: + return LOAD_STATE_RESOLVING_HOST; + case STATE_CONNECT_COMPLETE: + return LOAD_STATE_CONNECTING; + case STATE_WRITE_HEADERS_COMPLETE: + case STATE_WRITE_BODY_COMPLETE: + return LOAD_STATE_SENDING_REQUEST; + case STATE_READ_HEADERS_COMPLETE: + return LOAD_STATE_WAITING_FOR_RESPONSE; + case STATE_READ_BODY_COMPLETE: + return LOAD_STATE_READING_RESPONSE; + default: + return LOAD_STATE_IDLE; + } +} + +uint64 HttpNetworkTransaction::GetUploadProgress() const { + if (!request_body_stream_.get()) + return 0; + + return request_body_stream_->position(); +} + +} // namespace net diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h new file mode 100644 index 0000000..f53a5e7 --- /dev/null +++ b/net/http/http_network_transaction.h @@ -0,0 +1,179 @@ +// 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. + +#ifndef NET_HTTP_HTTP_NETWORK_TRANSACTION_H_ +#define NET_HTTP_HTTP_NETWORK_TRANSACTION_H_ + +#include "base/ref_counted.h" +#include "net/base/address_list.h" +#include "net/http/http_connection.h" +#include "net/http/http_proxy_service.h" +#include "net/http/http_response_info.h" +#include "net/http/http_transaction.h" + +namespace net { + +class ClientSocketFactory; +class HostResolver; +class HttpChunkedDecoder; +class HttpNetworkSession; +class UploadDataStream; + +class HttpNetworkTransaction : public HttpTransaction { + public: + HttpNetworkTransaction(HttpNetworkSession* session, + ClientSocketFactory* socket_factory); + + // HttpTransaction methods: + virtual void Destroy(); + virtual int Start(const HttpRequestInfo* request_info, + CompletionCallback* callback); + virtual int RestartIgnoringLastError(CompletionCallback* callback); + virtual int RestartWithAuth(const std::wstring& username, + const std::wstring& password, + CompletionCallback* callback); + virtual int Read(char* buf, int buf_len, CompletionCallback* callback); + virtual const HttpResponseInfo* GetResponseInfo() const; + virtual LoadState GetLoadState() const; + virtual uint64 GetUploadProgress() const; + + private: + ~HttpNetworkTransaction(); + void BuildRequestHeaders(); + void DoCallback(int result); + void OnIOComplete(int result); + + // Runs the state transition loop. + int DoLoop(int result); + + // Each of these methods corresponds to a State value. Those with an input + // argument receive the result from the previous state. If a method returns + // ERR_IO_PENDING, then the result from OnIOComplete will be passed to the + // next state method as the result arg. + int DoResolveProxy(); + int DoResolveProxyComplete(int result); + int DoInitConnection(); + int DoInitConnectionComplete(int result); + int DoResolveHost(); + int DoResolveHostComplete(int result); + int DoConnect(); + int DoConnectComplete(int result); + int DoWriteHeaders(); + int DoWriteHeadersComplete(int result); + int DoWriteBody(); + int DoWriteBodyComplete(int result); + int DoReadHeaders(); + int DoReadHeadersComplete(int result); + int DoReadBody(); + int DoReadBodyComplete(int result); + + // Called when read_buf_ contains the complete response headers. + int DidReadResponseHeaders(); + + // Called to possibly recover from the given error. Sets next_state_ and + // returns OK if recovering from the error. Otherwise, the same error code + // is returned. + int HandleIOError(int error); + + CompletionCallbackImpl<HttpNetworkTransaction> io_callback_; + CompletionCallback* user_callback_; + + scoped_refptr<HttpNetworkSession> session_; + + const HttpRequestInfo* request_; + HttpResponseInfo response_; + + HttpProxyService::PacRequest* pac_request_; + HttpProxyInfo proxy_info_; + + scoped_ptr<HostResolver> resolver_; + AddressList addresses_; + + ClientSocketFactory* socket_factory_; + HttpConnection connection_; + bool reused_socket_; + + bool using_ssl_; // True if handling a HTTPS request + bool using_proxy_; // True if using a HTTP proxy + bool using_tunnel_; // True if using a tunnel for HTTPS + + std::string request_headers_; + scoped_ptr<UploadDataStream> request_body_stream_; + uint64 bytes_sent_; + + // The read buffer may be larger than it is full. The 'capacity' indicates + // the allocation size of the buffer, and the 'len' indicates how much data + // is in the buffer already. The 'body offset' indicates the offset of the + // start of the response body within the read buffer. + scoped_ptr_malloc<char> header_buf_; + int header_buf_capacity_; + int header_buf_len_; + int header_buf_body_offset_; + enum { kHeaderBufInitialSize = 4096 }; + + // Indicates the content length remaining to read. If this value is less + // than zero (and chunked_decoder_ is null), then we read until the server + // closes the connection. + int64 content_length_; + + // Keeps track of the number of response body bytes read so far. + int64 content_read_; + + scoped_ptr<HttpChunkedDecoder> chunked_decoder_; + + // User buffer and length passed to the Read method. + char* read_buf_; + int read_buf_len_; + + // The different states for the 'Start' routine. + enum State { + STATE_RESOLVE_PROXY, + STATE_RESOLVE_PROXY_COMPLETE, + STATE_INIT_CONNECTION, + STATE_INIT_CONNECTION_COMPLETE, + STATE_RESOLVE_HOST, + STATE_RESOLVE_HOST_COMPLETE, + STATE_CONNECT, + STATE_CONNECT_COMPLETE, + STATE_WRITE_HEADERS, + STATE_WRITE_HEADERS_COMPLETE, + STATE_WRITE_BODY, + STATE_WRITE_BODY_COMPLETE, + STATE_READ_HEADERS, + STATE_READ_HEADERS_COMPLETE, + STATE_READ_BODY, + STATE_READ_BODY_COMPLETE, + STATE_NONE + }; + State next_state_; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_NETWORK_TRANSACTION_H_ diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc new file mode 100644 index 0000000..266c30e --- /dev/null +++ b/net/http/http_network_transaction_unittest.cc @@ -0,0 +1,425 @@ +// 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/base/client_socket_factory.h" +#include "net/base/test_completion_callback.h" +#include "net/base/upload_data.h" +#include "net/http/http_network_session.h" +#include "net/http/http_network_transaction.h" +#include "net/http/http_transaction_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +//----------------------------------------------------------------------------- + +namespace { + +struct MockConnect { + bool async; + int result; +}; + +struct MockRead { + bool async; + int result; // Ignored if data is non-null. + const char* data; + int data_len; // -1 if strlen(data) should be used. +}; + +struct MockSocket { + MockConnect connect; + MockRead* reads; // Terminated by a MockRead element with data == NULL. +}; + +// Holds an array of MockSocket elements. As MockTCPClientSocket objects get +// instantiated, they take their data from the i'th element of this array. +// +// Tests should assign the first N entries of mock_sockets to point to valid +// MockSocket objects. The first unused entry should be NULL'd. +// +MockSocket* mock_sockets[10]; + +// Index of the next mock_sockets element to use. +int mock_sockets_index; + +class MockTCPClientSocket : public net::ClientSocket { + public: + MockTCPClientSocket(const net::AddressList& addresses) +#pragma warning(suppress:4355) + : data_(mock_sockets[mock_sockets_index++]), + method_factory_(this), + callback_(NULL), + read_index_(0), + read_offset_(0), + connected_(false) { + DCHECK(data_) << "overran mock_sockets array"; + } + // ClientSocket methods: + virtual int Connect(net::CompletionCallback* callback) { + DCHECK(!callback_); + if (connected_) + return net::OK; + connected_ = true; + if (data_->connect.async) { + RunCallbackAsync(callback, data_->connect.result); + return net::ERR_IO_PENDING; + } + return data_->connect.result; + } + virtual int ReconnectIgnoringLastError(net::CompletionCallback* callback) { + NOTREACHED(); + return net::ERR_FAILED; + } + virtual void Disconnect() { + connected_ = false; + callback_ = NULL; + } + virtual bool IsConnected() const { + return connected_; + } + // Socket methods: + virtual int Read(char* buf, int buf_len, net::CompletionCallback* callback) { + DCHECK(!callback_); + MockRead& r = data_->reads[read_index_]; + int result; + if (r.data) { + if (r.data_len == -1) + r.data_len = static_cast<int>(strlen(r.data)); + if (r.data_len - read_offset_ > 0) { + result = std::min(buf_len, r.data_len - read_offset_); + memcpy(buf, r.data + read_offset_, result); + read_offset_ += result; + if (read_offset_ == r.data_len) { + read_index_++; + read_offset_ = 0; + } + } else { + result = 0; // EOF + } + } else { + result = r.result; + } + if (r.async) { + RunCallbackAsync(callback, result); + return net::ERR_IO_PENDING; + } + return result; + } + virtual int Write(const char* buf, int buf_len, + net::CompletionCallback* callback) { + DCHECK(!callback_); + return buf_len; // OK, we wrote it. + } + private: + void RunCallbackAsync(net::CompletionCallback* callback, int result) { + callback_ = callback; + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod( + &MockTCPClientSocket::RunCallback, result)); + } + void RunCallback(int result) { + net::CompletionCallback* c = callback_; + callback_ = NULL; + if (c) + c->Run(result); + } + MockSocket* data_; + ScopedRunnableMethodFactory<MockTCPClientSocket> method_factory_; + net::CompletionCallback* callback_; + int read_index_; + int read_offset_; + bool connected_; +}; + +class MockClientSocketFactory : public net::ClientSocketFactory { + public: + virtual net::ClientSocket* CreateTCPClientSocket( + const net::AddressList& addresses) { + return new MockTCPClientSocket(addresses); + } + virtual net::ClientSocket* CreateSSLClientSocket( + net::ClientSocket* transport_socket, + const std::string& hostname) { + return NULL; + } +}; + +MockClientSocketFactory mock_socket_factory; + +class NullProxyResolver : public net::HttpProxyResolver { + public: + virtual int GetProxyConfig(net::HttpProxyConfig* config) { + return net::ERR_FAILED; + } + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + net::HttpProxyInfo* results) { + return net::ERR_FAILED; + } +}; + +net::HttpNetworkSession* CreateSession() { + return new net::HttpNetworkSession(new NullProxyResolver()); +} + +class HttpNetworkTransactionTest : public testing::Test { + public: + virtual void SetUp() { + mock_sockets[0] = NULL; + mock_sockets_index = 0; + } +}; + +} // namespace + +//----------------------------------------------------------------------------- + +TEST_F(HttpNetworkTransactionTest, Basic) { + net::HttpTransaction* trans = new net::HttpNetworkTransaction( + CreateSession(), &mock_socket_factory); + trans->Destroy(); +} + +TEST_F(HttpNetworkTransactionTest, SimpleGET) { + net::HttpTransaction* trans = new net::HttpNetworkTransaction( + CreateSession(), &mock_socket_factory); + + net::HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + MockRead data_reads[] = { + { true, 0, "HTTP/1.0 200 OK\r\n\r\n", -1 }, + { true, 0, "hello world", -1 }, + { false, net::OK, NULL, 0 }, + }; + MockSocket data; + data.connect.async = true; + data.connect.result = net::OK; + data.reads = data_reads; + mock_sockets[0] = &data; + mock_sockets[1] = NULL; + + TestCompletionCallback callback; + + int rv = trans->Start(&request, &callback); + EXPECT_EQ(net::ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(net::OK, rv); + + const net::HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.0 200 OK"); + + std::string response_data; + rv = ReadTransaction(trans, &response_data); + EXPECT_EQ(net::OK, rv); + EXPECT_TRUE(response_data == "hello world"); + + trans->Destroy(); + + // Empty the current queue. + MessageLoop::current()->Quit(); + MessageLoop::current()->Run(); +} + +TEST_F(HttpNetworkTransactionTest, ReuseConnection) { + scoped_refptr<net::HttpNetworkSession> session = CreateSession(); + + MockRead data_reads[] = { + { true, 0, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n", -1 }, + { true, 0, "hello", -1 }, + { true, 0, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n", -1 }, + { true, 0, "world", -1 }, + { false, net::OK, NULL, 0 }, + }; + MockSocket data; + data.connect.async = true; + data.connect.result = net::OK; + data.reads = data_reads; + mock_sockets[0] = &data; + mock_sockets[1] = NULL; + + const char* kExpectedResponseData[] = { + "hello", "world" + }; + + for (int i = 0; i < 2; ++i) { + net::HttpTransaction* trans = + new net::HttpNetworkTransaction(session, &mock_socket_factory); + + net::HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + TestCompletionCallback callback; + + int rv = trans->Start(&request, &callback); + EXPECT_EQ(net::ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(net::OK, rv); + + const net::HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.1 200 OK"); + + std::string response_data; + rv = ReadTransaction(trans, &response_data); + EXPECT_EQ(net::OK, rv); + EXPECT_TRUE(response_data == kExpectedResponseData[i]); + + trans->Destroy(); + + // Empty the current queue. + MessageLoop::current()->Quit(); + MessageLoop::current()->Run(); + } +} + +TEST_F(HttpNetworkTransactionTest, Ignores100) { + net::HttpTransaction* trans = new net::HttpNetworkTransaction( + CreateSession(), &mock_socket_factory); + + net::HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data = new net::UploadData; + request.upload_data->AppendBytes("foo", 3); + request.load_flags = 0; + + MockRead data_reads[] = { + { true, 0, "HTTP/1.0 100 Continue\r\n\r\n", -1 }, + { true, 0, "HTTP/1.0 200 OK\r\n\r\n", -1 }, + { true, 0, "hello world", -1 }, + { false, net::OK, NULL, 0 }, + }; + MockSocket data; + data.connect.async = true; + data.connect.result = net::OK; + data.reads = data_reads; + mock_sockets[0] = &data; + mock_sockets[1] = NULL; + + TestCompletionCallback callback; + + int rv = trans->Start(&request, &callback); + EXPECT_EQ(net::ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(net::OK, rv); + + const net::HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.0 200 OK"); + + std::string response_data; + rv = ReadTransaction(trans, &response_data); + EXPECT_EQ(net::OK, rv); + EXPECT_TRUE(response_data == "hello world"); + + trans->Destroy(); + + // Empty the current queue. + MessageLoop::current()->Quit(); + MessageLoop::current()->Run(); +} + +TEST_F(HttpNetworkTransactionTest, KeepAliveConnectionReset) { + scoped_refptr<net::HttpNetworkSession> session = CreateSession(); + + net::HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.foo.com/"); + request.load_flags = 0; + + MockRead data1_reads[] = { + { true, 0, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n", -1 }, + { true, 0, "hello", -1 }, + { true, net::ERR_CONNECTION_RESET, NULL, 0 }, + }; + MockSocket data1; + data1.connect.async = true; + data1.connect.result = net::OK; + data1.reads = data1_reads; + mock_sockets[0] = &data1; + + MockRead data2_reads[] = { + { true, 0, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n", -1 }, + { true, 0, "world", -1 }, + { true, net::OK, NULL, 0 }, + }; + MockSocket data2; + data2.connect.async = true; + data2.connect.result = net::OK; + data2.reads = data2_reads; + mock_sockets[1] = &data2; + + const char* kExpectedResponseData[] = { + "hello", "world" + }; + + for (int i = 0; i < 2; ++i) { + TestCompletionCallback callback; + + net::HttpTransaction* trans = + new net::HttpNetworkTransaction(session, &mock_socket_factory); + + int rv = trans->Start(&request, &callback); + EXPECT_EQ(net::ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(net::OK, rv); + + const net::HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers != NULL); + EXPECT_TRUE(response->headers->GetStatusLine() == "HTTP/1.1 200 OK"); + + std::string response_data; + rv = ReadTransaction(trans, &response_data); + EXPECT_EQ(net::OK, rv); + EXPECT_TRUE(response_data == kExpectedResponseData[i]); + + trans->Destroy(); + + // Empty the current queue. + MessageLoop::current()->Quit(); + MessageLoop::current()->Run(); + } +} diff --git a/net/http/http_proxy_resolver_fixed.cc b/net/http/http_proxy_resolver_fixed.cc new file mode 100644 index 0000000..dd2ac8d --- /dev/null +++ b/net/http/http_proxy_resolver_fixed.cc @@ -0,0 +1,48 @@ +// 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_proxy_resolver_fixed.h" + +#include "net/base/net_errors.h" + +namespace net { + +int HttpProxyResolverFixed::GetProxyConfig(HttpProxyConfig* config) { + config->proxy_server = pi_.proxy_server(); + return OK; +} + +int HttpProxyResolverFixed::GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + HttpProxyInfo* results) { + NOTREACHED() << "Should not be asked to do proxy auto config"; + return ERR_FAILED; +} + +} // namespace net diff --git a/net/http/http_proxy_resolver_fixed.h b/net/http/http_proxy_resolver_fixed.h new file mode 100644 index 0000000..93beb33 --- /dev/null +++ b/net/http/http_proxy_resolver_fixed.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef NET_HTTP_HTTP_PROXY_RESOLVER_FIXED_H_ +#define NET_HTTP_HTTP_PROXY_RESOLVER_FIXED_H_ + +#include "net/http/http_proxy_service.h" + +namespace net { + +// Implementation of HttpProxyResolver that returns a fixed result. +class HttpProxyResolverFixed : public HttpProxyResolver { + public: + HttpProxyResolverFixed(const HttpProxyInfo& pi) { pi_.Use(pi); } + + // HttpProxyResolver methods: + virtual int GetProxyConfig(HttpProxyConfig* config); + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + HttpProxyInfo* results); + + private: + HttpProxyInfo pi_; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_PROXY_RESOLVER_FIXED_H_ diff --git a/net/http/http_proxy_resolver_winhttp.cc b/net/http/http_proxy_resolver_winhttp.cc new file mode 100644 index 0000000..2393245 --- /dev/null +++ b/net/http/http_proxy_resolver_winhttp.cc @@ -0,0 +1,185 @@ +// 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_proxy_resolver_winhttp.h" + +#include <windows.h> +#include <winhttp.h> + +#include "base/histogram.h" +#include "net/base/net_errors.h" + +#pragma comment(lib, "winhttp.lib") + +namespace net { + +// A small wrapper for histogramming purposes ;-) +static BOOL CallWinHttpGetProxyForUrl(HINTERNET session, LPCWSTR url, + WINHTTP_AUTOPROXY_OPTIONS* options, + WINHTTP_PROXY_INFO* results) { + TimeTicks time_start = TimeTicks::Now(); + BOOL rv = WinHttpGetProxyForUrl(session, url, options, results); + TimeDelta time_delta = TimeTicks::Now() - time_start; + // Record separately success and failure times since they will have very + // different characteristics. + if (rv) { + UMA_HISTOGRAM_LONG_TIMES(L"Net.GetProxyForUrl_OK", time_delta); + } else { + UMA_HISTOGRAM_LONG_TIMES(L"Net.GetProxyForUrl_FAIL", time_delta); + } + return rv; +} + +static void FreeConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* config) { + if (config->lpszAutoConfigUrl) + GlobalFree(config->lpszAutoConfigUrl); + if (config->lpszProxy) + GlobalFree(config->lpszProxy); + if (config->lpszProxyBypass) + GlobalFree(config->lpszProxyBypass); +} + +static void FreeInfo(WINHTTP_PROXY_INFO* info) { + if (info->lpszProxy) + GlobalFree(info->lpszProxy); + if (info->lpszProxyBypass) + GlobalFree(info->lpszProxyBypass); +} + +HttpProxyResolverWinHttp::HttpProxyResolverWinHttp() + : session_handle_(NULL) { +} + +HttpProxyResolverWinHttp::~HttpProxyResolverWinHttp() { + CloseWinHttpSession(); +} + +int HttpProxyResolverWinHttp::GetProxyConfig(HttpProxyConfig* config) { + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ie_config = {0}; + if (!WinHttpGetIEProxyConfigForCurrentUser(&ie_config)) { + LOG(ERROR) << "WinHttpGetIEProxyConfigForCurrentUser failed: " << + GetLastError(); + return ERR_FAILED; // TODO(darin): Bug 1189288: translate error code. + } + + if (ie_config.fAutoDetect) + config->auto_detect = true; + if (ie_config.lpszProxy) + config->proxy_server = ie_config.lpszProxy; + if (ie_config.lpszProxyBypass) + config->proxy_bypass = ie_config.lpszProxyBypass; + if (ie_config.lpszAutoConfigUrl) + config->pac_url = ie_config.lpszAutoConfigUrl; + + FreeConfig(&ie_config); + return OK; +} + +int HttpProxyResolverWinHttp::GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + HttpProxyInfo* results) { + // If we don't have a WinHTTP session, then create a new one. + if (!session_handle_ && !OpenWinHttpSession()) + return ERR_FAILED; + + // If we have been given an empty PAC url, then use auto-detection. + // + // NOTE: We just use DNS-based auto-detection here like Firefox. We do this + // to avoid WinHTTP's auto-detection code, which while more featureful (it + // supports DHCP based auto-detection) also appears to have issues. + // + WINHTTP_AUTOPROXY_OPTIONS options = {0}; + options.fAutoLogonIfChallenged = TRUE; + options.dwFlags = WINHTTP_AUTOPROXY_CONFIG_URL; + options.lpszAutoConfigUrl = + pac_url.empty() ? L"http://wpad/wpad.dat" : pac_url.c_str(); + + WINHTTP_PROXY_INFO info = {0}; + DCHECK(session_handle_); + if (!CallWinHttpGetProxyForUrl(session_handle_, query_url.c_str(), &options, + &info)) { + DWORD error = GetLastError(); + LOG(ERROR) << "WinHttpGetProxyForUrl failed: " << error; + + // If we got here because of RPC timeout during out of process PAC + // resolution, no further requests on this session are going to work. + if ((ERROR_WINHTTP_TIMEOUT == error) || + (ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR == error)) { + CloseWinHttpSession(); + } + + return ERR_FAILED; // TODO(darin): Bug 1189288: translate error code. + } + + int rv = OK; + + switch (info.dwAccessType) { + case WINHTTP_ACCESS_TYPE_NO_PROXY: + results->UseDirect(); + break; + case WINHTTP_ACCESS_TYPE_NAMED_PROXY: + results->UseNamedProxy(info.lpszProxy); + break; + default: + NOTREACHED(); + rv = ERR_FAILED; + } + + FreeInfo(&info); + return rv; +} + +bool HttpProxyResolverWinHttp::OpenWinHttpSession() { + DCHECK(!session_handle_); + session_handle_ = WinHttpOpen(NULL, + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (!session_handle_) + return false; + + // Since this session handle will never be used for winhttp connections, + // these timeouts don't really mean much individually. However, WinHTTP's + // out of process PAC resolution will use a combined (sum of all timeouts) + // value to wait for an RPC reply. + BOOL rv = WinHttpSetTimeouts(session_handle_, 10000, 10000, 5000, 5000); + DCHECK(rv); + + return true; +} + +void HttpProxyResolverWinHttp::CloseWinHttpSession() { + if (session_handle_) { + WinHttpCloseHandle(session_handle_); + session_handle_ = NULL; + } +} + +} // namespace net diff --git a/net/http/http_proxy_resolver_winhttp.h b/net/http/http_proxy_resolver_winhttp.h new file mode 100644 index 0000000..bea2bda --- /dev/null +++ b/net/http/http_proxy_resolver_winhttp.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef NET_HTTP_HTTP_PROXY_RESOLVER_WINHTTP_H__ +#define NET_HTTP_HTTP_PROXY_RESOLVER_WINHTTP_H__ + +#include "net/http/http_proxy_service.h" + +typedef LPVOID HINTERNET; // From winhttp.h + +namespace net { + +// An implementation of HttpProxyResolver that uses WinHTTP and the system +// proxy settings. +class HttpProxyResolverWinHttp : public HttpProxyResolver { + public: + HttpProxyResolverWinHttp(); + ~HttpProxyResolverWinHttp(); + + // HttpProxyResolver implementation: + virtual int GetProxyConfig(HttpProxyConfig* config); + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + HttpProxyInfo* results); + + private: + bool OpenWinHttpSession(); + void CloseWinHttpSession(); + + // Proxy configuration is cached on the session handle. + HINTERNET session_handle_; + + DISALLOW_EVIL_CONSTRUCTORS(HttpProxyResolverWinHttp); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_PROXY_RESOLVER_WINHTTP_H__ diff --git a/net/http/http_proxy_service.cc b/net/http/http_proxy_service.cc new file mode 100644 index 0000000..d851a6d --- /dev/null +++ b/net/http/http_proxy_service.cc @@ -0,0 +1,496 @@ +// 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_proxy_service.h" + +#include <windows.h> +#include <winhttp.h> + +#include <algorithm> + +#include "base/message_loop.h" +#include "base/string_tokenizer.h" +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_errors.h" + +namespace net { + +// HttpProxyConfig ------------------------------------------------------------ + +// static +HttpProxyConfig::ID HttpProxyConfig::last_id_ = HttpProxyConfig::INVALID_ID; + +HttpProxyConfig::HttpProxyConfig() + : auto_detect(false), + id_(++last_id_) { +} + +bool HttpProxyConfig::Equals(const HttpProxyConfig& other) const { + // The two configs can have different IDs. We are just interested in if they + // have the same settings. + return auto_detect == other.auto_detect && + pac_url == other.pac_url && + proxy_server == other.proxy_server && + proxy_bypass == other.proxy_bypass; +} + +// HttpProxyList -------------------------------------------------------------- +void HttpProxyList::SetVector(const std::vector<std::wstring>& proxies) { + proxies_.clear(); + std::vector<std::wstring>::const_iterator iter = proxies.begin(); + for (; iter != proxies.end(); ++iter) { + std::wstring proxy_sever; + TrimWhitespace(*iter, TRIM_ALL, &proxy_sever); + proxies_.push_back(proxy_sever); + } +} + +void HttpProxyList::Set(const std::wstring& proxy_list) { + // Extract the different proxies from the list. + std::vector<std::wstring> proxies; + SplitString(proxy_list, L';', &proxies); + SetVector(proxies); +} + +void HttpProxyList::RemoveBadProxies(const HttpProxyRetryInfoMap& + http_proxy_retry_info) { + std::vector<std::wstring> new_proxy_list; + std::vector<std::wstring>::const_iterator iter = proxies_.begin(); + for (; iter != proxies_.end(); ++iter) { + HttpProxyRetryInfoMap::const_iterator bad_proxy = + http_proxy_retry_info.find(*iter); + if (bad_proxy != http_proxy_retry_info.end()) { + // This proxy is bad. Check if it's time to retry. + if (bad_proxy->second.bad_until >= TimeTicks::Now()) { + // still invalid. + continue; + } + } + new_proxy_list.push_back(*iter); + } + + proxies_ = new_proxy_list; +} + +std::wstring HttpProxyList::Get() const { + if (!proxies_.empty()) + return proxies_[0]; + + return std::wstring(); +} + +const std::vector<std::wstring>& HttpProxyList::GetVector() const { + return proxies_; +} + +std::wstring HttpProxyList::GetList() const { + std::wstring proxy_list; + std::vector<std::wstring>::const_iterator iter = proxies_.begin(); + for (; iter != proxies_.end(); ++iter) { + if (!proxy_list.empty()) + proxy_list += L';'; + + proxy_list += *iter; + } + + return proxy_list; +} + +bool HttpProxyList::Fallback(HttpProxyRetryInfoMap* http_proxy_retry_info) { + // Number of minutes to wait before retrying a bad proxy server. + const TimeDelta kProxyRetryDelay = TimeDelta::FromMinutes(5); + + if (proxies_.empty()) { + NOTREACHED(); + return false; + } + + // Mark this proxy as bad. + HttpProxyRetryInfoMap::iterator iter = + http_proxy_retry_info->find(proxies_[0]); + if (iter != http_proxy_retry_info->end()) { + // TODO(nsylvain): This is not the first time we get this. We should + // double the retry time. Bug 997660. + iter->second.bad_until = TimeTicks::Now() + iter->second.current_delay; + } else { + HttpProxyRetryInfo retry_info; + retry_info.current_delay = kProxyRetryDelay; + retry_info.bad_until = TimeTicks().Now() + retry_info.current_delay; + (*http_proxy_retry_info)[proxies_[0]] = retry_info; + } + + // Remove this proxy from our list. + proxies_.erase(proxies_.begin()); + + return !proxies_.empty(); +} + +// HttpProxyInfo -------------------------------------------------------------- + +HttpProxyInfo::HttpProxyInfo() + : config_id_(HttpProxyConfig::INVALID_ID), + config_was_tried_(false) { +} + +void HttpProxyInfo::Use(const HttpProxyInfo& other) { + proxy_list_.SetVector(other.proxy_list_.GetVector()); +} + +void HttpProxyInfo::UseDirect() { + proxy_list_.Set(std::wstring()); +} + +void HttpProxyInfo::UseNamedProxy(const std::wstring& proxy_server) { + proxy_list_.Set(proxy_server); +} + +void HttpProxyInfo::Apply(HINTERNET request_handle) { + WINHTTP_PROXY_INFO pi; + std::wstring proxy; // We need to declare this variable here because + // lpszProxy needs to be valid in WinHttpSetOption. + if (is_direct()) { + pi.dwAccessType = WINHTTP_ACCESS_TYPE_NO_PROXY; + pi.lpszProxy = WINHTTP_NO_PROXY_NAME; + pi.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS; + } else { + proxy = proxy_list_.Get(); + pi.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + pi.lpszProxy = const_cast<LPWSTR>(proxy.c_str()); + // NOTE: Specifying a bypass list here would serve no purpose. + pi.lpszProxyBypass = WINHTTP_NO_PROXY_BYPASS; + } + WinHttpSetOption(request_handle, WINHTTP_OPTION_PROXY, &pi, sizeof(pi)); +} + +// HttpProxyService::PacRequest ----------------------------------------------- + +// We rely on the fact that the origin thread (and its message loop) will not +// be destroyed until after the PAC thread is destroyed. + +class HttpProxyService::PacRequest : + public base::RefCountedThreadSafe<HttpProxyService::PacRequest> { + public: + PacRequest(HttpProxyService* service, + const std::wstring& pac_url, + CompletionCallback* callback) + : service_(service), + callback_(callback), + results_(NULL), + config_id_(service->config_id()), + pac_url_(pac_url), + origin_loop_(NULL) { + // We need to remember original loop if only in case of asynchronous call + if (callback_) + origin_loop_ = MessageLoop::current(); + } + + void Query(const std::wstring& url, HttpProxyInfo* results) { + results_ = results; + // If we have a valid callback then execute Query asynchronously + if (callback_) { + AddRef(); // balanced in QueryComplete + service_->pac_thread()->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(this, + &HttpProxyService::PacRequest::DoQuery, + service_->resolver(), + url, + pac_url_)); + } else { + DoQuery(service_->resolver(), url, pac_url_); + } + } + + void Cancel() { + // Clear these to inform QueryComplete that it should not try to + // access them. + service_ = NULL; + callback_ = NULL; + results_ = NULL; + } + + private: + // Runs on the PAC thread if a valid callback is provided. + void DoQuery(HttpProxyResolver* resolver, + const std::wstring& query_url, + const std::wstring& pac_url) { + int rv = resolver->GetProxyForURL(query_url, pac_url, &results_buf_); + if (origin_loop_) { + origin_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &PacRequest::QueryComplete, rv)); + } else { + QueryComplete(rv); + } + } + + // If a valid callback is provided, this runs on the origin thread to + // indicate that the completion callback should be run. + void QueryComplete(int result_code) { + if (service_) + service_->DidCompletePacRequest(config_id_, result_code); + + if (result_code == OK && results_) { + results_->Use(results_buf_); + results_->RemoveBadProxies(service_->http_proxy_retry_info_); + } + + if (callback_) + callback_->Run(result_code); + + if (origin_loop_) { + Release(); // balances the AddRef in Query. we may get deleted after + // we return. + } + } + + // Must only be used on the "origin" thread. + HttpProxyService* service_; + CompletionCallback* callback_; + HttpProxyInfo* results_; + HttpProxyConfig::ID config_id_; + + // Usable from within DoQuery on the PAC thread. + HttpProxyInfo results_buf_; + std::wstring pac_url_; + MessageLoop* origin_loop_; +}; + +// HttpProxyService ----------------------------------------------------------- + +HttpProxyService::HttpProxyService(HttpProxyResolver* resolver) + : resolver_(resolver), + config_is_bad_(false) { + UpdateConfig(); +} + +int HttpProxyService::ResolveProxy(const GURL& url, HttpProxyInfo* result, + CompletionCallback* callback, + PacRequest** pac_request) { + // The overhead of calling WinHttpGetIEProxyConfigForCurrentUser is very low. + const TimeDelta kProxyConfigMaxAge = TimeDelta::FromSeconds(5); + + // Periodically check for a new config. + if ((TimeTicks::Now() - config_last_update_time_) > kProxyConfigMaxAge) + UpdateConfig(); + result->config_id_ = config_.id(); + + // Fallback to a "direct" (no proxy) connection if the current configuration + // is known to be bad. + if (config_is_bad_) { + // Reset this flag to false in case the HttpProxyInfo object is being + // re-used by the caller. + result->config_was_tried_ = false; + } else { + // Remember that we are trying to use the current proxy configuration. + result->config_was_tried_ = true; + + if (!config_.proxy_server.empty()) { + if (ShouldBypassProxyForURL(url)) { + result->UseDirect(); + } else { + // If proxies are specified on a per protocol basis, the proxy server + // field contains a list the format of which is as below:- + // "scheme1=url:port;scheme2=url:port", etc. + std::wstring url_scheme = ASCIIToWide(url.scheme()); + + WStringTokenizer proxy_server_list(config_.proxy_server, L";"); + while (proxy_server_list.GetNext()) { + WStringTokenizer proxy_server_for_scheme( + proxy_server_list.token_begin(), proxy_server_list.token_end(), + L"="); + + while (proxy_server_for_scheme.GetNext()) { + const std::wstring& proxy_server_scheme = + proxy_server_for_scheme.token(); + + // If we fail to get the proxy server here, it means that + // this is a regular proxy server configuration, i.e. proxies + // are not configured per protocol. + if (!proxy_server_for_scheme.GetNext()) { + result->UseNamedProxy(proxy_server_scheme); + return OK; + } + + if (proxy_server_scheme == url_scheme) { + result->UseNamedProxy(proxy_server_for_scheme.token()); + return OK; + } + } + } + // We failed to find a matching proxy server for the current URL + // scheme. Default to direct. + result->UseDirect(); + } + return OK; + } + + if (!config_.pac_url.empty() || config_.auto_detect) { + if (callback) { + // Create PAC thread for asynchronous mode. + if (!pac_thread_.get()) { + pac_thread_.reset(new Thread("pac-thread")); + pac_thread_->Start(); + } + } else { + // If this request is synchronous, then there's no point + // in returning PacRequest instance + DCHECK(!pac_request); + } + + scoped_refptr<PacRequest> req = + new PacRequest(this, config_.pac_url, callback); + req->Query(UTF8ToWide(url.spec()), result); + + if (callback) { + if (pac_request) + *pac_request = req; + return ERR_IO_PENDING; // Wait for callback. + } + return OK; + } + } + + // otherwise, we have no proxy config + result->UseDirect(); + return OK; +} + +int HttpProxyService::ReconsiderProxyAfterError(const GURL& url, + HttpProxyInfo* result, + CompletionCallback* callback, + PacRequest** pac_request) { + bool was_direct = result->is_direct(); + if (!was_direct && result->Fallback(&http_proxy_retry_info_)) + return OK; + + // Check to see if we have a new config since ResolveProxy was called. We + // want to re-run ResolveProxy in two cases: 1) we have a new config, or 2) a + // direct connection failed and we never tried the current config. + + bool re_resolve = result->config_id_ != config_.id(); + if (!re_resolve) { + UpdateConfig(); + if (result->config_id_ != config_.id()) { + // A new configuration! + re_resolve = true; + } else if (!result->config_was_tried_) { + // We never tried the proxy configuration since we thought it was bad, + // but because we failed to establish a connection, let's try the proxy + // configuration again to see if it will work now. + config_is_bad_ = false; + re_resolve = true; + } + } + if (re_resolve) + return ResolveProxy(url, result, callback, pac_request); + + if (!config_.auto_detect && !config_.proxy_server.empty()) { + // If auto detect is on, then we should try a DIRECT connection + // as the attempt to reach the proxy failed. + return ERR_FAILED; + } + + // If we already tried a direct connection, then just give up. + if (was_direct) + return ERR_FAILED; + + // Try going direct. + result->UseDirect(); + return OK; +} + +void HttpProxyService::CancelPacRequest(PacRequest* pac_request) { + pac_request->Cancel(); +} + +void HttpProxyService::DidCompletePacRequest(int config_id, int result_code) { + // If we get an error that indicates a bad PAC config, then we should + // remember that, and not try the PAC config again for a while. + + // Our config may have already changed. + if (result_code == OK || config_id != config_.id()) + return; + + // Remember that this configuration doesn't work. + config_is_bad_ = true; +} + +void HttpProxyService::UpdateConfig() { + HttpProxyConfig latest; + if (resolver_->GetProxyConfig(&latest) != OK) + return; + config_last_update_time_ = TimeTicks::Now(); + + if (latest.Equals(config_)) + return; + + config_ = latest; + config_is_bad_ = false; +} + +bool HttpProxyService::ShouldBypassProxyForURL(const GURL& url) { + std::wstring url_domain = ASCIIToWide(url.scheme()); + if (!url_domain.empty()) + url_domain += L"://"; + + url_domain += ASCIIToWide(url.host()); + StringToLowerASCII(url_domain); + + WStringTokenizer proxy_server_bypass_list(config_.proxy_bypass, L";"); + while (proxy_server_bypass_list.GetNext()) { + std::wstring bypass_url_domain = proxy_server_bypass_list.token(); + if (bypass_url_domain == L"<local>") { + // Any name without a DOT (.) is considered to be local. + if (url.host().find(L'.') == std::wstring::npos) + return true; + continue; + } + + // The proxy server bypass list can contain entities with http/https + // If no scheme is specified then it indicates that all schemes are + // allowed for the current entry. For matching this we just use + // the protocol scheme of the url passed in. + if (bypass_url_domain.find(L"://") == std::wstring::npos) { + std::wstring bypass_url_domain_with_scheme = ASCIIToWide(url.scheme()); + bypass_url_domain_with_scheme += L"://"; + bypass_url_domain_with_scheme += bypass_url_domain; + + bypass_url_domain = bypass_url_domain_with_scheme; + } + + StringToLowerASCII(bypass_url_domain); + + if (MatchPattern(url_domain, bypass_url_domain)) + return true; + } + + return false; +} + +} // namespace net diff --git a/net/http/http_proxy_service.h b/net/http/http_proxy_service.h new file mode 100644 index 0000000..d1ca955 --- /dev/null +++ b/net/http/http_proxy_service.h @@ -0,0 +1,303 @@ +// 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. + +#ifndef NET_HTTP_HTTP_PROXY_SERVICE_H__ +#define NET_HTTP_HTTP_PROXY_SERVICE_H__ + +#include <map> +#include <vector> + +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/thread.h" +#include "base/time.h" +#include "net/base/completion_callback.h" + +typedef LPVOID HINTERNET; // From winhttp.h + +class GURL; + +namespace net { + +class HttpProxyInfo; +class HttpProxyResolver; + +// Proxy configuration used to by the HttpProxyService. +class HttpProxyConfig { + public: + typedef int ID; + + // Indicates an invalid proxy config. + enum { INVALID_ID = 0 }; + + HttpProxyConfig(); + // Default copy-constructor an assignment operator are OK! + + // Used to numerically identify this configuration. + ID id() const { return id_; } + + // True if the proxy configuration should be auto-detected. + bool auto_detect; + + // If non-empty, indicates the URL of the proxy auto-config file to use. + std::wstring pac_url; + + // If non-empty, indicates the proxy server to use (of the form host:port). + std::wstring proxy_server; + + // If non-empty, indicates a comma-delimited list of hosts that should bypass + // any proxy configuration. For these hosts, a direct connection should + // always be used. + std::wstring proxy_bypass; + + // Returns true if the given config is equivalent to this config. + bool Equals(const HttpProxyConfig& other) const; + + private: + static int last_id_; + int id_; +}; + +// Contains the information about when to retry a proxy server. +struct HttpProxyRetryInfo { + // We should not retry until this time. + TimeTicks bad_until; + + // This is the current delay. If the proxy is still bad, we need to increase + // this delay. + TimeDelta current_delay; +}; + +// Map of proxy servers with the associated RetryInfo structures. +typedef std::map<std::wstring, HttpProxyRetryInfo> HttpProxyRetryInfoMap; + +// This class can be used to resolve the proxy server to use when loading a +// HTTP(S) URL. It uses to the given HttpProxyResolver to handle the actual +// proxy resolution. See HttpProxyResolverWinHttp for example. The consumer +// of this class is responsible for ensuring that the HttpProxyResolver +// instance remains valid for the lifetime of the HttpProxyService. +class HttpProxyService { + public: + explicit HttpProxyService(HttpProxyResolver* resolver); + + // Used internally to handle PAC queries. + class PacRequest; + + // Returns OK if proxy information could be provided synchronously. Else, + // ERR_IO_PENDING is returned to indicate that the result will be available + // when the callback is run. The callback is run on the thread that calls + // ResolveProxy. + // + // The caller is responsible for ensuring that |results| and |callback| + // remain valid until the callback is run or until |pac_request| is cancelled + // via CancelPacRequest. |pac_request| is only valid while the completion + // callback is still pending. + // + // We use the three possible proxy access types in the following order, and + // we only use one of them (no falling back to other access types if the + // chosen one doesn't work). + // 1. named proxy + // 2. PAC URL + // 3. WPAD auto-detection + // + int ResolveProxy(const GURL& url, + HttpProxyInfo* results, + CompletionCallback* callback, + PacRequest** pac_request); + + // This method is called after a failure to connect or resolve a host name. + // It gives the proxy service an opportunity to reconsider the proxy to use. + // The |results| parameter contains the results returned by an earlier call + // to ResolveProxy. The semantics of this call are otherwise similar to + // ResolveProxy. + // + // Returns ERR_FAILED if there is not another proxy config to try. + // + int ReconsiderProxyAfterError(const GURL& url, + HttpProxyInfo* results, + CompletionCallback* callback, + PacRequest** pac_request); + + // Call this method with a non-null |pac_request| to cancel the PAC request. + void CancelPacRequest(PacRequest* pac_request); + + private: + friend class PacRequest; + + HttpProxyResolver* resolver() { return resolver_; } + Thread* pac_thread() { return pac_thread_.get(); } + + // Identifies the proxy configuration. + HttpProxyConfig::ID config_id() const { return config_.id(); } + + // Checks to see if the proxy configuration changed, and then updates config_ + // to reference the new configuration. + void UpdateConfig(); + + // Called to indicate that a PacRequest completed. The |config_id| parameter + // indicates the proxy configuration that was queried. |result_code| is OK + // if the PAC file could be downloaded and executed. Otherwise, it is an + // error code, indicating a bad proxy configuration. + void DidCompletePacRequest(int config_id, int result_code); + + // Returns true if the URL passed in should not go through the proxy server. + // 1. If the bypass proxy list contains the string <local> and the URL + // passed in is a local URL, i.e. a URL without a DOT (.) + // 2. The URL matches one of the entities in the proxy bypass list. + bool ShouldBypassProxyForURL(const GURL& url); + + HttpProxyResolver* resolver_; + scoped_ptr<Thread> pac_thread_; + + // We store the IE proxy config and a counter that is incremented each time + // the config changes. + HttpProxyConfig config_; + + // Indicates that the configuration is bad and should be ignored. + bool config_is_bad_; + + // The time when the proxy configuration was last read from the system. + TimeTicks config_last_update_time_; + + // Map of the known bad proxies and the information about the retry time. + HttpProxyRetryInfoMap http_proxy_retry_info_; + + DISALLOW_EVIL_CONSTRUCTORS(HttpProxyService); +}; + +// This class is used to hold a list of proxies returned by GetProxyForUrl or +// manually configured. It handles proxy fallback if multiple servers are +// specified. +class HttpProxyList { + public: + // Initializes the proxy list to a string containing one or more proxy servers + // delimited by a semicolon. + void Set(const std::wstring& proxy_list); + + // Initializes the proxy list to a vector containing one or more proxy + // servers. + void SetVector(const std::vector<std::wstring>& proxy_list); + + // Remove all proxies known to be bad from the proxy list. + void RemoveBadProxies(const HttpProxyRetryInfoMap& http_proxy_retry_info); + + // Returns the first valid proxy server in the list. + std::wstring Get() const; + + // Returns all the valid proxies, delimited by a semicolon. + std::wstring GetList() const; + + // Returns all the valid proxies in a vector. + const std::vector<std::wstring>& GetVector() const; + + // Marks the current proxy server as bad and deletes it from the list. + // The list of known bad proxies is given by http_proxy_retry_info. + // Returns true if there is another server available in the list. + bool Fallback(HttpProxyRetryInfoMap* http_proxy_retry_info); + + private: + // List of proxies. + std::vector<std::wstring> proxies_; +}; + +// This object holds proxy information returned by ResolveProxy. +class HttpProxyInfo { + public: + HttpProxyInfo(); + + // Use the same proxy server as the given |proxy_info|. + void Use(const HttpProxyInfo& proxy_info); + + // Use a direct connection. + void UseDirect(); + + // Use a specific proxy server, of the form: <hostname> [":" <port>] + // This may optionally be a semi-colon delimited list of proxy servers. + void UseNamedProxy(const std::wstring& proxy_server); + + // Apply this proxy information to the given WinHTTP request handle. + void Apply(HINTERNET request_handle); + + // Returns true if this proxy info specifies a direct connection. + bool is_direct() const { return proxy_list_.Get().empty(); } + + // Returns the first valid proxy server. + const std::wstring proxy_server() const { return proxy_list_.Get(); } + + // Marks the current proxy as bad. Returns true if there is another proxy + // available to try in proxy list_. + bool Fallback(HttpProxyRetryInfoMap* http_proxy_retry_info) { + return proxy_list_.Fallback(http_proxy_retry_info); + } + + // Remove all proxies known to be bad from the proxy list. + void RemoveBadProxies(const HttpProxyRetryInfoMap& http_proxy_retry_info) { + proxy_list_.RemoveBadProxies(http_proxy_retry_info); + } + + private: + friend class HttpProxyService; + + // If proxy_list_ is set to empty, then a "direct" connection is indicated. + HttpProxyList proxy_list_; + + // This value identifies the proxy config used to initialize this object. + HttpProxyConfig::ID config_id_; + + // This flag is false when the proxy configuration was known to be bad when + // this proxy info was initialized. In such cases, we know that if this + // proxy info does not yield a connection that we might want to reconsider + // the proxy config given by config_id_. + bool config_was_tried_; + + DISALLOW_EVIL_CONSTRUCTORS(HttpProxyInfo); +}; + +// This interface provides the low-level functions to access the proxy +// configuration and resolve proxies for given URLs synchronously. +class HttpProxyResolver { + public: + virtual ~HttpProxyResolver() {} + + // Get the proxy configuration. Returns OK if successful or an error code if + // otherwise. |config| should be in its initial state when this method is + // called. + virtual int GetProxyConfig(HttpProxyConfig* config) = 0; + + // Query the proxy auto-config file (specified by |pac_url|) for the proxy to + // use to load the given |query_url|. Returns OK if successful or an error + // code if otherwise. + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + HttpProxyInfo* results) = 0; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_PROXY_SERVICE_H__ diff --git a/net/http/http_proxy_service_unittest.cc b/net/http/http_proxy_service_unittest.cc new file mode 100644 index 0000000..c047d9c --- /dev/null +++ b/net/http/http_proxy_service_unittest.cc @@ -0,0 +1,299 @@ +// 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 "googleurl/src/gurl.h" +#include "net/base/net_errors.h" +#include "net/http/http_proxy_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class MockProxyResolver : public net::HttpProxyResolver { + public: + MockProxyResolver() : fail_get_proxy_for_url(false) { + } + virtual int GetProxyConfig(net::HttpProxyConfig* results) { + *results = config; + return net::OK; + } + virtual int GetProxyForURL(const std::wstring& query_url, + const std::wstring& pac_url, + net::HttpProxyInfo* results) { + if (pac_url != config.pac_url) + return net::ERR_INVALID_ARGUMENT; + if (fail_get_proxy_for_url) + return net::ERR_FAILED; + if (GURL(query_url).host() == info_predicate_query_host) { + results->Use(info); + } else { + results->UseDirect(); + } + return net::OK; + } + net::HttpProxyConfig config; + net::HttpProxyInfo info; + + // info is only returned if query_url in GetProxyForURL matches this: + std::string info_predicate_query_host; + + // If true, then GetProxyForURL will fail, which simulates failure to + // download or execute the PAC file. + bool fail_get_proxy_for_url; +}; + +} // namespace + +TEST(HttpProxyServiceTest, Direct) { + MockProxyResolver resolver; + net::HttpProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + net::HttpProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info.is_direct()); +} + +TEST(HttpProxyServiceTest, PAC) { + MockProxyResolver resolver; + resolver.config.pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy"); + resolver.info_predicate_query_host = "www.google.com"; + + net::HttpProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + net::HttpProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy"); +} + +TEST(HttpProxyServiceTest, PAC_FailoverToDirect) { + MockProxyResolver resolver; + resolver.config.pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy:8080"); + resolver.info_predicate_query_host = "www.google.com"; + + net::HttpProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + net::HttpProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy:8080"); + + // Now, imagine that connecting to foopy:8080 fails. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info.is_direct()); +} + +TEST(HttpProxyServiceTest, PAC_FailsToDownload) { + // Test what happens when we fail to download the PAC URL. + + MockProxyResolver resolver; + resolver.config.pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy:8080"); + resolver.info_predicate_query_host = "www.google.com"; + resolver.fail_get_proxy_for_url = true; + + net::HttpProxyService service(&resolver); + + GURL url("http://www.google.com/"); + net::HttpProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info.is_direct()); + + rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info.is_direct()); + + resolver.fail_get_proxy_for_url = false; + resolver.info.UseNamedProxy(L"foopy_valid:8080"); + + // But, if that fails, then we should give the proxy config another shot + // since we have never tried it with this URL before. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy_valid:8080"); +} + +TEST(HttpProxyServiceTest, ProxyFallback) { + // Test what happens when we specify multiple proxy servers and some of them + // are bad. + + MockProxyResolver resolver; + resolver.config.pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy1:8080;foopy2:9090"); + resolver.info_predicate_query_host = "www.google.com"; + resolver.fail_get_proxy_for_url = false; + + net::HttpProxyService service(&resolver); + + GURL url("http://www.google.com/"); + + // Get the proxy information. + net::HttpProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + + // The first item is valid. + EXPECT_EQ(info.proxy_server(), L"foopy1:8080"); + + // Fake an error on the proxy. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + + // The second proxy should be specified. + EXPECT_EQ(info.proxy_server(), L"foopy2:9090"); + + // Create a new resolver that returns 3 proxies. The second one is already + // known to be bad. + resolver.config.pac_url = L"http://foopy/proxy.pac"; + resolver.info.UseNamedProxy(L"foopy3:7070;foopy1:8080;foopy2:9090"); + resolver.info_predicate_query_host = "www.google.com"; + resolver.fail_get_proxy_for_url = false; + + rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + EXPECT_EQ(info.proxy_server(), L"foopy3:7070"); + + // We fake another error. It should now try the third one. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_EQ(info.proxy_server(), L"foopy2:9090"); + + // Fake another error, the last proxy is gone, the list should now be empty. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); // We try direct. + EXPECT_TRUE(info.is_direct()); + + // If it fails again, we don't have anything else to try. + rv = service.ReconsiderProxyAfterError(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::ERR_FAILED); // We try direct. + + // TODO(nsylvain): Test that the proxy can be retried after the delay. +} + +TEST(HttpProxyServiceTest, ProxyBypassList) { + // Test what happens when a proxy bypass list is specified. + + MockProxyResolver resolver; + resolver.config.proxy_server = L"foopy1:8080;foopy2:9090"; + resolver.config.auto_detect = false; + resolver.config.proxy_bypass = L"<local>"; + + net::HttpProxyService service(&resolver); + GURL url("http://www.google.com/"); + // Get the proxy information. + net::HttpProxyInfo info; + int rv = service.ResolveProxy(url, &info, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info.is_direct()); + + net::HttpProxyService service1(&resolver); + GURL test_url1("local"); + net::HttpProxyInfo info1; + rv = service1.ResolveProxy(test_url1, &info1, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info1.is_direct()); + + resolver.config.proxy_bypass = L"<local>;*.org"; + net::HttpProxyService service2(&resolver); + GURL test_url2("http://www.webkit.org"); + net::HttpProxyInfo info2; + rv = service2.ResolveProxy(test_url2, &info2, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info2.is_direct()); + + resolver.config.proxy_bypass = L"<local>;*.org;7*"; + net::HttpProxyService service3(&resolver); + GURL test_url3("http://74.125.19.147"); + net::HttpProxyInfo info3; + rv = service3.ResolveProxy(test_url3, &info3, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info3.is_direct()); + + resolver.config.proxy_bypass = L"<local>;*.org;"; + net::HttpProxyService service4(&resolver); + GURL test_url4("http://www.msn.com"); + net::HttpProxyInfo info4; + rv = service4.ResolveProxy(test_url4, &info4, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info4.is_direct()); +} + +TEST(HttpProxyServiceTest, PerProtocolProxyTests) { + MockProxyResolver resolver; + resolver.config.proxy_server = L"http=foopy1:8080;https=foopy2:8080"; + resolver.config.auto_detect = false; + + net::HttpProxyService service1(&resolver); + GURL test_url1("http://www.msn.com"); + net::HttpProxyInfo info1; + int rv = service1.ResolveProxy(test_url1, &info1, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info1.is_direct()); + EXPECT_TRUE(info1.proxy_server() == L"foopy1:8080"); + + net::HttpProxyService service2(&resolver); + GURL test_url2("ftp://ftp.google.com"); + net::HttpProxyInfo info2; + rv = service2.ResolveProxy(test_url2, &info2, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_TRUE(info2.is_direct()); + EXPECT_TRUE(info2.proxy_server() == L""); + + net::HttpProxyService service3(&resolver); + GURL test_url3("https://webbranch.techcu.com"); + net::HttpProxyInfo info3; + rv = service3.ResolveProxy(test_url3, &info3, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info3.is_direct()); + EXPECT_TRUE(info3.proxy_server() == L"foopy2:8080"); + + resolver.config.proxy_server = L"foopy1:8080"; + net::HttpProxyService service4(&resolver); + GURL test_url4("www.microsoft.com"); + net::HttpProxyInfo info4; + rv = service4.ResolveProxy(test_url4, &info4, NULL, NULL); + EXPECT_EQ(rv, net::OK); + EXPECT_FALSE(info4.is_direct()); + EXPECT_TRUE(info4.proxy_server() == L"foopy1:8080"); +}
\ No newline at end of file diff --git a/net/http/http_request_info.h b/net/http/http_request_info.h new file mode 100644 index 0000000..c0099c6 --- /dev/null +++ b/net/http/http_request_info.h @@ -0,0 +1,66 @@ +// 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. + +#ifndef NET_HTTP_HTTP_REQUEST_INFO_H__ +#define NET_HTTP_HTTP_REQUEST_INFO_H__ + +#include "base/ref_counted.h" +#include "googleurl/src/gurl.h" +#include "net/base/upload_data.h" + +namespace net { + +class HttpRequestInfo { + public: + // The requested URL. + GURL url; + + // The referring URL (if any). + GURL referrer; + + // The method to use (GET, POST, etc.). + std::string method; + + // The user agent string to use. TODO(darin): we should just add this to + // extra_headers + std::string user_agent; + + // Any extra request headers (\r\n-delimited). + std::string extra_headers; + + // Any upload data. + scoped_refptr<UploadData> upload_data; + + // Any load flags (see load_flags.h). + int load_flags; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_REQUEST_INFO_H__ diff --git a/net/http/http_response_headers.cc b/net/http/http_response_headers.cc new file mode 100644 index 0000000..38e2fe2 --- /dev/null +++ b/net/http/http_response_headers.cc @@ -0,0 +1,936 @@ +// 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. + +// The rules for header parsing were borrowed from Firefox: +// http://lxr.mozilla.org/seamonkey/source/netwerk/protocol/http/src/nsHttpResponseHead.cpp +// The rules for parsing content-types were also borrowed from Firefox: +// http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsURLHelper.cpp#834 + +#include "net/http/http_response_headers.h" + +#include <algorithm> +#include <hash_map> + +#include "base/logging.h" +#include "base/pickle.h" +#include "base/string_util.h" +#include "base/time.h" +#include "net/base/escape.h" +#include "net/http/http_util.h" + +using std::string; + +namespace net { + +//----------------------------------------------------------------------------- + +namespace { + +// These response headers are not persisted in a cached representation of the +// response headers. (This list comes from Mozilla's nsHttpResponseHead.cpp) +const char* kTransientHeaders[] = { + "connection", + "proxy-connection", + "keep-alive", + "www-authenticate", + "proxy-authenticate", + "trailer", + "transfer-encoding", + "upgrade", + "set-cookie", + "set-cookie2" +}; + +// These respones headers are not copied from a 304/206 response to the cached +// response headers. This list is based on Mozilla's nsHttpResponseHead.cpp. +const char* kNonUpdatedHeaders[] = { + "connection", + "proxy-connection", + "keep-alive", + "www-authenticate", + "proxy-authenticate", + "trailer", + "transfer-encoding", + "upgrade", + // these should never change: + "content-location", + "content-md5", + "etag", + // assume cache-control: no-transform + "content-encoding", + "content-range", + "content-type", + // some broken microsoft servers send 'content-length: 0' with 304s + "content-length" +}; + +bool ShouldUpdateHeader(const string::const_iterator& name_begin, + const string::const_iterator& name_end) { + for (size_t i = 0; i < arraysize(kNonUpdatedHeaders); ++i) { + if (LowerCaseEqualsASCII(name_begin, name_end, kNonUpdatedHeaders[i])) + return false; + } + return true; +} + +} // namespace + +//----------------------------------------------------------------------------- + +HttpResponseHeaders::HttpResponseHeaders(const string& raw_input) + : response_code_(-1) { + Parse(raw_input); +} + +HttpResponseHeaders::HttpResponseHeaders(const Pickle& pickle, void** iter) + : response_code_(-1) { + string raw_input; + if (pickle.ReadString(iter, &raw_input)) + Parse(raw_input); +} + +void HttpResponseHeaders::Persist(Pickle* pickle, bool for_cache) { + if (for_cache) { + HeaderSet transient_headers; + GetTransientHeaders(&transient_headers); + + std::string blob; + blob.reserve(raw_headers_.size()); + + // this just copies the status line w/ terminator + blob.assign(raw_headers_.c_str(), strlen(raw_headers_.c_str()) + 1); + + for (size_t i = 0; i < parsed_.size(); ++i) { + DCHECK(!parsed_[i].is_continuation()); + + // locate the start of the next header + size_t k = i; + while (++k < parsed_.size() && parsed_[k].is_continuation()); + --k; + + string header_name(parsed_[i].name_begin, parsed_[i].name_end); + StringToLowerASCII(&header_name); + + if (transient_headers.find(header_name) == transient_headers.end()) { + // includes terminator + blob.append(parsed_[i].name_begin, parsed_[k].value_end + 1); + } + + i = k; + } + blob.push_back('\0'); + + pickle->WriteString(blob); + } else { + pickle->WriteString(raw_headers_); + } +} + +void HttpResponseHeaders::Update(const HttpResponseHeaders& new_headers) { + DCHECK(new_headers.response_code() == 304 || + new_headers.response_code() == 206); + + // copy up to the null byte. this just copies the status line. + string new_raw_headers(raw_headers_.c_str()); + new_raw_headers.push_back('\0'); + + HeaderSet updated_headers; + + // NOTE: we write the new headers then the old headers for convenience. the + // order should not matter. + + // figure out which headers we want to take from new_headers: + for (size_t i = 0; i < new_headers.parsed_.size(); ++i) { + const HeaderList& new_parsed = new_headers.parsed_; + + DCHECK(!new_parsed[i].is_continuation()); + + // locate the start of the next header + size_t k = i; + while (++k < new_parsed.size() && new_parsed[k].is_continuation()); + --k; + + const string::const_iterator& name_begin = new_parsed[i].name_begin; + const string::const_iterator& name_end = new_parsed[i].name_end; + if (ShouldUpdateHeader(name_begin, name_end)) { + string name(name_begin, name_end); + StringToLowerASCII(&name); + updated_headers.insert(name); + + // preserve this header line in the merged result (including trailing '\0') + new_raw_headers.append(name_begin, new_parsed[k].value_end + 1); + } + + i = k; + } + + // now, build the new raw headers + for (size_t i = 0; i < parsed_.size(); ++i) { + DCHECK(!parsed_[i].is_continuation()); + + // locate the start of the next header + size_t k = i; + while (++k < parsed_.size() && parsed_[k].is_continuation()); + --k; + + string name(parsed_[i].name_begin, parsed_[i].name_end); + StringToLowerASCII(&name); + if (updated_headers.find(name) == updated_headers.end()) { + // ok to preserve this header in the final result + new_raw_headers.append(parsed_[i].name_begin, parsed_[k].value_end + 1); + } + + i = k; + } + new_raw_headers.push_back('\0'); + + // ok, make this object hold the new data + raw_headers_.clear(); + parsed_.clear(); + Parse(new_raw_headers); +} + +void HttpResponseHeaders::Parse(const string& raw_input) { + raw_headers_.reserve(raw_input.size()); + + // ParseStatusLine adds a normalized status line to raw_headers_ + string::const_iterator line_begin = raw_input.begin(); + string::const_iterator line_end = find(line_begin, raw_input.end(), '\0'); + ParseStatusLine(line_begin, line_end); + + if (line_end == raw_input.end()) { + raw_headers_.push_back('\0'); + return; + } + + // Including a terminating null byte. + size_t status_line_len = raw_headers_.size(); + + // Now, we add the rest of the raw headers to raw_headers_, and begin parsing + // it (to populate our parsed_ vector). + raw_headers_.append(line_end + 1, raw_input.end()); + + // Adjust to point at the null byte following the status line + line_end = raw_headers_.begin() + status_line_len - 1; + + HttpUtil::HeadersIterator headers(line_end + 1, raw_headers_.end(), + string(1, '\0')); + while (headers.GetNext()) { + AddHeader(headers.name_begin(), + headers.name_end(), + headers.values_begin(), + headers.values_end()); + } +} + +// Append all of our headers to the final output string. +void HttpResponseHeaders::GetNormalizedHeaders(string* output) const { + // copy up to the null byte. this just copies the status line. + output->assign(raw_headers_.c_str()); + + // headers may appear multiple times (not necessarily in succession) in the + // header data, so we build a map from header name to generated header lines. + // to preserve the order of the original headers, the actual values are kept + // in a separate list. finally, the list of headers is flattened to form + // the normalized block of headers. + // + // NOTE: We take special care to preserve the whitespace around any commas + // that may occur in the original response headers. Because our consumer may + // be a web app, we cannot be certain of the semantics of commas despite the + // fact that RFC 2616 says that they should be regarded as value separators. + // + typedef stdext::hash_map<string, size_t> HeadersMap; + HeadersMap headers_map; + HeadersMap::iterator iter = headers_map.end(); + + std::vector<string> headers; + + for (size_t i = 0; i < parsed_.size(); ++i) { + DCHECK(!parsed_[i].is_continuation()); + + string name(parsed_[i].name_begin, parsed_[i].name_end); + string lower_name = StringToLowerASCII(name); + + iter = headers_map.find(lower_name); + if (iter == headers_map.end()) { + iter = headers_map.insert( + HeadersMap::value_type(lower_name, headers.size())).first; + headers.push_back(name + ": "); + } else { + headers[iter->second].append(", "); + } + + string::const_iterator value_begin = parsed_[i].value_begin; + string::const_iterator value_end = parsed_[i].value_end; + while (++i < parsed_.size() && parsed_[i].is_continuation()) + value_end = parsed_[i].value_end; + --i; + + headers[iter->second].append(value_begin, value_end); + } + + for (size_t i = 0; i < headers.size(); ++i) { + output->push_back('\n'); + output->append(headers[i]); + } + + output->push_back('\n'); +} + +bool HttpResponseHeaders::GetNormalizedHeader(const string& name, + string* value) const { + // If you hit this assertion, please use EnumerateHeader instead! + DCHECK(!HttpUtil::IsNonCoalescingHeader(name)); + + value->clear(); + + bool found = false; + size_t i = 0; + while (i < parsed_.size()) { + i = FindHeader(i, name); + if (i == string::npos) + break; + + found = true; + + if (!value->empty()) + value->append(", "); + + string::const_iterator value_begin = parsed_[i].value_begin; + string::const_iterator value_end = parsed_[i].value_end; + while (++i < parsed_.size() && parsed_[i].is_continuation()) + value_end = parsed_[i].value_end; + value->append(value_begin, value_end); + } + + return found; +} + +string HttpResponseHeaders::GetStatusLine() const { + // copy up to the null byte. + return string(raw_headers_.c_str()); +} + +bool HttpResponseHeaders::EnumerateHeaderLines(void** iter, + string* name, + string* value) const { + size_t i = reinterpret_cast<size_t>(*iter); + if (i == parsed_.size()) + return false; + + DCHECK(!parsed_[i].is_continuation()); + + name->assign(parsed_[i].name_begin, parsed_[i].name_end); + + string::const_iterator value_begin = parsed_[i].value_begin; + string::const_iterator value_end = parsed_[i].value_end; + while (++i < parsed_.size() && parsed_[i].is_continuation()) + value_end = parsed_[i].value_end; + + value->assign(value_begin, value_end); + + *iter = reinterpret_cast<void*>(i); + return true; +} + +bool HttpResponseHeaders::EnumerateHeader(void** iter, const string& name, + string* value) const { + size_t i; + if (!iter || !*iter) { + i = FindHeader(0, name); + } else { + i = reinterpret_cast<size_t>(*iter); + if (i >= parsed_.size()) { + i = string::npos; + } else if (!parsed_[i].is_continuation()) { + i = FindHeader(i, name); + } + } + + if (i == string::npos) { + value->clear(); + return false; + } + + if (iter) + *iter = reinterpret_cast<void*>(i + 1); + value->assign(parsed_[i].value_begin, parsed_[i].value_end); + return true; +} + +bool HttpResponseHeaders::HasHeaderValue(const std::string& name, + const std::string& value) const { + // The value has to be an exact match. This is important since + // 'cache-control: no-cache' != 'cache-control: no-cache="foo"' + void* iter = NULL; + std::string temp; + while (EnumerateHeader(&iter, name, &temp)) { + if (value.size() == temp.size() && + std::equal(temp.begin(), temp.end(), value.begin(), + CaseInsensitiveCompare<char>())) + return true; + } + return false; +} + +// Note: this implementation implicitly assumes that line_end points at a valid +// sentinel character (such as '\0'). +void HttpResponseHeaders::ParseVersion(string::const_iterator line_begin, + string::const_iterator line_end) { + string::const_iterator p = line_begin; + + // RFC2616 sec 3.1: HTTP-Version = "HTTP" "/" 1*DIGIT "." 1*DIGIT + // (1*DIGIT apparently means one or more digits, but we only handle 1). + + if ((line_end - p < 4) || !LowerCaseEqualsASCII(p, p + 4, "http")) { + DLOG(INFO) << "missing status line; assuming HTTP/0.9"; + // Morph this into HTTP/1.0 since HTTP/0.9 has no status line. + raw_headers_ = "HTTP/1.0"; + return; + } + + p += 4; + + if (p >= line_end || *p != '/') { + DLOG(INFO) << "missing version; assuming HTTP/1.0"; + raw_headers_ = "HTTP/1.0"; + return; + } + + string::const_iterator dot = find(p, line_end, '.'); + if (dot == line_end) { + DLOG(INFO) << "malformed version; assuming HTTP/1.0"; + raw_headers_ = "HTTP/1.0"; + return; + } + + ++p; // from / to first digit. + ++dot; // from . to second digit. + + if (!(*p >= '0' && *p <= '9' && *dot >= '0' && *dot <= '9')) { + DLOG(INFO) << "malformed version number; assuming HTTP/1.0"; + raw_headers_ = "HTTP/1.0"; + return; + } + + int major = *p - '0'; + int minor = *dot - '0'; + + if ((major > 1) || ((major == 1) && (minor >= 1))) { + // at least HTTP/1.1 + raw_headers_ = "HTTP/1.1"; + } else { + // treat anything else as version 1.0 + raw_headers_ = "HTTP/1.0"; + } +} + +// Note: this implementation implicitly assumes that line_end points at a valid +// sentinel character (such as '\0'). +void HttpResponseHeaders::ParseStatusLine(string::const_iterator line_begin, + string::const_iterator line_end) { + ParseVersion(line_begin, line_end); + + string::const_iterator p = find(line_begin, line_end, ' '); + + if (p == line_end) { + DLOG(INFO) << "missing response status; assuming 200 OK"; + raw_headers_.append(" 200 OK"); + raw_headers_.push_back('\0'); + response_code_ = 200; + return; + } + + // Skip whitespace. + while (*p == ' ') + ++p; + + string::const_iterator code = p; + while (*p >= '0' && *p <= '9') + ++p; + + if (p == code) { + DLOG(INFO) << "missing response status number; assuming 200"; + raw_headers_.append(" 200 "); + response_code_ = 200; + } else { + raw_headers_.push_back(' '); + raw_headers_.append(code, p); + raw_headers_.push_back(' '); + response_code_ = static_cast<int>(StringToInt64(string(code, p))); + } + + // Skip whitespace. + while (*p == ' ') + ++p; + + // Trim trailing whitespace. + while (line_end > p && line_end[-1] == ' ') + --line_end; + + if (p == line_end) { + DLOG(INFO) << "missing response status text; assuming OK"; + raw_headers_.append("OK"); + } else { + raw_headers_.append(p, line_end); + } + + raw_headers_.push_back('\0'); +} + +size_t HttpResponseHeaders::FindHeader(size_t from, + const string& search) const { + for (size_t i = from; i < parsed_.size(); ++i) { + if (parsed_[i].is_continuation()) + continue; + const string::const_iterator& name_begin = parsed_[i].name_begin; + const string::const_iterator& name_end = parsed_[i].name_end; + if ((name_end - name_begin) == search.size() && + std::equal(name_begin, name_end, search.begin(), + CaseInsensitiveCompare<char>())) + return i; + } + + return string::npos; +} + +void HttpResponseHeaders::AddHeader(string::const_iterator name_begin, + string::const_iterator name_end, + string::const_iterator values_begin, + string::const_iterator values_end) { + // If the header can be coalesced, then we should split it up. + if (values_begin == values_end || + HttpUtil::IsNonCoalescingHeader(name_begin, name_end)) { + AddToParsed(name_begin, name_end, values_begin, values_end); + } else { + HttpUtil::ValuesIterator it(values_begin, values_end, ','); + while (it.GetNext()) { + AddToParsed(name_begin, name_end, it.value_begin(), it.value_end()); + // clobber these so that subsequent values are treated as continuations + name_begin = name_end = raw_headers_.end(); + } + } +} + +void HttpResponseHeaders::AddToParsed(string::const_iterator name_begin, + string::const_iterator name_end, + string::const_iterator value_begin, + string::const_iterator value_end) { + ParsedHeader header; + header.name_begin = name_begin; + header.name_end = name_end; + header.value_begin = value_begin; + header.value_end = value_end; + parsed_.push_back(header); +} + +void HttpResponseHeaders::GetTransientHeaders(HeaderSet* result) const { + // Add server specified transients. Any 'cache-control: no-cache="foo,bar"' + // headers present in the response specify additional headers that we should + // not store in the cache. + const string kCacheControl = "cache-control"; + const string kPrefix = "no-cache=\""; + string value; + void* iter = NULL; + while (EnumerateHeader(&iter, kCacheControl, &value)) { + if (value.size() > kPrefix.size() && + value.compare(0, kPrefix.size(), kPrefix) == 0) { + // if it doesn't end with a quote, then treat as malformed + if (value[value.size()-1] != '\"') + continue; + + // trim off leading and trailing bits + size_t len = value.size() - kPrefix.size() - 1; + TrimString(value.substr(kPrefix.size(), len), HTTP_LWS, &value); + + size_t begin_pos = 0; + for (;;) { + // find the end of this header name + size_t comma_pos = value.find(',', begin_pos); + if (comma_pos == string::npos) + comma_pos = value.size(); + size_t end = comma_pos; + while (end > begin_pos && strchr(HTTP_LWS, value[end - 1])) + end--; + + // assuming the header is not emtpy, lowercase and insert into set + if (end > begin_pos) { + string name = value.substr(begin_pos, end - begin_pos); + StringToLowerASCII(&name); + result->insert(name); + } + + // repeat + begin_pos = comma_pos + 1; + while (begin_pos < value.size() && strchr(HTTP_LWS, value[begin_pos])) + begin_pos++; + if (begin_pos >= value.size()) + break; + } + } + } + + // Add standard transient headers. Perhaps we should move this to a + // statically cached hash_set to avoid duplicated work? + for (size_t i = 0; i < arraysize(kTransientHeaders); ++i) + result->insert(string(kTransientHeaders[i])); +} + +void HttpResponseHeaders::GetMimeTypeAndCharset(string* mime_type, + string* charset) const { + mime_type->clear(); + charset->clear(); + + string name = "content-type"; + string value; + + bool had_charset = false; + + void* iter = NULL; + while (EnumerateHeader(&iter, name, &value)) + HttpUtil::ParseContentType(value, mime_type, charset, &had_charset); +} + +bool HttpResponseHeaders::GetMimeType(string* mime_type) const { + string unused; + GetMimeTypeAndCharset(mime_type, &unused); + return !mime_type->empty(); +} + +bool HttpResponseHeaders::GetCharset(string* charset) const { + string unused; + GetMimeTypeAndCharset(&unused, charset); + return !charset->empty(); +} + +bool HttpResponseHeaders::IsRedirect(string* location) const { + // Users probably want to see 300 (multiple choice) pages, so we don't count + // them as redirects that need to be followed. + if (!(response_code_ == 301 || + response_code_ == 302 || + response_code_ == 303 || + response_code_ == 307)) + return false; + + // If we lack a Location header, then we can't treat this as a redirect. + // We assume that the first non-empty location value is the target URL that + // we want to follow. TODO(darin): Is this consistent with other browsers? + size_t i = -1; + do { + i = FindHeader(++i, "location"); + if (i == string::npos) + return false; + // If the location value is empty, then it doesn't count. + } while (parsed_[i].value_begin == parsed_[i].value_end); + + if (location) { + // Escape any non-ASCII characters to preserve them. The server should + // only be returning ASCII here, but for compat we need to do this. + *location = EscapeNonASCII( + std::string(parsed_[i].value_begin, parsed_[i].value_end)); + } + + return true; +} + +// From RFC 2616 section 13.2.4: +// +// The calculation to determine if a response has expired is quite simple: +// +// response_is_fresh = (freshness_lifetime > current_age) +// +// Of course, there are other factors that can force a response to always be +// validated or re-fetched. +// +bool HttpResponseHeaders::RequiresValidation(const Time& request_time, + const Time& response_time, + const Time& current_time) const { + + TimeDelta lifetime = + GetFreshnessLifetime(response_time); + if (lifetime == TimeDelta()) + return true; + + return lifetime <= GetCurrentAge(request_time, response_time, current_time); +} + +// From RFC 2616 section 13.2.4: +// +// The max-age directive takes priority over Expires, so if max-age is present +// in a response, the calculation is simply: +// +// freshness_lifetime = max_age_value +// +// Otherwise, if Expires is present in the response, the calculation is: +// +// freshness_lifetime = expires_value - date_value +// +// Note that neither of these calculations is vulnerable to clock skew, since +// all of the information comes from the origin server. +// +// Also, if the response does have a Last-Modified time, the heuristic +// expiration value SHOULD be no more than some fraction of the interval since +// that time. A typical setting of this fraction might be 10%: +// +// freshness_lifetime = (date_value - last_modified_value) * 0.10 +// +TimeDelta HttpResponseHeaders::GetFreshnessLifetime( + const Time& response_time) const { + // Check for headers that force a response to never be fresh. For backwards + // compat, we treat "Pragma: no-cache" as a synonym for "Cache-Control: + // no-cache" even though RFC 2616 does not specify it. + if (HasHeaderValue("cache-control", "no-cache") || + HasHeaderValue("cache-control", "no-store") || + HasHeaderValue("pragma", "no-cache") || + HasHeaderValue("vary", "*")) // see RFC 2616 section 13.6 + return TimeDelta(); // not fresh + + // NOTE: "Cache-Control: max-age" overrides Expires, so we only check the + // Expires header after checking for max-age in GetFreshnessLifetime. This + // is important since "Expires: <date in the past>" means not fresh, but + // it should not trump a max-age value. + + TimeDelta max_age_value; + if (GetMaxAgeValue(&max_age_value)) + return max_age_value; + + // If there is no Date header, then assume that the server response was + // generated at the time when we received the response. + Time date_value; + if (!GetDateValue(&date_value)) + date_value = response_time; + + Time expires_value; + if (GetExpiresValue(&expires_value)) { + // The expires value can be a date in the past! + if (expires_value > date_value) + return expires_value - date_value; + + return TimeDelta(); // not fresh + } + + // From RFC 2616 section 13.4: + // + // A response received with a status code of 200, 203, 206, 300, 301 or 410 + // MAY be stored by a cache and used in reply to a subsequent request, + // subject to the expiration mechanism, unless a cache-control directive + // prohibits caching. + // ... + // A response received with any other status code (e.g. status codes 302 + // and 307) MUST NOT be returned in a reply to a subsequent request unless + // there are cache-control directives or another header(s) that explicitly + // allow it. + // + // Since we do not support byte range requests yet, we exclude 206. See + // HttpCache::Transaction::ShouldPassThrough. + // + // From RFC 2616 section 14.9.4: + // + // When the must-revalidate directive is present in a response received by + // a cache, that cache MUST NOT use the entry after it becomes stale to + // respond to a subsequent request without first revalidating it with the + // origin server. (I.e., the cache MUST do an end-to-end revalidation every + // time, if, based solely on the origin server's Expires or max-age value, + // the cached response is stale.) + // + if ((response_code_ == 200 || response_code_ == 203) && + !HasHeaderValue("cache-control", "must-revalidate")) { + // TODO(darin): Implement a smarter heuristic. + Time last_modified_value; + if (GetLastModifiedValue(&last_modified_value)) { + // The last-modified value can be a date in the past! + if (last_modified_value <= date_value) + return (date_value - last_modified_value) / 10; + } + } + + // These responses are implicitly fresh (unless otherwise overruled): + if (response_code_ == 300 || response_code_ == 301 || response_code_ == 410) + return TimeDelta::FromMicroseconds(kint64max); + + return TimeDelta(); // not fresh +} + +// From RFC 2616 section 13.2.3: +// +// Summary of age calculation algorithm, when a cache receives a response: +// +// /* +// * age_value +// * is the value of Age: header received by the cache with +// * this response. +// * date_value +// * is the value of the origin server's Date: header +// * request_time +// * is the (local) time when the cache made the request +// * that resulted in this cached response +// * response_time +// * is the (local) time when the cache received the +// * response +// * now +// * is the current (local) time +// */ +// apparent_age = max(0, response_time - date_value); +// corrected_received_age = max(apparent_age, age_value); +// response_delay = response_time - request_time; +// corrected_initial_age = corrected_received_age + response_delay; +// resident_time = now - response_time; +// current_age = corrected_initial_age + resident_time; +// +TimeDelta HttpResponseHeaders::GetCurrentAge(const Time& request_time, + const Time& response_time, + const Time& current_time) const { + // If there is no Date header, then assume that the server response was + // generated at the time when we received the response. + Time date_value; + if (!GetDateValue(&date_value)) + date_value = response_time; + + // If there is no Age header, then assume age is zero. GetAgeValue does not + // modify its out param if the value does not exist. + TimeDelta age_value; + GetAgeValue(&age_value); + + TimeDelta apparent_age = std::max(TimeDelta(), response_time - date_value); + TimeDelta corrected_received_age = std::max(apparent_age, age_value); + TimeDelta response_delay = response_time - request_time; + TimeDelta corrected_initial_age = corrected_received_age + response_delay; + TimeDelta resident_time = current_time - response_time; + TimeDelta current_age = corrected_initial_age + resident_time; + + return current_age; +} + +bool HttpResponseHeaders::GetMaxAgeValue(TimeDelta* result) const { + string name = "cache-control"; + string value; + + const char kMaxAgePrefix[] = "max-age="; + const int kMaxAgePrefixLen = arraysize(kMaxAgePrefix) - 1; + + void* iter = NULL; + while (EnumerateHeader(&iter, name, &value)) { + if (value.size() > kMaxAgePrefixLen) { + if (LowerCaseEqualsASCII(value.begin(), + value.begin() + kMaxAgePrefixLen, + kMaxAgePrefix)) { + *result = TimeDelta::FromSeconds( + StringToInt64(value.substr(kMaxAgePrefixLen))); + return true; + } + } + } + + return false; +} + +bool HttpResponseHeaders::GetAgeValue(TimeDelta* result) const { + string value; + if (!EnumerateHeader(NULL, "Age", &value)) + return false; + + *result = TimeDelta::FromSeconds(StringToInt64(value)); + return true; +} + +bool HttpResponseHeaders::GetDateValue(Time* result) const { + return GetTimeValuedHeader("Date", result); +} + +bool HttpResponseHeaders::GetLastModifiedValue(Time* result) const { + return GetTimeValuedHeader("Last-Modified", result); +} + +bool HttpResponseHeaders::GetExpiresValue(Time* result) const { + return GetTimeValuedHeader("Expires", result); +} + +bool HttpResponseHeaders::GetTimeValuedHeader(const std::string& name, + Time* result) const { + string value; + if (!EnumerateHeader(NULL, name, &value)) + return false; + + std::wstring value_wide(value.begin(), value.end()); // inflate ascii + return Time::FromString(value_wide.c_str(), result); +} + +bool HttpResponseHeaders::IsKeepAlive() const { + const char kPrefix[] = "HTTP/1.0"; + const int kPrefixLen = arraysize(kPrefix) - 1; + if (raw_headers_.size() < kPrefixLen) // Lacking a status line? + return false; + + // NOTE: It is perhaps risky to assume that a Proxy-Connection header is + // meaningful when we don't know that this response was from a proxy, but + // Mozilla also does this, so we'll do the same. + string connection_val; + void* iter = NULL; + if (!EnumerateHeader(&iter, "connection", &connection_val)) + EnumerateHeader(&iter, "proxy-connection", &connection_val); + + bool keep_alive; + + if (std::equal(raw_headers_.begin(), + raw_headers_.begin() + kPrefixLen, kPrefix)) { + // HTTP/1.0 responses default to NOT keep-alive + keep_alive = LowerCaseEqualsASCII(connection_val, "keep-alive"); + } else { + // HTTP/1.1 responses default to keep-alive + keep_alive = !LowerCaseEqualsASCII(connection_val, "close"); + } + + return keep_alive; +} + +int64 HttpResponseHeaders::GetContentLength() const { + void* iter = NULL; + string content_length_val; + if (!EnumerateHeader(&iter, "content-length", &content_length_val)) + return -1; + + if (content_length_val.empty()) + return -1; + + // NOTE: We do not use StringToInt64 here since we want to know if + // parsing failed. + + char* end; + int64 result = _strtoi64(content_length_val.c_str(), &end, 10); + + if (result < 0) + return -1; + + if (end != content_length_val.c_str() + content_length_val.length()) + return -1; + + return result; +} + +} // namespace net diff --git a/net/http/http_response_headers.h b/net/http/http_response_headers.h new file mode 100644 index 0000000..91a5281 --- /dev/null +++ b/net/http/http_response_headers.h @@ -0,0 +1,295 @@ +// 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. + +#ifndef NET_HTTP_RESPONSE_HEADERS_H__ +#define NET_HTTP_RESPONSE_HEADERS_H__ + +#include <hash_set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/ref_counted.h" + +class Pickle; +class Time; +class TimeDelta; + +namespace net { + +// HttpResponseHeaders: parses and holds HTTP response headers. +class HttpResponseHeaders : + public base::RefCountedThreadSafe<HttpResponseHeaders> { + public: + // Parses the given raw_headers. raw_headers should be formatted thus: + // includes the http status response line, each line is \0-terminated, and + // it's terminated by an empty line (ie, 2 \0s in a row). + // + // NOTE: For now, raw_headers is not really 'raw' in that this constructor is + // called with a 'NativeMB' string on Windows because WinHTTP does not allow + // us to access the raw byte sequence as sent by a web server. In any case, + // HttpResponseHeaders does not perform any encoding changes on the input. + // + explicit HttpResponseHeaders(const std::string& raw_headers); + + // Initializes from the representation stored in the given pickle. The data + // for this object is found relative to the given pickle_iter, which should + // be passed to the pickle's various Read* methods. + HttpResponseHeaders(const Pickle& pickle, void** pickle_iter); + + // Appends a representation of this object to the given pickle. If the + // for_cache argument is true, then non-cacheable headers will be pruned from + // the persisted version of the response headers. + void Persist(Pickle* pickle, bool for_cache); + + // Performs header merging as described in 13.5.3 of RFC 2616. + void Update(const HttpResponseHeaders& new_headers); + + // Creates a normalized header string. The output will be formatted exactly + // like so: + // HTTP/<version> <status_code> <status_text>\n + // [<header-name>: <header-values>\n]* + // meaning, each line is \n-terminated, and there is no extra whitespace + // beyond the single space separators shown (of course, values can contain + // whitespace within them). If a given header-name appears more than once + // in the set of headers, they are combined into a single line like so: + // <header-name>: <header-value1>, <header-value2>, ...<header-valueN>\n + // + // DANGER: For some headers (e.g., "Set-Cookie"), the normalized form can be + // a lossy format. This is due to the fact that some servers generate + // Set-Cookie headers that contain unquoted commas (usually as part of the + // value of an "expires" attribute). So, use this function with caution. Do + // not expect to be able to re-parse Set-Cookie headers from this output. + // + // NOTE: Do not make any assumptions about the encoding of this output + // string. It may be non-ASCII, and the encoding used by the server is not + // necessarily known to us. Do not assume that this output is UTF-8! + // + // TODO(darin): remove this method + // + void GetNormalizedHeaders(std::string* output) const; + + // Fetch the "normalized" value of a single header, where all values for the + // header name are separated by commas. See the GetNormalizedHeaders for + // format details. Returns false if this header wasn't found. + // + // NOTE: Do not make any assumptions about the encoding of this output + // string. It may be non-ASCII, and the encoding used by the server is not + // necessarily known to us. Do not assume that this output is UTF-8! + // + // TODO(darin): remove this method + // + bool GetNormalizedHeader(const std::string& name, std::string* value) const; + + // Returns the normalized status line. For HTTP/0.9 responses (i.e., + // responses that lack a status line), this is the manufactured string + // "HTTP/0.9 200 OK". + std::string GetStatusLine() const; + + // Enumerate the "lines" of the response headers. This skips over the status + // line. Use GetStatusLine if you are interested in that. Note that this + // method returns the un-coalesced response header lines, so if a response + // header appears on multiple lines, then it will appear multiple times in + // this enumeration (in the order the header lines were received from the + // server). Initialize a 'void*' variable to NULL and pass it by address to + // EnumerateHeaderLines. Call EnumerateHeaderLines repeatedly until it + // returns false. The out-params 'name' and 'value' are set upon success. + bool EnumerateHeaderLines(void** iter, + std::string* name, + std::string* value) const; + + // Enumerate the values of the specified header. If you are only interested + // in the first header, then you can pass NULL for the 'iter' parameter. + // Otherwise, to iterate across all values for the specified header, + // initialize a 'void*' variable to NULL and pass it by address to + // EnumerateHeader. Call EnumerateHeader repeatedly until it returns false. + bool EnumerateHeader(void** iter, + const std::string& name, + std::string* value) const; + + // Returns true if the response contains the specified header-value pair. + // Both name and value are compared case insensitively. + bool HasHeaderValue(const std::string& name, const std::string& value) const; + + // Get the mime type and charset values in lower case form from the headers. + // Empty strings are returned if the values are not present. + void GetMimeTypeAndCharset(std::string* mime_type, + std::string* charset) const; + + // Get the mime type in lower case from the headers. If there's no mime + // type, returns false. + bool GetMimeType(std::string* mime_type) const; + + // Get the charset in lower case from the headers. If there's no charset, + // returns false. + bool GetCharset(std::string* charset) const; + + // Returns true if this response corresponds to a redirect. The target + // location of the redirect is optionally returned if location is non-null. + bool IsRedirect(std::string* location) const; + + // Returns true if the response cannot be reused without validation. The + // result is relative to the current_time parameter, which is a parameter to + // support unit testing. The request_time parameter indicates the time at + // which the request was made that resulted in this response, which was + // received at response_time. + bool RequiresValidation(const Time& request_time, + const Time& response_time, + const Time& current_time) const; + + // Returns the amount of time the server claims the response is fresh from + // the time the response was generated. See section 13.2.4 of RFC 2616. See + // RequiresValidation for a description of the response_time parameter. + TimeDelta GetFreshnessLifetime(const Time& response_time) const; + + // Returns the age of the response. See section 13.2.3 of RFC 2616. + // See RequiresValidation for a description of this method's parameters. + TimeDelta GetCurrentAge(const Time& request_time, + const Time& response_time, + const Time& current_time) const; + + // The following methods extract values from the response headers. If a + // value is not present, then false is returned. Otherwise, true is returned + // and the out param is assigned to the corresponding value. + bool GetMaxAgeValue(TimeDelta* value) const; + bool GetAgeValue(TimeDelta* value) const; + bool GetDateValue(Time* value) const; + bool GetLastModifiedValue(Time* value) const; + bool GetExpiresValue(Time* value) const; + + // Extracts the time value of a particular header. This method looks for the + // first matching header value and parses its value as a HTTP-date. + bool GetTimeValuedHeader(const std::string& name, Time* result) const; + + // Determines if this response indicates a keep-alive connection. + bool IsKeepAlive() const; + + // Extracts the value of the Content-Length header or returns -1 if there is + // no such header in the response. + int64 GetContentLength() const; + + // Returns the HTTP response code. This is 0 if the response code text seems + // to exist but could not be parsed. Otherwise, it defaults to 200 if the + // response code is not found in the raw headers. + int response_code() const { return response_code_; } + + // Returns the raw header string. + const std::string& raw_headers() const { return raw_headers_; } + + private: + friend RefCountedThreadSafe<HttpResponseHeaders>; + + HttpResponseHeaders() {} + ~HttpResponseHeaders() {} + + // Initializes from the given raw headers. + void Parse(const std::string& raw_input); + + // Helper function for ParseStatusLine. + // Tries to extract the "HTTP/X.Y" from a status line formatted like: + // HTTP/1.1 200 OK + // with line_begin and end pointing at the begin and end of this line. If the + // status line is malformed, we'll guess a version number. + // Output will be a normalized version of this, with a trailing \n. + void ParseVersion(std::string::const_iterator line_begin, + std::string::const_iterator line_end); + + // Tries to extract the status line from a header block, given the first + // line of said header block. If the status line is malformed, we'll construct + // a valid one. Example input: + // HTTP/1.1 200 OK + // with line_begin and end pointing at the begin and end of this line. + // Output will be a normalized version of this, with a trailing \n. + void ParseStatusLine(std::string::const_iterator line_begin, + std::string::const_iterator line_end); + + // Tries to extract the header line from a header block, given a single + // line of said header block. If the header is malformed, we skip it. + // Example input: + // Content-Length : text/html; charset=utf-8 + void ParseHeaderLine(std::string::const_iterator line_begin, + std::string::const_iterator line_end); + + // Find the header in our list (case-insensitive) starting with parsed_ at + // index |from|. Returns string::npos if not found. + size_t FindHeader(size_t from, const std::string& name) const; + + // Add a header->value pair to our list. If we already have header in our list, + // append the value to it. + void AddHeader(std::string::const_iterator name_begin, + std::string::const_iterator name_end, + std::string::const_iterator value_begin, + std::string::const_iterator value_end); + + // Add to parsed_ given the fields of a ParsedHeader object. + void AddToParsed(std::string::const_iterator name_begin, + std::string::const_iterator name_end, + std::string::const_iterator value_begin, + std::string::const_iterator value_end); + + typedef stdext::hash_set<std::string> HeaderSet; + + // Returns the values from any 'cache-control: no-cache="foo,bar"' headers as + // well as other known-to-be-transient header names. The header names are + // all lowercase to support fast lookup. + void GetTransientHeaders(HeaderSet* header_names) const; + + // The members of this structure point into raw_headers_. + struct ParsedHeader { + std::string::const_iterator name_begin; + std::string::const_iterator name_end; + std::string::const_iterator value_begin; + std::string::const_iterator value_end; + + // A header "continuation" contains only a subsequent value for the + // preceding header. (Header values are comma separated.) + bool is_continuation() const { return name_begin == name_end; } + }; + typedef std::vector<ParsedHeader> HeaderList; + + // We keep a list of ParsedHeader objects. These tell us where to locate the + // header-value pairs within raw_headers_. + HeaderList parsed_; + + // The raw_headers_ consists of the normalized status line (terminated with a + // null byte) and then followed by the raw null-terminated headers from the + // input that was passed to our constructor. We preserve the input to + // maintain as much ancillary fidelity as possible (since it is sometimes + // hard to tell what may matter down-stream to a consumer of XMLHttpRequest). + std::string raw_headers_; + + // This is the parsed HTTP response code. + int response_code_; + + DISALLOW_EVIL_CONSTRUCTORS(HttpResponseHeaders); +}; + +} // namespace net + +#endif // NET_HTTP_RESPONSE_HEADERS_H__ diff --git a/net/http/http_response_headers_unittest.cc b/net/http/http_response_headers_unittest.cc new file mode 100644 index 0000000..41b7c563 --- /dev/null +++ b/net/http/http_response_headers_unittest.cc @@ -0,0 +1,965 @@ +// 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 <algorithm> + +#include "base/basictypes.h" +#include "base/pickle.h" +#include "base/time.h" +#include "net/base/net_util.h" +#include "net/http/http_response_headers.h" +#include "testing/gtest/include/gtest/gtest.h" + +using namespace std; +using net::HttpResponseHeaders; + +namespace { + +struct TestData { + const char* raw_headers; + const char* expected_headers; + int expected_response_code; +}; + +struct ContentTypeTestData { + const string raw_headers; + const string mime_type; + const bool has_mimetype; + const string charset; + const bool has_charset; + const string all_content_type; +}; + +class HttpResponseHeadersTest : public testing::Test { +}; + +// Transform "normal"-looking headers (\n-separated) to the appropriate +// input format for ParseRawHeaders (\0-separated). +void HeadersToRaw(std::string* headers) { + replace(headers->begin(), headers->end(), '\n', '\0'); + if (!headers->empty()) + *headers += '\0'; +} + +void TestCommon(const TestData& test) { + string raw_headers(test.raw_headers); + HeadersToRaw(&raw_headers); + string expected_headers(test.expected_headers); + + string headers; + scoped_refptr<HttpResponseHeaders> parsed = + new HttpResponseHeaders(raw_headers); + parsed->GetNormalizedHeaders(&headers); + + // Transform to readable output format (so it's easier to see diffs). + replace(headers.begin(), headers.end(), ' ', '_'); + replace(headers.begin(), headers.end(), '\n', '\\'); + replace(expected_headers.begin(), expected_headers.end(), ' ', '_'); + replace(expected_headers.begin(), expected_headers.end(), '\n', '\\'); + + EXPECT_EQ(expected_headers, headers); + + EXPECT_EQ(test.expected_response_code, parsed->response_code()); +} + +} // end namespace + +// Check that we normalize headers properly. +TEST(HttpResponseHeadersTest, NormalizeHeadersWhitespace) { + TestData test = { + "HTTP/1.1 202 Accepted \n" + " Content-TYPE : text/html; charset=utf-8 \n" + "Set-Cookie: a \n" + "Set-Cookie: b \n", + + "HTTP/1.1 202 Accepted\n" + "Content-TYPE: text/html; charset=utf-8\n" + "Set-Cookie: a, b\n", + + 202 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, BlankHeaders) { + TestData test = { + "HTTP/1.1 200 OK\n" + "Header1 : \n" + "Header2: \n" + "Header3:\n" + "Header4\n" + "Header5 :\n", + + "HTTP/1.1 200 OK\n" + "Header1: \n" + "Header2: \n" + "Header3: \n" + "Header5: \n", + + 200 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, NormalizeHeadersVersion) { + TestData test = { + "hTtP/0.9 201\n" + "Content-TYPE: text/html; charset=utf-8\n", + + "HTTP/1.0 201 OK\n" + "Content-TYPE: text/html; charset=utf-8\n", + + 201 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, NormalizeHeadersMissingOK) { + TestData test = { + "HTTP/1.1 201\n" + "Content-TYPE: text/html; charset=utf-8\n", + + "HTTP/1.1 201 OK\n" + "Content-TYPE: text/html; charset=utf-8\n", + + 201 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, NormalizeHeadersBadStatus) { + TestData test = { + "SCREWED_UP_STATUS_LINE\n" + "Content-TYPE: text/html; charset=utf-8\n", + + "HTTP/1.0 200 OK\n" + "Content-TYPE: text/html; charset=utf-8\n", + + 200 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, NormalizeHeadersEmpty) { + TestData test = { + "", + + "HTTP/1.0 200 OK\n", + + 200 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColon) { + TestData test = { + "HTTP/1.1 202 Accepted \n" + "foo: bar\n" + ": a \n" + " : b\n" + "baz: blat \n", + + "HTTP/1.1 202 Accepted\n" + "foo: bar\n" + "baz: blat\n", + + 202 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColonAtEOL) { + TestData test = { + "HTTP/1.1 202 Accepted \n" + "foo: \n" + "bar:\n" + "baz: blat \n" + "zip:\n", + + "HTTP/1.1 202 Accepted\n" + "foo: \n" + "bar: \n" + "baz: blat\n" + "zip: \n", + + 202 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, NormalizeHeadersOfWhitepace) { + TestData test = { + "\n \n", + + "HTTP/1.0 200 OK\n", + + 200 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, RepeatedSetCookie) { + TestData test = { + "HTTP/1.1 200 OK\n" + "Set-Cookie: x=1\n" + "Set-Cookie: y=2\n", + + "HTTP/1.1 200 OK\n" + "Set-Cookie: x=1, y=2\n", + + 200 + }; + TestCommon(test); +} + +TEST(HttpResponseHeadersTest, GetNormalizedHeader) { + std::string headers = + "HTTP/1.1 200 OK\n" + "Cache-control: private\n" + "cache-Control: no-store\n"; + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers); + + std::string value; + EXPECT_TRUE(parsed->GetNormalizedHeader("cache-control", &value)); + EXPECT_EQ("private, no-store", value); +} + +TEST(HttpResponseHeadersTest, Persist) { + const struct { + const char* raw_headers; + const char* expected_headers; + } tests[] = { + { "HTTP/1.1 200 OK\n" + "Cache-control:private\n" + "cache-Control:no-store\n", + + "HTTP/1.1 200 OK\n" + "Cache-control: private, no-store\n" + }, + { "HTTP/1.1 200 OK\n" + "connection: keep-alive\n" + "server: blah\n", + + "HTTP/1.1 200 OK\n" + "server: blah\n" + }, + { "HTTP/1.1 200 OK\n" + "fOo: 1\n" + "Foo: 2\n" + "Transfer-Encoding: chunked\n" + "CoNnection: keep-alive\n" + "cache-control: private, no-cache=\"foo\"\n", + + "HTTP/1.1 200 OK\n" + "cache-control: private, no-cache=\"foo\"\n" + }, + { "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private,no-cache=\"foo, bar\"\n" + "bar", + + "HTTP/1.1 200 OK\n" + "Cache-Control: private,no-cache=\"foo, bar\"\n" + }, + // ignore bogus no-cache value + { "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private,no-cache=foo\n", + + "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private,no-cache=foo\n" + }, + // ignore bogus no-cache value + { "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\n", + + "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\n" + }, + // ignore empty no-cache value + { "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\"\"\n", + + "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\"\"\n" + }, + // ignore wrong quotes no-cache value + { "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\'foo\'\n", + + "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\'foo\'\n" + }, + // ignore unterminated quotes no-cache value + { "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\"foo\n", + + "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\"foo\n" + }, + // accept sloppy LWS + { "HTTP/1.1 200 OK\n" + "Foo: 2\n" + "Cache-Control: private, no-cache=\" foo\t, bar\"\n", + + "HTTP/1.1 200 OK\n" + "Cache-Control: private, no-cache=\" foo\t, bar\"\n" + }, + // header name appears twice, separated by another header + { "HTTP/1.1 200 OK\n" + "Foo: 1\n" + "Bar: 2\n" + "Foo: 3\n", + + "HTTP/1.1 200 OK\n" + "Foo: 1, 3\n" + "Bar: 2\n" + }, + // header name appears twice, separated by another header (type 2) + { "HTTP/1.1 200 OK\n" + "Foo: 1, 3\n" + "Bar: 2\n" + "Foo: 4\n", + + "HTTP/1.1 200 OK\n" + "Foo: 1, 3, 4\n" + "Bar: 2\n" + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + std::string headers = tests[i].raw_headers; + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed1 = + new HttpResponseHeaders(headers); + + Pickle pickle; + parsed1->Persist(&pickle, true); + + void* iter = NULL; + scoped_refptr<HttpResponseHeaders> parsed2 = + new HttpResponseHeaders(pickle, &iter); + + std::string h2; + parsed2->GetNormalizedHeaders(&h2); + EXPECT_EQ(string(tests[i].expected_headers), h2); + } +} + +TEST(HttpResponseHeadersTest, EnumerateHeader_Coalesced) { + // Ensure that commas in quoted strings are not regarded as value separators. + // Ensure that whitespace following a value is trimmed properly + std::string headers = + "HTTP/1.1 200 OK\n" + "Cache-control:private , no-cache=\"set-cookie,server\" \n" + "cache-Control: no-store\n"; + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers); + + void* iter = NULL; + std::string value; + EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value)); + EXPECT_EQ("private", value); + EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value)); + EXPECT_EQ("no-cache=\"set-cookie,server\"", value); + EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value)); + EXPECT_EQ("no-store", value); +} + +TEST(HttpResponseHeadersTest, EnumerateHeader_DateValued) { + // The comma in a date valued header should not be treated as a + // field-value separator + std::string headers = + "HTTP/1.1 200 OK\n" + "Date: Tue, 07 Aug 2007 23:10:55 GMT\n" + "Last-Modified: Wed, 01 Aug 2007 23:23:45 GMT\n"; + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers); + + std::string value; + EXPECT_TRUE(parsed->EnumerateHeader(NULL, "date", &value)); + EXPECT_EQ("Tue, 07 Aug 2007 23:10:55 GMT", value); + EXPECT_TRUE(parsed->EnumerateHeader(NULL, "last-modified", &value)); + EXPECT_EQ("Wed, 01 Aug 2007 23:23:45 GMT", value); +} + +TEST(HttpResponseHeadersTest, GetMimeType) { + const ContentTypeTestData tests[] = { + { "HTTP/1.1 200 OK\n" + "Content-type: text/html\n", + "text/html", true, + "", false, + "text/html" }, + // Multiple content-type headers should give us the last one. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html\n" + "Content-type: text/html\n", + "text/html", true, + "", false, + "text/html, text/html" }, + { "HTTP/1.1 200 OK\n" + "Content-type: text/plain\n" + "Content-type: text/html\n" + "Content-type: text/plain\n" + "Content-type: text/html\n", + "text/html", true, + "", false, + "text/plain, text/html, text/plain, text/html" }, + // Test charset parsing. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html\n" + "Content-type: text/html; charset=ISO-8859-1\n", + "text/html", true, + "iso-8859-1", true, + "text/html, text/html; charset=ISO-8859-1" }, + // Test charset in double quotes. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html\n" + "Content-type: text/html; charset=\"ISO-8859-1\"\n", + "text/html", true, + "iso-8859-1", true, + "text/html, text/html; charset=\"ISO-8859-1\"" }, + // If there are multiple matching content-type headers, we carry + // over the charset value. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html;charset=utf-8\n" + "Content-type: text/html\n", + "text/html", true, + "utf-8", true, + "text/html;charset=utf-8, text/html" }, + // Test single quotes. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html;charset='utf-8'\n" + "Content-type: text/html\n", + "text/html", true, + "utf-8", true, + "text/html;charset='utf-8', text/html" }, + // Last charset wins if matching content-type. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html;charset=utf-8\n" + "Content-type: text/html;charset=iso-8859-1\n", + "text/html", true, + "iso-8859-1", true, + "text/html;charset=utf-8, text/html;charset=iso-8859-1" }, + // Charset is ignored if the content types change. + { "HTTP/1.1 200 OK\n" + "Content-type: text/plain;charset=utf-8\n" + "Content-type: text/html\n", + "text/html", true, + "", false, + "text/plain;charset=utf-8, text/html" }, + // Empty content-type + { "HTTP/1.1 200 OK\n" + "Content-type: \n", + "", false, + "", false, + "" }, + // Emtpy charset + { "HTTP/1.1 200 OK\n" + "Content-type: text/html;charset=\n", + "text/html", true, + "", false, + "text/html;charset=" }, + // Multiple charsets, last one wins. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html;charset=utf-8; charset=iso-8859-1\n", + "text/html", true, + "iso-8859-1", true, + "text/html;charset=utf-8; charset=iso-8859-1" }, + // Multiple params. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html; foo=utf-8; charset=iso-8859-1\n", + "text/html", true, + "iso-8859-1", true, + "text/html; foo=utf-8; charset=iso-8859-1" }, + { "HTTP/1.1 200 OK\n" + "Content-type: text/html ; charset=utf-8 ; bar=iso-8859-1\n", + "text/html", true, + "utf-8", true, + "text/html ; charset=utf-8 ; bar=iso-8859-1" }, + // Comma embeded in quotes. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html ; charset='utf-8,text/plain' ;\n", + "text/html", true, + "utf-8,text/plain", true, + "text/html ; charset='utf-8,text/plain' ;" }, + // Charset with leading spaces. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html ; charset= 'utf-8' ;\n", + "text/html", true, + "utf-8", true, + "text/html ; charset= 'utf-8' ;" }, + // Media type comments in mime-type. + { "HTTP/1.1 200 OK\n" + "Content-type: text/html (html)\n", + "text/html", true, + "", false, + "text/html (html)" }, + // Incomplete charset= param + { "HTTP/1.1 200 OK\n" + "Content-type: text/html; char=\n", + "text/html", true, + "", false, + "text/html; char=" }, + // Invalid media type: no slash + { "HTTP/1.1 200 OK\n" + "Content-type: texthtml\n", + "", false, + "", false, + "texthtml" }, + // Invalid media type: */* + { "HTTP/1.1 200 OK\n" + "Content-type: */*\n", + "", false, + "", false, + "*/*" }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + string headers(tests[i].raw_headers); + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers); + + std::string value; + EXPECT_EQ(tests[i].has_mimetype, parsed->GetMimeType(&value)); + EXPECT_EQ(tests[i].mime_type, value); + value.clear(); + EXPECT_EQ(tests[i].has_charset, parsed->GetCharset(&value)); + EXPECT_EQ(tests[i].charset, value); + EXPECT_TRUE(parsed->GetNormalizedHeader("content-type", &value)); + EXPECT_EQ(tests[i].all_content_type, value); + } +} + +TEST(HttpResponseHeadersTest, RequiresValidation) { + const struct { + const char* headers; + bool requires_validation; + } tests[] = { + // no expiry info: expires immediately + { "HTTP/1.1 200 OK\n" + "\n", + true + }, + // valid for a little while + { "HTTP/1.1 200 OK\n" + "cache-control: max-age=10000\n" + "\n", + false + }, + // expires in the future + { "HTTP/1.1 200 OK\n" + "date: Wed, 28 Nov 2007 00:40:11 GMT\n" + "expires: Wed, 28 Nov 2007 01:00:00 GMT\n" + "\n", + false + }, + // expired already + { "HTTP/1.1 200 OK\n" + "date: Wed, 28 Nov 2007 00:40:11 GMT\n" + "expires: Wed, 28 Nov 2007 00:00:00 GMT\n" + "\n", + true + }, + // max-age trumps expires + { "HTTP/1.1 200 OK\n" + "date: Wed, 28 Nov 2007 00:40:11 GMT\n" + "expires: Wed, 28 Nov 2007 00:00:00 GMT\n" + "cache-control: max-age=10000\n" + "\n", + false + }, + // last-modified heuristic: modified a while ago + { "HTTP/1.1 200 OK\n" + "date: Wed, 28 Nov 2007 00:40:11 GMT\n" + "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n" + "\n", + false + }, + // last-modified heuristic: modified recently + { "HTTP/1.1 200 OK\n" + "date: Wed, 28 Nov 2007 00:40:11 GMT\n" + "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n" + "\n", + true + }, + // cached permanent redirect + { "HTTP/1.1 301 Moved Permanently\n" + "\n", + false + }, + // cached redirect: not reusable even though by default it would be + { "HTTP/1.1 300 Multiple Choices\n" + "Cache-Control: no-cache\n" + "\n", + true + }, + // cached forever by default + { "HTTP/1.1 410 Gone\n" + "\n", + false + }, + // cached temporary redirect: not reusable + { "HTTP/1.1 302 Found\n" + "\n", + true + }, + // cached temporary redirect: reusable + { "HTTP/1.1 302 Found\n" + "cache-control: max-age=10000\n" + "\n", + false + }, + // cache-control: max-age=N overrides expires: date in the past + { "HTTP/1.1 200 OK\n" + "date: Wed, 28 Nov 2007 00:40:11 GMT\n" + "expires: Wed, 28 Nov 2007 00:20:11 GMT\n" + "cache-control: max-age=10000\n" + "\n", + false + }, + // cache-control: no-store overrides expires: in the future + { "HTTP/1.1 200 OK\n" + "date: Wed, 28 Nov 2007 00:40:11 GMT\n" + "expires: Wed, 29 Nov 2007 00:40:11 GMT\n" + "cache-control: no-store,private,no-cache=\"foo\"\n" + "\n", + true + }, + // pragma: no-cache overrides last-modified heuristic + { "HTTP/1.1 200 OK\n" + "date: Wed, 28 Nov 2007 00:40:11 GMT\n" + "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n" + "pragma: no-cache\n" + "\n", + true + }, + // TODO(darin): add many many more tests here + }; + Time request_time, response_time, current_time; + Time::FromString(L"Wed, 28 Nov 2007 00:40:09 GMT", &request_time); + Time::FromString(L"Wed, 28 Nov 2007 00:40:12 GMT", &response_time); + Time::FromString(L"Wed, 28 Nov 2007 00:45:20 GMT", ¤t_time); + + for (size_t i = 0; i < arraysize(tests); ++i) { + string headers(tests[i].headers); + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers); + + bool requires_validation = + parsed->RequiresValidation(request_time, response_time, current_time); + EXPECT_EQ(tests[i].requires_validation, requires_validation); + } +} + +TEST(HttpResponseHeadersTest, Update) { + const struct { + const char* orig_headers; + const char* new_headers; + const char* expected_headers; + } tests[] = { + { "HTTP/1.1 200 OK\n", + + "HTTP/1/1 304 Not Modified\n" + "connection: keep-alive\n" + "Cache-control: max-age=10000\n", + + "HTTP/1.1 200 OK\n" + "Cache-control: max-age=10000\n" + }, + { "HTTP/1.1 200 OK\n" + "Foo: 1\n" + "Cache-control: private\n", + + "HTTP/1/1 304 Not Modified\n" + "connection: keep-alive\n" + "Cache-control: max-age=10000\n", + + "HTTP/1.1 200 OK\n" + "Cache-control: max-age=10000\n" + "Foo: 1\n" + }, + { "HTTP/1.1 200 OK\n" + "Foo: 1\n" + "Cache-control: private\n", + + "HTTP/1/1 304 Not Modified\n" + "connection: keep-alive\n" + "Cache-CONTROL: max-age=10000\n", + + "HTTP/1.1 200 OK\n" + "Cache-CONTROL: max-age=10000\n" + "Foo: 1\n" + }, + }; + + for (size_t i = 0; i < arraysize(tests); ++i) { + string orig_headers(tests[i].orig_headers); + HeadersToRaw(&orig_headers); + scoped_refptr<HttpResponseHeaders> parsed = + new HttpResponseHeaders(orig_headers); + + string new_headers(tests[i].new_headers); + HeadersToRaw(&new_headers); + scoped_refptr<HttpResponseHeaders> new_parsed = + new HttpResponseHeaders(new_headers); + + parsed->Update(*new_parsed); + + string resulting_headers; + parsed->GetNormalizedHeaders(&resulting_headers); + EXPECT_EQ(string(tests[i].expected_headers), resulting_headers); + } +} + +TEST(HttpResponseHeadersTest, EnumerateHeaderLines) { + const struct { + const char* headers; + const char* expected_lines; + } tests[] = { + { "HTTP/1.1 200 OK\n", + + "" + }, + { "HTTP/1.1 200 OK\n" + "Foo: 1\n", + + "Foo: 1\n" + }, + { "HTTP/1.1 200 OK\n" + "Foo: 1\n" + "Bar: 2\n" + "Foo: 3\n", + + "Foo: 1\nBar: 2\nFoo: 3\n" + }, + { "HTTP/1.1 200 OK\n" + "Foo: 1, 2, 3\n", + + "Foo: 1, 2, 3\n" + }, + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + string headers(tests[i].headers); + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = + new HttpResponseHeaders(headers); + + string name, value, lines; + + void* iter = NULL; + while (parsed->EnumerateHeaderLines(&iter, &name, &value)) { + lines.append(name); + lines.append(": "); + lines.append(value); + lines.append("\n"); + } + + EXPECT_EQ(string(tests[i].expected_lines), lines); + } +} + +TEST(HttpResponseHeadersTest, IsRedirect) { + const struct { + const char* headers; + const char* location; + bool is_redirect; + } tests[] = { + { "HTTP/1.1 200 OK\n", + "", + false + }, + { "HTTP/1.1 301 Moved\n" + "Location: http://foopy/\n", + "http://foopy/", + true + }, + { "HTTP/1.1 301 Moved\n" + "Location: \t \n", + "", + false + }, + // we use the first location header as the target of the redirect + { "HTTP/1.1 301 Moved\n" + "Location: http://foo/\n" + "Location: http://bar/\n", + "http://foo/", + true + }, + // we use the first _valid_ location header as the target of the redirect + { "HTTP/1.1 301 Moved\n" + "Location: \n" + "Location: http://bar/\n", + "http://bar/", + true + }, + // bug 1050541 (location header w/ an unescaped comma) + { "HTTP/1.1 301 Moved\n" + "Location: http://foo/bar,baz.html\n", + "http://foo/bar,baz.html", + true + }, + // bug 1224617 (location header w/ non-ASCII bytes) + { "HTTP/1.1 301 Moved\n" + "Location: http://foo/bar?key=\xE4\xF6\xFC\n", + "http://foo/bar?key=%E4%F6%FC", + true + }, + // Shift_JIS, Big5, and GBK contain multibyte characters with the trailing + // byte falling in the ASCII range. + { "HTTP/1.1 301 Moved\n" + "Location: http://foo/bar?key=\x81\x5E\xD8\xBF\n", + "http://foo/bar?key=%81^%D8%BF", + true + }, + { "HTTP/1.1 301 Moved\n" + "Location: http://foo/bar?key=\x82\x40\xBD\xC4\n", + "http://foo/bar?key=%82@%BD%C4", + true + }, + { "HTTP/1.1 301 Moved\n" + "Location: http://foo/bar?key=\x83\x5C\x82\x5D\xCB\xD7\n", + "http://foo/bar?key=%83\\%82]%CB%D7", + true + }, + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + string headers(tests[i].headers); + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = + new HttpResponseHeaders(headers); + + std::string location; + EXPECT_EQ(parsed->IsRedirect(&location), tests[i].is_redirect); + EXPECT_EQ(location, tests[i].location); + } +} + +TEST(HttpResponseHeadersTest, GetContentLength) { + const struct { + const char* headers; + int64 expected_len; + } tests[] = { + { "HTTP/1.1 200 OK\n", + -1 + }, + { "HTTP/1.1 200 OK\n" + "Content-Length: 10\n", + 10 + }, + { "HTTP/1.1 200 OK\n" + "Content-Length: \n", + -1 + }, + { "HTTP/1.1 200 OK\n" + "Content-Length: abc\n", + -1 + }, + { "HTTP/1.1 200 OK\n" + "Content-Length: -10\n", + -1 + }, + { "HTTP/1.1 200 OK\n" + "Content-Length: 23xb5\n", + -1 + }, + { "HTTP/1.1 200 OK\n" + "Content-Length: 0xA\n", + -1 + }, + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + string headers(tests[i].headers); + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = + new HttpResponseHeaders(headers); + + EXPECT_EQ(tests[i].expected_len, parsed->GetContentLength()); + } +} + +TEST(HttpResponseHeadersTest, IsKeepAlive) { + const struct { + const char* headers; + bool expected_keep_alive; + } tests[] = { + { "HTTP/1.1 200 OK\n", + true + }, + { "HTTP/1.0 200 OK\n", + false + }, + { "HTTP/1.0 200 OK\n" + "connection: close\n", + false + }, + { "HTTP/1.0 200 OK\n" + "connection: keep-alive\n", + true + }, + { "HTTP/1.0 200 OK\n" + "connection: kEeP-AliVe\n", + true + }, + { "HTTP/1.0 200 OK\n" + "connection: keep-aliveX\n", + false + }, + { "HTTP/1.1 200 OK\n" + "connection: close\n", + false + }, + { "HTTP/1.1 200 OK\n" + "connection: keep-alive\n", + true + }, + { "HTTP/1.1 200 OK\n" + "proxy-connection: close\n", + false + }, + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + string headers(tests[i].headers); + HeadersToRaw(&headers); + scoped_refptr<HttpResponseHeaders> parsed = + new HttpResponseHeaders(headers); + + EXPECT_EQ(tests[i].expected_keep_alive, parsed->IsKeepAlive()); + } +} diff --git a/net/http/http_response_info.h b/net/http/http_response_info.h new file mode 100644 index 0000000..7199cbd --- /dev/null +++ b/net/http/http_response_info.h @@ -0,0 +1,67 @@ +// 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. + +#ifndef NET_HTTP_HTTP_RESPONSE_INFO_H__ +#define NET_HTTP_HTTP_RESPONSE_INFO_H__ + +#include "base/time.h" +#include "net/base/auth.h" +#include "net/base/ssl_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_vary_data.h" + +namespace net { + +class HttpResponseInfo { + public: + // The time at which the request was made that resulted in this response. + // For cached responses, this time could be "far" in the past. + Time request_time; + + // The time at which the response headers were received. For cached + // responses, this time could be "far" in the past. + Time response_time; + + // If the response headers indicate a 401 or 407 failure, then this structure + // will contain additional information about the authentication challenge. + scoped_refptr<AuthChallengeInfo> auth_challenge; + + // The SSL connection info (if HTTPS). + SSLInfo ssl_info; + + // The parsed response headers and status line. + scoped_refptr<HttpResponseHeaders> headers; + + // The "Vary" header data for this response. + HttpVaryData vary_data; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_RESPONSE_INFO_H__ diff --git a/net/http/http_transaction.h b/net/http/http_transaction.h new file mode 100644 index 0000000..4a13f7c --- /dev/null +++ b/net/http/http_transaction.h @@ -0,0 +1,111 @@ +// 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. + +#ifndef NET_HTTP_HTTP_TRANSACTION_H_ +#define NET_HTTP_HTTP_TRANSACTION_H_ + +#include "net/base/completion_callback.h" +#include "net/base/load_states.h" + +namespace net { + +class HttpRequestInfo; +class HttpResponseInfo; + +// Represents a single HTTP transaction (i.e., a single request/response pair). +// HTTP redirects are not followed and authentication challenges are not +// answered. Cookies are assumed to be managed by the caller. +class HttpTransaction { + public: + // Stops any pending IO and destroys the transaction object. + virtual void Destroy() = 0; + + // Starts the HTTP transaction (i.e., sends the HTTP request). + // + // Returns OK if the transaction could be started synchronously, which means + // that the request was served from the cache. ERR_IO_PENDING is returned to + // indicate that the CompletionCallback will be notified once response info + // is available or if an IO error occurs. Any other return value indicates + // that the transaction could not be started. + // + // Regardless of the return value, the caller is expected to keep the + // request_info object alive until Destroy is called on the transaction. + // + // NOTE: The transaction is not responsible for deleting the callback object. + // + virtual int Start(const HttpRequestInfo* request_info, + CompletionCallback* callback) = 0; + + // Restarts the HTTP transaction, ignoring the last error. This call can + // only be made after a call to Start (or RestartIgnoringLastError) failed. + // Once Read has been called, this method cannot be called. This method is + // used, for example, to continue past various SSL related errors. + // + // Not all errors can be ignored using this method. See error code + // descriptions for details about errors that can be ignored. + // + // NOTE: The transaction is not responsible for deleting the callback object. + // + virtual int RestartIgnoringLastError(CompletionCallback* callback) = 0; + + // Restarts the HTTP transaction with authentication credentials. + virtual int RestartWithAuth(const std::wstring& username, + const std::wstring& password, + CompletionCallback* callback) = 0; + + // Once response info is available for the transaction, response data may be + // read by calling this method. + // + // Response data is copied into the given buffer and the number of bytes + // copied is returned. ERR_IO_PENDING is returned if response data is not + // yet available. The CompletionCallback is notified when the data copy + // completes, and it is passed the number of bytes that were successfully + // copied. Or, if a read error occurs, the CompletionCallback is notified of + // the error. Any other negative return value indicates that the transaction + // could not be read. + // + // NOTE: The transaction is not responsible for deleting the callback object. + // + virtual int Read(char* buf, int buf_len, CompletionCallback* callback) = 0; + + // Returns the response info for this transaction or NULL if the response + // info is not available. + virtual const HttpResponseInfo* GetResponseInfo() const = 0; + + // Returns the load state for this transaction. + virtual LoadState GetLoadState() const = 0; + + // Returns the upload progress in bytes. If there is no upload data, + // zero will be returned. This does not include the request headers. + virtual uint64 GetUploadProgress() const = 0; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_TRANSACTION_H_ diff --git a/net/http/http_transaction_factory.h b/net/http/http_transaction_factory.h new file mode 100644 index 0000000..013a48d --- /dev/null +++ b/net/http/http_transaction_factory.h @@ -0,0 +1,61 @@ +// 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. + +#ifndef NET_HTTP_HTTP_TRANSACTION_FACTORY_H__ +#define NET_HTTP_HTTP_TRANSACTION_FACTORY_H__ + +class AuthCache; + +namespace net { + +class HttpCache; +class HttpTransaction; + +// An interface to a class that can create HttpTransaction objects. +class HttpTransactionFactory { + public: + virtual ~HttpTransactionFactory() {} + + // Creates a HttpTransaction object. + virtual HttpTransaction* CreateTransaction() = 0; + + // Returns the associated cache if any (may be NULL). + virtual HttpCache* GetCache() = 0; + + // Returns the associated HTTP auth cache if any (may be NULL). + virtual AuthCache* GetAuthCache() = 0; + + // Suspends the creation of new transactions. If |suspend| is false, creation + // of new transactions is resumed. + virtual void Suspend(bool suspend) = 0; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_TRANSACTION_FACTORY_H__ diff --git a/net/http/http_transaction_unittest.cc b/net/http/http_transaction_unittest.cc new file mode 100644 index 0000000..bfa2a79 --- /dev/null +++ b/net/http/http_transaction_unittest.cc @@ -0,0 +1,183 @@ +// 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_unittest.h" + +#include <windows.h> + +#include <hash_map> + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "net/base/load_flags.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_cache.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" +#include "net/http/http_transaction.h" +#include "testing/gtest/include/gtest/gtest.h" + +#pragma warning(disable: 4355) + +//----------------------------------------------------------------------------- +// mock transaction data + +const MockTransaction kSimpleGET_Transaction = { + "http://www.google.com/", + "GET", + "", + net::LOAD_NORMAL, + "HTTP/1.1 200 OK", + "Cache-Control: max-age=10000\n", + "<html><body>Google Blah Blah</body></html>", + TEST_MODE_NORMAL, + NULL, + 0 +}; + +const MockTransaction kSimplePOST_Transaction = { + "http://bugdatabase.com/edit", + "POST", + "", + net::LOAD_NORMAL, + "HTTP/1.1 200 OK", + "", + "<html><body>Google Blah Blah</body></html>", + TEST_MODE_NORMAL, + NULL, + 0 +}; + +const MockTransaction kTypicalGET_Transaction = { + "http://www.example.com/~foo/bar.html", + "GET", + "", + net::LOAD_NORMAL, + "HTTP/1.1 200 OK", + "Date: Wed, 28 Nov 2007 09:40:09 GMT\n" + "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n", + "<html><body>Google Blah Blah</body></html>", + TEST_MODE_NORMAL, + NULL, + 0 +}; + +const MockTransaction kETagGET_Transaction = { + "http://www.google.com/foopy", + "GET", + "", + net::LOAD_NORMAL, + "HTTP/1.1 200 OK", + "Cache-Control: max-age=10000\n" + "Etag: foopy\n", + "<html><body>Google Blah Blah</body></html>", + TEST_MODE_NORMAL, + NULL, + 0 +}; + +const MockTransaction kRangeGET_Transaction = { + "http://www.google.com/", + "GET", + "Range: 0-100\r\n", + net::LOAD_NORMAL, + "HTTP/1.1 200 OK", + "Cache-Control: max-age=10000\n", + "<html><body>Google Blah Blah</body></html>", + TEST_MODE_NORMAL, + NULL, + 0 +}; + +static const MockTransaction* const kBuiltinMockTransactions[] = { + &kSimpleGET_Transaction, + &kSimplePOST_Transaction, + &kTypicalGET_Transaction, + &kETagGET_Transaction, + &kRangeGET_Transaction +}; + +typedef stdext::hash_map<std::string, const MockTransaction*> + MockTransactionMap; +static MockTransactionMap mock_transactions; + +void AddMockTransaction(const MockTransaction* trans) { + mock_transactions[GURL(trans->url).spec()] = trans; +} + +void RemoveMockTransaction(const MockTransaction* trans) { + mock_transactions.erase(GURL(trans->url).spec()); +} + +const MockTransaction* FindMockTransaction(const GURL& url) { + // look for overrides: + MockTransactionMap::const_iterator it = mock_transactions.find(url.spec()); + if (it != mock_transactions.end()) + return it->second; + + // look for builtins: + for (int i = 0; i < arraysize(kBuiltinMockTransactions); ++i) { + if (url == GURL(kBuiltinMockTransactions[i]->url)) + return kBuiltinMockTransactions[i]; + } + return NULL; +} + + +//----------------------------------------------------------------------------- + +// static +int TestTransactionConsumer::quit_counter_ = 0; + + +//----------------------------------------------------------------------------- +// helpers + +int ReadTransaction(net::HttpTransaction* trans, std::string* result) { + int rv; + + TestCompletionCallback callback; + + std::string content; + do { + char buf[256]; + rv = trans->Read(buf, sizeof(buf), &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + if (rv > 0) { + content.append(buf, rv); + } else if (rv < 0) { + return rv; + } + } while (rv > 0); + + result->swap(content); + return net::OK; +} diff --git a/net/http/http_transaction_unittest.h b/net/http/http_transaction_unittest.h new file mode 100644 index 0000000..32f8a5a --- /dev/null +++ b/net/http/http_transaction_unittest.h @@ -0,0 +1,346 @@ +// 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. + +#ifndef NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_ +#define NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_ + +#include "net/http/http_transaction.h" + +#include <windows.h> + +#include <string> + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "net/base/load_flags.h" +#include "net/base/test_completion_callback.h" +#include "net/disk_cache/disk_cache.h" +#include "net/http/http_cache.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" + +#pragma warning(disable: 4355) + +//----------------------------------------------------------------------------- +// mock transaction data + +// these flags may be combined to form the test_mode field +enum { + TEST_MODE_NORMAL = 0, + TEST_MODE_SYNC_NET_START = 1 << 0, + TEST_MODE_SYNC_NET_READ = 1 << 1, + TEST_MODE_SYNC_CACHE_START = 1 << 2, + TEST_MODE_SYNC_CACHE_READ = 1 << 3, +}; + +typedef void (*MockTransactionHandler)(const net::HttpRequestInfo* request, + std::string* response_status, + std::string* response_headers, + std::string* response_data); + +struct MockTransaction { + const char* url; + const char* method; + const char* request_headers; + int load_flags; + const char* status; + const char* response_headers; + const char* data; + int test_mode; + MockTransactionHandler handler; + int cert_status; +}; + +extern const MockTransaction kSimpleGET_Transaction; +extern const MockTransaction kSimplePOST_Transaction; +extern const MockTransaction kTypicalGET_Transaction; +extern const MockTransaction kETagGET_Transaction; +extern const MockTransaction kRangeGET_Transaction; + +// returns the mock transaction for the given URL +const MockTransaction* FindMockTransaction(const GURL& url); + +// Add/Remove a mock transaction that can be accessed via FindMockTransaction. +// There can be only one MockTransaction associated with a given URL. +void AddMockTransaction(const MockTransaction* trans); +void RemoveMockTransaction(const MockTransaction* trans); + +struct ScopedMockTransaction : MockTransaction { + ScopedMockTransaction() { + AddMockTransaction(this); + } + explicit ScopedMockTransaction(const MockTransaction& t) + : MockTransaction(t) { + AddMockTransaction(this); + } + ~ScopedMockTransaction() { + RemoveMockTransaction(this); + } +}; + +//----------------------------------------------------------------------------- +// mock http request + +class MockHttpRequest : public net::HttpRequestInfo { + public: + explicit MockHttpRequest(const MockTransaction& t) { + url = GURL(t.url); + method = t.method; + extra_headers = t.request_headers; + load_flags = t.load_flags; + } +}; + +//----------------------------------------------------------------------------- +// use this class to test completely consuming a transaction + +class TestTransactionConsumer : public CallbackRunner< Tuple1<int> > { + public: + explicit TestTransactionConsumer(net::HttpTransactionFactory* factory) + : trans_(factory->CreateTransaction()), + state_(IDLE), + error_(net::OK) { + ++quit_counter_; + } + + ~TestTransactionConsumer() { + trans_->Destroy(); + } + + void Start(const net::HttpRequestInfo* request) { + state_ = STARTING; + int result = trans_->Start(request, this); + if (result != net::ERR_IO_PENDING) + DidStart(result); + } + + bool is_done() const { return state_ == DONE; } + int error() const { return error_; } + + const net::HttpResponseInfo* response_info() const { + return trans_->GetResponseInfo(); + } + const std::string& content() const { return content_; } + + private: + // Callback implementation: + virtual void RunWithParams(const Tuple1<int>& params) { + int result = params.a; + switch (state_) { + case STARTING: + DidStart(result); + break; + case READING: + DidRead(result); + break; + default: + NOTREACHED(); + } + } + + void DidStart(int result) { + if (result != net::OK) { + DidFinish(result); + } else { + Read(); + } + } + + void DidRead(int result) { + if (result <= 0) { + DidFinish(result); + } else { + content_.append(read_buf_, result); + Read(); + } + } + + void DidFinish(int result) { + state_ = DONE; + error_ = result; + if (--quit_counter_ == 0) + MessageLoop::current()->Quit(); + } + + void Read() { + state_ = READING; + int result = trans_->Read(read_buf_, sizeof(read_buf_), this); + if (result != net::ERR_IO_PENDING) + DidRead(result); + } + + enum State { + IDLE, + STARTING, + READING, + DONE + } state_; + + net::HttpTransaction* trans_; + std::string content_; + char read_buf_[1024]; + int error_; + + static int quit_counter_; +}; + +//----------------------------------------------------------------------------- +// mock network layer + +// This transaction class inspects the available set of mock transactions to +// find data for the request URL. It supports IO operations that complete +// synchronously or asynchronously to help exercise different code paths in the +// HttpCache implementation. +class MockNetworkTransaction : public net::HttpTransaction { + public: + MockNetworkTransaction() : task_factory_(this), data_cursor_(0) { + } + + virtual void Destroy() { + delete this; + } + + virtual int Start(const net::HttpRequestInfo* request, + net::CompletionCallback* callback) { + const MockTransaction* t = FindMockTransaction(request->url); + if (!t) + return net::ERR_FAILED; + + std::string resp_status = t->status; + std::string resp_headers = t->response_headers; + std::string resp_data = t->data; + if (t->handler) + (t->handler)(request, &resp_status, &resp_headers, &resp_data); + + std::string header_data = + StringPrintf("%s\n%s\n", resp_status.c_str(), resp_headers.c_str()); + std::replace(header_data.begin(), header_data.end(), '\n', '\0'); + + response_.request_time = Time::Now(); + response_.response_time = Time::Now(); + response_.headers = new net::HttpResponseHeaders(header_data); + response_.ssl_info.cert_status = t->cert_status; + data_ = resp_data; + test_mode_ = t->test_mode; + + if (test_mode_ & TEST_MODE_SYNC_NET_START) + return net::OK; + + CallbackLater(callback, net::OK); + return net::ERR_IO_PENDING; + } + + virtual int RestartIgnoringLastError(net::CompletionCallback* callback) { + return net::ERR_FAILED; + } + + virtual int RestartWithAuth(const std::wstring& username, + const std::wstring& password, + net::CompletionCallback* callback) { + return net::ERR_FAILED; + } + + virtual int Read(char* buf, int buf_len, net::CompletionCallback* callback) { + int data_len = static_cast<int>(data_.size()); + int num = std::min(buf_len, data_len - data_cursor_); + if (num) { + memcpy(buf, data_.data() + data_cursor_, num); + data_cursor_ += num; + } + if (test_mode_ & TEST_MODE_SYNC_NET_READ) + return num; + + CallbackLater(callback, num); + return net::ERR_IO_PENDING; + } + + virtual const net::HttpResponseInfo* GetResponseInfo() const { + return &response_; + } + + virtual net::LoadState GetLoadState() const { + NOTREACHED() << "define some mock state transitions"; + return net::LOAD_STATE_IDLE; + } + + virtual uint64 GetUploadProgress() const { + return 0; + } + + private: + void CallbackLater(net::CompletionCallback* callback, int result) { + MessageLoop::current()->PostTask(FROM_HERE, task_factory_.NewRunnableMethod( + &MockNetworkTransaction::RunCallback, callback, result)); + } + void RunCallback(net::CompletionCallback* callback, int result) { + callback->Run(result); + } + + ScopedRunnableMethodFactory<MockNetworkTransaction> task_factory_; + net::HttpResponseInfo response_; + std::string data_; + int data_cursor_; + int test_mode_; +}; + +class MockNetworkLayer : public net::HttpTransactionFactory { + public: + MockNetworkLayer() : transaction_count_(0) { + } + + virtual net::HttpTransaction* CreateTransaction() { + transaction_count_++; + return new MockNetworkTransaction(); + } + + virtual net::HttpCache* GetCache() { + return NULL; + } + + virtual AuthCache* GetAuthCache() { + return NULL; + } + + virtual void Suspend(bool suspend) {} + + int transaction_count() const { return transaction_count_; } + + private: + int transaction_count_; +}; + + +//----------------------------------------------------------------------------- +// helpers + +// read the transaction completely +int ReadTransaction(net::HttpTransaction* trans, std::string* result); + +#endif // NET_HTTP_HTTP_TRANSACTION_UNITTEST_H_ 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 diff --git a/net/http/http_transaction_winhttp.h b/net/http/http_transaction_winhttp.h new file mode 100644 index 0000000..bb8ec1f --- /dev/null +++ b/net/http/http_transaction_winhttp.h @@ -0,0 +1,222 @@ +// 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. + +#ifndef NET_HTTP_HTTP_TRANSACTION_WINHTTP_H__ +#define NET_HTTP_HTTP_TRANSACTION_WINHTTP_H__ + +#include <windows.h> +#include <winhttp.h> + +#include <string> + +#include "base/ref_counted.h" +#include "net/base/completion_callback.h" +#include "net/http/http_proxy_service.h" +#include "net/http/http_response_info.h" +#include "net/http/http_transaction.h" +#include "net/http/http_transaction_factory.h" + +namespace net { + +class UploadDataStream; + +class HttpTransactionWinHttp : public HttpTransaction { + class Session; // Represents a WinHttp session handle. + class SessionCallback; + public: + // Instantiate this class, and use it to create HttpTransaction objects. + class Factory : public HttpTransactionFactory { + public: + Factory() : session_(NULL), proxy_info_(NULL), is_suspended_(false) {} + explicit Factory(const HttpProxyInfo* info) + : session_(NULL), proxy_info_(NULL), is_suspended_(false) { + if (info) { + proxy_info_.reset(new HttpProxyInfo()); + proxy_info_->Use(*info); + } + } + ~Factory(); + + virtual HttpTransaction* CreateTransaction(); + virtual HttpCache* GetCache(); + virtual AuthCache* GetAuthCache(); + virtual void Suspend(bool suspend); + + private: + Session* session_; + scoped_ptr<HttpProxyInfo> proxy_info_; + bool is_suspended_; + DISALLOW_EVIL_CONSTRUCTORS(Factory); + }; + + // HttpTransaction methods: + virtual void Destroy(); + virtual int Start(const HttpRequestInfo*, CompletionCallback*); + virtual int RestartIgnoringLastError(CompletionCallback*); + virtual int RestartWithAuth(const std::wstring&, + const std::wstring&, + CompletionCallback*); + virtual int Read(char*, int, CompletionCallback*); + virtual const HttpResponseInfo* GetResponseInfo() const; + virtual LoadState GetLoadState() const; + virtual uint64 GetUploadProgress() const; + + static void CALLBACK StatusCallback(HINTERNET handle, + DWORD_PTR context, + DWORD status, + LPVOID status_info, + DWORD status_info_len); + + // Called via the message loop in response to a WinHttp status callback. + void HandleStatusCallback(DWORD status, + DWORD_PTR result, + DWORD error, + DWORD secure_failure); + + private: + friend class Factory; + + // Methods ------------------------------------------------------------------ + + HttpTransactionWinHttp(Session* session, const HttpProxyInfo* info); + ~HttpTransactionWinHttp(); + + void DoCallback(int rv); + int ResolveProxy(); + bool OpenRequest(); + int SendRequest(); + bool ReopenRequest(); + int Restart(CompletionCallback* callback); + int DidResolveProxy(); + int DidReceiveError(DWORD error, DWORD secure_failure); + int DidSendRequest(); + int DidWriteData(DWORD num_bytes); + int DidReadData(DWORD num_bytes); + int DidReceiveHeaders(); + + void PopulateAuthChallenge(); + void ApplyAuth(); + + std::string GetRequestHeaders() const; + X509Certificate* GetServerCertificate() const; + int GetSecurityBits() const; + void PopulateSSLInfo(DWORD secure_failure); + + void OnProxyInfoAvailable(int result); + + // Variables ---------------------------------------------------------------- + + Session* session_; + const HttpRequestInfo* request_; + + // A copy of request_->load_flags that we can modify in + // RestartIgnoringLastError. + int load_flags_; + + // Optional auth data for proxy and origin server. + scoped_refptr<AuthData> proxy_auth_; + scoped_refptr<AuthData> server_auth_; + + // The key for looking up the auth data in the auth cache, consisting + // of the scheme, host, and port of the request URL and the realm in + // the auth challenge. + std::string proxy_auth_cache_key_; + std::string server_auth_cache_key_; + + // The peer of the connection. For a direct connection, this is the + // destination server. If we use a proxy, this is the proxy. + std::string connect_peer_; + + // The last error from SendRequest that occurred. Used by + // RestartIgnoringLastError to adjust load_flags_ to ignore this error. + DWORD last_error_; + + // This value is non-negative when we are streaming a response over a + // non-keepalive connection. We decrement this value as we receive data to + // allow us to discover end-of-file. This is used to workaround a bug in + // WinHttp (see bug 1063336). + int64 content_length_remaining_; + + HttpProxyInfo proxy_info_; + HttpProxyService::PacRequest* pac_request_; + CompletionCallbackImpl<HttpTransactionWinHttp> proxy_callback_; + + HttpResponseInfo response_; + CompletionCallback* callback_; + HINTERNET connect_handle_; + HINTERNET request_handle_; + scoped_refptr<SessionCallback> session_callback_; + scoped_ptr<UploadDataStream> upload_stream_; + uint64 upload_progress_; + + // True if the URL's scheme is https. + bool is_https_; + + // True if the SSL server doesn't support TLS but also cannot correctly + // negotiate with a TLS-enabled client to use SSL 3.0. The workaround is + // for the client to downgrade to SSL 3.0 and retry the SSL handshake. + bool is_tls_intolerant_; + + // True if revocation checking of the SSL server certificate is enabled. + bool rev_checking_enabled_; + + // A flag to indicate whether or not we already have proxy information. + // If false, we will attempt to resolve proxy information from the proxy + // service. This flag is set to true if proxy information is supplied by + // a client. + bool have_proxy_info_; + + // If WinHTTP is still using our caller's data (upload data or read buffer), + // we need to wait for the HANDLE_CLOSING status notification after we close + // the request handle. + // + // There are only five WinHTTP functions that work asynchronously (listed in + // the order in which they're called): + // WinHttpSendRequest, WinHttpWriteData, WinHttpReceiveResponse, + // WinHttpQueryDataAvailable, WinHttpReadData. + // WinHTTP is using our caller's data during the two time intervals: + // - From the first WinHttpWriteData call to the completion of the last + // WinHttpWriteData call. (We may call WinHttpWriteData multiple times.) + // - From the WinHttpReadData call to its completion. + // We set need_to_wait_for_handle_closing_ to true at the beginning of these + // time intervals and set it to false at the end. We're not sandwiching the + // intervals as tightly as possible. (To do that, we'd need to give WinHTTP + // worker threads access to the need_to_wait_for_handle_closing_ flag and + // worry about thread synchronization issues.) + bool need_to_wait_for_handle_closing_; + + // True if we have called WinHttpRequestThrottle::SubmitRequest. + bool request_submitted_; + + DISALLOW_EVIL_CONSTRUCTORS(HttpTransactionWinHttp); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_TRANSACTION_WINHTTP_H__ diff --git a/net/http/http_transaction_winhttp_unittest.cc b/net/http/http_transaction_winhttp_unittest.cc new file mode 100644 index 0000000..cbb07f9 --- /dev/null +++ b/net/http/http_transaction_winhttp_unittest.cc @@ -0,0 +1,80 @@ +// 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 "net/http/http_transaction_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(HttpTransactionWinHttp, CreateAndDestroy) { + net::HttpTransactionWinHttp::Factory factory; + + net::HttpTransaction* trans = factory.CreateTransaction(); + trans->Destroy(); +} + +TEST(HttpTransactionWinHttp, Suspend) { + net::HttpTransactionWinHttp::Factory factory; + + net::HttpTransaction* trans = factory.CreateTransaction(); + trans->Destroy(); + + factory.Suspend(true); + + trans = factory.CreateTransaction(); + ASSERT_TRUE(trans == NULL); + + factory.Suspend(false); + + trans = factory.CreateTransaction(); + trans->Destroy(); +} + +TEST(HttpTransactionWinHttp, GoogleGET) { + net::HttpTransactionWinHttp::Factory factory; + TestCompletionCallback callback; + + net::HttpTransaction* trans = factory.CreateTransaction(); + + net::HttpRequestInfo request_info; + request_info.url = GURL("http://www.google.com/"); + request_info.method = "GET"; + request_info.user_agent = "Foo/1.0"; + request_info.load_flags = net::LOAD_NORMAL; + + int rv = trans->Start(&request_info, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + EXPECT_EQ(net::OK, rv); + + std::string contents; + rv = ReadTransaction(trans, &contents); + EXPECT_EQ(net::OK, rv); + + trans->Destroy(); +} diff --git a/net/http/http_util.cc b/net/http/http_util.cc new file mode 100644 index 0000000..a56a4db --- /dev/null +++ b/net/http/http_util.cc @@ -0,0 +1,358 @@ +// 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. + +// The rules for parsing content-types were borrowed from Firefox: +// http://lxr.mozilla.org/mozilla/source/netwerk/base/src/nsURLHelper.cpp#834 + +#include "net/http/http_util.h" + +#include <algorithm> + +#include "base/logging.h" +#include "base/string_util.h" + +using std::string; + +namespace net { + +//----------------------------------------------------------------------------- + +// Return the index of the closing quote of the string, if any. +static size_t FindStringEnd(const string& line, size_t start, char delim) { + DCHECK(start < line.length() && line[start] == delim && + (delim == '"' || delim == '\'')); + + const char set[] = { delim, '\\', '\0' }; + for (;;) { + // start points to either the start quote or the last + // escaped char (the char following a '\\') + + size_t end = line.find_first_of(set, start + 1); + if (end == string::npos) + return line.length(); + + if (line[end] == '\\') { + // Hit a backslash-escaped char. Need to skip over it. + start = end + 1; + if (start == line.length()) + return start; + + // Go back to looking for the next escape or the string end + continue; + } + + return end; + } + + NOTREACHED(); + return line.length(); +} + +//----------------------------------------------------------------------------- + +// static +size_t HttpUtil::FindDelimiter(const string& line, size_t search_start, + char delimiter) { + do { + // search_start points to the spot from which we should start looking + // for the delimiter. + const char delim_str[] = { delimiter, '"', '\'', '\0' }; + size_t cur_delim_pos = line.find_first_of(delim_str, search_start); + if (cur_delim_pos == string::npos) + return line.length(); + + char ch = line[cur_delim_pos]; + if (ch == delimiter) { + // Found delimiter + return cur_delim_pos; + } + + // We hit the start of a quoted string. Look for its end. + search_start = FindStringEnd(line, cur_delim_pos, ch); + if (search_start == line.length()) + return search_start; + + ++search_start; + + // search_start now points to the first char after the end of the + // string, so just go back to the top of the loop and look for + // |delimiter| again. + } while (true); + + NOTREACHED(); + return line.length(); +} + +// static +void HttpUtil::ParseContentType(const string& content_type_str, + string* mime_type, string* charset, + bool *had_charset) { + // Trim leading and trailing whitespace from type. We include '(' in + // the trailing trim set to catch media-type comments, which are not at all + // standard, but may occur in rare cases. + size_t type_val = content_type_str.find_first_not_of(HTTP_LWS); + type_val = std::min(type_val, content_type_str.length()); + size_t type_end = content_type_str.find_first_of(HTTP_LWS ";(", type_val); + if (string::npos == type_end) + type_end = content_type_str.length(); + + size_t charset_val = 0; + size_t charset_end = 0; + + // Iterate over parameters + bool type_has_charset = false; + size_t param_start = content_type_str.find_first_of(';', type_end); + if (param_start != string::npos) { + // We have parameters. Iterate over them. + size_t cur_param_start = param_start + 1; + do { + size_t cur_param_end = + FindDelimiter(content_type_str, cur_param_start, ';'); + + size_t param_name_start = content_type_str.find_first_not_of(HTTP_LWS, + cur_param_start); + param_name_start = std::min(param_name_start, cur_param_end); + + static const char charset_str[] = "charset="; + size_t charset_end_offset = std::min(param_name_start + + sizeof(charset_str) - 1, cur_param_end); + if (LowerCaseEqualsASCII(content_type_str.begin() + param_name_start, + content_type_str.begin() + charset_end_offset, charset_str)) { + charset_val = param_name_start + sizeof(charset_str) - 1; + charset_end = cur_param_end; + type_has_charset = true; + } + + cur_param_start = cur_param_end + 1; + } while (cur_param_start < content_type_str.length()); + } + + if (type_has_charset) { + // Trim leading and trailing whitespace from charset_val. We include + // '(' in the trailing trim set to catch media-type comments, which are + // not at all standard, but may occur in rare cases. + charset_val = content_type_str.find_first_not_of(HTTP_LWS, charset_val); + charset_val = std::min(charset_val, charset_end); + char first_char = content_type_str[charset_val]; + if (first_char == '"' || first_char == '\'') { + charset_end = FindStringEnd(content_type_str, charset_val, first_char); + ++charset_val; + DCHECK(charset_end >= charset_val); + } else { + charset_end = std::min(content_type_str.find_first_of(HTTP_LWS ";(", + charset_val), + charset_end); + } + } + + // if the server sent "*/*", it is meaningless, so do not store it. + // also, if type_val is the same as mime_type, then just update the + // charset. however, if charset is empty and mime_type hasn't + // changed, then don't wipe-out an existing charset. We + // also want to reject a mime-type if it does not include a slash. + // some servers give junk after the charset parameter, which may + // include a comma, so this check makes us a bit more tolerant. + if (content_type_str.length() != 0 && + content_type_str != "*/*" && + content_type_str.find_first_of('/') != string::npos) { + // Common case here is that mime_type is empty + bool eq = !mime_type->empty() && + LowerCaseEqualsASCII(content_type_str.begin() + type_val, + content_type_str.begin() + type_end, + mime_type->data()); + if (!eq) { + mime_type->assign(content_type_str.begin() + type_val, + content_type_str.begin() + type_end); + StringToLowerASCII(mime_type); + } + if ((!eq && *had_charset) || type_has_charset) { + *had_charset = true; + charset->assign(content_type_str.begin() + charset_val, + content_type_str.begin() + charset_end); + StringToLowerASCII(charset); + } + } +} + +// static +bool HttpUtil::HasHeader(const std::string& headers, const char* name) { + size_t name_len = strlen(name); + string::const_iterator it = + std::search(headers.begin(), + headers.end(), + name, + name + name_len, + CaseInsensitiveCompareASCII<char>()); + if (it == headers.end()) + return false; + + // ensure match is prefixed by newline + if (it != headers.begin() && it[-1] != '\n') + return false; + + // ensure match is suffixed by colon + if (it + name_len >= headers.end() || it[name_len] != ':') + return false; + + return true; +} + +// static +bool HttpUtil::IsNonCoalescingHeader(string::const_iterator name_begin, + string::const_iterator name_end) { + // NOTE: "set-cookie2" headers do not support expires attributes, so we don't + // have to list them here. + const char* kNonCoalescingHeaders[] = { + "date", + "expires", + "last-modified", + "location", // See bug 1050541 for details + "retry-after", + "set-cookie" + }; + for (size_t i = 0; i < arraysize(kNonCoalescingHeaders); ++i) { + if (LowerCaseEqualsASCII(name_begin, name_end, kNonCoalescingHeaders[i])) + return true; + } + return false; +} + +void HttpUtil::TrimLWS(string::const_iterator* begin, + string::const_iterator* end) { + // leading whitespace + while (*begin < *end && strchr(HTTP_LWS, (*begin)[0])) + ++(*begin); + + // trailing whitespace + while (*begin < *end && strchr(HTTP_LWS, (*end)[-1])) + --(*end); +} + +int HttpUtil::LocateEndOfHeaders(const char* buf, int buf_len) { + bool was_lf = false; + char last_c = '\0'; + for (int i = 0; i < buf_len; ++i) { + char c = buf[i]; + if (c == '\n') { + if (was_lf) + return i + 1; + was_lf = true; + } else if (c != '\r' || last_c != '\n') { + was_lf = false; + } + last_c = c; + } + return -1; +} + +std::string HttpUtil::AssembleRawHeaders(const char* buf, int buf_len) { + std::string raw_headers; + + // TODO(darin): + // - Handle header line continuations. + // - Be careful about CRs that appear spuriously mid header line. + + int line_start = 0; + for (int i = 0; i < buf_len; ++i) { + char c = buf[i]; + if (c == '\r' || c == '\n') { + if (line_start != i) { + // (line_start,i) is a header line. + raw_headers.append(buf + line_start, buf + i); + raw_headers.push_back('\0'); + } + line_start = i + 1; + } + } + raw_headers.push_back('\0'); + + return raw_headers; +} + +// BNF from section 4.2 of RFC 2616: +// +// message-header = field-name ":" [ field-value ] +// field-name = token +// field-value = *( field-content | LWS ) +// field-content = <the OCTETs making up the field-value +// and consisting of either *TEXT or combinations +// of token, separators, and quoted-string> +// + +HttpUtil::HeadersIterator::HeadersIterator(string::const_iterator headers_begin, + string::const_iterator headers_end, + const std::string& line_delimiter) + : lines_(headers_begin, headers_end, line_delimiter) { +} + +bool HttpUtil::HeadersIterator::GetNext() { + while (lines_.GetNext()) { + name_begin_ = lines_.token_begin(); + values_end_ = lines_.token_end(); + + string::const_iterator colon = find(name_begin_, values_end_, ':'); + if (colon == values_end_) + continue; // skip malformed header + + name_end_ = colon; + TrimLWS(&name_begin_, &name_end_); + if (name_begin_ == name_end_) + continue; // skip malformed header + + values_begin_ = colon + 1; + TrimLWS(&values_begin_, &values_end_); + + // if we got a header name, then we are done. + return true; + } + return false; +} + +HttpUtil::ValuesIterator::ValuesIterator( + string::const_iterator values_begin, + string::const_iterator values_end, + char delimiter) + : values_(values_begin, values_end, string(1, delimiter)) { + values_.set_quote_chars("\'\""); +} + +bool HttpUtil::ValuesIterator::GetNext() { + while (values_.GetNext()) { + value_begin_ = values_.token_begin(); + value_end_ = values_.token_end(); + TrimLWS(&value_begin_, &value_end_); + + // bypass empty values. + if (value_begin_ != value_end_) + return true; + } + return false; +} + +} // namespace net diff --git a/net/http/http_util.h b/net/http/http_util.h new file mode 100644 index 0000000..f7802a7 --- /dev/null +++ b/net/http/http_util.h @@ -0,0 +1,173 @@ +// 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. + +#ifndef NET_HTTP_HTTP_UTIL_H_ +#define NET_HTTP_HTTP_UTIL_H_ + +#include "base/string_tokenizer.h" + +// This is a macro to support extending this string literal at compile time. +// Please excuse me polluting your global namespace! +#define HTTP_LWS " \t" + +namespace net { + +class HttpUtil { + public: + // Locates the next occurance of delimiter in line, skipping over quoted + // strings (e.g., commas will not be treated as delimiters if they appear + // within a quoted string). Returns the offset of the found delimiter or + // line.size() if no delimiter was found. + static size_t FindDelimiter(const std::string& line, + size_t search_start, + char delimiter); + + // Parses the value of a Content-Type header. The resulting mime_type and + // charset values are normalized to lowercase. The mime_type and charset + // output values are only modified if the content_type_str contains a mime + // type and charset value, respectively. + static void ParseContentType(const std::string& content_type_str, + std::string* mime_type, + std::string* charset, + bool *had_charset); + + // Scans the '\r\n'-delimited headers for the given header name. Returns + // true if a match is found. Input is assumed to be well-formed. + // TODO(darin): kill this + static bool HasHeader(const std::string& headers, const char* name); + + // Multiple occurances of some headers cannot be coalesced into a comma- + // separated list since their values are (or contain) unquoted HTTP-date + // values, which may contain a comma (see RFC 2616 section 3.3.1). + static bool IsNonCoalescingHeader(std::string::const_iterator name_begin, + std::string::const_iterator name_end); + static bool IsNonCoalescingHeader(const std::string& name) { + return IsNonCoalescingHeader(name.begin(), name.end()); + } + + // Trim HTTP_LWS chars from the beginning and end of the string. + static void TrimLWS(std::string::const_iterator* begin, + std::string::const_iterator* end); + + // Returns index beyond the end-of-headers marker or -1 if not found. RFC + // 2616 defines the end-of-headers marker as a double CRLF; however, some + // servers only send back LFs (e.g., Unix-based CGI scripts written using the + // ASIS Apache module). This function therefore accepts the pattern LF[CR]LF + // as end-of-headers (just like Mozilla). + static int LocateEndOfHeaders(const char* buf, int buf_len); + + // Assemble "raw headers" in the format required by HttpResponseHeaders. + // This involves normalizing line terminators, converting [CR]LF to \0 and + // handling HTTP line continuations (i.e., lines starting with LWS are + // continuations of the previous line). |buf_len| indicates the position of + // the end-of-headers marker as defined by LocateEndOfHeaders. + static std::string AssembleRawHeaders(const char* buf, int buf_len); + + // Used to iterate over the name/value pairs of HTTP headers. To iterate + // over the values in a multi-value header, use ValuesIterator. + class HeadersIterator { + public: + HeadersIterator(std::string::const_iterator headers_begin, + std::string::const_iterator headers_end, + const std::string& line_delimiter); + + // Advances the iterator to the next header, if any. Returns true if there + // is a next header. Use name* and values* methods to access the resultant + // header name and values. + bool GetNext(); + + std::string::const_iterator name_begin() const { + return name_begin_; + } + std::string::const_iterator name_end() const { + return name_end_; + } + std::string name() const { + return std::string(name_begin_, name_end_); + } + + std::string::const_iterator values_begin() const { + return values_begin_; + } + std::string::const_iterator values_end() const { + return values_end_; + } + std::string values() const { + return std::string(values_begin_, values_end_); + } + + private: + StringTokenizer lines_; + std::string::const_iterator name_begin_; + std::string::const_iterator name_end_; + std::string::const_iterator values_begin_; + std::string::const_iterator values_end_; + }; + + // Used to iterate over deliminated values in a HTTP header. HTTP LWS is + // automatically trimmed from the resulting values. + // + // When using this class to iterate over response header values, beware that + // for some headers (e.g., Last-Modified), commas are not used as delimiters. + // This iterator should be avoided for headers like that which are considered + // non-coalescing (see IsNonCoalescingHeader). + // + // This iterator is careful to skip over delimiters found inside an HTTP + // quoted string. + // + class ValuesIterator { + public: + ValuesIterator(std::string::const_iterator values_begin, + std::string::const_iterator values_end, + char delimiter); + + // Advances the iterator to the next value, if any. Returns true if there + // is a next value. Use value* methods to access the resultant value. + bool GetNext(); + + std::string::const_iterator value_begin() const { + return value_begin_; + } + std::string::const_iterator value_end() const { + return value_end_; + } + std::string value() const { + return std::string(value_begin_, value_end_); + } + + private: + StringTokenizer values_; + std::string::const_iterator value_begin_; + std::string::const_iterator value_end_; + }; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_UTIL_H_ diff --git a/net/http/http_util_unittest.cc b/net/http/http_util_unittest.cc new file mode 100644 index 0000000..bfadb30 --- /dev/null +++ b/net/http/http_util_unittest.cc @@ -0,0 +1,159 @@ +// 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 <algorithm> + +#include "base/basictypes.h" +#include "net/http/http_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::HttpUtil; + +namespace { +class HttpUtilTest : public testing::Test {}; +} + +TEST(HttpUtilTest, HasHeader) { + static const struct { + const char* headers; + const char* name; + bool expected_result; + } tests[] = { + { "", "foo", false }, + { "foo\r\nbar", "foo", false }, + { "ffoo: 1", "foo", false }, + { "foo: 1", "foo", true }, + { "foo: 1\r\nbar: 2", "foo", true }, + { "fOO: 1\r\nbar: 2", "foo", true }, + { "g: 0\r\nfoo: 1\r\nbar: 2", "foo", true }, + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + bool result = HttpUtil::HasHeader(tests[i].headers, tests[i].name); + EXPECT_EQ(tests[i].expected_result, result); + } +} + +TEST(HttpUtilTest, HeadersIterator) { + std::string headers = "foo: 1\t\r\nbar: hello world\r\nbaz: 3 \r\n"; + + HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n"); + + ASSERT_TRUE(it.GetNext()); + EXPECT_EQ(std::string("foo"), it.name()); + EXPECT_EQ(std::string("1"), it.values()); + + ASSERT_TRUE(it.GetNext()); + EXPECT_EQ(std::string("bar"), it.name()); + EXPECT_EQ(std::string("hello world"), it.values()); + + ASSERT_TRUE(it.GetNext()); + EXPECT_EQ(std::string("baz"), it.name()); + EXPECT_EQ(std::string("3"), it.values()); + + EXPECT_FALSE(it.GetNext()); +} + +TEST(HttpUtilTest, HeadersIterator_MalformedLine) { + std::string headers = "foo: 1\n: 2\n3\nbar: 4"; + + HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n"); + + ASSERT_TRUE(it.GetNext()); + EXPECT_EQ(std::string("foo"), it.name()); + EXPECT_EQ(std::string("1"), it.values()); + + ASSERT_TRUE(it.GetNext()); + EXPECT_EQ(std::string("bar"), it.name()); + EXPECT_EQ(std::string("4"), it.values()); + + EXPECT_FALSE(it.GetNext()); +} + +TEST(HttpUtilTest, ValuesIterator) { + std::string values = " must-revalidate, no-cache=\"foo, bar\"\t, private "; + + HttpUtil::ValuesIterator it(values.begin(), values.end(), ','); + + ASSERT_TRUE(it.GetNext()); + EXPECT_EQ(std::string("must-revalidate"), it.value()); + + ASSERT_TRUE(it.GetNext()); + EXPECT_EQ(std::string("no-cache=\"foo, bar\""), it.value()); + + ASSERT_TRUE(it.GetNext()); + EXPECT_EQ(std::string("private"), it.value()); + + EXPECT_FALSE(it.GetNext()); +} + +TEST(HttpUtilTest, ValuesIterator_Blanks) { + std::string values = " \t "; + + HttpUtil::ValuesIterator it(values.begin(), values.end(), ','); + + EXPECT_FALSE(it.GetNext()); +} + +TEST(HttpUtilTest, LocateEndOfHeaders) { + struct { + const char* input; + int expected_result; + } tests[] = { + { "foo\r\nbar\r\n\r\n", 12 }, + { "foo\nbar\n\n", 9 }, + { "foo\r\nbar\r\n\r\njunk", 12 }, + { "foo\nbar\n\njunk", 9 }, + { "foo\nbar\n\r\njunk", 10 }, + { "foo\nbar\r\n\njunk", 10 }, + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + int input_len = static_cast<int>(strlen(tests[i].input)); + int eoh = HttpUtil::LocateEndOfHeaders(tests[i].input, input_len); + EXPECT_EQ(tests[i].expected_result, eoh); + } +} + +TEST(HttpUtilTest, AssembleRawHeaders) { + struct { + const char* input; + const char* expected_result; // with '\0' changed to '|' + } tests[] = { + { "HTTP/1.0 200 OK\r\nFoo: 1\r\nBar: 2\r\n\r\n", + "HTTP/1.0 200 OK|Foo: 1|Bar: 2||" }, + + { "HTTP/1.0 200 OK\nFoo: 1\nBar: 2\n\n", + "HTTP/1.0 200 OK|Foo: 1|Bar: 2||" }, + }; + for (size_t i = 0; i < arraysize(tests); ++i) { + int input_len = static_cast<int>(strlen(tests[i].input)); + std::string raw = HttpUtil::AssembleRawHeaders(tests[i].input, input_len); + std::replace(raw.begin(), raw.end(), '\0', '|'); + EXPECT_TRUE(raw == tests[i].expected_result); + } +} diff --git a/net/http/http_vary_data.cc b/net/http/http_vary_data.cc new file mode 100644 index 0000000..b10f279 --- /dev/null +++ b/net/http/http_vary_data.cc @@ -0,0 +1,167 @@ +// 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_vary_data.h" + +#include <stdlib.h> + +#include "base/pickle.h" +#include "base/string_util.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" + +namespace net { + +HttpVaryData::HttpVaryData() : is_valid_(false) { + memset(&request_digest_, 0, sizeof(request_digest_)); +} + +bool HttpVaryData::Init(const HttpRequestInfo& request_info, + const HttpResponseHeaders& response_headers) { + MD5Context ctx; + MD5Init(&ctx); + + bool processed_header = false; + + // Feed the MD5 context in the order of the Vary header enumeration. If the + // Vary header repeats a header name, then that's OK. + // + // If the Vary header contains '*' then we should not construct any vary data + // since it is all usurped by a '*'. See section 13.6 of RFC 2616. + // + void* iter = NULL; + std::string name = "vary", request_header; + while (response_headers.EnumerateHeader(&iter, name, &request_header)) { + if (request_header == "*") + return false; + AddField(request_info, request_header, &ctx); + processed_header = true; + } + + // Add an implicit 'Vary: cookie' header to any redirect to avoid redirect + // loops which may result from redirects that are incorrectly marked as + // cachable by the server. Unfortunately, other browsers do not cache + // redirects that result from requests containing a cookie header. We are + // treading on untested waters here, so we want to be extra careful to make + // sure we do not end up with a redirect loop served from cache. + // + // If there is an explicit 'Vary: cookie' header, then we will just end up + // digesting the cookie header twice. Not a problem. + // + std::string location; + if (response_headers.IsRedirect(&location)) { + AddField(request_info, "cookie", &ctx); + processed_header = true; + } + + if (!processed_header) + return false; + + MD5Final(&request_digest_, &ctx); + return is_valid_ = true; +} + +bool HttpVaryData::InitFromPickle(const Pickle& pickle, void** iter) { + const char* data; + if (pickle.ReadBytes(iter, &data, sizeof(request_digest_))) { + memcpy(&request_digest_, data, sizeof(request_digest_)); + return is_valid_ = true; + } + return false; +} + +void HttpVaryData::Persist(Pickle* pickle) const { + pickle->WriteBytes(&request_digest_, sizeof(request_digest_)); +} + +bool HttpVaryData::MatchesRequest( + const HttpRequestInfo& request_info, + const HttpResponseHeaders& cached_response_headers) const { + HttpVaryData new_vary_data; + if (!new_vary_data.Init(request_info, cached_response_headers)) { + // This shouldn't happen provided the same response headers passed here + // were also used when initializing |this|. + NOTREACHED(); + return false; + } + return memcmp(&new_vary_data.request_digest_, &request_digest_, + sizeof(request_digest_)) == 0; +} + +// static +std::string HttpVaryData::GetRequestValue( + const HttpRequestInfo& request_info, + const std::string& request_header) { + // Some special cases: + if (LowerCaseEqualsASCII(request_header, "referer")) + return request_info.referrer.spec(); + if (LowerCaseEqualsASCII(request_header, "user-agent")) + return request_info.user_agent; + + std::string result; + + // Check extra headers: + HttpUtil::HeadersIterator it(request_info.extra_headers.begin(), + request_info.extra_headers.end(), + "\r\n"); + while (it.GetNext()) { + size_t name_len = it.name_end() - it.name_begin(); + if (request_header.size() == name_len && + std::equal(it.name_begin(), it.name_end(), request_header.begin(), + CaseInsensitiveCompare<char>())) { + if (!result.empty()) + result.append(1, ','); + result.append(it.values()); + } + } + + // Unfortunately, we do not have access to all of the request headers at this + // point. Most notably, we do not have access to an Authorization header if + // one will be added to the request. + + return result; +} + +// static +void HttpVaryData::AddField(const HttpRequestInfo& request_info, + const std::string& request_header, + MD5Context* ctx) { + std::string request_value = GetRequestValue(request_info, request_header); + + // Append a character that cannot appear in the request header line so that we + // protect against case where the concatenation of two request headers could + // look the same for a variety of values for the individual request headers. + // For example, "foo: 12\nbar: 3" looks like "foo: 1\nbar: 23" otherwise. + request_value.append(1, '\n'); + + MD5Update(ctx, request_value.data(), request_value.size()); +} + +} // namespace net diff --git a/net/http/http_vary_data.h b/net/http/http_vary_data.h new file mode 100644 index 0000000..ff4f753 --- /dev/null +++ b/net/http/http_vary_data.h @@ -0,0 +1,109 @@ +// 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. + +#ifndef NET_HTTP_HTTP_VARY_DATA_H__ +#define NET_HTTP_HTTP_VARY_DATA_H__ + +#include "base/md5.h" +#include "base/ref_counted.h" + +class Pickle; + +namespace net { + +class HttpRequestInfo; +class HttpResponseHeaders; + +// Used to implement the HTTP/1.1 Vary header. This class contains a MD5 hash +// over the request headers indicated by a Vary header. +// +// While RFC 2616 requires strict request header comparisons, it is much +// cheaper to store a MD5 sum, which should be sufficient. Storing a hash also +// avoids messy privacy issues as some of the request headers could hold +// sensitive data (e.g., cookies). +// +// NOTE: This class does not hold onto the contents of the Vary header. +// Instead, it relies on the consumer to store that and to supply it again to +// the MatchesRequest function for comparing against future HTTP requests. +// +class HttpVaryData { + public: + HttpVaryData(); + + bool is_valid() const { return is_valid_; } + + // Initialize from a request and its corresponding response headers. + // + // Returns true if a Vary header was found in the response headers and that + // Vary header was not empty and did not contain the '*' value. Upon + // success, the object is also marked as valid such that is_valid() will + // return true. Otherwise, false is returned to indicate that this object + // was not modified. + // + bool Init(const HttpRequestInfo& request_info, + const HttpResponseHeaders& response_headers); + + // Initialize from a pickle that contains data generated by a call to the + // vary data's Persist method. + // + // Upon success, true is returned and the object is marked as valid such that + // is_valid() will return true. Otherwise, false is returned to indicate + // that this object was not modified. + // + bool InitFromPickle(const Pickle& pickle, void** pickle_iter); + + // Call this method to persist the vary data. + void Persist(Pickle* pickle) const; + + // Call this method to test if the given request matches the previous request + // with which this vary data corresponds. The |cached_response_headers| must + // be the same response headers used to generate this vary data. + bool MatchesRequest(const HttpRequestInfo& request_info, + const HttpResponseHeaders& cached_response_headers) const; + + private: + // Returns the corresponding request header value. + static std::string GetRequestValue(const HttpRequestInfo& request_info, + const std::string& request_header); + + // Append to the MD5 context for the given request header. + static void AddField(const HttpRequestInfo& request_info, + const std::string& request_header, + MD5Context* context); + + // A digested version of the request headers corresponding to the Vary header. + MD5Digest request_digest_; + + // True when request_digest_ contains meaningful data. + bool is_valid_; +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_VARY_DATA_H__ diff --git a/net/http/http_vary_data_unittest.cc b/net/http/http_vary_data_unittest.cc new file mode 100644 index 0000000..553a023 --- /dev/null +++ b/net/http/http_vary_data_unittest.cc @@ -0,0 +1,154 @@ +// 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 <algorithm> + +#include "net/http/http_request_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_vary_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +typedef testing::Test HttpVaryDataTest; + +struct TestTransaction { + net::HttpRequestInfo request; + scoped_refptr<net::HttpResponseHeaders> response; + + void Init(const std::string& request_headers, + const std::string& response_headers) { + std::string temp(response_headers); + std::replace(temp.begin(), temp.end(), '\n', '\0'); + response = new net::HttpResponseHeaders(temp); + + request.extra_headers = request_headers; + } +}; + +} // namespace + +TEST(HttpVaryDataTest, IsValid) { + // All of these responses should result in an invalid vary data object. + const char* kTestResponses[] = { + "HTTP/1.1 200 OK\n\n", + "HTTP/1.1 200 OK\nVary: *\n\n", + "HTTP/1.1 200 OK\nVary: cookie, *, bar\n\n", + "HTTP/1.1 200 OK\nVary: cookie\nFoo: 1\nVary: *\n\n", + }; + + for (size_t i = 0; i < arraysize(kTestResponses); ++i) { + TestTransaction t; + t.Init("", kTestResponses[i]); + + net::HttpVaryData v; + EXPECT_FALSE(v.Init(t.request, *t.response)); + EXPECT_FALSE(v.is_valid()); + } +} + +TEST(HttpVaryDataTest, DoesVary) { + TestTransaction a; + a.Init("Foo: 1", "HTTP/1.1 200 OK\nVary: foo\n\n"); + + TestTransaction b; + b.Init("Foo: 2", "HTTP/1.1 200 OK\nVary: foo\n\n"); + + net::HttpVaryData v; + EXPECT_TRUE(v.Init(a.request, *a.response)); + + EXPECT_FALSE(v.MatchesRequest(b.request, *b.response)); +} + +TEST(HttpVaryDataTest, DoesVary2) { + TestTransaction a; + a.Init("Foo: 1\nbar: 23", "HTTP/1.1 200 OK\nVary: foo, bar\n\n"); + + TestTransaction b; + b.Init("Foo: 12\nbar: 3", "HTTP/1.1 200 OK\nVary: foo, bar\n\n"); + + net::HttpVaryData v; + EXPECT_TRUE(v.Init(a.request, *a.response)); + + EXPECT_FALSE(v.MatchesRequest(b.request, *b.response)); +} + +TEST(HttpVaryDataTest, DoesntVary) { + TestTransaction a; + a.Init("Foo: 1", "HTTP/1.1 200 OK\nVary: foo\n\n"); + + TestTransaction b; + b.Init("Foo: 1", "HTTP/1.1 200 OK\nVary: foo\n\n"); + + net::HttpVaryData v; + EXPECT_TRUE(v.Init(a.request, *a.response)); + + EXPECT_TRUE(v.MatchesRequest(b.request, *b.response)); +} + +TEST(HttpVaryDataTest, DoesntVary2) { + TestTransaction a; + a.Init("Foo: 1\nbAr: 2", "HTTP/1.1 200 OK\nVary: foo, bar\n\n"); + + TestTransaction b; + b.Init("Foo: 1\nbaR: 2", "HTTP/1.1 200 OK\nVary: foo\nVary: bar\n\n"); + + net::HttpVaryData v; + EXPECT_TRUE(v.Init(a.request, *a.response)); + + EXPECT_TRUE(v.MatchesRequest(b.request, *b.response)); +} + +TEST(HttpVaryDataTest, ImplicitCookieForRedirect) { + TestTransaction a; + a.Init("Cookie: 1", "HTTP/1.1 301 Moved\nLocation: x\n\n"); + + TestTransaction b; + b.Init("Cookie: 2", "HTTP/1.1 301 Moved\nLocation: x\n\n"); + + net::HttpVaryData v; + EXPECT_TRUE(v.Init(a.request, *a.response)); + + EXPECT_FALSE(v.MatchesRequest(b.request, *b.response)); +} + +TEST(HttpVaryDataTest, ImplicitCookieForRedirect2) { + // This should be no different than the test above + + TestTransaction a; + a.Init("Cookie: 1", "HTTP/1.1 301 Moved\nLocation: x\nVary: coOkie\n\n"); + + TestTransaction b; + b.Init("Cookie: 2", "HTTP/1.1 301 Moved\nLocation: x\nVary: cooKie\n\n"); + + net::HttpVaryData v; + EXPECT_TRUE(v.Init(a.request, *a.response)); + + EXPECT_FALSE(v.MatchesRequest(b.request, *b.response)); +} diff --git a/net/http/winhttp_request_throttle.cc b/net/http/winhttp_request_throttle.cc new file mode 100644 index 0000000..b8eeaf2 --- /dev/null +++ b/net/http/winhttp_request_throttle.cc @@ -0,0 +1,200 @@ +// 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/winhttp_request_throttle.h" + +#include "base/logging.h" +#include "net/http/http_transaction_winhttp.h" + +namespace { + +// The arguments to a WinHttpSendRequest call. +struct SendRequestArgs { + SendRequestArgs() : request_handle(NULL), total_size(0), context(0) {} + + SendRequestArgs(HINTERNET handle, DWORD size, DWORD_PTR context_value) + : request_handle(handle), total_size(size), context(context_value) {} + + HINTERNET request_handle; + DWORD total_size; + DWORD_PTR context; +}; + +} // namespace + +namespace net { + +// Per-server queue for WinHttpSendRequest calls. +class WinHttpRequestThrottle::RequestQueue { + public: + RequestQueue() {} + + // Adds |args| to the end of the queue. + void PushBack(const SendRequestArgs& args) { queue_.push_back(args); } + + // If the queue is not empty, pops the first entry off the queue, saves it + // in |*args|, and returns true. If the queue is empty, returns false. + bool GetFront(SendRequestArgs* args); + + // If the queue has an entry containing |request_handle|, removes it and + // returns true. Otherwise, returns false. + bool Remove(HINTERNET request_handle); + + bool empty() const { return queue_.empty(); } + + private: + std::list<SendRequestArgs> queue_; + + DISALLOW_EVIL_CONSTRUCTORS(RequestQueue); +}; + +bool WinHttpRequestThrottle::RequestQueue::GetFront(SendRequestArgs* args) { + if (queue_.empty()) + return false; + *args = queue_.front(); + queue_.pop_front(); + return true; +} + +bool WinHttpRequestThrottle::RequestQueue::Remove(HINTERNET request_handle) { + std::list<SendRequestArgs>::iterator it; + for (it = queue_.begin(); it != queue_.end(); ++it) { + if (it->request_handle == request_handle) { + queue_.erase(it); + return true; + } + } + return false; +} + +WinHttpRequestThrottle::~WinHttpRequestThrottle() { +#ifndef NDEBUG + ThrottleMap::const_iterator throttle_iter = throttles_.begin(); + for (; throttle_iter != throttles_.end(); ++throttle_iter) { + const PerServerThrottle& throttle = throttle_iter->second; + DCHECK(throttle.num_requests == 0); + DCHECK(!throttle.request_queue.get() || throttle.request_queue->empty()); + } +#endif +} + +BOOL WinHttpRequestThrottle::SubmitRequest(const std::string &server, + HINTERNET request_handle, + DWORD total_size, + DWORD_PTR context) { + PerServerThrottle& throttle = throttles_[server]; + DCHECK(throttle.num_requests >= 0 && + throttle.num_requests <= kMaxConnectionsPerServer); + if (throttle.num_requests >= kMaxConnectionsPerServer) { + if (!throttle.request_queue.get()) + throttle.request_queue.reset(new RequestQueue); + SendRequestArgs args(request_handle, total_size, context); + throttle.request_queue->PushBack(args); + return TRUE; + } + + BOOL ok = SendRequest(request_handle, total_size, context, false); + if (ok) + throttle.num_requests += 1; + return ok; +} + +void WinHttpRequestThrottle::NotifyRequestDone(const std::string& server) { + PerServerThrottle& throttle = throttles_[server]; + DCHECK(throttle.num_requests > 0 && + throttle.num_requests <= kMaxConnectionsPerServer); + throttle.num_requests -= 1; + SendRequestArgs args; + if (throttle.request_queue.get() && + throttle.request_queue->GetFront(&args)) { + throttle.num_requests += 1; + SendRequest(args.request_handle, args.total_size, args.context, true); + } + if (throttles_.size() > static_cast<size_t>(kGarbageCollectionThreshold)) + GarbageCollect(); +} + +void WinHttpRequestThrottle::RemoveRequest(const std::string& server, + HINTERNET request_handle) { + PerServerThrottle& throttle = throttles_[server]; + if (throttle.request_queue.get() && + throttle.request_queue->Remove(request_handle)) + return; + NotifyRequestDone(server); +} + +BOOL WinHttpRequestThrottle::SendRequest(HINTERNET request_handle, + DWORD total_size, + DWORD_PTR context, + bool report_async_error) { + BOOL ok = WinHttpSendRequest(request_handle, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + WINHTTP_NO_REQUEST_DATA, + 0, + total_size, + context); + if (!ok && report_async_error) { + WINHTTP_ASYNC_RESULT async_result = { API_SEND_REQUEST, GetLastError() }; + HttpTransactionWinHttp::StatusCallback( + request_handle, context, + WINHTTP_CALLBACK_STATUS_REQUEST_ERROR, + &async_result, sizeof(async_result)); + } + return ok; +} + +WinHttpRequestThrottle::PerServerThrottle::PerServerThrottle() + : num_requests(0) { +} + +WinHttpRequestThrottle::PerServerThrottle::~PerServerThrottle() { +} + +// static +const int WinHttpRequestThrottle::kMaxConnectionsPerServer = 6; + +// static +const int WinHttpRequestThrottle::kGarbageCollectionThreshold = 64; + +void WinHttpRequestThrottle::GarbageCollect() { + ThrottleMap::iterator throttle_iter = throttles_.begin(); + while (throttle_iter != throttles_.end()) { + PerServerThrottle& throttle = throttle_iter->second; + if (throttle.num_requests == 0 && + (!throttle.request_queue.get() || throttle.request_queue->empty())) { + // Erase the current item but keep the iterator valid. + throttles_.erase(throttle_iter++); + } else { + ++throttle_iter; + } + } +} + +} // namespace net diff --git a/net/http/winhttp_request_throttle.h b/net/http/winhttp_request_throttle.h new file mode 100644 index 0000000..0bca7e5 --- /dev/null +++ b/net/http/winhttp_request_throttle.h @@ -0,0 +1,128 @@ +// 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. + +#ifndef NET_HTTP_WINHTTP_REQUEST_THROTTLE_H_ +#define NET_HTTP_WINHTTP_REQUEST_THROTTLE_H_ + +#include <windows.h> +#include <winhttp.h> + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/linked_ptr.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +namespace net { + +// The WinHttpRequestThrottle class regulates the rate at which we call +// WinHttpSendRequest, ensuring that at any time there are at most 6 WinHTTP +// requests in progress for each server or proxy. +// +// The throttling is intended to cause WinHTTP to maintain at most 6 +// persistent HTTP connections with each server or proxy. This works well in +// most cases, except when making HTTPS requests via a proxy, in which case +// WinHTTP may open much more than 6 connections to the proxy in spite of our +// rate limiting. +// +// Because we identify a server by its hostname rather than its IP address, +// we also can't distinguish between two different hostnames that resolve to +// the same IP address. +// +// Although WinHTTP has the WINHTTP_OPTION_MAX_CONNS_PER_SERVER option to +// limit the number of connections allowed per server, we can't use it +// because it has two serious bugs: +// 1. It causes WinHTTP to not close idle persistent connections, leaving +// many connections in the CLOSE_WAIT state. This may cause system +// crashes (Blue Screen of Death) when VPN is used. +// 2. It causes WinHTTP to crash intermittently in +// HTTP_REQUEST_HANDLE_OBJECT::OpenProxyTunnel_Fsm() if a proxy is used. +// Therefore, we have to resort to throttling our WinHTTP requests to achieve +// the same effect. +// +// Note on thread safety: The WinHttpRequestThrottle class is only used by +// the IO thread, so it doesn't need to be protected with a lock. The +// drawback is that the time we mark a request done is only approximate. +// We do that in the HttpTransactionWinHttp destructor, rather than in the +// WinHTTP status callback upon receiving HANDLE_CLOSING. +class WinHttpRequestThrottle { + public: + WinHttpRequestThrottle() {} + + virtual ~WinHttpRequestThrottle(); + + // Intended to be a near drop-in replacement of WinHttpSendRequest. + BOOL SubmitRequest(const std::string& server, + HINTERNET request_handle, + DWORD total_size, + DWORD_PTR context); + + // Called when a request failed or completed successfully. + void NotifyRequestDone(const std::string& server); + + // Called from the HttpTransactionWinHttp destructor. + void RemoveRequest(const std::string& server, + HINTERNET request_handle); + + protected: + // Unit tests can stub out this method in a derived class. + virtual BOOL SendRequest(HINTERNET request_handle, + DWORD total_size, + DWORD_PTR context, + bool report_async_error); + + private: + FRIEND_TEST(WinHttpRequestThrottleTest, GarbageCollect); + + class RequestQueue; + + struct PerServerThrottle { + PerServerThrottle(); + ~PerServerThrottle(); + + int num_requests; // Number of requests in progress + linked_ptr<RequestQueue> request_queue; // Requests waiting to be sent + }; + + typedef std::map<std::string, PerServerThrottle> ThrottleMap; + + static const int kMaxConnectionsPerServer; + static const int kGarbageCollectionThreshold; + + void GarbageCollect(); + + ThrottleMap throttles_; + + DISALLOW_EVIL_CONSTRUCTORS(WinHttpRequestThrottle); +}; + +} // namespace net + +#endif // NET_HTTP_WINHTTP_REQUEST_THROTTLE_H_ diff --git a/net/http/winhttp_request_throttle_unittest.cc b/net/http/winhttp_request_throttle_unittest.cc new file mode 100644 index 0000000..44643e3 --- /dev/null +++ b/net/http/winhttp_request_throttle_unittest.cc @@ -0,0 +1,136 @@ +// 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 "base/string_util.h" +#include "net/http/winhttp_request_throttle.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Converts an int i to an HINTERNET (void *) request handle. +HINTERNET RequestHandle(int i) { + return reinterpret_cast<HINTERNET>(static_cast<intptr_t>(i)); +} + +class MockRequestThrottle : public net::WinHttpRequestThrottle { + public: + MockRequestThrottle() : last_sent_request_(NULL) { } + + // The request handle of the last sent request. This allows us to determine + // whether a submitted request was sent or queued. + HINTERNET last_sent_request() const { return last_sent_request_; } + + protected: + virtual BOOL SendRequest(HINTERNET request_handle, + DWORD total_size, + DWORD_PTR context, + bool report_async_error) { + last_sent_request_ = request_handle; + return TRUE; + } + + private: + HINTERNET last_sent_request_; + + DISALLOW_EVIL_CONSTRUCTORS(MockRequestThrottle); +}; + +} // namespace + +namespace net { + +TEST(WinHttpRequestThrottleTest, OneServer) { + MockRequestThrottle throttle; + std::string server("http://www.foo.com"); + HINTERNET request_handle; + + // Submit 20 requests to the request throttle. + // Expected outcome: 6 requests should be in progress, and requests 7-20 + // should be queued. + for (int i = 1; i <= 20; i++) { + request_handle = RequestHandle(i); + EXPECT_TRUE(throttle.SubmitRequest(server, request_handle, 0, 0)); + if (i <= 6) + EXPECT_EQ(request_handle, throttle.last_sent_request()); + else + EXPECT_EQ(RequestHandle(6), throttle.last_sent_request()); + } + + // Notify the request throttle of the completion of 10 requests. + // Expected outcome: 6 requests should be in progress, and requests 17-20 + // should be queued. + for (int j = 0; j < 10; j++) { + throttle.NotifyRequestDone(server); + EXPECT_EQ(RequestHandle(7 + j), throttle.last_sent_request()); + } + + // Remove request 17, which is queued. + // Expected outcome: Requests 18-20 should remain queued. + request_handle = RequestHandle(17); + throttle.RemoveRequest(server, request_handle); + EXPECT_EQ(RequestHandle(16), throttle.last_sent_request()); + + // Remove request 16, which is in progress. + // Expected outcome: The request throttle should send request 18. + // Requests 19-20 should remained queued. + request_handle = RequestHandle(16); + throttle.RemoveRequest(server, request_handle); + EXPECT_EQ(RequestHandle(18), throttle.last_sent_request()); + + // Notify the request throttle of the completion of the remaining + // 8 requests. + for (int j = 0; j < 8; j++) { + throttle.NotifyRequestDone(server); + if (j < 2) + EXPECT_EQ(RequestHandle(19 + j), throttle.last_sent_request()); + else + EXPECT_EQ(RequestHandle(20), throttle.last_sent_request()); + } +} + +// Submit requests to a large number (> 64) of servers to force the garbage +// collection of idle PerServerThrottles. +TEST(WinHttpRequestThrottleTest, GarbageCollect) { + MockRequestThrottle throttle; + for (int i = 0; i < 150; i++) { + std::string server("http://www.foo"); + server.append(IntToString(i)); + server.append(".com"); + throttle.SubmitRequest(server, RequestHandle(1), 0, 0); + throttle.NotifyRequestDone(server); + if (i < 64) + EXPECT_EQ(i + 1, throttle.throttles_.size()); + else if (i < 129) + EXPECT_EQ(i - 64, throttle.throttles_.size()); + else + EXPECT_EQ(i - 129, throttle.throttles_.size()); + } +} + +} // namespace net |