diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 22:42:52 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 22:42:52 +0000 |
commit | 586acc5fe142f498261f52c66862fa417c3d52d2 (patch) | |
tree | c98b3417a883f2477029c8cd5888f4078681e24e /net/http/http_cache.cc | |
parent | a814a8d55429605fe6d7045045cd25b6bf624580 (diff) | |
download | chromium_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.cc | 1359 |
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 |