// Copyright (c) 2006-2008 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.h" #include "base/hash_tables.h" #include "base/message_loop.h" #include "base/platform_file.h" #include "base/string_util.h" #include "net/base/net_errors.h" #include "net/base/load_flags.h" #include "net/disk_cache/disk_cache.h" #include "net/http/http_request_info.h" #include "net/http/http_response_info.h" #include "net/http/http_transaction.h" #include "net/http/http_transaction_unittest.h" #include "testing/gtest/include/gtest/gtest.h" using base::Time; namespace { //----------------------------------------------------------------------------- // mock disk cache (a very basic memory cache implementation) class MockDiskEntry : public disk_cache::Entry, public base::RefCounted { public: MockDiskEntry() : test_mode_(0), doomed_(false), platform_file_(global_platform_file_) { } MockDiskEntry(const std::string& key) : key_(key), doomed_(false), platform_file_(global_platform_file_) { // // 'key' is prefixed with an identifier if it corresponds to a cached POST. // Skip past that to locate the actual URL. // // TODO(darin): It breaks the abstraction a bit that we assume 'key' is an // URL corresponding to a registered MockTransaction. It would be good to // have another way to access the test_mode. // GURL url; if (isdigit(key[0])) { size_t slash = key.find('/'); DCHECK(slash != std::string::npos); url = GURL(key.substr(slash + 1)); } else { url = GURL(key); } const MockTransaction* t = FindMockTransaction(url); DCHECK(t); test_mode_ = t->test_mode; } ~MockDiskEntry() { } bool is_doomed() const { return doomed_; } virtual void Doom() { doomed_ = true; } virtual void Close() { Release(); } virtual std::string GetKey() const { return key_; } virtual Time GetLastUsed() const { return Time::FromInternalValue(0); } virtual Time GetLastModified() const { return Time::FromInternalValue(0); } virtual int32 GetDataSize(int index) const { DCHECK(index >= 0 && index < 2); return static_cast(data_[index].size()); } virtual int ReadData(int index, int offset, net::IOBuffer* buf, int buf_len, net::CompletionCallback* callback) { DCHECK(index >= 0 && index < 2); if (offset < 0 || offset > static_cast(data_[index].size())) return net::ERR_FAILED; if (static_cast(offset) == data_[index].size()) return 0; int num = std::min(buf_len, static_cast(data_[index].size()) - offset); memcpy(buf->data(), &data_[index][offset], num); if (!callback || (test_mode_ & TEST_MODE_SYNC_CACHE_READ)) return num; CallbackLater(callback, num); return net::ERR_IO_PENDING; } virtual int WriteData(int index, int offset, net::IOBuffer* buf, int buf_len, net::CompletionCallback* callback, bool truncate) { DCHECK(index >= 0 && index < 2); DCHECK(truncate); if (offset < 0 || offset > static_cast(data_[index].size())) return net::ERR_FAILED; data_[index].resize(offset + buf_len); if (buf_len) memcpy(&data_[index][offset], buf->data(), buf_len); return buf_len; } base::PlatformFile UseExternalFile(int index) { return platform_file_; } base::PlatformFile GetPlatformFile(int index) { return platform_file_; } static void set_global_platform_file(base::PlatformFile platform_file) { global_platform_file_ = platform_file; } private: // Unlike the callbacks for MockHttpTransaction, we want this one to run even // if the consumer called Close on the MockDiskEntry. We achieve that by // leveraging the fact that this class is reference counted. void CallbackLater(net::CompletionCallback* callback, int result) { MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(this, &MockDiskEntry::RunCallback, callback, result)); } void RunCallback(net::CompletionCallback* callback, int result) { callback->Run(result); } std::string key_; std::vector data_[2]; int test_mode_; bool doomed_; base::PlatformFile platform_file_; static base::PlatformFile global_platform_file_; }; base::PlatformFile MockDiskEntry::global_platform_file_ = base::kInvalidPlatformFileValue; class MockDiskCache : public disk_cache::Backend { public: MockDiskCache() : open_count_(0), create_count_(0), fail_requests_(0) { } ~MockDiskCache() { EntryMap::iterator it = entries_.begin(); for (; it != entries_.end(); ++it) it->second->Release(); } virtual int32 GetEntryCount() const { return static_cast(entries_.size()); } virtual bool OpenEntry(const std::string& key, disk_cache::Entry** entry) { if (fail_requests_) return false; EntryMap::iterator it = entries_.find(key); if (it == entries_.end()) return false; if (it->second->is_doomed()) { it->second->Release(); entries_.erase(it); return false; } open_count_++; it->second->AddRef(); *entry = it->second; return true; } virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry) { if (fail_requests_) return false; EntryMap::iterator it = entries_.find(key); DCHECK(it == entries_.end()); create_count_++; MockDiskEntry* new_entry = new MockDiskEntry(key); new_entry->AddRef(); entries_[key] = new_entry; new_entry->AddRef(); *entry = new_entry; return true; } virtual bool DoomEntry(const std::string& key) { EntryMap::iterator it = entries_.find(key); if (it != entries_.end()) { it->second->Release(); entries_.erase(it); } return true; } virtual bool DoomAllEntries() { return false; } virtual bool DoomEntriesBetween(const Time initial_time, const Time end_time) { return true; } virtual bool DoomEntriesSince(const Time initial_time) { return true; } virtual bool OpenNextEntry(void** iter, disk_cache::Entry** next_entry) { return false; } virtual void EndEnumeration(void** iter) {} virtual void GetStats( std::vector >* stats) { } // returns number of times a cache entry was successfully opened int open_count() const { return open_count_; } // returns number of times a cache entry was successfully created int create_count() const { return create_count_; } // Fail any subsequent CreateEntry and OpenEntry. void set_fail_requests() { fail_requests_ = true; } private: typedef base::hash_map EntryMap; EntryMap entries_; int open_count_; int create_count_; bool fail_requests_; }; class MockHttpCache { public: MockHttpCache() : http_cache_(new MockNetworkLayer(), new MockDiskCache()) { } net::HttpCache* http_cache() { return &http_cache_; } MockNetworkLayer* network_layer() { return static_cast(http_cache_.network_layer()); } MockDiskCache* disk_cache() { return static_cast(http_cache_.disk_cache()); } private: net::HttpCache http_cache_; }; //----------------------------------------------------------------------------- // helpers void ReadAndVerifyTransaction(net::HttpTransaction* trans, const MockTransaction& trans_info) { std::string content; int rv = ReadTransaction(trans, &content); EXPECT_EQ(net::OK, rv); EXPECT_EQ(strlen(trans_info.data), content.size()); EXPECT_EQ(0, memcmp(trans_info.data, content.data(), content.size())); } void RunTransactionTestWithRequest(net::HttpCache* cache, const MockTransaction& trans_info, const MockHttpRequest& request) { TestCompletionCallback callback; // write to the cache scoped_ptr trans(cache->CreateTransaction()); ASSERT_TRUE(trans.get()); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::OK, rv); const net::HttpResponseInfo* response = trans->GetResponseInfo(); ASSERT_TRUE(response); ReadAndVerifyTransaction(trans.get(), trans_info); } void RunTransactionTest(net::HttpCache* cache, const MockTransaction& trans_info) { return RunTransactionTestWithRequest( cache, trans_info, MockHttpRequest(trans_info)); } // This class provides a handler for kFastNoStoreGET_Transaction so that the // no-store header can be included on demand. class FastTransactionServer { public: FastTransactionServer() { no_store = false; } ~FastTransactionServer() {} void set_no_store(bool value) { no_store = value; } static void FastNoStoreHandler(const net::HttpRequestInfo* request, std::string* response_status, std::string* response_headers, std::string* response_data) { if (no_store) *response_headers = "Cache-Control: no-store\n"; } private: static bool no_store; DISALLOW_COPY_AND_ASSIGN(FastTransactionServer); }; bool FastTransactionServer::no_store; const MockTransaction kFastNoStoreGET_Transaction = { "http://www.google.com/nostore", "GET", "", net::LOAD_VALIDATE_CACHE, "HTTP/1.1 200 OK", "Cache-Control: max-age=10000\n", "Google Blah Blah", TEST_MODE_SYNC_NET_START, &FastTransactionServer::FastNoStoreHandler, 0 }; } // namespace //----------------------------------------------------------------------------- // tests TEST(HttpCache, CreateThenDestroy) { MockHttpCache cache; scoped_ptr trans( cache.http_cache()->CreateTransaction()); ASSERT_TRUE(trans.get()); } TEST(HttpCache, SimpleGET) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGETNoDiskCache) { MockHttpCache cache; cache.disk_cache()->set_fail_requests(); // Read from the network, and don't use the cache. RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(0, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // force this transaction to read from the cache MockTransaction transaction(kSimpleGET_Transaction); transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Miss) { MockHttpCache cache; // force this transaction to read from the cache MockTransaction transaction(kSimpleGET_Transaction); transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE; MockHttpRequest request(transaction); TestCompletionCallback callback; scoped_ptr trans( cache.http_cache()->CreateTransaction()); ASSERT_TRUE(trans.get()); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::ERR_CACHE_MISS, rv); trans.reset(); EXPECT_EQ(0, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(0, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadPreferringCache_Hit) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // force this transaction to read from the cache if valid MockTransaction transaction(kSimpleGET_Transaction); transaction.load_flags |= net::LOAD_PREFERRING_CACHE; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadPreferringCache_Miss) { MockHttpCache cache; // force this transaction to read from the cache if valid MockTransaction transaction(kSimpleGET_Transaction); transaction.load_flags |= net::LOAD_PREFERRING_CACHE; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadBypassCache) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // force this transaction to write to the cache again MockTransaction transaction(kSimpleGET_Transaction); transaction.load_flags |= net::LOAD_BYPASS_CACHE; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(2, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadBypassCache_Implicit) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // force this transaction to write to the cache again MockTransaction transaction(kSimpleGET_Transaction); transaction.request_headers = "pragma: no-cache"; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(2, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadBypassCache_Implicit2) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // force this transaction to write to the cache again MockTransaction transaction(kSimpleGET_Transaction); transaction.request_headers = "cache-control: no-cache"; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(2, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadValidateCache) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // read from the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // force this transaction to validate the cache MockTransaction transaction(kSimpleGET_Transaction); transaction.load_flags |= net::LOAD_VALIDATE_CACHE; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } TEST(HttpCache, SimpleGET_LoadValidateCache_Implicit) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // read from the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); // force this transaction to validate the cache MockTransaction transaction(kSimpleGET_Transaction); transaction.request_headers = "cache-control: max-age=0"; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } struct Context { int result; TestCompletionCallback callback; scoped_ptr trans; Context(net::HttpTransaction* t) : result(net::ERR_IO_PENDING), trans(t) { } }; TEST(HttpCache, SimpleGET_ManyReaders) { MockHttpCache cache; MockHttpRequest request(kSimpleGET_Transaction); std::vector context_list; const int kNumTransactions = 5; for (int i = 0; i < kNumTransactions; ++i) { context_list.push_back( new Context(cache.http_cache()->CreateTransaction())); Context* c = context_list[i]; int rv = c->trans->Start(&request, &c->callback); if (rv != net::ERR_IO_PENDING) c->result = rv; } // the first request should be a writer at this point, and the subsequent // requests should be pending. EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); for (int i = 0; i < kNumTransactions; ++i) { Context* c = context_list[i]; if (c->result == net::ERR_IO_PENDING) c->result = c->callback.WaitForResult(); ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); } // we should not have had to re-open the disk entry EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); for (int i = 0; i < kNumTransactions; ++i) { Context* c = context_list[i]; delete c; } } // This is a test for http://code.google.com/p/chromium/issues/detail?id=4769. // If cancelling a request is racing with another request for the same resource // finishing, we have to make sure that we remove both transactions from the // entry. TEST(HttpCache, SimpleGET_RacingReaders) { MockHttpCache cache; MockHttpRequest request(kSimpleGET_Transaction); MockHttpRequest reader_request(kSimpleGET_Transaction); reader_request.load_flags = net::LOAD_ONLY_FROM_CACHE; std::vector context_list; const int kNumTransactions = 5; for (int i = 0; i < kNumTransactions; ++i) { context_list.push_back( new Context(cache.http_cache()->CreateTransaction())); Context* c = context_list[i]; MockHttpRequest* this_request = &request; if (i == 1 || i == 2) this_request = &reader_request; int rv = c->trans->Start(this_request, &c->callback); if (rv != net::ERR_IO_PENDING) c->result = rv; } // The first request should be a writer at this point, and the subsequent // requests should be pending. EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); Context* c = context_list[0]; ASSERT_EQ(net::ERR_IO_PENDING, c->result); c->result = c->callback.WaitForResult(); ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); // Now we have 2 active readers and two queued transactions. c = context_list[1]; ASSERT_EQ(net::ERR_IO_PENDING, c->result); c->result = c->callback.WaitForResult(); ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); // At this point we have one reader, two pending transactions and a task on // the queue to move to the next transaction. Now we cancel the request that // is the current reader, and expect the queued task to be able to start the // next request. c = context_list[2]; c->trans.reset(); for (int i = 3; i < kNumTransactions; ++i) { Context* c = context_list[i]; if (c->result == net::ERR_IO_PENDING) c->result = c->callback.WaitForResult(); ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); } // We should not have had to re-open the disk entry. EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); for (int i = 0; i < kNumTransactions; ++i) { Context* c = context_list[i]; delete c; } } // This is a test for http://code.google.com/p/chromium/issues/detail?id=4731. // We may attempt to delete an entry synchronously with the act of adding a new // transaction to said entry. TEST(HttpCache, FastNoStoreGET_DoneWithPending) { MockHttpCache cache; // The headers will be served right from the call to Start() the request. MockHttpRequest request(kFastNoStoreGET_Transaction); FastTransactionServer request_handler; AddMockTransaction(&kFastNoStoreGET_Transaction); std::vector context_list; const int kNumTransactions = 3; for (int i = 0; i < kNumTransactions; ++i) { context_list.push_back( new Context(cache.http_cache()->CreateTransaction())); Context* c = context_list[i]; int rv = c->trans->Start(&request, &c->callback); if (rv != net::ERR_IO_PENDING) c->result = rv; } // The first request should be a writer at this point, and the subsequent // requests should be pending. EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // Now, make sure that the second request asks for the entry not to be stored. request_handler.set_no_store(true); for (int i = 0; i < kNumTransactions; ++i) { Context* c = context_list[i]; if (c->result == net::ERR_IO_PENDING) c->result = c->callback.WaitForResult(); ReadAndVerifyTransaction(c->trans.get(), kFastNoStoreGET_Transaction); delete c; } EXPECT_EQ(3, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(2, cache.disk_cache()->create_count()); RemoveMockTransaction(&kFastNoStoreGET_Transaction); } TEST(HttpCache, SimpleGET_ManyWriters_CancelFirst) { MockHttpCache cache; MockHttpRequest request(kSimpleGET_Transaction); std::vector context_list; const int kNumTransactions = 2; for (int i = 0; i < kNumTransactions; ++i) { context_list.push_back( new Context(cache.http_cache()->CreateTransaction())); Context* c = context_list[i]; int rv = c->trans->Start(&request, &c->callback); if (rv != net::ERR_IO_PENDING) c->result = rv; } // the first request should be a writer at this point, and the subsequent // requests should be pending. EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); for (int i = 0; i < kNumTransactions; ++i) { Context* c = context_list[i]; if (c->result == net::ERR_IO_PENDING) c->result = c->callback.WaitForResult(); // destroy only the first transaction if (i == 0) { delete c; context_list[i] = NULL; } } // complete the rest of the transactions for (int i = 1; i < kNumTransactions; ++i) { Context* c = context_list[i]; ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction); } // we should have had to re-open the disk entry EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(2, cache.disk_cache()->create_count()); for (int i = 1; i < kNumTransactions; ++i) { Context* c = context_list[i]; delete c; } } TEST(HttpCache, SimpleGET_AbandonedCacheRead) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); MockHttpRequest request(kSimpleGET_Transaction); TestCompletionCallback callback; scoped_ptr trans( cache.http_cache()->CreateTransaction()); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::OK, rv); scoped_refptr buf = new net::IOBuffer(256); rv = trans->Read(buf, 256, &callback); EXPECT_EQ(net::ERR_IO_PENDING, rv); // Test that destroying the transaction while it is reading from the cache // works properly. trans.reset(); // Make sure we pump any pending events, which should include a call to // HttpCache::Transaction::OnCacheReadCompleted. MessageLoop::current()->RunAllPending(); } TEST(HttpCache, TypicalGET_ConditionalRequest) { MockHttpCache cache; // write to the cache RunTransactionTest(cache.http_cache(), kTypicalGET_Transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // get the same URL again, but this time we expect it to result // in a conditional request. RunTransactionTest(cache.http_cache(), kTypicalGET_Transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } static void ETagGet_ConditionalRequest_Handler( const net::HttpRequestInfo* request, std::string* response_status, std::string* response_headers, std::string* response_data) { EXPECT_TRUE(request->extra_headers.find("If-None-Match") != std::string::npos); response_status->assign("HTTP/1.1 304 Not Modified"); response_headers->assign(kETagGET_Transaction.response_headers); response_data->clear(); } TEST(HttpCache, ETagGET_ConditionalRequest_304) { MockHttpCache cache; ScopedMockTransaction transaction(kETagGET_Transaction); // write to the cache RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // get the same URL again, but this time we expect it to result // in a conditional request. transaction.load_flags = net::LOAD_VALIDATE_CACHE; transaction.handler = ETagGet_ConditionalRequest_Handler; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } static void ETagGet_ConditionalRequest_NoStore_Handler( const net::HttpRequestInfo* request, std::string* response_status, std::string* response_headers, std::string* response_data) { EXPECT_TRUE(request->extra_headers.find("If-None-Match") != std::string::npos); response_status->assign("HTTP/1.1 304 Not Modified"); response_headers->assign("Cache-Control: no-store\n"); response_data->clear(); } TEST(HttpCache, ETagGET_ConditionalRequest_304_NoStore) { MockHttpCache cache; ScopedMockTransaction transaction(kETagGET_Transaction); // Write to the cache. RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // Get the same URL again, but this time we expect it to result // in a conditional request. transaction.load_flags = net::LOAD_VALIDATE_CACHE; transaction.handler = ETagGet_ConditionalRequest_NoStore_Handler; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); ScopedMockTransaction transaction2(kETagGET_Transaction); // Write to the cache again. This should create a new entry. RunTransactionTest(cache.http_cache(), transaction2); EXPECT_EQ(3, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(2, cache.disk_cache()->create_count()); } TEST(HttpCache, SimplePOST_SkipsCache) { MockHttpCache cache; // Test that we skip the cache for POST requests that do not have an upload // identifier. RunTransactionTest(cache.http_cache(), kSimplePOST_Transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(0, cache.disk_cache()->create_count()); } TEST(HttpCache, SimplePOST_LoadOnlyFromCache_Miss) { MockHttpCache cache; // Test that we skip the cache for POST requests. Eventually, we will want // to cache these, but we'll still have cases where skipping the cache makes // sense, so we want to make sure that it works properly. MockTransaction transaction(kSimplePOST_Transaction); transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE; MockHttpRequest request(transaction); TestCompletionCallback callback; scoped_ptr trans( cache.http_cache()->CreateTransaction()); ASSERT_TRUE(trans.get()); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::ERR_CACHE_MISS, rv); trans.reset(); EXPECT_EQ(0, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(0, cache.disk_cache()->create_count()); } TEST(HttpCache, SimplePOST_LoadOnlyFromCache_Hit) { MockHttpCache cache; // Test that we hit the cache for POST requests. MockTransaction transaction(kSimplePOST_Transaction); const int64 kUploadId = 1; // Just a dummy value. MockHttpRequest request(transaction); request.upload_data = new net::UploadData(); request.upload_data->set_identifier(kUploadId); request.upload_data->AppendBytes("hello", 5); // Populate the cache. RunTransactionTestWithRequest(cache.http_cache(), transaction, request); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // Load from cache. request.load_flags |= net::LOAD_ONLY_FROM_CACHE; RunTransactionTestWithRequest(cache.http_cache(), transaction, request); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } TEST(HttpCache, RangeGET_SkipsCache) { MockHttpCache cache; // Test that we skip the cache for POST requests. Eventually, we will want // to cache these, but we'll still have cases where skipping the cache makes // sense, so we want to make sure that it works properly. RunTransactionTest(cache.http_cache(), kRangeGET_Transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(0, cache.disk_cache()->create_count()); MockTransaction transaction(kSimpleGET_Transaction); transaction.request_headers = "If-None-Match: foo"; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(0, cache.disk_cache()->create_count()); transaction.request_headers = "If-Modified-Since: Wed, 28 Nov 2007 00:45:20 GMT"; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(3, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(0, cache.disk_cache()->create_count()); } TEST(HttpCache, SyncRead) { MockHttpCache cache; // This test ensures that a read that completes synchronously does not cause // any problems. ScopedMockTransaction transaction(kSimpleGET_Transaction); transaction.test_mode |= (TEST_MODE_SYNC_CACHE_START | TEST_MODE_SYNC_CACHE_READ); MockHttpRequest r1(transaction), r2(transaction), r3(transaction); TestTransactionConsumer c1(cache.http_cache()), c2(cache.http_cache()), c3(cache.http_cache()); c1.Start(&r1); r2.load_flags |= net::LOAD_ONLY_FROM_CACHE; c2.Start(&r2); r3.load_flags |= net::LOAD_ONLY_FROM_CACHE; c3.Start(&r3); MessageLoop::current()->Run(); EXPECT_TRUE(c1.is_done()); EXPECT_TRUE(c2.is_done()); EXPECT_TRUE(c3.is_done()); EXPECT_EQ(net::OK, c1.error()); EXPECT_EQ(net::OK, c2.error()); EXPECT_EQ(net::OK, c3.error()); } TEST(HttpCache, ValidationResultsIn200) { MockHttpCache cache; // This test ensures that a conditional request, which results in a 200 // instead of a 304, properly truncates the existing response data. // write to the cache RunTransactionTest(cache.http_cache(), kETagGET_Transaction); // force this transaction to validate the cache MockTransaction transaction(kETagGET_Transaction); transaction.load_flags |= net::LOAD_VALIDATE_CACHE; RunTransactionTest(cache.http_cache(), transaction); // read from the cache RunTransactionTest(cache.http_cache(), kETagGET_Transaction); } TEST(HttpCache, CachedRedirect) { MockHttpCache cache; ScopedMockTransaction kTestTransaction(kSimpleGET_Transaction); kTestTransaction.status = "HTTP/1.1 301 Moved Permanently"; kTestTransaction.response_headers = "Location: http://www.bar.com/\n"; MockHttpRequest request(kTestTransaction); TestCompletionCallback callback; // write to the cache { scoped_ptr trans( cache.http_cache()->CreateTransaction()); ASSERT_TRUE(trans.get()); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::OK, rv); const net::HttpResponseInfo* info = trans->GetResponseInfo(); ASSERT_TRUE(info); EXPECT_EQ(info->headers->response_code(), 301); std::string location; info->headers->EnumerateHeader(NULL, "Location", &location); EXPECT_EQ(location, "http://www.bar.com/"); // Destroy transaction when going out of scope. We have not actually // read the response body -- want to test that it is still getting cached. } EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // read from the cache { scoped_ptr trans( cache.http_cache()->CreateTransaction()); ASSERT_TRUE(trans.get()); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::OK, rv); const net::HttpResponseInfo* info = trans->GetResponseInfo(); ASSERT_TRUE(info); EXPECT_EQ(info->headers->response_code(), 301); std::string location; info->headers->EnumerateHeader(NULL, "Location", &location); EXPECT_EQ(location, "http://www.bar.com/"); // Destroy transaction when going out of scope. We have not actually // read the response body -- want to test that it is still getting cached. } EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); } TEST(HttpCache, CacheControlNoStore) { MockHttpCache cache; ScopedMockTransaction transaction(kSimpleGET_Transaction); transaction.response_headers = "cache-control: no-store\n"; // initial load RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // try loading again; it should result in a network fetch RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(2, cache.disk_cache()->create_count()); disk_cache::Entry* entry; bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry); EXPECT_FALSE(exists); } TEST(HttpCache, CacheControlNoStore2) { // this test is similar to the above test, except that the initial response // is cachable, but when it is validated, no-store is received causing the // cached document to be deleted. MockHttpCache cache; ScopedMockTransaction transaction(kETagGET_Transaction); // initial load RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // try loading again; it should result in a network fetch transaction.load_flags = net::LOAD_VALIDATE_CACHE; transaction.response_headers = "cache-control: no-store\n"; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); disk_cache::Entry* entry; bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry); EXPECT_FALSE(exists); } TEST(HttpCache, CacheControlNoStore3) { // this test is similar to the above test, except that the response is a 304 // instead of a 200. this should never happen in practice, but it seems like // a good thing to verify that we still destroy the cache entry. MockHttpCache cache; ScopedMockTransaction transaction(kETagGET_Transaction); // initial load RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); // try loading again; it should result in a network fetch transaction.load_flags = net::LOAD_VALIDATE_CACHE; transaction.response_headers = "cache-control: no-store\n"; transaction.status = "HTTP/1.1 304 Not Modified"; RunTransactionTest(cache.http_cache(), transaction); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); EXPECT_EQ(1, cache.disk_cache()->create_count()); disk_cache::Entry* entry; bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry); EXPECT_FALSE(exists); } // Ensure that we don't cache requests served over bad HTTPS. TEST(HttpCache, SimpleGET_SSLError) { MockHttpCache cache; MockTransaction transaction = kSimpleGET_Transaction; transaction.cert_status = net::CERT_STATUS_REVOKED; ScopedMockTransaction scoped_transaction(transaction); // write to the cache RunTransactionTest(cache.http_cache(), transaction); // Test that it was not cached. transaction.load_flags |= net::LOAD_ONLY_FROM_CACHE; MockHttpRequest request(transaction); TestCompletionCallback callback; scoped_ptr trans( cache.http_cache()->CreateTransaction()); ASSERT_TRUE(trans.get()); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::ERR_CACHE_MISS, rv); } // Ensure that we don't crash by if left-behind transactions. TEST(HttpCache, OutlivedTransactions) { MockHttpCache* cache = new MockHttpCache; net::HttpTransaction* trans = cache->http_cache()->CreateTransaction(); delete cache; delete trans; } // Make sure Entry::UseExternalFile is called when a new entry is created in // a HttpCache with MEDIA type. Also make sure Entry::GetPlatformFile is called // when an entry is loaded from a HttpCache with MEDIA type. Also confirm we // will receive a file handle in ResponseInfo from a media cache. TEST(HttpCache, SimpleGET_MediaCache) { // Initialize the HttpCache with MEDIA_CACHE type. MockHttpCache cache; cache.http_cache()->set_type(net::MEDIA_CACHE); // Define some fake file handles for testing. base::PlatformFile kFakePlatformFile1, kFakePlatformFile2; #if defined(OS_WIN) kFakePlatformFile1 = reinterpret_cast(1); kFakePlatformFile2 = reinterpret_cast(2); #else kFakePlatformFile1 = 1; kFakePlatformFile2 = 2; #endif ScopedMockTransaction trans_info(kSimpleGET_Transaction); trans_info.load_flags |= net::LOAD_ENABLE_DOWNLOAD_FILE; TestCompletionCallback callback; { // Set the fake file handle to MockDiskEntry so cache is written with an // entry created with our fake file handle. MockDiskEntry::set_global_platform_file(kFakePlatformFile1); scoped_ptr trans( cache.http_cache()->CreateTransaction()); ASSERT_TRUE(trans.get()); MockHttpRequest request(trans_info); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::OK, rv); const net::HttpResponseInfo* response = trans->GetResponseInfo(); ASSERT_TRUE(response); ASSERT_EQ(kFakePlatformFile1, response->response_data_file); ReadAndVerifyTransaction(trans.get(), trans_info); } // Load only from cache so we would get the same file handle. trans_info.load_flags |= net::LOAD_ONLY_FROM_CACHE; { // Set a different file handle value to MockDiskEntry so any new entry // created in the cache won't have the same file handle value. MockDiskEntry::set_global_platform_file(kFakePlatformFile2); scoped_ptr trans( cache.http_cache()->CreateTransaction()); ASSERT_TRUE(trans.get()); MockHttpRequest request(trans_info); int rv = trans->Start(&request, &callback); if (rv == net::ERR_IO_PENDING) rv = callback.WaitForResult(); ASSERT_EQ(net::OK, rv); const net::HttpResponseInfo* response = trans->GetResponseInfo(); ASSERT_TRUE(response); // Make sure we get the same file handle as in the first request. ASSERT_EQ(kFakePlatformFile1, response->response_data_file); ReadAndVerifyTransaction(trans.get(), trans_info); } }