summaryrefslogtreecommitdiffstats
path: root/net/http/http_cache.cc
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 22:42:52 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 22:42:52 +0000
commit586acc5fe142f498261f52c66862fa417c3d52d2 (patch)
treec98b3417a883f2477029c8cd5888f4078681e24e /net/http/http_cache.cc
parenta814a8d55429605fe6d7045045cd25b6bf624580 (diff)
downloadchromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.zip
chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.tar.gz
chromium_src-586acc5fe142f498261f52c66862fa417c3d52d2.tar.bz2
Add net to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@14 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/http/http_cache.cc')
-rw-r--r--net/http/http_cache.cc1359
1 files changed, 1359 insertions, 0 deletions
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