| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <memory> |
| |
| #include "base/memory/memory_pressure_listener.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/system/data_pipe_utils.h" |
| #include "net/base/features.h" |
| #include "net/base/mime_sniffer.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/base/url_util.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_builder.h" |
| #include "services/network/cors/cors_url_loader_factory.h" |
| #include "services/network/network_context.h" |
| #include "services/network/network_service.h" |
| #include "services/network/network_service_memory_cache.h" |
| #include "services/network/network_service_memory_cache_writer.h" |
| #include "services/network/public/cpp/cors/origin_access_list.h" |
| #include "services/network/public/cpp/cross_origin_embedder_policy.h" |
| #include "services/network/public/cpp/features.h" |
| #include "services/network/public/mojom/client_security_state.mojom.h" |
| #include "services/network/public/mojom/http_raw_headers.mojom.h" |
| #include "services/network/public/mojom/url_loader.mojom.h" |
| #include "services/network/public/mojom/url_loader_factory.mojom.h" |
| #include "services/network/resource_scheduler/resource_scheduler_client.h" |
| #include "services/network/test/fake_test_cert_verifier_params_factory.h" |
| #include "services/network/test/mock_devtools_observer.h" |
| #include "services/network/test/test_url_loader_client.h" |
| #include "services/network/test/test_utils.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace network { |
| |
| namespace { |
| |
| constexpr int kMaxTotalSize = 8 * 1024; |
| constexpr int kMaxPerEntrySize = 4 * 1024; |
| |
| struct LoaderPair { |
| LoaderPair() : client(std::make_unique<TestURLLoaderClient>()) {} |
| ~LoaderPair() = default; |
| |
| LoaderPair(LoaderPair&&) = default; |
| LoaderPair& operator=(LoaderPair&&) = default; |
| LoaderPair(const LoaderPair&) = delete; |
| LoaderPair& operator=(const LoaderPair&) = delete; |
| |
| mojo::Remote<mojom::URLLoader> loader_remote; |
| std::unique_ptr<TestURLLoaderClient> client; |
| }; |
| |
| mojom::URLResponseHeadPtr CreateCacheableURLResponseHead() { |
| mojom::URLResponseHeadPtr response_head = CreateURLResponseHead(net::HTTP_OK); |
| response_head->headers->AddHeader("Cache-Control", "max-age=60"); |
| base::Time now = base::Time::Now(); |
| response_head->request_time = now; |
| response_head->response_time = now; |
| return response_head; |
| } |
| |
| // An EmbeddedTestServer request handler that returns a cacheable response of |
| // which body size and max-age are specified by the query string. The content |
| // body consists of 'a'. |
| std::unique_ptr<net::test_server::HttpResponse> CacheableResponseHandler( |
| const net::test_server::HttpRequest& request) { |
| if (request.GetURL().path_piece() != "/cacheable") |
| return nullptr; |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| |
| uint64_t body_size = 64; |
| std::string query_body_size; |
| if (net::GetValueForKeyInQuery(request.GetURL(), "body-size", |
| &query_body_size)) { |
| EXPECT_TRUE(base::StringToUint64(query_body_size, &body_size)); |
| } |
| |
| uint64_t max_age = 60; |
| std::string query_max_age; |
| if (net::GetValueForKeyInQuery(request.GetURL(), "max-age", &query_max_age)) { |
| EXPECT_TRUE(base::StringToUint64(query_max_age, &max_age)); |
| } |
| |
| response->AddCustomHeader("cache-control", |
| base::StringPrintf("max-age=%" PRId64, max_age)); |
| response->set_content(std::string(body_size, 'a')); |
| return response; |
| } |
| |
| // Similar to above, but doesn't send Content-Length header. |
| std::unique_ptr<net::test_server::HttpResponse> |
| CacheableWithoutContentLengthHandler( |
| const net::test_server::HttpRequest& request) { |
| if (request.GetURL().path_piece() != "/cacheable_without_content_length") |
| return nullptr; |
| |
| uint64_t body_size = 64; |
| std::string query_body_size; |
| if (net::GetValueForKeyInQuery(request.GetURL(), "body-size", |
| &query_body_size)) { |
| EXPECT_TRUE(base::StringToUint64(query_body_size, &body_size)); |
| } |
| |
| constexpr const char kHeader[] = |
| "HTTP/1.1 200 OK\n" |
| "Content-Type: text/plain\n"; |
| auto response = std::make_unique<net::test_server::RawHttpResponse>( |
| kHeader, std::string(body_size, 'a')); |
| return response; |
| } |
| |
| // Used for cross origin read blocking check. |
| std::unique_ptr<net::test_server::HttpResponse> CorbCheckHandler( |
| const net::test_server::HttpRequest& request) { |
| if (request.GetURL().path_piece() == "/corb_nosniff") { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->AddCustomHeader("cache-control", "max-age=60"); |
| response->AddCustomHeader("x-content-type-options", "nosniff"); |
| response->AddCustomHeader("content-type", "text/javascript"); |
| response->set_content("{\"key\": true}"); |
| return response; |
| } |
| |
| if (request.GetURL().path_piece() == "/corb_sniff") { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->AddCustomHeader("cache-control", "max-age=60"); |
| response->AddCustomHeader("content-type", "text/html"); |
| // Set a html content of which size is larger than net::kMaxBytestosniff. |
| std::string content("<html>sniffed content"); |
| content.append(std::string(net::kMaxBytesToSniff, ' ')); |
| response->set_content(content); |
| return response; |
| } |
| |
| return nullptr; |
| } |
| |
| // Used in NetworkServiceMemoryCacheWithFactoryOverrideTest. |
| class TestURLLoaderFactory : public mojom::URLLoaderFactory { |
| public: |
| explicit TestURLLoaderFactory( |
| mojo::PendingReceiver<mojom::URLLoaderFactory> receiver) { |
| receivers_.Add(this, std::move(receiver)); |
| } |
| |
| void CreateLoaderAndStart( |
| mojo::PendingReceiver<mojom::URLLoader> receiver, |
| int32_t request_id, |
| uint32_t options, |
| const ResourceRequest& resource_request, |
| mojo::PendingRemote<mojom::URLLoaderClient> pending_client, |
| const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) |
| override { |
| url_loader_receivers_.Add(&noop_loader_, std::move(receiver)); |
| |
| mojo::Remote<mojom::URLLoaderClient> client(std::move(pending_client)); |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| client->OnReceiveResponse(std::move(response_head), /*body=*/{}, |
| std::nullopt); |
| client->OnComplete(URLLoaderCompletionStatus(net::OK)); |
| } |
| void Clone(mojo::PendingReceiver<mojom::URLLoaderFactory> receiver) override { |
| receivers_.Add(this, std::move(receiver)); |
| } |
| |
| private: |
| class NoopURLLoader : public mojom::URLLoader { |
| public: |
| void FollowRedirect( |
| const std::vector<std::string>& removed_headers, |
| const net::HttpRequestHeaders& modified_headers, |
| const net::HttpRequestHeaders& modified_cors_exempt_headers, |
| const std::optional<GURL>& new_url) override {} |
| void SetPriority(net::RequestPriority priority, |
| int32_t intra_priority_value) override {} |
| void PauseReadingBodyFromNet() override {} |
| void ResumeReadingBodyFromNet() override {} |
| }; |
| |
| NoopURLLoader noop_loader_; |
| mojo::ReceiverSet<mojom::URLLoaderFactory> receivers_; |
| mojo::ReceiverSet<mojom::URLLoader> url_loader_receivers_; |
| }; |
| |
| } // namespace |
| |
| class NetworkServiceMemoryCacheTest : public testing::Test { |
| public: |
| NetworkServiceMemoryCacheTest() |
| : task_environment_(base::test::TaskEnvironment::MainThreadType::IO) { |
| scoped_feature_list_.InitAndEnableFeatureWithParameters( |
| features::kNetworkServiceMemoryCache, |
| {{"max_total_size", base::NumberToString(kMaxTotalSize)}, |
| {"max_per_entry_size", base::NumberToString(kMaxPerEntrySize)}}); |
| } |
| |
| ~NetworkServiceMemoryCacheTest() override = default; |
| |
| void SetUp() override { |
| should_redirect_in_cacheable_handler_ = false; |
| |
| test_server_.AddDefaultHandlers(); |
| test_server_.RegisterRequestHandler( |
| base::BindRepeating(&CacheableResponseHandler)); |
| test_server_.RegisterRequestHandler( |
| base::BindRepeating(&CacheableWithoutContentLengthHandler)); |
| test_server_.RegisterRequestHandler(base::BindRepeating(&CorbCheckHandler)); |
| test_server_.RegisterRequestHandler(base::BindRepeating( |
| &NetworkServiceMemoryCacheTest::CacheableOrRedirectHandler, |
| base::Unretained(this))); |
| ASSERT_TRUE(test_server_.Start()); |
| |
| // The following setup similar to CorsURLLoaderFactoryTest. |
| |
| network_service_ = NetworkService::CreateForTesting(); |
| |
| auto context_params = mojom::NetworkContextParams::New(); |
| context_params->cert_verifier_params = |
| FakeTestCertVerifierParamsFactory::GetCertVerifierParams(); |
| context_params->initial_proxy_config = |
| net::ProxyConfigWithAnnotation::CreateDirect(); |
| network_context_ = std::make_unique<NetworkContext>( |
| network_service_.get(), |
| network_context_remote_.BindNewPipeAndPassReceiver(), |
| std::move(context_params)); |
| |
| auto factory_params = network::mojom::URLLoaderFactoryParams::New(); |
| constexpr int kProcessId = 123; |
| factory_params->process_id = kProcessId; |
| factory_params->is_trusted = true; |
| factory_params->request_initiator_origin_lock = |
| url::Origin::Create(test_server_.base_url()); |
| if (HasFactoryOverride()) { |
| factory_params->factory_override = mojom::URLLoaderFactoryOverride::New(); |
| mojo::PendingRemote<mojom::URLLoaderFactory> factory_remote; |
| overriding_factory_ = std::make_unique<TestURLLoaderFactory>( |
| factory_remote.InitWithNewPipeAndPassReceiver()); |
| factory_params->factory_override->overriding_factory = |
| std::move(factory_remote); |
| } |
| |
| url::Origin test_server_origin = |
| url::Origin::Create(test_server_.base_url()); |
| factory_params->isolation_info = |
| net::IsolationInfo::CreateForInternalRequest(test_server_origin); |
| |
| cors_url_loader_factory_ = std::make_unique<cors::CorsURLLoaderFactory>( |
| network_context_.get(), std::move(factory_params), |
| /*resource_scheduler_client=*/nullptr, |
| cors_url_loader_factory_remote_.BindNewPipeAndPassReceiver(), |
| &origin_access_list_, /*resource_block_list=*/nullptr); |
| } |
| |
| base::test::TaskEnvironment& task_environment() { return task_environment_; } |
| |
| net::test_server::EmbeddedTestServer& test_server() { return test_server_; } |
| |
| net::URLRequestContext& url_request_context() { |
| return *network_context_->url_request_context(); |
| } |
| |
| NetworkServiceMemoryCache& memory_cache() { |
| return *network_context_->GetMemoryCache(); |
| } |
| |
| void MakeCacheableHandlerSendRedirect() { |
| should_redirect_in_cacheable_handler_ = true; |
| } |
| |
| ResourceRequest CreateRequest(const std::string& relative_path) { |
| ResourceRequest request; |
| GURL url = test_server().GetURL(relative_path); |
| url::Origin origin = url::Origin::Create(url); |
| request.url = url; |
| request.request_initiator = origin; |
| request.enable_load_timing = true; |
| return request; |
| } |
| |
| std::unique_ptr<net::URLRequest> CreateURLRequest(const GURL& url) { |
| return url_request_context().CreateRequest(url, net::DEFAULT_PRIORITY, |
| /*delegate=*/nullptr, |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| } |
| |
| LoaderPair CreateLoaderAndStart(const ResourceRequest& request) { |
| LoaderPair pair; |
| cors_url_loader_factory_->CreateLoaderAndStart( |
| pair.loader_remote.BindNewPipeAndPassReceiver(), /*request_id=*/1, |
| mojom::kURLLoadOptionNone, request, pair.client->CreateRemote(), |
| net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); |
| return pair; |
| } |
| |
| void StoreResponseToMemoryCache(const ResourceRequest& request) { |
| LoaderPair pair = CreateLoaderAndStart(request); |
| pair.client->RunUntilComplete(); |
| } |
| |
| bool WriterWillBeCreatedToStoreResponse( |
| net::URLRequest* url_request, |
| const mojom::URLResponseHeadPtr& response_head) { |
| std::unique_ptr<NetworkServiceMemoryCacheWriter> writer = |
| memory_cache().MaybeCreateWriter(url_request, |
| mojom::RequestDestination::kDocument, |
| net::TransportInfo(), response_head); |
| return writer.get() != nullptr; |
| } |
| |
| bool CanServeFromMemoryCache(const ResourceRequest& request) { |
| net::SchemefulSite site(request.url); |
| net::NetworkIsolationKey network_isolation_key(/*top_frame_site=*/site, |
| /*frame_site=*/site); |
| return CanServeFromMemoryCache(request, network_isolation_key); |
| } |
| |
| bool CanServeFromMemoryCache( |
| const ResourceRequest& request, |
| const net::NetworkIsolationKey& network_isolation_key) { |
| return CanServeFromMemoryCache(request, network_isolation_key, |
| CrossOriginEmbedderPolicy()); |
| } |
| |
| bool CanServeFromMemoryCache( |
| const ResourceRequest& request, |
| const net::NetworkIsolationKey& network_isolation_key, |
| const CrossOriginEmbedderPolicy& cross_origin_embedder_policy) { |
| return memory_cache() |
| .CanServe(mojom::kURLLoadOptionNone, request, network_isolation_key, |
| cross_origin_embedder_policy, |
| /*client_security_state=*/nullptr) |
| .has_value(); |
| } |
| |
| virtual bool HasFactoryOverride() const { return false; } |
| |
| private: |
| std::unique_ptr<net::test_server::HttpResponse> CacheableOrRedirectHandler( |
| const net::test_server::HttpRequest& request) { |
| if (request.GetURL().path_piece() != "/cacheable_or_redirect") |
| return nullptr; |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| |
| if (should_redirect_in_cacheable_handler_) { |
| response->set_code(net::HttpStatusCode::HTTP_FOUND); |
| response->AddCustomHeader("location", "/cacheable"); |
| } else { |
| constexpr size_t kBodySize = 64; |
| response->AddCustomHeader("cache-control", "max-age=60"); |
| response->set_content(std::string(kBodySize, 'a')); |
| } |
| |
| return response; |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::test::TaskEnvironment task_environment_; |
| std::unique_ptr<net::URLRequestContext> url_request_context_; |
| std::unique_ptr<NetworkService> network_service_; |
| std::unique_ptr<NetworkContext> network_context_; |
| mojo::Remote<mojom::NetworkContext> network_context_remote_; |
| |
| net::test_server::EmbeddedTestServer test_server_; |
| |
| bool should_redirect_in_cacheable_handler_ = false; |
| |
| std::unique_ptr<mojom::URLLoaderFactory> cors_url_loader_factory_; |
| mojo::Remote<mojom::URLLoaderFactory> cors_url_loader_factory_remote_; |
| |
| cors::OriginAccessList origin_access_list_; |
| |
| std::unique_ptr<TestURLLoaderFactory> overriding_factory_; |
| }; |
| |
| class NetworkServiceMemoryCacheWithFactoryOverrideTest |
| : public NetworkServiceMemoryCacheTest { |
| public: |
| bool HasFactoryOverride() const override { return true; } |
| }; |
| |
| TEST_F(NetworkServiceMemoryCacheTest, |
| CreateWriter_SchemeIsNeitherHTTPNorHTTPS) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(GURL("data:text/plain;foo")); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_MethodIsNotGet) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->set_method("POST"); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, |
| CreateWriter_NetworkIsolationKeyIsTransient) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| net::features::kSplitCacheByNetworkIsolationKey); |
| |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->set_isolation_info(net::IsolationInfo::CreateTransient()); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_StatusCodeIsNotOK) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| |
| mojom::URLResponseHeadPtr response_head = |
| CreateURLResponseHead(net::HTTP_BAD_REQUEST); |
| |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_EmptyResponse) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| |
| auto response_head = mojom::URLResponseHead::New(); |
| |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_BypassCache) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetLoadFlags(net::LOAD_BYPASS_CACHE); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_DisableCache) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetLoadFlags(net::LOAD_DISABLE_CACHE); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_ResponseNotCacheable) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| response_head->headers->RemoveHeader("cache-control"); |
| response_head->headers->AddHeader("cache-control", "no-store"); |
| |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_IfUnmodifiedSince) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetExtraRequestHeaderByName("iF-unMOdified-since", "hello", |
| /*overwrite=*/true); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_IfMatch) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetExtraRequestHeaderByName("IF-match", "foo", |
| /*overwrite=*/true); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_IfRange) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetExtraRequestHeaderByName("if-rangE", "bar", |
| /*overwrite=*/true); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_IfModifiedSince) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetExtraRequestHeaderByName("if-modified-since", "bar", |
| /*overwrite=*/true); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CreateWriter_IfNoneMatch) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetExtraRequestHeaderByName("if-none-match", "bar", |
| /*overwrite=*/true); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CacheControlBogus) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetExtraRequestHeaderByName("cache-control", "bogus", |
| /*overwrite=*/true); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| ASSERT_TRUE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CacheControlNoCache) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetExtraRequestHeaderByName("cache-control", "no-cache", |
| /*overwrite=*/true); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, PragmaNoCatche) { |
| std::unique_ptr<net::URLRequest> url_request = |
| CreateURLRequest(test_server().GetURL("/cacheable")); |
| url_request->SetExtraRequestHeaderByName("pragma", "no-cache", |
| /*overwrite=*/true); |
| |
| mojom::URLResponseHeadPtr response_head = CreateCacheableURLResponseHead(); |
| ASSERT_FALSE( |
| WriterWillBeCreatedToStoreResponse(url_request.get(), response_head)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_Basic) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_NetworkIsolationKeyIsTransient) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| net::features::kSplitCacheByNetworkIsolationKey); |
| |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| ASSERT_FALSE(CanServeFromMemoryCache( |
| request, net::NetworkIsolationKey::CreateTransientForTesting())); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_InvalidURL) { |
| ResourceRequest request; |
| request.url = GURL(); |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_SchemeIsNeitherHTTPNorHTTPS) { |
| ResourceRequest request; |
| request.url = GURL("data:text/plain;foo"); |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_MethodIsNotGet) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| request.method = net::HttpRequestHeaders::kPostMethod; |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_BypassCache) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| request.load_flags |= net::LOAD_BYPASS_CACHE; |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_DisableCache) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| request.load_flags |= net::LOAD_DISABLE_CACHE; |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_ValidateCache) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| request.load_flags |= net::LOAD_VALIDATE_CACHE; |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_BlockedByRequestHeaders) { |
| constexpr const char* kSpecialHeaders[][2] = { |
| {"if-Unmodified-since", "foo"}, |
| {"if-mAtch", "foo"}, |
| {"if-raNge", "foo"}, |
| {"if-modiFied-since", "foo"}, |
| {"IF-NONE-MATCH", "foo"}, |
| {"cachE-control", "no-cache"}, |
| {"praGma", "no-cache"}, |
| {"Cache-Control", "max-age=0"}, |
| {"Range", "bytes=0-"}, |
| }; |
| |
| // Store a response to the in-memory cache first. |
| { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| } |
| |
| for (const auto& [name, value] : kSpecialHeaders) { |
| SCOPED_TRACE(base::StringPrintf("header='%s', value='%s'", name, value)); |
| ResourceRequest request = CreateRequest("/cacheable"); |
| request.headers.SetHeader(name, value); |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_Expired) { |
| const uint64_t kMaxAge = 60; |
| ResourceRequest request = |
| CreateRequest(base::StringPrintf("/cacheable?max-age=%" PRId64, kMaxAge)); |
| StoreResponseToMemoryCache(request); |
| |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| // The response has `max-age=60`. Set the current time 61 seconds later to |
| // make the cached response stale. |
| memory_cache().SetCurrentTimeForTesting(base::Time::Now() + |
| base::Seconds(kMaxAge + 1)); |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_ResponseTooLarge) { |
| ResourceRequest request = CreateRequest( |
| base::StringPrintf("/cacheable?body-size=%d", kMaxPerEntrySize + 1)); |
| StoreResponseToMemoryCache(request); |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, |
| CanServe_ResponseTooLargeWithoutContentLength) { |
| ResourceRequest request = CreateRequest(base::StringPrintf( |
| "/cacheable_without_content_length?body-size=%d", kMaxPerEntrySize + 1)); |
| StoreResponseToMemoryCache(request); |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, |
| CanServe_SplitCacheByNetworkIsolationKeyEnabled) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature( |
| net::features::kSplitCacheByNetworkIsolationKey); |
| |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| |
| net::SchemefulSite same_site(request.url); |
| net::NetworkIsolationKey same_site_network_isolation_key( |
| /*top_frame_site=*/same_site, /*frame_site=*/same_site); |
| ASSERT_TRUE( |
| CanServeFromMemoryCache(request, same_site_network_isolation_key)); |
| |
| net::SchemefulSite other_site(GURL("https://example.test")); |
| net::NetworkIsolationKey other_site_network_isolation_key( |
| /*top_frame_site=*/same_site, /*frame_site=*/other_site); |
| ASSERT_FALSE( |
| CanServeFromMemoryCache(request, other_site_network_isolation_key)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_CorpBlocked) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| |
| request.mode = mojom::RequestMode::kNoCors; |
| request.request_initiator = |
| url::Origin::Create(GURL("https://other-origin.test/")); |
| |
| net::SchemefulSite site(request.url); |
| net::NetworkIsolationKey network_isolation_key(/*top_frame_site=*/site, |
| /*frame_site=*/site); |
| |
| CrossOriginEmbedderPolicy cross_origin_embedder_policy; |
| cross_origin_embedder_policy.value = |
| mojom::CrossOriginEmbedderPolicyValue::kRequireCorp; |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request, network_isolation_key, |
| cross_origin_embedder_policy)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_CorbBlockedNoSniff) { |
| ResourceRequest request = CreateRequest("/corb_nosniff"); |
| StoreResponseToMemoryCache(request); |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| const auto other_origin = |
| url::Origin::Create(GURL("https://other-origin.test")); |
| net::SchemefulSite other_site(other_origin); |
| net::NetworkIsolationKey network_isolation_key( |
| /*top_frame_site=*/other_site, /*frame_site=*/other_site); |
| |
| request.request_initiator = other_origin; |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request, network_isolation_key)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_CorbBlockedSniff) { |
| ResourceRequest request = CreateRequest("/corb_sniff"); |
| StoreResponseToMemoryCache(request); |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| const auto other_origin = |
| url::Origin::Create(GURL("https://other-origin.test")); |
| net::SchemefulSite other_site(other_origin); |
| net::NetworkIsolationKey network_isolation_key( |
| /*top_frame_site=*/other_site, /*frame_site=*/other_site); |
| |
| request.request_initiator = other_origin; |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request, network_isolation_key)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_VaryHeaderAcceptEncoding) { |
| // The `/echoheadercache` handler sends `Vary: foo` header. |
| ResourceRequest request = CreateRequest("/echoheadercache?Accept-Encoding"); |
| request.headers.SetHeader("accept-encoding", "gzip"); |
| |
| StoreResponseToMemoryCache(request); |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| // Stored response should not be served when the header that is specified in |
| // `Vary` has different value. |
| request.headers.SetHeader("accept-encoding", "br"); |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| |
| // Specifying the net::LOAD_SKIP_VARY_CHECK flag skips Vary header checks. |
| request.load_flags |= net::LOAD_SKIP_VARY_CHECK; |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_MultipleVaryHeader) { |
| ResourceRequest request = |
| CreateRequest("/echoheadercache?Accept-Encoding,Origin"); |
| request.headers.SetHeader("accept-encoding", "gzip"); |
| request.headers.SetHeader("origin", "https://a.test"); |
| |
| StoreResponseToMemoryCache(request); |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| request.headers.SetHeader("origin", "https://b.test"); |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| // TODO(https://crbug.com/1339708): Change the test name and the expectation |
| // once we implement appropriate Vary checks. |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_UnsupportedVaryHeaderCookie) { |
| ResourceRequest request = CreateRequest("/echoheadercache?Cookie"); |
| request.headers.SetHeader("cookie", "foo"); |
| |
| StoreResponseToMemoryCache(request); |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_UnsupportedMultipleVaryHeader) { |
| ResourceRequest request = |
| CreateRequest("/echoheadercache?Accept-Encoding,X-Foo"); |
| request.headers.SetHeader("accept-encoding", "gzip"); |
| request.headers.SetHeader("x-foo", "bar"); |
| |
| StoreResponseToMemoryCache(request); |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_DevToolsAttached) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature(net::features::kPartitionedCookies); |
| |
| ResourceRequest request = CreateRequest("/cacheable?max-age=120"); |
| request.devtools_request_id = "fake-id"; |
| StoreResponseToMemoryCache(request); |
| |
| MockDevToolsObserver devtools_observer; |
| request.trusted_params = ResourceRequest::TrustedParams(); |
| request.trusted_params->devtools_observer = devtools_observer.Bind(); |
| |
| LoaderPair loader_pair = CreateLoaderAndStart(request); |
| loader_pair.client->RunUntilComplete(); |
| const URLLoaderCompletionStatus& status = |
| loader_pair.client->completion_status(); |
| ASSERT_EQ(status.error_code, net::OK); |
| ASSERT_TRUE(status.exists_in_memory_cache); |
| |
| devtools_observer.WaitUntilRawResponse(0u); |
| ASSERT_EQ(200, devtools_observer.raw_response_http_status_code()); |
| |
| // Check whether the cached response has `Cache-Control: max-age=120` as the |
| // original response had. |
| bool has_expected_header = false; |
| for (const auto& header_pair : devtools_observer.response_headers()) { |
| if (base::EqualsCaseInsensitiveASCII(header_pair->key, "cache-control") && |
| header_pair->value == "max-age=120") { |
| has_expected_header = true; |
| break; |
| } |
| } |
| ASSERT_TRUE(has_expected_header); |
| |
| EXPECT_EQ(net::CookiePartitionKey::FromURLForTesting(request.url), |
| devtools_observer.response_cookie_partition_key()); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, CanServe_ClientSecurityStateProvided) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| |
| request.trusted_params = ResourceRequest::TrustedParams(); |
| request.trusted_params->client_security_state = |
| mojom::ClientSecurityState::New(); |
| |
| // This should not hit any (D)CHECKs. |
| LoaderPair loader_pair = CreateLoaderAndStart(request); |
| loader_pair.client->RunUntilComplete(); |
| const URLLoaderCompletionStatus& status = |
| loader_pair.client->completion_status(); |
| ASSERT_EQ(status.error_code, net::OK); |
| ASSERT_TRUE(status.exists_in_memory_cache); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, UpdateStoredCache) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| |
| StoreResponseToMemoryCache(request); |
| |
| net::SchemefulSite site(request.url); |
| net::NetworkIsolationKey network_isolation_key(/*top_frame_site=*/site, |
| /*frame_site=*/site); |
| |
| std::optional<std::string> cache_key = memory_cache().CanServe( |
| mojom::kURLLoadOptionNone, request, network_isolation_key, |
| CrossOriginEmbedderPolicy(), |
| /*client_security_state=*/nullptr); |
| ASSERT_TRUE(cache_key.has_value()); |
| mojom::URLResponseHeadPtr response = |
| memory_cache().GetResponseHeadForTesting(*cache_key); |
| base::Time first_response_time = response->response_time; |
| |
| // Store the same response again. Force validation to update the response. |
| request.load_flags |= net::LOAD_VALIDATE_CACHE; |
| StoreResponseToMemoryCache(request); |
| response = memory_cache().GetResponseHeadForTesting(*cache_key); |
| base::Time second_response_time = response->response_time; |
| |
| // Compare response time to make sure the stored response is updated. |
| ASSERT_LT(first_response_time, second_response_time); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, EvictLeastRecentlyUsed) { |
| constexpr size_t kBodySize = kMaxPerEntrySize; |
| |
| // Stores two responses to consume the full budget of the in-memory cache. |
| ResourceRequest request1 = CreateRequest( |
| base::StringPrintf("/cacheable?id=1&body-size=%zu", kBodySize)); |
| StoreResponseToMemoryCache(request1); |
| |
| ResourceRequest request2 = CreateRequest( |
| base::StringPrintf("/cacheable?id=2&body-size=%zu", kBodySize)); |
| StoreResponseToMemoryCache(request2); |
| |
| ASSERT_TRUE(CanServeFromMemoryCache(request1)); |
| ASSERT_TRUE(CanServeFromMemoryCache(request2)); |
| |
| // Stores the third response. It should evict the first stored response. |
| ResourceRequest request3 = CreateRequest( |
| base::StringPrintf("/cacheable?id=3&body-size=%zu", kBodySize)); |
| StoreResponseToMemoryCache(request3); |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request1)); |
| ASSERT_TRUE(CanServeFromMemoryCache(request2)); |
| ASSERT_TRUE(CanServeFromMemoryCache(request3)); |
| ASSERT_EQ(memory_cache().total_bytes(), kBodySize * 2); |
| } |
| |
| // Tests that a stored response is deleted when a subsequent request that |
| // bypasses the cache results in a redirect. |
| TEST_F(NetworkServiceMemoryCacheTest, CachedAfterRedirect) { |
| ResourceRequest request = CreateRequest("/cacheable_or_redirect"); |
| |
| StoreResponseToMemoryCache(request); |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| MakeCacheableHandlerSendRedirect(); |
| request.load_flags |= net::LOAD_BYPASS_CACHE; |
| |
| LoaderPair pair = CreateLoaderAndStart(request); |
| pair.client->RunUntilRedirectReceived(); |
| pair.loader_remote->FollowRedirect( |
| /*removed_headers=*/{}, /*modified_headers=*/{}, |
| /*modified_cors_exempt_headers=*/{}, /*new_url=*/std::nullopt); |
| pair.client->RunUntilComplete(); |
| |
| request.load_flags &= ~net::LOAD_BYPASS_CACHE; |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, Clear) { |
| constexpr int kBodySize = 64; |
| |
| // Stores three responses. |
| ResourceRequest request1 = CreateRequest( |
| base::StringPrintf("/cacheable?id=1&body-size=%d", kBodySize)); |
| StoreResponseToMemoryCache(request1); |
| |
| ResourceRequest request2 = CreateRequest( |
| base::StringPrintf("/cacheable?id=2&body-size=%d", kBodySize)); |
| StoreResponseToMemoryCache(request2); |
| |
| ResourceRequest request3 = CreateRequest( |
| base::StringPrintf("/cacheable?id=3&body-size=%d", kBodySize)); |
| StoreResponseToMemoryCache(request3); |
| |
| ASSERT_TRUE(CanServeFromMemoryCache(request1)); |
| ASSERT_TRUE(CanServeFromMemoryCache(request2)); |
| ASSERT_TRUE(CanServeFromMemoryCache(request3)); |
| |
| memory_cache().Clear(); |
| |
| ASSERT_EQ(memory_cache().total_bytes(), 0u); |
| ASSERT_FALSE(CanServeFromMemoryCache(request1)); |
| ASSERT_FALSE(CanServeFromMemoryCache(request2)); |
| ASSERT_FALSE(CanServeFromMemoryCache(request3)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, ClearOnMemoryPressure) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| base::MemoryPressureListener::NotifyMemoryPressure( |
| base::MemoryPressureListener::MemoryPressureLevel:: |
| MEMORY_PRESSURE_LEVEL_CRITICAL); |
| task_environment().RunUntilIdle(); |
| |
| ASSERT_EQ(memory_cache().total_bytes(), 0u); |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, ClientDisconnectedWhileCaching) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| LoaderPair pair = CreateLoaderAndStart(request); |
| |
| pair.client->RunUntilResponseReceived(); |
| pair.client->Unbind(); |
| |
| ASSERT_TRUE(pair.loader_remote.is_connected()); |
| base::RunLoop loop; |
| pair.loader_remote.set_disconnect_handler(loop.QuitClosure()); |
| loop.Run(); |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, ServeFromCache_Basic) { |
| constexpr int kBodySize = 371; |
| const std::string kExpectedBody(kBodySize, 'a'); |
| |
| ResourceRequest request = |
| CreateRequest(base::StringPrintf("/cacheable?body-size=%d", kBodySize)); |
| StoreResponseToMemoryCache(request); |
| |
| const base::TimeTicks before_start = base::TimeTicks::Now(); |
| |
| LoaderPair pair = CreateLoaderAndStart(request); |
| pair.client->RunUntilComplete(); |
| const URLLoaderCompletionStatus& status = pair.client->completion_status(); |
| ASSERT_EQ(status.error_code, net::OK); |
| ASSERT_TRUE(status.exists_in_memory_cache); |
| |
| const mojom::URLResponseHeadPtr& response = pair.client->response_head(); |
| ASSERT_FALSE(response->network_accessed); |
| ASSERT_FALSE(response->is_validated); |
| ASSERT_TRUE(response->was_fetched_via_cache); |
| ASSERT_LT(before_start, response->request_start); |
| ASSERT_LT(before_start, response->response_start); |
| |
| const net::LoadTimingInfo& load_timing = response->load_timing; |
| ASSERT_LT(before_start, load_timing.request_start); |
| ASSERT_LT(before_start, load_timing.send_start); |
| ASSERT_LT(before_start, load_timing.send_end); |
| ASSERT_LT(before_start, load_timing.receive_headers_start); |
| ASSERT_LT(before_start, load_timing.receive_headers_end); |
| |
| std::string received_body; |
| ASSERT_TRUE(mojo::BlockingCopyToString(pair.client->response_body_release(), |
| &received_body)); |
| ASSERT_EQ(kExpectedBody, received_body); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, ServeFromCache_DisableLoadTiming) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| |
| const base::TimeTicks before_start = base::TimeTicks::Now(); |
| |
| request.enable_load_timing = false; |
| LoaderPair pair = CreateLoaderAndStart(request); |
| pair.client->RunUntilComplete(); |
| const URLLoaderCompletionStatus& status = pair.client->completion_status(); |
| ASSERT_EQ(status.error_code, net::OK); |
| ASSERT_TRUE(status.exists_in_memory_cache); |
| |
| const mojom::URLResponseHeadPtr& response = pair.client->response_head(); |
| ASSERT_FALSE(response->network_accessed); |
| ASSERT_FALSE(response->is_validated); |
| ASSERT_TRUE(response->was_fetched_via_cache); |
| ASSERT_LT(before_start, response->request_start); |
| ASSERT_LT(before_start, response->response_start); |
| |
| const net::LoadTimingInfo& load_timing = response->load_timing; |
| ASSERT_TRUE(load_timing.request_start.is_null()); |
| ASSERT_TRUE(load_timing.send_start.is_null()); |
| ASSERT_TRUE(load_timing.send_end.is_null()); |
| ASSERT_TRUE(load_timing.receive_headers_start.is_null()); |
| ASSERT_TRUE(load_timing.receive_headers_end.is_null()); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, ServeFromCache_LargeBody) { |
| constexpr uint32_t kReadDataSize = 512; |
| // Arbitrary response body size larger than `kReadDataSize`. |
| constexpr int kBodySize = 2 * 1024 + 659; |
| DCHECK_GE(kMaxPerEntrySize, kBodySize); |
| |
| ResourceRequest request = |
| CreateRequest(base::StringPrintf("/cacheable?body-size=%d", kBodySize)); |
| StoreResponseToMemoryCache(request); |
| |
| LoaderPair pair = CreateLoaderAndStart(request); |
| pair.client->RunUntilResponseReceived(); |
| |
| mojo::ScopedDataPipeConsumerHandle consumer_handle = |
| pair.client->response_body_release(); |
| std::string received_body; |
| while (true) { |
| char buf[kReadDataSize]; |
| uint32_t num_bytes = kReadDataSize; |
| MojoResult result = |
| consumer_handle->ReadData(buf, &num_bytes, MOJO_READ_DATA_FLAG_NONE); |
| |
| if (result == MOJO_RESULT_SHOULD_WAIT) { |
| base::RunLoop run_loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| continue; |
| } |
| |
| if (result == MOJO_RESULT_FAILED_PRECONDITION) |
| break; |
| |
| ASSERT_EQ(result, MOJO_RESULT_OK); |
| received_body.append(buf, num_bytes); |
| } |
| |
| pair.client->RunUntilComplete(); |
| const URLLoaderCompletionStatus& status = pair.client->completion_status(); |
| ASSERT_EQ(status.error_code, net::OK); |
| ASSERT_TRUE(status.exists_in_memory_cache); |
| |
| const std::string kExpectedBody(kBodySize, 'a'); |
| ASSERT_EQ(kExpectedBody, received_body); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, |
| ServeFromCache_DataPipeDisconnectWhileReading) { |
| constexpr int kBodySize = 512; |
| constexpr int kReadDataSize = kBodySize / 2; |
| ResourceRequest request = |
| CreateRequest(base::StringPrintf("/cacheable?body-size=%d", kBodySize)); |
| StoreResponseToMemoryCache(request); |
| |
| // Set a small data pipe capacity so that writing data to a data pipe doesn't |
| // complete at once. |
| memory_cache().SetDataPipeCapacityForTesting(kReadDataSize); |
| |
| LoaderPair pair = CreateLoaderAndStart(request); |
| pair.client->RunUntilResponseReceived(); |
| |
| mojo::ScopedDataPipeConsumerHandle consumer_handle = |
| pair.client->response_body_release(); |
| |
| // Read the half of the response body. |
| int num_read = 0; |
| while (num_read < kReadDataSize) { |
| char buf[kReadDataSize]; |
| uint32_t num_bytes = kReadDataSize; |
| MojoResult result = |
| consumer_handle->ReadData(buf, &num_bytes, MOJO_READ_DATA_FLAG_NONE); |
| if (result == MOJO_RESULT_SHOULD_WAIT) { |
| base::RunLoop run_loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, run_loop.QuitClosure()); |
| run_loop.Run(); |
| continue; |
| } |
| ASSERT_EQ(result, MOJO_RESULT_OK); |
| num_read += num_bytes; |
| } |
| |
| consumer_handle.reset(); |
| pair.client->RunUntilComplete(); |
| ASSERT_EQ(pair.client->completion_status().error_code, net::ERR_FAILED); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, ServeFromCache_GzipResponse) { |
| constexpr int64_t kBodySize = 100; |
| const std::string kContent(kBodySize, 'x'); |
| ResourceRequest request = |
| CreateRequest(base::StringPrintf("/gzip-body?%s", kContent.c_str())); |
| StoreResponseToMemoryCache(request); |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| LoaderPair pair = CreateLoaderAndStart(request); |
| pair.client->RunUntilComplete(); |
| const URLLoaderCompletionStatus& status = pair.client->completion_status(); |
| ASSERT_EQ(status.error_code, net::OK); |
| ASSERT_EQ(status.decoded_body_length, kBodySize); |
| ASSERT_LT(status.encoded_body_length, kBodySize); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheTest, InvalidateOnNonSafeMethod) { |
| ResourceRequest request = CreateRequest("/cacheable"); |
| StoreResponseToMemoryCache(request); |
| |
| ASSERT_TRUE(CanServeFromMemoryCache(request)); |
| |
| ResourceRequest request2 = CreateRequest("/cacheable"); |
| request2.method = "PUT"; |
| |
| LoaderPair pair = CreateLoaderAndStart(request2); |
| pair.client->RunUntilResponseReceived(); |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| TEST_F(NetworkServiceMemoryCacheWithFactoryOverrideTest, |
| MemoryCacheShouldNotBeUsed) { |
| ResourceRequest request = CreateRequest(base::StringPrintf("/cacheable")); |
| StoreResponseToMemoryCache(request); |
| |
| ASSERT_FALSE(CanServeFromMemoryCache(request)); |
| } |
| |
| } // namespace network |