| // 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 "net/shared_dictionary/shared_dictionary_network_transaction.h" |
| |
| #include <memory> |
| #include <optional> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/notreached.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "crypto/hash.h" |
| #include "net/base/features.h" |
| #include "net/base/hash_value.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/http/http_transaction.h" |
| #include "net/http/http_transaction_test_util.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/shared_dictionary/shared_dictionary.h" |
| #include "net/shared_dictionary/shared_dictionary_constants.h" |
| #include "net/ssl/ssl_private_key.h" |
| #include "net/test/gtest_util.h" |
| #include "net/test/test_with_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| const std::string kTestDictionaryData = "HelloHallo你好こんにちは"; |
| // The hex of sha256 of `kTestDictionaryData`. |
| const std::string kTestDictionarySha256 = |
| "c19728aed36503cfc81a0f5359e6f472e121f77bf20a2faac7994191293c0623"; |
| // The Structured Field sf-binary hash of sha256 of `kTestDictionaryData`. |
| const std::string kTestDictionarySha256Base64 = |
| ":wZcortNlA8/IGg9TWeb0cuEh93vyCi+qx5lBkSk8BiM=:"; |
| const std::string kTestData = |
| "HelloこんにちはHallo你好HelloこんにちはHallo你好"; |
| // The brotli encoded data of `kTestData` using `kTestDictionaryData` as a |
| // dictionary. |
| // kBrotliEncodedData is generated using the following commands: |
| // $ echo -n "HelloHallo你好こんにちは" > /tmp/dict |
| // $ echo -n "HelloこんにちはHallo你好HelloこんにちはHallo你好" > /tmp/data |
| // $ echo -en '\xffDCB' > /tmp/out.dcb |
| // $ openssl dgst -sha256 -binary /tmp/dict >> /tmp/out.dcb |
| // $ brotli --stdout -D /tmp/dict /tmp/data >> /tmp/out.dcb |
| // $ xxd -i /tmp/out.dcb |
| const uint8_t kBrotliEncodedData[] = { |
| 0xff, 0x44, 0x43, 0x42, 0xc1, 0x97, 0x28, 0xae, 0xd3, 0x65, 0x03, 0xcf, |
| 0xc8, 0x1a, 0x0f, 0x53, 0x59, 0xe6, 0xf4, 0x72, 0xe1, 0x21, 0xf7, 0x7b, |
| 0xf2, 0x0a, 0x2f, 0xaa, 0xc7, 0x99, 0x41, 0x91, 0x29, 0x3c, 0x06, 0x23, |
| 0xa1, 0xe8, 0x01, 0x00, 0x22, 0x8d, 0x54, 0xc6, 0xf6, 0x26, 0x81, 0x69, |
| 0x46, 0x9d, 0xb2, 0x60, 0x0e, 0x6b, 0xf5, 0x07, 0x02}; |
| const std::string kBrotliEncodedDataString = |
| std::string(reinterpret_cast<const char*>(kBrotliEncodedData), |
| sizeof(kBrotliEncodedData)); |
| |
| // The zstd encoded data of `kTestData` using `kTestDictionaryData` as a |
| // dictionary. |
| // kZstdEncodedData is generated using the following commands: |
| // $ echo -n "HelloHallo你好こんにちは" > /tmp/dict |
| // $ echo -n "HelloこんにちはHallo你好HelloこんにちはHallo你好" > /tmp/data |
| // $ echo -en '\x5e\x2a\x4d\x18\x20\x00\x00\x00' > /tmp/out.dcz |
| // $ openssl dgst -sha256 -binary /tmp/dict >> /tmp/out.dcz |
| // $ zstd -D /tmp/dict -f -o /tmp/tmp.zstd /tmp/data |
| // $ cat /tmp/tmp.zstd >> /tmp/out.dcz |
| // $ xxd -i /tmp/out.dcz |
| const uint8_t kZstdEncodedData[] = { |
| 0x5e, 0x2a, 0x4d, 0x18, 0x20, 0x00, 0x00, 0x00, 0xc1, 0x97, 0x28, 0xae, |
| 0xd3, 0x65, 0x03, 0xcf, 0xc8, 0x1a, 0x0f, 0x53, 0x59, 0xe6, 0xf4, 0x72, |
| 0xe1, 0x21, 0xf7, 0x7b, 0xf2, 0x0a, 0x2f, 0xaa, 0xc7, 0x99, 0x41, 0x91, |
| 0x29, 0x3c, 0x06, 0x23, 0x28, 0xb5, 0x2f, 0xfd, 0x24, 0x3e, 0x85, 0x00, |
| 0x00, 0x28, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x03, 0x00, 0x42, 0x35, 0x88, |
| 0x6a, 0x03, 0x87, 0x4c, 0x2d, 0xcd, 0x1e, 0xde, 0x25}; |
| const std::string kZstdEncodedDataString = |
| std::string(reinterpret_cast<const char*>(kZstdEncodedData), |
| sizeof(kZstdEncodedData)); |
| |
| const size_t kDefaultBufferSize = 1023; |
| |
| class DummySyncDictionary : public SharedDictionary { |
| public: |
| explicit DummySyncDictionary(const std::string& data_string, |
| const std::string& id = "") |
| : data_(base::MakeRefCounted<StringIOBuffer>(data_string)), |
| id_(id) { |
| hash_ = crypto::hash::Sha256(data_->span()); |
| } |
| |
| // SharedDictionary |
| int ReadAll(base::OnceCallback<void(int)> callback) override { return OK; } |
| scoped_refptr<IOBuffer> data() const override { return data_; } |
| size_t size() const override { return data_->size(); } |
| const SHA256HashValue& hash() const override { return hash_; } |
| const std::string& id() const override { return id_; } |
| |
| protected: |
| ~DummySyncDictionary() override = default; |
| |
| private: |
| const scoped_refptr<StringIOBuffer> data_; |
| const std::string id_; |
| SHA256HashValue hash_; |
| }; |
| |
| class DummyAsyncDictionary : public DummySyncDictionary { |
| public: |
| explicit DummyAsyncDictionary(const std::string& data_string) |
| : DummySyncDictionary(data_string) {} |
| |
| // SharedDictionary |
| int ReadAll(base::OnceCallback<void(int)> callback) override { |
| read_all_callback_ = std::move(callback); |
| return ERR_IO_PENDING; |
| } |
| base::OnceCallback<void(int)> TakeReadAllCallback() { |
| return std::move(read_all_callback_); |
| } |
| |
| private: |
| ~DummyAsyncDictionary() override = default; |
| |
| base::OnceCallback<void(int)> read_all_callback_; |
| }; |
| |
| TransportInfo TestSpdyTransportInfo() { |
| return TransportInfo( |
| TransportType::kDirect, IPEndPoint(IPAddress::IPv4Localhost(), 80), |
| /*accept_ch_frame_arg=*/"", |
| /*cert_is_issued_by_known_root=*/false, NextProto::kProtoHTTP2); |
| } |
| |
| static void BrotliTestTransactionHandler(const HttpRequestInfo* request, |
| std::string* response_status, |
| std::string* response_headers, |
| std::string* response_data) { |
| EXPECT_THAT(request->extra_headers.GetHeader( |
| shared_dictionary::kAvailableDictionaryHeaderName), |
| testing::Optional(kTestDictionarySha256Base64)); |
| *response_data = kBrotliEncodedDataString; |
| } |
| |
| static void ZstdTestTransactionHandler(const HttpRequestInfo* request, |
| std::string* response_status, |
| std::string* response_headers, |
| std::string* response_data) { |
| EXPECT_THAT(request->extra_headers.GetHeader( |
| shared_dictionary::kAvailableDictionaryHeaderName), |
| testing::Optional(kTestDictionarySha256Base64)); |
| *response_data = kZstdEncodedDataString; |
| } |
| |
| static const auto kTestTransactionHandlerWithoutAvailableDictionary = |
| base::BindRepeating([](const HttpRequestInfo* request, |
| std::string* response_status, |
| std::string* response_headers, |
| std::string* response_data) { |
| EXPECT_FALSE(request->extra_headers.HasHeader( |
| shared_dictionary::kAvailableDictionaryHeaderName)); |
| *response_data = kTestData; |
| }); |
| |
| constexpr char kTestUrl[] = "https://test.example/test"; |
| |
| const MockTransaction kBrotliDictionaryTestTransaction = { |
| .url = kTestUrl, |
| .method = "GET", |
| .request_time = base::Time(), |
| .request_headers = "sec-fetch-dest: document\r\n", |
| .load_flags = LOAD_CAN_USE_SHARED_DICTIONARY, |
| .transport_info = TestSpdyTransportInfo(), |
| .status = "HTTP/1.1 200 OK", |
| .response_headers = "content-encoding: dcb\n", |
| .response_time = base::Time(), |
| .data = "", // We set the body in the `handler` function. |
| .dns_aliases = {}, |
| .fps_cache_filter = std::nullopt, |
| .browser_run_id = std::nullopt, |
| .test_mode = TEST_MODE_NORMAL, |
| .handler = base::BindRepeating(&BrotliTestTransactionHandler), |
| .read_handler = MockTransactionReadHandler(), |
| .cert = nullptr, |
| .cert_status = 0, |
| .ssl_connection_status = 0, |
| .start_return_code = OK, |
| .read_return_code = OK, |
| }; |
| |
| const MockTransaction kZstdDictionaryTestTransaction = { |
| .url = kTestUrl, |
| .method = "GET", |
| .request_time = base::Time(), |
| .request_headers = "sec-fetch-dest: document\r\n", |
| .load_flags = LOAD_CAN_USE_SHARED_DICTIONARY, |
| .transport_info = TestSpdyTransportInfo(), |
| .status = "HTTP/1.1 200 OK", |
| .response_headers = "content-encoding: dcz\n", |
| .response_time = base::Time(), |
| .data = "", // We set the body in the `handler` function. |
| .dns_aliases = {}, |
| .fps_cache_filter = std::nullopt, |
| .browser_run_id = std::nullopt, |
| .test_mode = TEST_MODE_NORMAL, |
| .handler = base::BindRepeating(&ZstdTestTransactionHandler), |
| .read_handler = MockTransactionReadHandler(), |
| .cert = nullptr, |
| .cert_status = 0, |
| .ssl_connection_status = 0, |
| .start_return_code = OK, |
| .read_return_code = OK, |
| }; |
| |
| class SharedDictionaryNetworkTransactionTest : public ::testing::Test { |
| public: |
| SharedDictionaryNetworkTransactionTest() |
| : scoped_mock_transaction_(kBrotliDictionaryTestTransaction), |
| network_layer_(std::make_unique<MockNetworkLayer>()) { |
| scoped_feature_list_.InitWithFeatures( |
| /*enabled_features=*/{}, |
| /*disabled_features=*/{ |
| features::kCompressionDictionaryTransportRequireKnownRootCert}); |
| } |
| ~SharedDictionaryNetworkTransactionTest() override = default; |
| |
| SharedDictionaryNetworkTransactionTest( |
| const SharedDictionaryNetworkTransactionTest&) = delete; |
| SharedDictionaryNetworkTransactionTest& operator=( |
| const SharedDictionaryNetworkTransactionTest&) = delete; |
| |
| protected: |
| std::unique_ptr<HttpTransaction> CreateNetworkTransaction() { |
| return network_layer_->CreateTransaction(DEFAULT_PRIORITY); |
| } |
| |
| void RunUntilIdle() { task_environment_.RunUntilIdle(); } |
| |
| MockNetworkLayer& network_layer() { return *network_layer_.get(); } |
| |
| std::optional<ScopedMockTransaction> scoped_mock_transaction_; |
| |
| private: |
| std::unique_ptr<MockNetworkLayer> network_layer_; |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, SyncDictionary) { |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, NotAllowedToUseDictionary) { |
| // Change MockTransaction to check that there is no available-dictionary |
| // header. |
| scoped_mock_transaction_->handler = |
| kTestTransactionHandlerWithoutAvailableDictionary; |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return false; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, DictionaryId) { |
| // Change MockTransaction to check the dictionary-id header |
| scoped_mock_transaction_->handler = base::BindRepeating( |
| [](const HttpRequestInfo* request, std::string* response_status, |
| std::string* response_headers, std::string* response_data) { |
| EXPECT_THAT(request->extra_headers.GetHeader("dictionary-id"), |
| testing::Optional(std::string("\"test-id\""))); |
| *response_data = kBrotliEncodedDataString; |
| }); |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData, |
| "test-id"); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| DictionaryIdWithBackSlashAndDquote) { |
| // Change MockTransaction to check the dictionary-id header |
| scoped_mock_transaction_->handler = base::BindRepeating( |
| [](const HttpRequestInfo* request, std::string* response_status, |
| std::string* response_headers, std::string* response_data) { |
| EXPECT_THAT( |
| request->extra_headers.GetHeader("dictionary-id"), |
| testing::Optional(std::string("\"test\\\\dictionary\\\"id\""))); |
| *response_data = kBrotliEncodedDataString; |
| }); |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>( |
| kTestDictionaryData, "test\\dictionary\"id"); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, EmptyDictionaryId) { |
| // Change MockTransaction to check the dictionary-id header |
| scoped_mock_transaction_->handler = base::BindRepeating( |
| [](const HttpRequestInfo* request, std::string* response_status, |
| std::string* response_headers, std::string* response_data) { |
| EXPECT_FALSE(request->extra_headers.HasHeader("dictionary-id")); |
| *response_data = kBrotliEncodedDataString; |
| }); |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData, |
| ""); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| RequireKnownRootCertCheckFailure) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature( |
| features::kCompressionDictionaryTransportRequireKnownRootCert); |
| // Change MockTransaction to check that there is no available-dictionary |
| // header. |
| scoped_mock_transaction_->handler = |
| kTestTransactionHandlerWithoutAvailableDictionary; |
| scoped_mock_transaction_->transport_info.cert_is_issued_by_known_root = false; |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| RequireKnownRootCertCheckSuccess) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature( |
| features::kCompressionDictionaryTransportRequireKnownRootCert); |
| // The BrotliTestTransactionHandler `scoped_mock_transaction_->handler` will |
| // check that the there is a correct available-dictionary request header. |
| scoped_mock_transaction_->transport_info.cert_is_issued_by_known_root = true; |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| RequireKnownRootCertCheckSuccessForLocalhost) { |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitAndEnableFeature( |
| features::kCompressionDictionaryTransportRequireKnownRootCert); |
| // The BrotliTestTransactionHandler `new_mock_transaction.handler` will check |
| // that the there is a correct available-dictionary request header. |
| ScopedMockTransaction scoped_mock_transaction( |
| kBrotliDictionaryTestTransaction, "http:///localhost:1234/test"); |
| scoped_mock_transaction.transport_info.cert_is_issued_by_known_root = false; |
| |
| MockHttpRequest request(scoped_mock_transaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, NoMatchingDictionary) { |
| // Change MockTransaction to check that there is no available-dictionary |
| // header. |
| scoped_mock_transaction_->handler = |
| kTestTransactionHandlerWithoutAvailableDictionary; |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return nullptr; |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, OpaqueFrameOrigin) { |
| // Change MockTransaction to check that there is no available-dictionary |
| // header. |
| scoped_mock_transaction_->handler = |
| kTestTransactionHandlerWithoutAvailableDictionary; |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| // dictionary_getter must be called with a nullopt isolation_key. |
| CHECK(!isolation_key); |
| return nullptr; |
| }); |
| request.frame_origin = url::Origin(); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, WithoutValidLoadFlag) { |
| // Change MockTransaction to check that there is no available-dictionary |
| // header. |
| scoped_mock_transaction_->handler = |
| kTestTransactionHandlerWithoutAvailableDictionary; |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| bool getter_called = false; |
| request.dictionary_getter = base::BindRepeating( |
| [](bool* getter_called, |
| const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| *getter_called = true; |
| return nullptr; |
| }, |
| base::Unretained(&getter_called)); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| |
| CHECK_EQ(LOAD_CAN_USE_SHARED_DICTIONARY, request.load_flags); |
| // Change load_flags not to trigger the shared dictionary logic. |
| request.load_flags = LOAD_NORMAL; |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| |
| // SharedDictionaryGetter must not be called when |
| // LOAD_CAN_USE_SHARED_DICTIONARY is not set. |
| EXPECT_FALSE(getter_called); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, NoSbrContentEncoding) { |
| // Change MockTransaction to remove `content-encoding: dcb`. |
| scoped_mock_transaction_->response_headers = ""; |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| |
| // When there is no "content-encoding: dcb" header, |
| // SharedDictionaryNetworkTransaction must not decode the body. |
| EXPECT_THAT(read_result, kBrotliEncodedDataString.size()); |
| EXPECT_EQ(kBrotliEncodedDataString, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, WrongContentDictionaryHeader) { |
| scoped_mock_transaction_->handler = base::BindRepeating( |
| [](const HttpRequestInfo* request, std::string* response_status, |
| std::string* response_headers, std::string* response_data) { |
| std::string data = kBrotliEncodedDataString; |
| // Change the first byte of the compressed data to trigger |
| // UNEXPECTED_CONTENT_DICTIONARY_HEADER error. |
| ++data[0]; |
| *response_data = data; |
| }); |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(start_callback.GetResult(transaction.Start( |
| &request, start_callback.callback(), NetLogWithSource())), |
| test::IsError(OK)); |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT(read_callback.GetResult(transaction.Read( |
| buf.get(), buf->size(), read_callback.callback())), |
| test::IsError(ERR_UNEXPECTED_CONTENT_DICTIONARY_HEADER)); |
| } |
| TEST_F(SharedDictionaryNetworkTransactionTest, MultipleContentEncodingWithSbr) { |
| // Change MockTransaction to set `content-encoding: dcb, deflate`. |
| scoped_mock_transaction_->response_headers = |
| "content-encoding: dcb, deflate\n"; |
| |
| MockHttpRequest request(*scoped_mock_transaction_); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| |
| // When there is Content-Encoding header which value is other than "dcb", |
| // SharedDictionaryNetworkTransaction must not decode the body. |
| EXPECT_THAT(read_result, kBrotliEncodedDataString.size()); |
| EXPECT_EQ(kBrotliEncodedDataString, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| AsyncDictionarySuccessBeforeStartReading) { |
| scoped_refptr<DummyAsyncDictionary> dictionary = |
| base::MakeRefCounted<DummyAsyncDictionary>(kTestDictionaryData); |
| DummyAsyncDictionary* dictionary_ptr = dictionary.get(); |
| |
| MockHttpRequest request(kBrotliDictionaryTestTransaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](scoped_refptr<DummyAsyncDictionary>* dictionary, |
| const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| CHECK(*dictionary); |
| return std::move(*dictionary); |
| }, |
| base::Unretained(&dictionary)); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| base::OnceCallback<void(int)> dictionary_read_all_callback = |
| dictionary_ptr->TakeReadAllCallback(); |
| ASSERT_TRUE(dictionary_read_all_callback); |
| std::move(dictionary_read_all_callback).Run(OK); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| AsyncDictionarySuccessAfterStartReading) { |
| scoped_refptr<DummyAsyncDictionary> dictionary = |
| base::MakeRefCounted<DummyAsyncDictionary>(kTestDictionaryData); |
| DummyAsyncDictionary* dictionary_ptr = dictionary.get(); |
| |
| MockHttpRequest request(kBrotliDictionaryTestTransaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](scoped_refptr<DummyAsyncDictionary>* dictionary, |
| const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| CHECK(*dictionary); |
| return std::move(*dictionary); |
| }, |
| base::Unretained(&dictionary)); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| base::OnceCallback<void(int)> dictionary_read_all_callback = |
| dictionary_ptr->TakeReadAllCallback(); |
| ASSERT_TRUE(dictionary_read_all_callback); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| RunUntilIdle(); |
| EXPECT_FALSE(read_callback.have_result()); |
| |
| std::move(dictionary_read_all_callback).Run(OK); |
| |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| AsyncDictionarySuccessAfterTransactionDestroy) { |
| scoped_refptr<DummyAsyncDictionary> dictionary = |
| base::MakeRefCounted<DummyAsyncDictionary>(kTestDictionaryData); |
| DummyAsyncDictionary* dictionary_ptr = dictionary.get(); |
| |
| MockHttpRequest request(kBrotliDictionaryTestTransaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](scoped_refptr<DummyAsyncDictionary>* dictionary, |
| const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| CHECK(*dictionary); |
| return std::move(*dictionary); |
| }, |
| base::Unretained(&dictionary)); |
| std::unique_ptr<SharedDictionaryNetworkTransaction> transaction = |
| std::make_unique<SharedDictionaryNetworkTransaction>( |
| CreateNetworkTransaction(), /*enable_shared_zstd=*/false); |
| transaction->SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction->Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| base::OnceCallback<void(int)> dictionary_read_all_callback = |
| dictionary_ptr->TakeReadAllCallback(); |
| ASSERT_TRUE(dictionary_read_all_callback); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction->Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| RunUntilIdle(); |
| EXPECT_FALSE(read_callback.have_result()); |
| |
| transaction.reset(); |
| |
| std::move(dictionary_read_all_callback).Run(OK); |
| |
| EXPECT_FALSE(read_callback.have_result()); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| AsyncDictionaryFailureBeforeStartReading) { |
| scoped_refptr<DummyAsyncDictionary> dictionary = |
| base::MakeRefCounted<DummyAsyncDictionary>(kTestDictionaryData); |
| DummyAsyncDictionary* dictionary_ptr = dictionary.get(); |
| |
| MockHttpRequest request(kBrotliDictionaryTestTransaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](scoped_refptr<DummyAsyncDictionary>* dictionary, |
| const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| CHECK(*dictionary); |
| return std::move(*dictionary); |
| }, |
| base::Unretained(&dictionary)); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| base::OnceCallback<void(int)> dictionary_read_all_callback = |
| dictionary_ptr->TakeReadAllCallback(); |
| ASSERT_TRUE(dictionary_read_all_callback); |
| std::move(dictionary_read_all_callback).Run(ERR_FAILED); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_DICTIONARY_LOAD_FAILED)); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, |
| AsyncDictionaryFailureAfterStartReading) { |
| scoped_refptr<DummyAsyncDictionary> dictionary = |
| base::MakeRefCounted<DummyAsyncDictionary>(kTestDictionaryData); |
| DummyAsyncDictionary* dictionary_ptr = dictionary.get(); |
| |
| MockHttpRequest request(kBrotliDictionaryTestTransaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](scoped_refptr<DummyAsyncDictionary>* dictionary, |
| const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| CHECK(*dictionary); |
| return std::move(*dictionary); |
| }, |
| base::Unretained(&dictionary)); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| base::OnceCallback<void(int)> dictionary_read_all_callback = |
| dictionary_ptr->TakeReadAllCallback(); |
| ASSERT_TRUE(dictionary_read_all_callback); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| RunUntilIdle(); |
| EXPECT_FALSE(read_callback.have_result()); |
| |
| std::move(dictionary_read_all_callback).Run(ERR_FAILED); |
| |
| EXPECT_EQ(ERR_DICTIONARY_LOAD_FAILED, read_callback.WaitForResult()); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, Restart) { |
| ScopedMockTransaction mock_transaction(kSimpleGET_Transaction); |
| mock_transaction.start_return_code = ERR_FAILED; |
| MockHttpRequest request(mock_transaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return nullptr; |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(ERR_FAILED)); |
| |
| { |
| TestCompletionCallback restart_callback; |
| ASSERT_THAT( |
| transaction.RestartIgnoringLastError(restart_callback.callback()), |
| test::IsError(ERR_FAILED)); |
| } |
| { |
| TestCompletionCallback restart_callback; |
| ASSERT_THAT( |
| transaction.RestartWithCertificate( |
| /*client_cert=*/nullptr, |
| /*client_private_key=*/nullptr, restart_callback.callback()), |
| test::IsError(ERR_FAILED)); |
| } |
| { |
| TestCompletionCallback restart_callback; |
| ASSERT_THAT(transaction.RestartWithAuth(AuthCredentials(), |
| restart_callback.callback()), |
| test::IsError(ERR_FAILED)); |
| } |
| ASSERT_FALSE(transaction.IsReadyToRestartForAuth()); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, StopCaching) { |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| EXPECT_FALSE(network_layer().stop_caching_called()); |
| transaction.StopCaching(); |
| EXPECT_TRUE(network_layer().stop_caching_called()); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, DoneReading) { |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| EXPECT_FALSE(network_layer().done_reading_called()); |
| transaction.DoneReading(); |
| EXPECT_TRUE(network_layer().done_reading_called()); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, GetLoadState) { |
| ScopedMockTransaction scoped_mock_transaction(kSimpleGET_Transaction); |
| MockHttpRequest request(scoped_mock_transaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return nullptr; |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| EXPECT_EQ(LOAD_STATE_IDLE, transaction.GetLoadState()); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(1); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, 1); |
| |
| EXPECT_EQ(LOAD_STATE_READING_RESPONSE, transaction.GetLoadState()); |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, SharedZstd) { |
| // Override MockTransaction to use `content-encoding: dcz`. |
| scoped_mock_transaction_.reset(); |
| ScopedMockTransaction new_mock_transaction(kZstdDictionaryTestTransaction); |
| |
| MockHttpRequest request(new_mock_transaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/true); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| #if defined(NET_DISABLE_ZSTD) |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_CONTENT_DECODING_FAILED)); |
| #else // defined(NET_DISABLE_ZSTD) |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| #endif // defined(NET_DISABLE_ZSTD) |
| } |
| |
| TEST_F(SharedDictionaryNetworkTransactionTest, NoZstdDContentEncoding) { |
| // Change MockTransaction to remove `content-encoding: dcz`. |
| scoped_mock_transaction_.reset(); |
| ScopedMockTransaction scoped_mock_transaction(kZstdDictionaryTestTransaction); |
| scoped_mock_transaction.response_headers = ""; |
| |
| MockHttpRequest request(scoped_mock_transaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/true); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| |
| // When there is no "content-encoding: dcz" header, |
| // SharedDictionaryNetworkTransaction must not decode the body. |
| EXPECT_THAT(read_result, kZstdEncodedDataString.size()); |
| EXPECT_EQ(kZstdEncodedDataString, std::string(buf->data(), read_result)); |
| } |
| |
| enum class ProtocolCheckHostTestCase { |
| kLocalHost, |
| kNonLocalhost, |
| }; |
| std::string ToString(ProtocolCheckHostTestCase host_type) { |
| switch (host_type) { |
| case ProtocolCheckHostTestCase::kLocalHost: |
| return "LocalHost"; |
| case ProtocolCheckHostTestCase::kNonLocalhost: |
| return "NonLocalhost"; |
| } |
| } |
| |
| class SharedDictionaryNetworkTransactionProtocolCheckTest |
| : public SharedDictionaryNetworkTransactionTest, |
| public testing::WithParamInterface<ProtocolCheckHostTestCase> { |
| public: |
| SharedDictionaryNetworkTransactionProtocolCheckTest() {} |
| SharedDictionaryNetworkTransactionProtocolCheckTest( |
| const SharedDictionaryNetworkTransactionProtocolCheckTest&) = delete; |
| SharedDictionaryNetworkTransactionProtocolCheckTest& operator=( |
| const SharedDictionaryNetworkTransactionProtocolCheckTest&) = delete; |
| ~SharedDictionaryNetworkTransactionProtocolCheckTest() override = default; |
| |
| protected: |
| MockTransaction CreateMockTransaction() { |
| MockTransaction mock_transaction = kBrotliDictionaryTestTransaction; |
| if (IsLocalHost()) { |
| mock_transaction.url = "http://localhost/test"; |
| } |
| return mock_transaction; |
| } |
| |
| private: |
| bool IsLocalHost() const { |
| return GetParam() == ProtocolCheckHostTestCase::kLocalHost; |
| } |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| SharedDictionaryNetworkTransactionProtocolCheckTest, |
| testing::ValuesIn({ProtocolCheckHostTestCase::kLocalHost, |
| ProtocolCheckHostTestCase::kNonLocalhost}), |
| [](const testing::TestParamInfo<ProtocolCheckHostTestCase>& info) { |
| return ToString(info.param); |
| }); |
| |
| TEST_P(SharedDictionaryNetworkTransactionProtocolCheckTest, Basic) { |
| // Reset `scoped_mock_transaction_` to use the custom ScopedMockTransaction. |
| scoped_mock_transaction_.reset(); |
| ScopedMockTransaction new_mock_transaction(CreateMockTransaction()); |
| |
| MockHttpRequest request(new_mock_transaction); |
| request.dictionary_getter = base::BindRepeating( |
| [](const std::optional<SharedDictionaryIsolationKey>& isolation_key, |
| const GURL& request_url) -> scoped_refptr<SharedDictionary> { |
| return base::MakeRefCounted<DummySyncDictionary>(kTestDictionaryData); |
| }); |
| SharedDictionaryNetworkTransaction transaction(CreateNetworkTransaction(), |
| /*enable_shared_zstd=*/false); |
| transaction.SetIsSharedDictionaryReadAllowedCallback( |
| base::BindRepeating([]() { return true; })); |
| |
| TestCompletionCallback start_callback; |
| ASSERT_THAT(transaction.Start(&request, start_callback.callback(), |
| NetLogWithSource()), |
| test::IsError(ERR_IO_PENDING)); |
| EXPECT_THAT(start_callback.WaitForResult(), test::IsError(OK)); |
| |
| scoped_refptr<IOBufferWithSize> buf = |
| base::MakeRefCounted<IOBufferWithSize>(kDefaultBufferSize); |
| TestCompletionCallback read_callback; |
| ASSERT_THAT( |
| transaction.Read(buf.get(), buf->size(), read_callback.callback()), |
| test::IsError(ERR_IO_PENDING)); |
| int read_result = read_callback.WaitForResult(); |
| EXPECT_THAT(read_result, kTestData.size()); |
| EXPECT_EQ(kTestData, std::string(buf->data(), read_result)); |
| } |
| |
| } // namespace |
| |
| } // namespace net |