summaryrefslogtreecommitdiffstats
path: root/net/http
diff options
context:
space:
mode:
Diffstat (limited to 'net/http')
-rw-r--r--net/http/cert_status_cache.cc89
-rw-r--r--net/http/cert_status_cache.h74
-rw-r--r--net/http/http_atom_list.h86
-rw-r--r--net/http/http_cache.cc1359
-rw-r--r--net/http/http_cache.h200
-rw-r--r--net/http/http_cache_unittest.cc1012
-rw-r--r--net/http/http_chunked_decoder.cc136
-rw-r--r--net/http/http_chunked_decoder.h107
-rw-r--r--net/http/http_chunked_decoder_unittest.cc111
-rw-r--r--net/http/http_connection.cc64
-rw-r--r--net/http/http_connection.h94
-rw-r--r--net/http/http_connection_manager.cc199
-rw-r--r--net/http/http_connection_manager.h136
-rw-r--r--net/http/http_connection_manager_unittest.cc277
-rw-r--r--net/http/http_network_layer.cc99
-rw-r--r--net/http/http_network_layer.h68
-rw-r--r--net/http/http_network_layer_unittest.cc87
-rw-r--r--net/http/http_network_session.h62
-rw-r--r--net/http/http_network_transaction.cc673
-rw-r--r--net/http/http_network_transaction.h179
-rw-r--r--net/http/http_network_transaction_unittest.cc425
-rw-r--r--net/http/http_proxy_resolver_fixed.cc48
-rw-r--r--net/http/http_proxy_resolver_fixed.h54
-rw-r--r--net/http/http_proxy_resolver_winhttp.cc185
-rw-r--r--net/http/http_proxy_resolver_winhttp.h64
-rw-r--r--net/http/http_proxy_service.cc496
-rw-r--r--net/http/http_proxy_service.h303
-rw-r--r--net/http/http_proxy_service_unittest.cc299
-rw-r--r--net/http/http_request_info.h66
-rw-r--r--net/http/http_response_headers.cc936
-rw-r--r--net/http/http_response_headers.h295
-rw-r--r--net/http/http_response_headers_unittest.cc965
-rw-r--r--net/http/http_response_info.h67
-rw-r--r--net/http/http_transaction.h111
-rw-r--r--net/http/http_transaction_factory.h61
-rw-r--r--net/http/http_transaction_unittest.cc183
-rw-r--r--net/http/http_transaction_unittest.h346
-rw-r--r--net/http/http_transaction_winhttp.cc1807
-rw-r--r--net/http/http_transaction_winhttp.h222
-rw-r--r--net/http/http_transaction_winhttp_unittest.cc80
-rw-r--r--net/http/http_util.cc358
-rw-r--r--net/http/http_util.h173
-rw-r--r--net/http/http_util_unittest.cc159
-rw-r--r--net/http/http_vary_data.cc167
-rw-r--r--net/http/http_vary_data.h109
-rw-r--r--net/http/http_vary_data_unittest.cc154
-rw-r--r--net/http/winhttp_request_throttle.cc200
-rw-r--r--net/http/winhttp_request_throttle.h128
-rw-r--r--net/http/winhttp_request_throttle_unittest.cc136
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", &current_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