summaryrefslogtreecommitdiffstats
path: root/net/http/http_cache_transaction.cc
diff options
context:
space:
mode:
authorPatrick Scott <phanna@android.com>2010-02-04 10:37:17 -0500
committerPatrick Scott <phanna@android.com>2010-02-04 10:39:42 -0500
commitc7f5f8508d98d5952d42ed7648c2a8f30a4da156 (patch)
treedd51dbfbf6670daa61279b3a19e7b1835b301dbf /net/http/http_cache_transaction.cc
parent139d8152182f9093f03d9089822b688e49fa7667 (diff)
downloadexternal_chromium-c7f5f8508d98d5952d42ed7648c2a8f30a4da156.zip
external_chromium-c7f5f8508d98d5952d42ed7648c2a8f30a4da156.tar.gz
external_chromium-c7f5f8508d98d5952d42ed7648c2a8f30a4da156.tar.bz2
Initial source checkin.
The source files were determined by building net_unittests in chromium's source tree. Some of the obvious libraries were left out (v8, gmock, gtest). The Android.mk file has all the sources (minus unittests and tools) that were used during net_unittests compilation. Nothing builds yet because of STL but that is the next task. The .cpp files will most likely not compile anyways because of the LOCAL_CPP_EXTENSION mod. I will have to break this into multiple projects to get around that limitation.
Diffstat (limited to 'net/http/http_cache_transaction.cc')
-rw-r--r--net/http/http_cache_transaction.cc1760
1 files changed, 1760 insertions, 0 deletions
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
new file mode 100644
index 0000000..3be313f
--- /dev/null
+++ b/net/http/http_cache_transaction.cc
@@ -0,0 +1,1760 @@
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/http/http_cache_transaction.h"
+
+#include "base/compiler_specific.h"
+
+#if defined(OS_POSIX)
+#include <unistd.h>
+#endif
+
+#include "base/histogram.h"
+#include "base/ref_counted.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/load_log.h"
+#include "net/base/net_errors.h"
+#include "net/base/ssl_cert_request_info.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_request_info.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_util.h"
+#include "net/http/partial_data.h"
+
+using base::Time;
+
+namespace net {
+
+// disk cache entry data indices.
+enum {
+ kResponseInfoIndex,
+ kResponseContentIndex
+};
+
+//-----------------------------------------------------------------------------
+
+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[] = {
+ { "if-unmodified-since", NULL }, // causes unexpected 412s
+ { "if-match", NULL }, // causes unexpected 412s
+ { "if-range", NULL },
+ { NULL, NULL }
+};
+
+struct ValidationHeaderInfo {
+ const char* request_header_name;
+ const char* related_response_header_name;
+};
+
+static const ValidationHeaderInfo kValidationHeaders[] = {
+ { "if-modified-since", "last-modified" },
+ { "if-none-match", "etag" },
+};
+
+// 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;
+}
+
+//-----------------------------------------------------------------------------
+
+HttpCache::Transaction::Transaction(HttpCache* cache, bool enable_range_support)
+ : next_state_(STATE_NONE),
+ request_(NULL),
+ cache_(cache->AsWeakPtr()),
+ entry_(NULL),
+ new_entry_(NULL),
+ network_trans_(NULL),
+ callback_(NULL),
+ new_response_(NULL),
+ mode_(NONE),
+ target_state_(STATE_NONE),
+ reading_(false),
+ invalid_range_(false),
+ enable_range_support_(enable_range_support),
+ truncated_(false),
+ server_responded_206_(false),
+ cache_pending_(false),
+ read_offset_(0),
+ effective_load_flags_(0),
+ final_upload_progress_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ io_callback_(this, &Transaction::OnIOComplete)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ cache_callback_(new CancelableCompletionCallback<Transaction>(
+ this, &Transaction::OnIOComplete))),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ write_headers_callback_(new CancelableCompletionCallback<Transaction>(
+ this, &Transaction::OnIOComplete))) {
+ COMPILE_ASSERT(HttpCache::Transaction::kNumValidationHeaders ==
+ ARRAYSIZE_UNSAFE(kValidationHeaders),
+ Invalid_number_of_validation_headers);
+}
+
+#if defined(OS_WIN)
+#pragma optimize("", off)
+#pragma warning(disable:4748)
+#endif
+HttpCache::Transaction::~Transaction() {
+ // TODO(rvargas): remove this after finding the cause for bug 31723.
+ char local_obj[sizeof(*this)];
+ memcpy(local_obj, this, sizeof(local_obj));
+ if (cache_) {
+ if (entry_) {
+ bool cancel_request = reading_ && enable_range_support_;
+ if (cancel_request) {
+ if (partial_.get()) {
+ entry_->disk_entry->CancelSparseIO();
+ } else {
+ cancel_request &= (response_.headers->response_code() == 200);
+ }
+ }
+
+ cache_->DoneWithEntry(entry_, this, cancel_request);
+ } else if (cache_pending_) {
+ cache_->RemovePendingTransaction(this, &io_callback_);
+ }
+ }
+
+ // If there is an outstanding callback, mark it as cancelled so running it
+ // does nothing.
+ cache_callback_->Cancel();
+ write_headers_callback_->Cancel();
+
+ // We could still have a cache read or write in progress, so we just null the
+ // cache_ pointer to signal that we are dead. See DoCacheReadCompleted.
+ cache_.reset();
+}
+#if defined(OS_WIN)
+#pragma warning(default:4748)
+#pragma optimize("", on)
+#endif
+
+int HttpCache::Transaction::Start(const HttpRequestInfo* request,
+ CompletionCallback* callback,
+ LoadLog* load_log) {
+ DCHECK(request);
+ DCHECK(callback);
+
+ // Ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+ DCHECK(!reading_);
+ DCHECK(!network_trans_.get());
+ DCHECK(!entry_);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ SetRequest(load_log, request);
+
+ int rv;
+
+ if (!ShouldPassThrough()) {
+ 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;
+ }
+
+ // Downgrade to UPDATE if the request has been externally conditionalized.
+ if (external_validation_.initialized) {
+ if (mode_ & WRITE) {
+ // Strip off the READ_DATA bit (and maybe add back a READ_META bit
+ // in case READ was off).
+ mode_ = UPDATE;
+ } else {
+ mode_ = NONE;
+ }
+ }
+ }
+
+ // If must use cache, then we must fail. This can happen for back/forward
+ // navigations to a page generated via a form post.
+ if (!(mode_ & READ) && effective_load_flags_ & LOAD_ONLY_FROM_CACHE)
+ return ERR_CACHE_MISS;
+
+ if (mode_ == NONE) {
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ rv = BeginNetworkRequest();
+ } else {
+ 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_);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ int rv = RestartNetworkRequest();
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+int HttpCache::Transaction::RestartWithCertificate(
+ X509Certificate* client_cert,
+ CompletionCallback* callback) {
+ DCHECK(callback);
+
+ // Ensure that we only have one asynchronous call at a time.
+ DCHECK(!callback_);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ int rv = RestartNetworkRequestWithCertificate(client_cert);
+
+ 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_);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ // 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;
+}
+
+bool HttpCache::Transaction::IsReadyToRestartForAuth() {
+ if (!network_trans_.get())
+ return false;
+ return network_trans_->IsReadyToRestartForAuth();
+}
+
+int HttpCache::Transaction::Read(IOBuffer* buf, int buf_len,
+ CompletionCallback* callback) {
+ DCHECK(buf);
+ DCHECK_GT(buf_len, 0);
+ DCHECK(callback);
+
+ DCHECK(!callback_);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ // 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;
+ }
+
+ reading_ = true;
+ int rv;
+
+ switch (mode_) {
+ case READ_WRITE:
+ DCHECK(partial_.get());
+ if (!network_trans_.get()) {
+ // We are just reading from the cache, but we may be writing later.
+ rv = ReadFromEntry(buf, buf_len);
+ break;
+ }
+ case NONE:
+ case WRITE:
+ DCHECK(network_trans_.get());
+ rv = ReadFromNetwork(buf, buf_len);
+ break;
+ case READ:
+ rv = ReadFromEntry(buf, buf_len);
+ break;
+ default:
+ NOTREACHED();
+ rv = ERR_FAILED;
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ DCHECK(!callback_);
+ 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_.cert_request_info) ? &response_ : NULL;
+}
+
+LoadState HttpCache::Transaction::GetLoadState() const {
+ if (network_trans_.get())
+ 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_.get())
+ return network_trans_->GetUploadProgress();
+ return final_upload_progress_;
+}
+
+int HttpCache::Transaction::AddToEntry() {
+ next_state_ = STATE_INIT_ENTRY;
+ cache_pending_ = false;
+ return DoLoop(OK);
+}
+
+int HttpCache::Transaction::DoInitEntry() {
+ DCHECK(!new_entry_);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ if (mode_ == WRITE) {
+ next_state_ = STATE_DOOM_ENTRY;
+ return OK;
+ }
+
+ next_state_ = STATE_OPEN_ENTRY;
+ return OK;
+}
+
+int HttpCache::Transaction::DoDoomEntry() {
+ next_state_ = STATE_DOOM_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ // TODO(rvargas): Add a LoadLog event.
+ return cache_->DoomEntry(cache_key_, &io_callback_);
+}
+
+int HttpCache::Transaction::DoDoomEntryComplete(int result) {
+ next_state_ = STATE_CREATE_ENTRY;
+ cache_pending_ = false;
+ if (result == ERR_CACHE_RACE)
+ next_state_ = STATE_INIT_ENTRY;
+
+ return OK;
+}
+
+int HttpCache::Transaction::DoOpenEntry() {
+ DCHECK(!new_entry_);
+ next_state_ = STATE_OPEN_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY);
+ return cache_->OpenEntry(cache_key_, &new_entry_, &io_callback_);
+}
+
+int HttpCache::Transaction::DoOpenEntryComplete(int result) {
+ // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
+ // OK, otherwise the cache will end up with an active entry without any
+ // transaction attached.
+ LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY);
+ cache_pending_ = false;
+ if (result == OK) {
+ next_state_ = STATE_ADD_TO_ENTRY;
+ return OK;
+ }
+
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (mode_ == READ_WRITE) {
+ mode_ = WRITE;
+ next_state_ = STATE_CREATE_ENTRY;
+ return OK;
+ }
+ if (mode_ == UPDATE) {
+ // There is no cache entry to update; proceed without caching.
+ mode_ = NONE;
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+ }
+ if (cache_->mode() == PLAYBACK)
+ DLOG(INFO) << "Playback Cache Miss: " << request_->url;
+
+ // The entry does not exist, and we are not permitted to create a new entry,
+ // so we must fail.
+ return ERR_CACHE_MISS;
+}
+
+int HttpCache::Transaction::DoCreateEntry() {
+ DCHECK(!new_entry_);
+ next_state_ = STATE_CREATE_ENTRY_COMPLETE;
+ cache_pending_ = true;
+ LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY);
+ return cache_->CreateEntry(cache_key_, &new_entry_, &io_callback_);
+}
+
+int HttpCache::Transaction::DoCreateEntryComplete(int result) {
+ // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
+ // OK, otherwise the cache will end up with an active entry without any
+ // transaction attached.
+ LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY);
+ cache_pending_ = false;
+ next_state_ = STATE_ADD_TO_ENTRY;
+
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (result != OK) {
+ // We have a race here: Maybe we failed to open the entry and decided to
+ // create one, but by the time we called create, another transaction already
+ // created the entry. If we want to eliminate this issue, we need an atomic
+ // OpenOrCreate() method exposed by the disk cache.
+ DLOG(WARNING) << "Unable to create cache entry";
+ mode_ = NONE;
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ next_state_ = STATE_SEND_REQUEST;
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoAddToEntry() {
+ DCHECK(new_entry_);
+ cache_pending_ = true;
+ LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_WAITING);
+ int rv = cache_->AddTransactionToEntry(new_entry_, this);
+ new_entry_ = NULL;
+ return rv;
+}
+
+int HttpCache::Transaction::EntryAvailable(ActiveEntry* entry) {
+ LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_WAITING);
+ cache_pending_ = false;
+ entry_ = entry;
+
+ next_state_ = STATE_ENTRY_AVAILABLE;
+ if (new_entry_) {
+ // We are inside AddTransactionToEntry() so avoid reentering DoLoop().
+ DCHECK_EQ(new_entry_, entry);
+ return OK;
+ }
+ return DoLoop(OK);
+}
+
+int HttpCache::Transaction::DoEntryAvailable() {
+ DCHECK(!new_entry_);
+ if (mode_ == WRITE) {
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ next_state_ = STATE_SEND_REQUEST;
+ } else {
+ // We have to read the headers from the cached entry.
+ DCHECK(mode_ & READ_META);
+ next_state_ = STATE_CACHE_READ_RESPONSE;
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoCacheReadResponseComplete(int result) {
+ cache_callback_->Release(); // Balance the AddRef from DoCacheReadResponse.
+ LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_READ_INFO);
+ if (result != io_buf_len_ ||
+ !HttpCache::ParseResponseInfo(read_buf_->data(), io_buf_len_,
+ &response_, &truncated_)) {
+ DLOG(ERROR) << "ReadData failed: " << result;
+ return ERR_CACHE_READ_FAILURE;
+ }
+
+ // We now have access to the cache entry.
+ //
+ // 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.
+ //
+ // o if we are set to UPDATE, then we are handling an externally
+ // conditionalized request (if-modified-since / if-none-match). We check
+ // if the request headers define a validation request.
+ //
+ switch (mode_) {
+ case READ:
+ result = BeginCacheRead();
+ break;
+ case READ_WRITE:
+ result = BeginPartialCacheValidation();
+ break;
+ case UPDATE:
+ result = BeginExternallyConditionalizedRequest();
+ break;
+ case WRITE:
+ default:
+ NOTREACHED();
+ result = ERR_FAILED;
+ }
+ return result;
+}
+
+bool HttpCache::Transaction::AddTruncatedFlag() {
+ DCHECK(mode_ & WRITE);
+
+ // Don't set the flag for sparse entries.
+ if (partial_.get() && !truncated_)
+ return true;
+
+ // Double check that there is something worth keeping.
+ if (!entry_->disk_entry->GetDataSize(kResponseContentIndex))
+ return false;
+
+ if (response_.headers->GetContentLength() <= 0 ||
+ response_.headers->HasHeaderValue("Accept-Ranges", "none") ||
+ !response_.headers->HasStrongValidators())
+ return false;
+
+ truncated_ = true;
+ target_state_ = STATE_NONE;
+ next_state_ = STATE_CACHE_WRITE_TRUNCATED_RESPONSE;
+ DoLoop(OK);
+ return true;
+}
+
+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;
+}
+
+int HttpCache::Transaction::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ rv = DoSendRequest();
+ break;
+ case STATE_SEND_REQUEST_COMPLETE:
+ rv = DoSendRequestComplete(rv);
+ break;
+ case STATE_SUCCESSFUL_SEND_REQUEST:
+ DCHECK_EQ(OK, rv);
+ rv = DoSuccessfulSendRequest();
+ break;
+ case STATE_NETWORK_READ:
+ DCHECK_EQ(OK, rv);
+ rv = DoNetworkRead();
+ break;
+ case STATE_NETWORK_READ_COMPLETE:
+ rv = DoNetworkReadComplete(rv);
+ break;
+ case STATE_INIT_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoInitEntry();
+ break;
+ case STATE_OPEN_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoOpenEntry();
+ break;
+ case STATE_OPEN_ENTRY_COMPLETE:
+ rv = DoOpenEntryComplete(rv);
+ break;
+ case STATE_CREATE_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoCreateEntry();
+ break;
+ case STATE_CREATE_ENTRY_COMPLETE:
+ rv = DoCreateEntryComplete(rv);
+ break;
+ case STATE_DOOM_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoDoomEntry();
+ break;
+ case STATE_DOOM_ENTRY_COMPLETE:
+ rv = DoDoomEntryComplete(rv);
+ break;
+ case STATE_ADD_TO_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoAddToEntry();
+ break;
+ case STATE_ENTRY_AVAILABLE:
+ DCHECK_EQ(OK, rv);
+ rv = DoEntryAvailable();
+ break;
+ case STATE_PARTIAL_CACHE_VALIDATION:
+ DCHECK_EQ(OK, rv);
+ rv = DoPartialCacheValidation();
+ break;
+ case STATE_UPDATE_CACHED_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoUpdateCachedResponse();
+ break;
+ case STATE_UPDATE_CACHED_RESPONSE_COMPLETE:
+ rv = DoUpdateCachedResponseComplete(rv);
+ break;
+ case STATE_OVERWRITE_CACHED_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoOverwriteCachedResponse();
+ break;
+ case STATE_TRUNCATE_CACHED_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoTruncateCachedData();
+ break;
+ case STATE_TRUNCATE_CACHED_DATA_COMPLETE:
+ rv = DoTruncateCachedDataComplete(rv);
+ break;
+ case STATE_PARTIAL_HEADERS_RECEIVED:
+ DCHECK_EQ(OK, rv);
+ rv = DoPartialHeadersReceived();
+ break;
+ case STATE_CACHE_READ_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheReadResponse();
+ break;
+ case STATE_CACHE_READ_RESPONSE_COMPLETE:
+ rv = DoCacheReadResponseComplete(rv);
+ break;
+ case STATE_CACHE_WRITE_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheWriteResponse();
+ break;
+ case STATE_CACHE_WRITE_TRUNCATED_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheWriteTruncatedResponse();
+ break;
+ case STATE_CACHE_WRITE_RESPONSE_COMPLETE:
+ rv = DoCacheWriteResponseComplete(rv);
+ break;
+ case STATE_CACHE_QUERY_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheQueryData();
+ break;
+ case STATE_CACHE_QUERY_DATA_COMPLETE:
+ rv = DoCacheQueryDataComplete(rv);
+ break;
+ case STATE_CACHE_READ_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoCacheReadData();
+ break;
+ case STATE_CACHE_READ_DATA_COMPLETE:
+ rv = DoCacheReadDataComplete(rv);
+ break;
+ case STATE_CACHE_WRITE_DATA:
+ rv = DoCacheWriteData(rv);
+ break;
+ case STATE_CACHE_WRITE_DATA_COMPLETE:
+ rv = DoCacheWriteDataComplete(rv);
+ break;
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ if (rv != ERR_IO_PENDING)
+ HandleResult(rv);
+
+ return rv;
+}
+
+void HttpCache::Transaction::SetRequest(LoadLog* load_log,
+ const HttpRequestInfo* request) {
+ load_log_ = load_log;
+ request_ = request;
+ effective_load_flags_ = request_->load_flags;
+
+ switch (cache_->mode()) {
+ case NORMAL:
+ break;
+ case RECORD:
+ // 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.
+ effective_load_flags_ |= LOAD_BYPASS_CACHE;
+ break;
+ case PLAYBACK:
+ // When in playback mode, we want to load exclusively from the cache.
+ effective_load_flags_ |= LOAD_ONLY_FROM_CACHE;
+ break;
+ case DISABLE:
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ break;
+ }
+
+ // 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 },
+ };
+
+ std::string new_extra_headers;
+ bool range_found = false;
+ bool external_validation_error = false;
+
+ // 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()) {
+ if (!LowerCaseEqualsASCII(it.name(), "range")) {
+ new_extra_headers.append(it.name_begin(), it.values_end());
+ new_extra_headers.append("\r\n");
+ } else {
+ if (enable_range_support_) {
+ range_found = true;
+ } else {
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ continue;
+ }
+ }
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSpecialHeaders); ++i) {
+ if (HeaderMatches(it, kSpecialHeaders[i].search)) {
+ effective_load_flags_ |= kSpecialHeaders[i].load_flag;
+ break;
+ }
+ }
+
+ // Check for conditionalization headers which may correspond with a
+ // cache validation request.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidationHeaders); ++i) {
+ const ValidationHeaderInfo& info = kValidationHeaders[i];
+ if (LowerCaseEqualsASCII(it.name_begin(), it.name_end(),
+ info.request_header_name)) {
+ if (!external_validation_.values[i].empty() || it.values().empty())
+ external_validation_error = true;
+ external_validation_.values[i] = it.values();
+ external_validation_.initialized = true;
+ break;
+ }
+ }
+ }
+
+ // We don't support ranges and validation headers.
+ if (range_found && external_validation_.initialized) {
+ LOG(WARNING) << "Byte ranges AND validation headers found.";
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ }
+
+ // If there is more than one validation header, we can't treat this request as
+ // a cache validation, since we don't know for sure which header the server
+ // will give us a response for (and they could be contradictory).
+ if (external_validation_error) {
+ LOG(WARNING) << "Multiple or malformed validation headers found.";
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ }
+
+ if (range_found && !(effective_load_flags_ & LOAD_DISABLE_CACHE)) {
+ partial_.reset(new PartialData);
+ if (partial_->Init(request_->extra_headers)) {
+ // We will be modifying the actual range requested to the server, so
+ // let's remove the header here.
+ custom_request_.reset(new HttpRequestInfo(*request_));
+ request_ = custom_request_.get();
+ custom_request_->extra_headers = new_extra_headers;
+ partial_->SetHeaders(new_extra_headers);
+ } else {
+ // The range is invalid or we cannot handle it properly.
+ LOG(INFO) << "Invalid byte range found.";
+ effective_load_flags_ |= LOAD_DISABLE_CACHE;
+ partial_.reset(NULL);
+ }
+ }
+}
+
+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_.get())
+ 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;
+
+ if (request_->method == "GET")
+ return false;
+
+ if (request_->method == "POST" &&
+ request_->upload_data && request_->upload_data->identifier())
+ return false;
+
+ // TODO(darin): add support for caching HEAD responses
+ return true;
+}
+
+int HttpCache::Transaction::BeginCacheRead() {
+ // We don't support any combination of LOAD_ONLY_FROM_CACHE and byte ranges.
+ if (response_.headers->response_code() == 206 || partial_.get()) {
+ NOTREACHED();
+ return ERR_CACHE_MISS;
+ }
+
+ // We don't have the whole resource.
+ if (truncated_)
+ return ERR_CACHE_MISS;
+
+ return OK;
+}
+
+int HttpCache::Transaction::BeginCacheValidation() {
+ DCHECK(mode_ == READ_WRITE);
+
+ if ((effective_load_flags_ & LOAD_PREFERRING_CACHE ||
+ !RequiresValidation()) && !partial_.get()) {
+ 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;
+ next_state_ = STATE_SEND_REQUEST;
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::BeginPartialCacheValidation() {
+ DCHECK(mode_ == READ_WRITE);
+
+ if (response_.headers->response_code() != 206 && !partial_.get() &&
+ !truncated_)
+ return BeginCacheValidation();
+
+ if (!enable_range_support_)
+ return BeginCacheValidation();
+
+ bool byte_range_requested = partial_.get() != NULL;
+ if (byte_range_requested) {
+ next_state_ = STATE_CACHE_QUERY_DATA;
+ return OK;
+ }
+ // The request is not for a range, but we have stored just ranges.
+ partial_.reset(new PartialData());
+ partial_->SetHeaders(request_->extra_headers);
+ if (!custom_request_.get()) {
+ custom_request_.reset(new HttpRequestInfo(*request_));
+ request_ = custom_request_.get();
+ }
+
+ return ValidateEntryHeadersAndContinue(false);
+}
+
+int HttpCache::Transaction::DoCacheQueryData() {
+ next_state_ = STATE_CACHE_QUERY_DATA_COMPLETE;
+
+ // Balanced in ValidateEntryHeadersAndContinue.
+ cache_callback_->AddRef();
+ return entry_->disk_entry->ReadyForSparseIO(cache_callback_);
+}
+
+int HttpCache::Transaction::DoCacheQueryDataComplete(int result) {
+ DCHECK_EQ(OK, result);
+ // Balance the AddRef from BeginPartialCacheValidation.
+ cache_callback_->Release();
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ return ValidateEntryHeadersAndContinue(true);
+}
+
+int HttpCache::Transaction::ValidateEntryHeadersAndContinue(
+ bool byte_range_requested) {
+ DCHECK(mode_ == READ_WRITE);
+
+ if (!partial_->UpdateFromStoredHeaders(response_.headers, entry_->disk_entry,
+ truncated_)) {
+ // The stored data cannot be used. Get rid of it and restart this request.
+ // We need to also reset the |truncated_| flag as a new entry is created.
+ DoomPartialEntry(!byte_range_requested);
+ mode_ = WRITE;
+ truncated_ = false;
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (!partial_->IsRequestedRangeOK()) {
+ // The stored data is fine, but the request may be invalid.
+ invalid_range_ = true;
+ }
+
+ next_state_ = STATE_PARTIAL_CACHE_VALIDATION;
+ return OK;
+}
+
+int HttpCache::Transaction::DoPartialCacheValidation() {
+ if (mode_ == NONE)
+ return OK;
+
+ int rv = partial_->PrepareCacheValidation(entry_->disk_entry,
+ &custom_request_->extra_headers);
+
+ if (!rv) {
+ // This is the end of the request.
+ if (mode_ & WRITE) {
+ DoneWritingToEntry(true);
+ } else {
+ cache_->DoneReadingFromEntry(entry_, this);
+ entry_ = NULL;
+ }
+ return rv;
+ }
+
+ if (rv < 0) {
+ DCHECK(rv != ERR_IO_PENDING);
+ return rv;
+ }
+
+ if (reading_ && partial_->IsCurrentRangeCached()) {
+ next_state_ = STATE_CACHE_READ_DATA;
+ return OK;
+ }
+
+ return BeginCacheValidation();
+}
+
+int HttpCache::Transaction::BeginExternallyConditionalizedRequest() {
+ DCHECK_EQ(UPDATE, mode_);
+ DCHECK(external_validation_.initialized);
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidationHeaders); i++) {
+ if (external_validation_.values[i].empty())
+ continue;
+ // Retrieve either the cached response's "etag" or "last-modified" header.
+ std::string validator;
+ response_.headers->EnumerateHeader(
+ NULL,
+ kValidationHeaders[i].related_response_header_name,
+ &validator);
+
+ if (response_.headers->response_code() != 200 || truncated_ ||
+ validator.empty() || validator != external_validation_.values[i]) {
+ // The externally conditionalized request is not a validation request
+ // for our existing cache entry. Proceed with caching disabled.
+ DoneWritingToEntry(true);
+ }
+ }
+
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+}
+
+int HttpCache::Transaction::BeginNetworkRequest() {
+ next_state_ = STATE_SEND_REQUEST;
+ return DoLoop(OK);
+}
+
+int HttpCache::Transaction::DoSendRequest() {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(!network_trans_.get());
+
+ // Create a network transaction.
+ int rv = cache_->network_layer_->CreateTransaction(&network_trans_);
+ if (rv != OK)
+ return rv;
+
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ rv = network_trans_->Start(request_, &io_callback_, load_log_);
+ return rv;
+}
+
+int HttpCache::Transaction::RestartNetworkRequest() {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(network_trans_.get());
+ DCHECK_EQ(STATE_NONE, next_state_);
+
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ int rv = network_trans_->RestartIgnoringLastError(&io_callback_);
+ if (rv != ERR_IO_PENDING)
+ return DoLoop(rv);
+ return rv;
+}
+
+int HttpCache::Transaction::RestartNetworkRequestWithCertificate(
+ X509Certificate* client_cert) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(network_trans_.get());
+ DCHECK_EQ(STATE_NONE, next_state_);
+
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ int rv = network_trans_->RestartWithCertificate(client_cert, &io_callback_);
+ if (rv != ERR_IO_PENDING)
+ return DoLoop(rv);
+ return rv;
+}
+
+int HttpCache::Transaction::RestartNetworkRequestWithAuth(
+ const std::wstring& username,
+ const std::wstring& password) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+ DCHECK(network_trans_.get());
+ DCHECK_EQ(STATE_NONE, next_state_);
+
+ next_state_ = STATE_SEND_REQUEST_COMPLETE;
+ int rv = network_trans_->RestartWithAuth(username, password, &io_callback_);
+ if (rv != ERR_IO_PENDING)
+ return DoLoop(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 (cache_->mode() == net::HttpCache::PLAYBACK)
+ return false;
+
+ if (effective_load_flags_ & LOAD_VALIDATE_CACHE)
+ return true;
+
+ if (response_.headers->response_code() == 206 && !enable_range_support_)
+ 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);
+
+ if (!enable_range_support_ && response_.headers->response_code() != 200) {
+ // This only makes sense for cached 200 responses.
+ return false;
+ }
+
+ // This only makes sense for cached 200 or 206 responses.
+ if (response_.headers->response_code() != 200 &&
+ response_.headers->response_code() != 206)
+ 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;
+
+ if (!partial_.get()) {
+ // Need to customize the request, so this forces us to allocate :(
+ custom_request_.reset(new HttpRequestInfo(*request_));
+ request_ = custom_request_.get();
+ }
+ DCHECK(custom_request_.get());
+
+ if (!etag_value.empty()) {
+ if (partial_.get() && !partial_->IsCurrentRangeCached()) {
+ // We don't want to switch to WRITE mode if we don't have this block of a
+ // byte-range request because we may have other parts cached.
+ custom_request_->extra_headers.append("If-Range: ");
+ } else {
+ custom_request_->extra_headers.append("If-None-Match: ");
+ }
+ custom_request_->extra_headers.append(etag_value);
+ custom_request_->extra_headers.append("\r\n");
+ // For byte-range requests, make sure that we use only one way to validate
+ // the request.
+ if (partial_.get() && !partial_->IsCurrentRangeCached())
+ return true;
+ }
+
+ if (!last_modified_value.empty()) {
+ if (partial_.get() && !partial_->IsCurrentRangeCached()) {
+ custom_request_->extra_headers.append("If-Range: ");
+ } else {
+ 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;
+}
+
+// We just received some headers from the server. We may have asked for a range,
+// in which case partial_ has an object. This could be the first network request
+// we make to fulfill the original request, or we may be already reading (from
+// the net and / or the cache). If we are not expecting a certain response, we
+// just bypass the cache for this request (but again, maybe we are reading), and
+// delete partial_ (so we are not able to "fix" the headers that we return to
+// the user). This results in either a weird response for the caller (we don't
+// expect it after all), or maybe a range that was not exactly what it was asked
+// for.
+//
+// If the server is simply telling us that the resource has changed, we delete
+// the cached entry and restart the request as the caller intended (by returning
+// false from this method). However, we may not be able to do that at any point,
+// for instance if we already returned the headers to the user.
+//
+// WARNING: Whenever this code returns false, it has to make sure that the next
+// time it is called it will return true so that we don't keep retrying the
+// request.
+bool HttpCache::Transaction::ValidatePartialResponse(
+ const HttpResponseHeaders* headers, bool* partial_content) {
+ int response_code = headers->response_code();
+ bool partial_response = enable_range_support_ ? response_code == 206 : false;
+ *partial_content = false;
+
+ if (!entry_)
+ return true;
+
+ if (invalid_range_) {
+ // We gave up trying to match this request with the stored data. If the
+ // server is ok with the request, delete the entry, otherwise just ignore
+ // this request
+ if (partial_response || response_code == 200 || response_code == 304) {
+ DoomPartialEntry(true);
+ mode_ = NONE;
+ } else {
+ IgnoreRangeRequest();
+ }
+ return true;
+ }
+
+ if (!partial_.get()) {
+ // We are not expecting 206 but we may have one.
+ if (partial_response)
+ IgnoreRangeRequest();
+
+ return true;
+ }
+
+ // TODO(rvargas): Do we need to consider other results here?.
+ bool failure = response_code == 200 || response_code == 416;
+
+ if (partial_->IsCurrentRangeCached()) {
+ // We asked for "If-None-Match: " so a 206 means a new object.
+ if (partial_response)
+ failure = true;
+
+ if (response_code == 304 && partial_->ResponseHeadersOK(headers))
+ return true;
+ } else {
+ // We asked for "If-Range: " so a 206 means just another range.
+ if (partial_response && partial_->ResponseHeadersOK(headers)) {
+ *partial_content = true;
+ return true;
+ }
+
+ // 304 is not expected here, but we'll spare the entry (unless it was
+ // truncated).
+ if (truncated_)
+ failure = true;
+ }
+
+ if (failure) {
+ // We cannot truncate this entry, it has to be deleted.
+ DoomPartialEntry(false);
+ mode_ = NONE;
+ if (!reading_ && !partial_->IsLastRange()) {
+ // We'll attempt to issue another network request, this time without us
+ // messing up the headers.
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
+ partial_.reset();
+ truncated_ = false;
+ return false;
+ }
+ LOG(WARNING) << "Failed to revalidate partial entry";
+ partial_.reset();
+ return true;
+ }
+
+ IgnoreRangeRequest();
+ return true;
+}
+
+void HttpCache::Transaction::IgnoreRangeRequest() {
+ // We have a problem. We may or may not be reading already (in which case we
+ // returned the headers), but we'll just pretend that this request is not
+ // using the cache and see what happens. Most likely this is the first
+ // response from the server (it's not changing its mind midway, right?).
+ if (mode_ & WRITE) {
+ DoneWritingToEntry(mode_ != WRITE);
+ } else if (mode_ & READ && entry_) {
+ cache_->DoneReadingFromEntry(entry_, this);
+ }
+
+ partial_.reset(NULL);
+ entry_ = NULL;
+ mode_ = NONE;
+}
+
+int HttpCache::Transaction::ReadFromNetwork(IOBuffer* data, int data_len) {
+ read_buf_ = data;
+ io_buf_len_ = data_len;
+ next_state_ = STATE_NETWORK_READ;
+ return DoLoop(OK);
+}
+
+int HttpCache::Transaction::DoNetworkRead() {
+ next_state_ = STATE_NETWORK_READ_COMPLETE;
+ return network_trans_->Read(read_buf_, io_buf_len_, &io_callback_);
+}
+
+int HttpCache::Transaction::ReadFromEntry(IOBuffer* data, int data_len) {
+ read_buf_ = data;
+ io_buf_len_ = data_len;
+ next_state_ = STATE_CACHE_READ_DATA;
+ return DoLoop(OK);
+}
+
+int HttpCache::Transaction::DoCacheReadData() {
+ DCHECK(entry_);
+ next_state_ = STATE_CACHE_READ_DATA_COMPLETE;
+ cache_callback_->AddRef(); // Balanced in DoCacheReadDataComplete.
+ if (partial_.get()) {
+ return partial_->CacheRead(entry_->disk_entry, read_buf_, io_buf_len_,
+ cache_callback_);
+ }
+
+ return entry_->disk_entry->ReadData(kResponseContentIndex, read_offset_,
+ read_buf_, io_buf_len_, cache_callback_);
+}
+
+int HttpCache::Transaction::DoCacheReadResponse() {
+ DCHECK(entry_);
+ next_state_ = STATE_CACHE_READ_RESPONSE_COMPLETE;
+
+ io_buf_len_ = entry_->disk_entry->GetDataSize(kResponseInfoIndex);
+ read_buf_ = new IOBuffer(io_buf_len_);
+
+ LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_READ_INFO);
+ cache_callback_->AddRef(); // Balanced in DoCacheReadResponseComplete.
+ return entry_->disk_entry->ReadData(kResponseInfoIndex, 0, read_buf_,
+ io_buf_len_, cache_callback_);
+}
+
+int HttpCache::Transaction::WriteToEntry(int index, int offset,
+ IOBuffer* data, int data_len,
+ CompletionCallback* callback) {
+ if (!entry_)
+ return data_len;
+
+ int rv = 0;
+ if (!partial_.get() || !data_len) {
+ rv = entry_->disk_entry->WriteData(index, offset, data, data_len, callback,
+ true);
+ } else {
+ rv = partial_->CacheWrite(entry_->disk_entry, data, data_len, callback);
+ }
+
+ if (rv != ERR_IO_PENDING && rv != data_len) {
+ DLOG(ERROR) << "failed to write response data to cache";
+ DoneWritingToEntry(false);
+
+ // We want to ignore errors writing to disk and just keep reading from
+ // the network.
+ rv = data_len;
+ }
+ return rv;
+}
+
+int HttpCache::Transaction::DoCacheWriteResponse() {
+ return WriteResponseInfoToEntry(false);
+}
+
+int HttpCache::Transaction::DoCacheWriteTruncatedResponse() {
+ return WriteResponseInfoToEntry(true);
+}
+
+int HttpCache::Transaction::WriteResponseInfoToEntry(bool truncated) {
+ next_state_ = STATE_CACHE_WRITE_RESPONSE_COMPLETE;
+ if (!entry_)
+ return OK;
+
+ // 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 OK;
+ }
+
+ // 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 (truncated) {
+ DCHECK_EQ(200, response_.headers->response_code());
+ }
+
+ scoped_refptr<PickledIOBuffer> data = new PickledIOBuffer();
+ response_.Persist(data->pickle(), skip_transient_headers, truncated);
+ data->Done();
+
+ // Balanced in DoCacheWriteResponseComplete. We may be running from the
+ // destructor of this object so cache_callback_ may be currently in use.
+ write_headers_callback_->AddRef();
+ io_buf_len_ = data->pickle()->size();
+ return entry_->disk_entry->WriteData(kResponseInfoIndex, 0, data, io_buf_len_,
+ write_headers_callback_, true);
+}
+
+int HttpCache::Transaction::DoCacheWriteResponseComplete(int result) {
+ next_state_ = target_state_;
+ target_state_ = STATE_NONE;
+ if (!entry_)
+ return OK;
+
+ // Balance the AddRef from WriteResponseInfoToEntry.
+ write_headers_callback_->Release();
+ if (result != io_buf_len_) {
+ DLOG(ERROR) << "failed to write response info to cache";
+ DoneWritingToEntry(false);
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::AppendResponseDataToEntry(
+ IOBuffer* data, int data_len, CompletionCallback* callback) {
+ if (!entry_ || !data_len)
+ return data_len;
+
+ int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex);
+ return WriteToEntry(kResponseContentIndex, current_size, data, data_len,
+ callback);
+}
+
+int HttpCache::Transaction::DoTruncateCachedData() {
+ next_state_ = STATE_TRUNCATE_CACHED_DATA_COMPLETE;
+ if (!entry_)
+ return OK;
+
+ // Truncate the stream.
+ int rv = WriteToEntry(kResponseContentIndex, 0, NULL, 0, NULL);
+ DCHECK(rv != ERR_IO_PENDING);
+ return OK;
+}
+
+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::DoomPartialEntry(bool delete_object) {
+ int rv = cache_->DoomEntry(cache_key_, NULL);
+ DCHECK_EQ(OK, rv);
+ cache_->DoneWithEntry(entry_, this, false);
+ entry_ = NULL;
+ if (delete_object)
+ partial_.reset(NULL);
+}
+
+int HttpCache::Transaction::DoNetworkReadComplete(int result) {
+ DCHECK(mode_ & WRITE || mode_ == NONE);
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ next_state_ = STATE_CACHE_WRITE_DATA;
+ return result;
+}
+
+int HttpCache::Transaction::DoCacheWriteData(int num_bytes) {
+ next_state_ = STATE_CACHE_WRITE_DATA_COMPLETE;
+ cache_callback_->AddRef(); // Balanced in DoCacheWriteDataComplete.
+
+ return AppendResponseDataToEntry(read_buf_, num_bytes, cache_callback_);
+}
+
+int HttpCache::Transaction::DoPartialNetworkReadCompleted(int result) {
+ partial_->OnNetworkReadCompleted(result);
+
+ if (result == 0) {
+ // We need to move on to the next range.
+ network_trans_.reset();
+ next_state_ = STATE_PARTIAL_CACHE_VALIDATION;
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoCacheReadDataComplete(int result) {
+ cache_callback_->Release(); // Balance the AddRef from DoCacheReadData.
+
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ if (partial_.get())
+ return DoPartialCacheReadCompleted(result);
+
+ if (result > 0) {
+ read_offset_ += result;
+ } else if (result == 0) { // End of file.
+ cache_->DoneReadingFromEntry(entry_, this);
+ entry_ = NULL;
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoPartialCacheReadCompleted(int result) {
+ partial_->OnCacheReadCompleted(result);
+
+ if (result == 0 && mode_ == READ_WRITE) {
+ // We need to move on to the next range.
+ next_state_ = STATE_PARTIAL_CACHE_VALIDATION;
+ }
+ return result;
+}
+
+int HttpCache::Transaction::DoCacheWriteDataComplete(int result) {
+ // Balance the AddRef from DoCacheWriteData.
+ cache_callback_->Release();
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ if (result < 0)
+ return result;
+
+ if (partial_.get()) {
+ // This may be the last request.
+ if (!(result == 0 && !truncated_ &&
+ (partial_->IsLastRange() || mode_ == WRITE)))
+ return DoPartialNetworkReadCompleted(result);
+ }
+
+ if (result == 0) // End of file.
+ DoneWritingToEntry(true);
+
+ return result;
+}
+
+// We received the response headers and there is no error.
+int HttpCache::Transaction::DoSuccessfulSendRequest() {
+ DCHECK(!new_response_);
+ const HttpResponseInfo* new_response = network_trans_->GetResponseInfo();
+ if (new_response->headers->response_code() == 401 ||
+ new_response->headers->response_code() == 407) {
+ auth_response_ = *new_response;
+ return OK;
+ }
+
+ if (!ValidatePartialResponse(new_response->headers, &server_responded_206_) &&
+ !auth_response_.headers) {
+ // Something went wrong with this request and we have to restart it.
+ // If we have an authentication response, we are exposed to weird things
+ // hapenning if the user cancels the authentication before we receive
+ // the new response.
+ response_ = HttpResponseInfo();
+ network_trans_.reset();
+ next_state_ = STATE_SEND_REQUEST;
+ return OK;
+ }
+ if (server_responded_206_ && mode_ == READ_WRITE && !truncated_ &&
+ response_.headers->response_code() == 200) {
+ // We have stored the full entry, but it changed and the server is
+ // sending a range. We have to delete the old entry.
+ DoneWritingToEntry(false);
+ }
+
+ HistogramHeaders(new_response->headers);
+
+ new_response_ = new_response;
+ // Are we expecting a response to a conditional query?
+ if (mode_ == READ_WRITE || mode_ == UPDATE) {
+ if (new_response->headers->response_code() == 304 ||
+ server_responded_206_) {
+ next_state_ = STATE_UPDATE_CACHED_RESPONSE;
+ return OK;
+ }
+ mode_ = WRITE;
+ }
+
+ next_state_ = STATE_OVERWRITE_CACHED_RESPONSE;
+ return OK;
+}
+
+// We received 304 or 206 and we want to update the cached response headers.
+int HttpCache::Transaction::DoUpdateCachedResponse() {
+ next_state_ = STATE_UPDATE_CACHED_RESPONSE_COMPLETE;
+ int rv = OK;
+ // Update cached response based on headers in new_response.
+ // TODO(wtc): should we update cached certificate (response_.ssl_info), too?
+ response_.headers->Update(*new_response_->headers);
+ response_.response_time = new_response_->response_time;
+ response_.request_time = new_response_->request_time;
+
+ if (response_.headers->HasHeaderValue("cache-control", "no-store")) {
+ int ret = cache_->DoomEntry(cache_key_, NULL);
+ DCHECK_EQ(OK, ret);
+ } else {
+ // If we are already reading, we already updated the headers for this
+ // request; doing it again will change Content-Length.
+ if (!reading_) {
+ target_state_ = STATE_UPDATE_CACHED_RESPONSE_COMPLETE;
+ next_state_ = STATE_CACHE_WRITE_RESPONSE;
+ rv = OK;
+ }
+ }
+ return rv;
+}
+
+int HttpCache::Transaction::DoUpdateCachedResponseComplete(int result) {
+ if (mode_ == UPDATE) {
+ DCHECK(!server_responded_206_);
+ // We got a "not modified" response and already updated the corresponding
+ // cache entry above.
+ //
+ // By closing the cached entry now, we make sure that the 304 rather than
+ // the cached 200 response, is what will be returned to the user.
+ DoneWritingToEntry(true);
+ } else if (entry_ && !server_responded_206_) {
+ DCHECK_EQ(READ_WRITE, mode_);
+ if (!partial_.get() || partial_->IsLastRange()) {
+ cache_->ConvertWriterToReader(entry_);
+ mode_ = READ;
+ }
+ // We no longer need the network transaction, so destroy it.
+ final_upload_progress_ = network_trans_->GetUploadProgress();
+ network_trans_.reset();
+ }
+ next_state_ = STATE_OVERWRITE_CACHED_RESPONSE;
+ return OK;
+}
+
+int HttpCache::Transaction::DoOverwriteCachedResponse() {
+ if (mode_ & READ) {
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+ }
+
+ // We change the value of Content-Length for partial content.
+ if (server_responded_206_ && partial_.get())
+ partial_->FixContentLength(new_response_->headers);
+
+ response_ = *new_response_;
+ target_state_ = STATE_TRUNCATE_CACHED_DATA;
+ next_state_ = truncated_ ? STATE_CACHE_WRITE_TRUNCATED_RESPONSE :
+ STATE_CACHE_WRITE_RESPONSE;
+ return OK;
+}
+
+int HttpCache::Transaction::DoTruncateCachedDataComplete(int result) {
+ // 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);
+ next_state_ = STATE_PARTIAL_HEADERS_RECEIVED;
+ return OK;
+}
+
+int HttpCache::Transaction::DoPartialHeadersReceived() {
+ new_response_ = NULL;
+ if (!partial_.get())
+ return OK;
+
+ if (reading_) {
+ if (network_trans_.get()) {
+ next_state_ = STATE_NETWORK_READ;
+ } else {
+ next_state_ = STATE_CACHE_READ_DATA;
+ }
+ } else if (mode_ != NONE) {
+ // We are about to return the headers for a byte-range request to the user,
+ // so let's fix them.
+ partial_->FixResponseHeaders(response_.headers);
+ }
+ return OK;
+}
+
+int HttpCache::Transaction::DoSendRequestComplete(int result) {
+ if (!cache_)
+ return ERR_UNEXPECTED;
+
+ if (result == OK) {
+ next_state_ = STATE_SUCCESSFUL_SEND_REQUEST;
+ return OK;
+ }
+
+ if (IsCertificateError(result)) {
+ const HttpResponseInfo* response = network_trans_->GetResponseInfo();
+ // If we get a certificate error, then there is a certificate in ssl_info,
+ // so GetResponseInfo() should never returns NULL here.
+ DCHECK(response);
+ response_.ssl_info = response->ssl_info;
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ const HttpResponseInfo* response = network_trans_->GetResponseInfo();
+ DCHECK(response);
+ response_.cert_request_info = response->cert_request_info;
+ }
+ return result;
+}
+
+// For a 200 response we'll add a histogram with one bit set per header:
+// 0x01 Content-Length
+// 0x02 Date
+// 0x04 Last-Modified
+// 0x08 Etag
+// 0x10 Accept-Ranges: bytes
+// 0x20 Accept-Ranges: none
+//
+// TODO(rvargas): remove after having some results.
+void HttpCache::Transaction::HistogramHeaders(
+ const HttpResponseHeaders* headers) {
+ if (headers->response_code() != 200)
+ return;
+
+ int64 content_length = headers->GetContentLength();
+ int value = 0;
+ if (content_length > 0)
+ value = 1;
+
+ Time date;
+ if (headers->GetDateValue(&date))
+ value += 2;
+ if (headers->GetLastModifiedValue(&date))
+ value += 4;
+
+ std::string etag;
+ headers->EnumerateHeader(NULL, "etag", &etag);
+ if (!etag.empty())
+ value += 8;
+
+ std::string accept_ranges("Accept-Ranges");
+ if (headers->HasHeaderValue(accept_ranges, "bytes"))
+ value += 0x10;
+ if (headers->HasHeaderValue(accept_ranges, "none"))
+ value += 0x20;
+
+ // |value| goes from 0 to 63. Actually, the max value should be 47 (0x2f)
+ // but we'll see.
+ UMA_HISTOGRAM_ENUMERATION("HttpCache.ResponseHeaders", value, 65);
+}
+
+void HttpCache::Transaction::OnIOComplete(int result) {
+ DoLoop(result);
+}
+
+} // namespace net