| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "services/network/shared_dictionary/shared_dictionary_manager.h" |
| |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/callback.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/task_environment.h" |
| #include "base/time/time.h" |
| #include "crypto/secure_hash.h" |
| #include "net/base/hash_value.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "net/disk_cache/disk_cache_test_util.h" |
| #include "net/extras/shared_dictionary/shared_dictionary_info.h" |
| #include "net/http/http_response_headers.h" |
| #include "services/network/shared_dictionary/shared_dictionary.h" |
| #include "services/network/shared_dictionary/shared_dictionary_constants.h" |
| #include "services/network/shared_dictionary/shared_dictionary_disk_cache.h" |
| #include "services/network/shared_dictionary/shared_dictionary_manager_on_disk.h" |
| #include "services/network/shared_dictionary/shared_dictionary_storage.h" |
| #include "services/network/shared_dictionary/shared_dictionary_storage_in_memory.h" |
| #include "services/network/shared_dictionary/shared_dictionary_storage_on_disk.h" |
| #include "services/network/shared_dictionary/shared_dictionary_writer.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace network { |
| |
| namespace { |
| |
| enum class TestManagerType { |
| kInMemory, |
| kOnDisk, |
| }; |
| |
| const GURL kUrl1("https://origin1.test/"); |
| const GURL kUrl2("https://origin2.test/"); |
| const net::SchemefulSite kSite1(kUrl1); |
| const net::SchemefulSite kSite2(kUrl2); |
| |
| const std::string kTestData1 = "Hello world"; |
| const std::string kTestData2 = "Bonjour le monde"; |
| |
| void CheckDiskCacheEntryDataEquals( |
| SharedDictionaryDiskCache& disk_cache, |
| const base::UnguessableToken& disk_cache_key_token, |
| const std::string& expected_data) { |
| TestEntryResultCompletionCallback open_callback; |
| disk_cache::EntryResult open_result = open_callback.GetResult( |
| disk_cache.OpenOrCreateEntry(disk_cache_key_token.ToString(), |
| /*create=*/false, open_callback.callback())); |
| EXPECT_EQ(net::OK, open_result.net_error()); |
| disk_cache::ScopedEntryPtr entry; |
| entry.reset(open_result.ReleaseEntry()); |
| ASSERT_TRUE(entry); |
| |
| EXPECT_EQ(base::checked_cast<int32_t>(expected_data.size()), |
| entry->GetDataSize(/*index=*/1)); |
| |
| scoped_refptr<net::IOBufferWithSize> read_buffer = |
| base::MakeRefCounted<net::IOBufferWithSize>(expected_data.size()); |
| net::TestCompletionCallback read_callback; |
| EXPECT_EQ(read_buffer->size(), |
| read_callback.GetResult(entry->ReadData( |
| /*index=*/1, /*offset=*/0, read_buffer.get(), |
| expected_data.size(), read_callback.callback()))); |
| EXPECT_EQ(expected_data, |
| std::string(reinterpret_cast<const char*>(read_buffer->data()), |
| read_buffer->size())); |
| } |
| |
| void WriteDictionary(SharedDictionaryStorage* storage, |
| const GURL& dictionary_url, |
| const std::string& match, |
| const std::vector<std::string>& data_list, |
| base::Time now_time = base::Time::Now()) { |
| scoped_refptr<net::HttpResponseHeaders> headers = |
| net::HttpResponseHeaders::TryToCreate(base::StrCat( |
| {"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName, |
| ": match=\"/", match, "\"\n\n"})); |
| ASSERT_TRUE(headers); |
| scoped_refptr<SharedDictionaryWriter> writer = |
| storage->MaybeCreateWriter(dictionary_url, now_time, *headers); |
| ASSERT_TRUE(writer); |
| for (const std::string& data : data_list) { |
| writer->Append(data.c_str(), data.size()); |
| } |
| writer->Finish(); |
| } |
| |
| } // namespace |
| |
| class SharedDictionaryManagerTest |
| : public ::testing::Test, |
| public testing::WithParamInterface<TestManagerType> { |
| public: |
| SharedDictionaryManagerTest() = default; |
| ~SharedDictionaryManagerTest() override = default; |
| |
| SharedDictionaryManagerTest(const SharedDictionaryManagerTest&) = delete; |
| SharedDictionaryManagerTest& operator=(const SharedDictionaryManagerTest&) = |
| delete; |
| |
| void SetUp() override { |
| if (GetParam() == TestManagerType::kOnDisk) { |
| ASSERT_TRUE(tmp_directory_.CreateUniqueTempDir()); |
| database_path_ = tmp_directory_.GetPath().Append(FILE_PATH_LITERAL("db")); |
| cache_directory_path_ = |
| tmp_directory_.GetPath().Append(FILE_PATH_LITERAL("cache")); |
| } |
| } |
| void TearDown() override { |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| } |
| |
| protected: |
| std::unique_ptr<SharedDictionaryManager> CreateSharedDictionaryManager() { |
| switch (GetParam()) { |
| case TestManagerType::kInMemory: |
| return SharedDictionaryManager::CreateInMemory(/*cache_max_size=*/0); |
| case TestManagerType::kOnDisk: |
| return SharedDictionaryManager::CreateOnDisk( |
| database_path_, cache_directory_path_, /*cache_max_size=*/0, |
| #if BUILDFLAG(IS_ANDROID) |
| /*app_status_listener=*/nullptr, |
| #endif // BUILDFLAG(IS_ANDROID) |
| /*file_operations_factory=*/nullptr); |
| } |
| } |
| const std::map< |
| url::SchemeHostPort, |
| std::map<std::string, SharedDictionaryStorageInMemory::DictionaryInfo>>& |
| GetInMemoryDictionaryMap(SharedDictionaryStorage* storage) { |
| return static_cast<SharedDictionaryStorageInMemory*>(storage) |
| ->GetDictionaryMap(); |
| } |
| const std::map<url::SchemeHostPort, |
| std::map<std::string, net::SharedDictionaryInfo>>& |
| GetOnDiskDictionaryMap(SharedDictionaryStorage* storage) { |
| return static_cast<SharedDictionaryStorageOnDisk*>(storage) |
| ->GetDictionaryMapForTesting(); |
| } |
| void FlushCacheTasks() { |
| disk_cache::FlushCacheThreadForTesting(); |
| task_environment_.RunUntilIdle(); |
| } |
| |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| private: |
| base::ScopedTempDir tmp_directory_; |
| base::FilePath database_path_; |
| base::FilePath cache_directory_path_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| SharedDictionaryManagerTest, |
| testing::ValuesIn({TestManagerType::kInMemory, TestManagerType::kOnDisk}), |
| [](const testing::TestParamInfo<TestManagerType>& info) { |
| switch (info.param) { |
| case TestManagerType::kInMemory: |
| return "InMemory"; |
| case TestManagerType::kOnDisk: |
| return "OnDisk"; |
| } |
| }); |
| |
| TEST_P(SharedDictionaryManagerTest, SameStorageForSameIsolationKey) { |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| |
| net::SharedDictionaryStorageIsolationKey isolation_key1( |
| url::Origin::Create(kUrl1), kSite1); |
| net::SharedDictionaryStorageIsolationKey isolation_key2( |
| url::Origin::Create(kUrl1), kSite1); |
| |
| EXPECT_EQ(isolation_key1, isolation_key1); |
| |
| scoped_refptr<SharedDictionaryStorage> storage1 = |
| manager->GetStorage(isolation_key1); |
| scoped_refptr<SharedDictionaryStorage> storage2 = |
| manager->GetStorage(isolation_key2); |
| |
| EXPECT_TRUE(storage1); |
| EXPECT_TRUE(storage2); |
| EXPECT_EQ(storage1.get(), storage2.get()); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, DifferentStorageForDifferentIsolationKey) { |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| |
| net::SharedDictionaryStorageIsolationKey isolation_key1( |
| url::Origin::Create(kUrl1), kSite1); |
| net::SharedDictionaryStorageIsolationKey isolation_key2( |
| url::Origin::Create(kUrl2), kSite2); |
| EXPECT_NE(isolation_key1, isolation_key2); |
| |
| scoped_refptr<SharedDictionaryStorage> storage1 = |
| manager->GetStorage(isolation_key1); |
| scoped_refptr<SharedDictionaryStorage> storage2 = |
| manager->GetStorage(isolation_key2); |
| |
| EXPECT_TRUE(storage1); |
| EXPECT_TRUE(storage2); |
| EXPECT_NE(storage1.get(), storage2.get()); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, NoWriterForNoUseAsDictionaryHeader) { |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(kUrl1), kSite1); |
| |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| |
| ASSERT_TRUE(storage); |
| scoped_refptr<net::HttpResponseHeaders> headers = |
| net::HttpResponseHeaders::TryToCreate("HTTP/1.1 200 OK\n"); |
| ASSERT_TRUE(headers); |
| scoped_refptr<SharedDictionaryWriter> writer = storage->MaybeCreateWriter( |
| GURL("https://origin1.test/testfile.txt"), base::Time::Now(), *headers); |
| EXPECT_FALSE(writer); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, WriterForUseAsDictionaryHeader) { |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(kUrl1), kSite1); |
| |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| ASSERT_TRUE(storage); |
| |
| struct { |
| std::string header_string; |
| bool expect_success; |
| } kTestCases[] = { |
| // Empty |
| {"", false}, |
| |
| // Invalid dictionary. |
| {"()", false}, |
| |
| // No `match` value. |
| {"dummy", false}, |
| |
| // Valid `match` value. |
| {"match=\"/test\"", true}, |
| {"match=\"test\"", true}, |
| |
| // List `match` value is not supported. |
| {"match=(\"test1\" \"test2\")", false}, |
| // Token `match` value is not supported. |
| {"match=test", false}, |
| |
| // Valid `expires` value. |
| {"match=\"test\", expires=1000", true}, |
| // List `expires` value is not supported. |
| {"match=\"test\", expires=(1000 2000)", false}, |
| // String `expires` value is not supported. |
| {"match=\"test\", expires=PI", false}, |
| |
| // Valid `algorithms` value. |
| {"match=\"test\", algorithms=sha-256", true}, |
| {"match=\"test\", algorithms=(sha-256)", true}, |
| {"match=\"test\", algorithms=(sha-256 sha-512)", true}, |
| |
| // The sha-256 token must be lowercase. |
| // TODO(crbug.com/1413922): Investigate the spec and decide whether to |
| // support it or not. |
| {"match=\"test\", algorithms=SHA-256", false}, |
| |
| // Each item in `algorithms` value must be a token. |
| {"match=\"test\", algorithms=(\"sha-256\")", false}, |
| |
| // Unsupported `algorithms` value. We only support sha-256. |
| {"match=\"test\", algorithms=(sha-512)", false}, |
| }; |
| for (const auto& testcase : kTestCases) { |
| SCOPED_TRACE(base::StringPrintf("header_string: %s", |
| testcase.header_string.c_str())); |
| scoped_refptr<net::HttpResponseHeaders> headers = |
| net::HttpResponseHeaders::TryToCreate(base::StrCat( |
| {"HTTP/1.1 200 OK\n", shared_dictionary::kUseAsDictionaryHeaderName, |
| ": ", testcase.header_string, "\n\n"})); |
| ASSERT_TRUE(headers); |
| scoped_refptr<SharedDictionaryWriter> writer = storage->MaybeCreateWriter( |
| GURL("https://origin1.test/testfile.txt"), base::Time::Now(), *headers); |
| EXPECT_EQ(testcase.expect_success, !!writer); |
| } |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, WriteAndGetDictionary) { |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(kUrl1), kSite1); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| ASSERT_TRUE(storage); |
| WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*", |
| {"hello world"}); |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| |
| // Check the returned dictionary from GetDictionary(). |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin1.test/testfile"))); |
| // Different origin. |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin2.test/testfile"))); |
| // No matching dictionary. |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin1.test/test"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, WriteAndReadDictionary) { |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(kUrl1), kSite1); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| base::Time now_time = base::Time::Now(); |
| |
| const std::string data1 = "hello "; |
| const std::string data2 = "world"; |
| // Write the test data to the dictionary. |
| WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*", |
| {data1, data2}, now_time); |
| |
| // Calculate the hash. |
| std::unique_ptr<crypto::SecureHash> secure_hash = |
| crypto::SecureHash::Create(crypto::SecureHash::SHA256); |
| secure_hash->Update(data1.c_str(), data1.size()); |
| secure_hash->Update(data2.c_str(), data2.size()); |
| net::SHA256HashValue sha256; |
| secure_hash->Finish(sha256.data, sizeof(sha256.data)); |
| |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| |
| // Check the returned dictionary from GetDictionary(). |
| std::unique_ptr<SharedDictionary> dict = |
| storage->GetDictionary(GURL("https://origin1.test/testfile?hello")); |
| ASSERT_TRUE(dict); |
| EXPECT_EQ(data1.size() + data2.size(), dict->size()); |
| EXPECT_EQ(sha256, dict->hash()); |
| |
| // Read and check the dictionary binary. |
| switch (GetParam()) { |
| case TestManagerType::kInMemory: { |
| EXPECT_EQ(net::OK, |
| dict->ReadAll(base::BindOnce([](int rv) { NOTREACHED(); }))); |
| break; |
| } |
| case TestManagerType::kOnDisk: { |
| base::RunLoop run_loop; |
| EXPECT_EQ(net::ERR_IO_PENDING, |
| dict->ReadAll(base::BindLambdaForTesting([&](int rv) { |
| EXPECT_EQ(net::OK, rv); |
| run_loop.Quit(); |
| }))); |
| run_loop.Run(); |
| break; |
| } |
| } |
| |
| ASSERT_TRUE(dict->data()); |
| EXPECT_EQ(data1 + data2, std::string(dict->data()->data(), dict->size())); |
| |
| switch (GetParam()) { |
| case TestManagerType::kInMemory: { |
| // Check the internal state of SharedDictionaryStorageInMemory. |
| const auto& dictionary_map = GetInMemoryDictionaryMap(storage.get()); |
| EXPECT_EQ(1u, dictionary_map.size()); |
| EXPECT_EQ(url::SchemeHostPort(GURL("https://origin1.test/")), |
| dictionary_map.begin()->first); |
| |
| EXPECT_EQ(1u, dictionary_map.begin()->second.size()); |
| EXPECT_EQ("/testfile*", dictionary_map.begin()->second.begin()->first); |
| const auto& dictionary_info = |
| dictionary_map.begin()->second.begin()->second; |
| EXPECT_EQ(GURL("https://origin1.test/dict"), dictionary_info.url()); |
| EXPECT_EQ(now_time, dictionary_info.response_time()); |
| EXPECT_EQ(shared_dictionary::kDefaultExpiration, |
| dictionary_info.expiration()); |
| EXPECT_EQ("/testfile*", dictionary_info.match()); |
| EXPECT_EQ(data1.size() + data2.size(), dictionary_info.size()); |
| EXPECT_EQ(data1 + data2, std::string(dictionary_info.data()->data(), |
| dictionary_info.size())); |
| EXPECT_EQ(sha256, dictionary_info.hash()); |
| break; |
| } |
| case TestManagerType::kOnDisk: { |
| // Check the internal state of SharedDictionaryStorageOnDisk. |
| const auto& dictionary_map = GetOnDiskDictionaryMap(storage.get()); |
| EXPECT_EQ(1u, dictionary_map.size()); |
| EXPECT_EQ(url::SchemeHostPort(GURL("https://origin1.test/")), |
| dictionary_map.begin()->first); |
| |
| EXPECT_EQ(1u, dictionary_map.begin()->second.size()); |
| EXPECT_EQ("/testfile*", dictionary_map.begin()->second.begin()->first); |
| const auto& dictionary_info = |
| dictionary_map.begin()->second.begin()->second; |
| EXPECT_EQ(GURL("https://origin1.test/dict"), dictionary_info.url()); |
| EXPECT_EQ(now_time, dictionary_info.response_time()); |
| EXPECT_EQ(shared_dictionary::kDefaultExpiration, |
| dictionary_info.expiration()); |
| EXPECT_EQ("/testfile*", dictionary_info.match()); |
| EXPECT_EQ(data1.size() + data2.size(), dictionary_info.size()); |
| CheckDiskCacheEntryDataEquals( |
| static_cast<SharedDictionaryManagerOnDisk*>(manager.get()) |
| ->disk_cache(), |
| dictionary_info.disk_cache_key_token(), data1 + data2); |
| EXPECT_EQ(sha256, dictionary_info.hash()); |
| break; |
| } |
| } |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, ZeroSizeDictionaryShouldNotBeStored) { |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(kUrl1), kSite1); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| // Write the zero size data to the dictionary. |
| WriteDictionary(storage.get(), GURL("https://origin1.test/dict"), "testfile*", |
| {}); |
| |
| // Check the returned dictionary from GetDictionary(). |
| std::unique_ptr<SharedDictionary> dict = |
| storage->GetDictionary(GURL("https://origin1.test/testfile?hello")); |
| EXPECT_FALSE(dict); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, CacheEvictionOnSetCacheMaxSize) { |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(kUrl1), kSite1); |
| |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| ASSERT_TRUE(storage); |
| |
| WriteDictionary(storage.get(), GURL("https://origin1.test/d1"), "p1*", |
| {kTestData1}); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| WriteDictionary(storage.get(), GURL("https://origin2.test/d2"), "p2*", |
| {kTestData1}); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| WriteDictionary(storage.get(), GURL("https://origin3.test/d1"), "p3*", |
| {kTestData1}); |
| |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| |
| manager->SetCacheMaxSize(/*cache_max_size=*/kTestData1.size() * 2); |
| |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin1.test/p1?"))); |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin2.test/p2?"))); |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin3.test/p3?"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, CacheEvictionOnNewDictionary) { |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(kUrl1), kSite1); |
| |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| manager->SetCacheMaxSize(/*cache_max_size=*/kTestData1.size() * 2); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| ASSERT_TRUE(storage); |
| |
| WriteDictionary(storage.get(), GURL("https://origin1.test/d1"), "p1*", |
| {kTestData1}); |
| WriteDictionary(storage.get(), GURL("https://origin2.test/d2"), "p2*", |
| {kTestData1}); |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin1.test/p1?"))); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin2.test/p2?"))); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| WriteDictionary(storage.get(), GURL("https://origin3.test/d1"), "p3*", |
| {kTestData1}); |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin1.test/p1?"))); |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin2.test/p2?"))); |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin3.test/p3?"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, |
| CacheEvictionOnNewDictionaryMultiIsolation) { |
| net::SharedDictionaryStorageIsolationKey isolation_key1( |
| url::Origin::Create(kUrl1), kSite1); |
| net::SharedDictionaryStorageIsolationKey isolation_key2( |
| url::Origin::Create(kUrl2), kSite2); |
| |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| manager->SetCacheMaxSize(/*cache_max_size=*/kTestData1.size() * 2); |
| scoped_refptr<SharedDictionaryStorage> storage1 = |
| manager->GetStorage(isolation_key1); |
| ASSERT_TRUE(storage1); |
| scoped_refptr<SharedDictionaryStorage> storage2 = |
| manager->GetStorage(isolation_key2); |
| ASSERT_TRUE(storage2); |
| |
| WriteDictionary(storage1.get(), GURL("https://origin1.test/d1"), "p1*", |
| {kTestData1}); |
| WriteDictionary(storage2.get(), GURL("https://origin2.test/d2"), "p2*", |
| {kTestData1}); |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| EXPECT_TRUE(storage1->GetDictionary(GURL("https://origin1.test/p1?"))); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| EXPECT_TRUE(storage2->GetDictionary(GURL("https://origin2.test/p2?"))); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| WriteDictionary(storage2.get(), GURL("https://origin3.test/d1"), "p3*", |
| {kTestData1}); |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| EXPECT_FALSE(storage1->GetDictionary(GURL("https://origin1.test/p1?"))); |
| EXPECT_FALSE(storage2->GetDictionary(GURL("https://origin2.test/p2?"))); |
| EXPECT_TRUE(storage2->GetDictionary(GURL("https://origin3.test/p3?"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, CacheEvictionAfterUpdatingLastUsedTime) { |
| net::SharedDictionaryStorageIsolationKey isolation_key1( |
| url::Origin::Create(kUrl1), kSite1); |
| net::SharedDictionaryStorageIsolationKey isolation_key2( |
| url::Origin::Create(kUrl2), kSite2); |
| |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| scoped_refptr<SharedDictionaryStorage> storage1 = |
| manager->GetStorage(isolation_key1); |
| ASSERT_TRUE(storage1); |
| scoped_refptr<SharedDictionaryStorage> storage2 = |
| manager->GetStorage(isolation_key2); |
| ASSERT_TRUE(storage2); |
| |
| // Dictionary 1-1. |
| WriteDictionary(storage1.get(), GURL("https://origin1.test/d1"), "p1*", |
| {kTestData1}); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| // Dictionary 1-2. |
| WriteDictionary(storage1.get(), GURL("https://origin1.test/d2"), "p2*", |
| {kTestData1}); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| // Dictionary 2-1. |
| WriteDictionary(storage2.get(), GURL("https://origin2.test/d1"), "p1*", |
| {kTestData1}); |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| // Dictionary 2-2. |
| WriteDictionary(storage2.get(), GURL("https://origin2.test/d2"), "p2*", |
| {kTestData1}); |
| |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| |
| task_environment_.FastForwardBy(base::Seconds(1)); |
| |
| // Call GetDictionary to update the last used time of the dictionary 1-1. |
| std::unique_ptr<SharedDictionary> dict1 = |
| storage1->GetDictionary(GURL("https://origin1.test/p1?")); |
| ASSERT_TRUE(dict1); |
| |
| // Set the max size to kTestData1.size() * 3. The low water mark will be |
| // kTestData1.size() * 2.7 (3 * 0.9). |
| manager->SetCacheMaxSize(/*cache_max_size=*/kTestData1.size() * 3); |
| |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| |
| EXPECT_TRUE(storage1->GetDictionary(GURL("https://origin1.test/p1?"))); |
| EXPECT_FALSE(storage1->GetDictionary(GURL("https://origin1.test/p2?"))); |
| EXPECT_FALSE(storage2->GetDictionary(GURL("https://origin2.test/p1?"))); |
| EXPECT_TRUE(storage2->GetDictionary(GURL("https://origin2.test/p2?"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, ClearDataMatchFrameOrigin) { |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(GURL("https://target.test/")), |
| net::SchemefulSite(GURL("https://top-frame.test"))); |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| WriteDictionary(storage.get(), GURL("https://origin.test/1"), "p1*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://origin.test/2"), "p2*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://origin.test/3"), "p3*", |
| {kTestData1}); |
| // Move the clock forward by 12 hours. |
| task_environment_.FastForwardBy(base::Hours(12)); |
| |
| base::RunLoop run_loop; |
| manager->ClearData(base::Time::Now() - base::Days(2), |
| base::Time::Now() - base::Days(1), |
| base::BindRepeating([](const GURL& url) { |
| return url == GURL("https://target.test/"); |
| }), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin.test/p1?"))); |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin.test/p2?"))); |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin.test/p3?"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, ClearDataMatchTopFrameSite) { |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(GURL("https://frame.test/")), |
| net::SchemefulSite(GURL("https://target.test"))); |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| WriteDictionary(storage.get(), GURL("https://origin.test/1"), "p1*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://origin.test/2"), "p2*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://origin.test/3"), "p3*", |
| {kTestData1}); |
| // Move the clock forward by 12 hours. |
| task_environment_.FastForwardBy(base::Hours(12)); |
| |
| base::RunLoop run_loop; |
| manager->ClearData(base::Time::Now() - base::Days(2), |
| base::Time::Now() - base::Days(1), |
| base::BindRepeating([](const GURL& url) { |
| return url == GURL("https://target.test/"); |
| }), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin.test/p1?"))); |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin.test/p2?"))); |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin.test/p3?"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, ClearDataMatchDictionaryUrl) { |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(GURL("https://frame.test/")), |
| net::SchemefulSite(GURL("https://top-frame.test"))); |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| WriteDictionary(storage.get(), GURL("https://target.test/1"), "p1*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://target.test/2"), "p2*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://target.test/3"), "p3*", |
| {kTestData1}); |
| // Move the clock forward by 12 hours. |
| task_environment_.FastForwardBy(base::Hours(12)); |
| |
| base::RunLoop run_loop; |
| manager->ClearData(base::Time::Now() - base::Days(2), |
| base::Time::Now() - base::Days(1), |
| base::BindRepeating([](const GURL& url) { |
| return url == GURL("https://target.test/"); |
| }), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://target.test/p1?"))); |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://target.test/p2?"))); |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://target.test/p3?"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, ClearDataNullUrlMatcher) { |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(GURL("https://frame.test/")), |
| net::SchemefulSite(GURL("https://top-frame.test"))); |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| WriteDictionary(storage.get(), GURL("https://origin.test/1"), "p1*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://origin.test/2"), "p2*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://origin.test/3"), "p3*", |
| {kTestData1}); |
| // Move the clock forward by 12 hours. |
| task_environment_.FastForwardBy(base::Hours(12)); |
| |
| base::RunLoop run_loop; |
| manager->ClearData( |
| base::Time::Now() - base::Days(2), base::Time::Now() - base::Days(1), |
| base::RepeatingCallback<bool(const GURL&)>(), run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin.test/p1?"))); |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin.test/p2?"))); |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin.test/p3?"))); |
| } |
| |
| TEST_P(SharedDictionaryManagerTest, ClearDataDoNotInvalidateActiveDictionary) { |
| net::SharedDictionaryStorageIsolationKey isolation_key( |
| url::Origin::Create(GURL("https://frame.test/")), |
| net::SchemefulSite(GURL("https://top-frame.test"))); |
| std::unique_ptr<SharedDictionaryManager> manager = |
| CreateSharedDictionaryManager(); |
| scoped_refptr<SharedDictionaryStorage> storage = |
| manager->GetStorage(isolation_key); |
| WriteDictionary(storage.get(), GURL("https://origin.test/1"), "p1*", |
| {kTestData1}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://origin.test/2"), "p2*", |
| {kTestData2}); |
| // Move the clock forward by 1 day. |
| task_environment_.FastForwardBy(base::Days(1)); |
| WriteDictionary(storage.get(), GURL("https://origin.test/3"), "p3*", |
| {kTestData1}); |
| // Move the clock forward by 12 hours. |
| task_environment_.FastForwardBy(base::Hours(12)); |
| |
| if (GetParam() == TestManagerType::kOnDisk) { |
| FlushCacheTasks(); |
| } |
| |
| // Get a dictionary before calling ClearData(). |
| std::unique_ptr<SharedDictionary> dict = |
| storage->GetDictionary(GURL("https://origin.test/p2?")); |
| ASSERT_TRUE(dict); |
| |
| base::RunLoop run_loop; |
| manager->ClearData( |
| base::Time::Now() - base::Days(2), base::Time::Now() - base::Days(1), |
| base::RepeatingCallback<bool(const GURL&)>(), run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin.test/p1?"))); |
| EXPECT_FALSE(storage->GetDictionary(GURL("https://origin.test/p2?"))); |
| EXPECT_TRUE(storage->GetDictionary(GURL("https://origin.test/p3?"))); |
| |
| // We can still read the deleted dictionary from `dict`. |
| net::TestCompletionCallback read_callback; |
| EXPECT_EQ(net::OK, |
| read_callback.GetResult(dict->ReadAll(read_callback.callback()))); |
| EXPECT_EQ(kTestData2, |
| std::string(reinterpret_cast<const char*>(dict->data()->data()), |
| dict->size())); |
| } |
| |
| } // namespace network |