| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind_test_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "build/build_config.h" |
| #include "net/base/features.h" |
| #include "net/base/isolation_info.h" |
| #include "net/base/load_timing_info.h" |
| #include "net/base/network_delegate.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/cert/ct_policy_enforcer.h" |
| #include "net/cert/ct_policy_status.h" |
| #include "net/cert/mock_cert_verifier.h" |
| #include "net/dns/mapped_host_resolver.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/log/net_log_event_type.h" |
| #include "net/log/test_net_log.h" |
| #include "net/log/test_net_log_util.h" |
| #include "net/quic/crypto/proof_source_chromium.h" |
| #include "net/quic/quic_context.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/gtest_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "net/test/test_with_task_environment.h" |
| #include "net/third_party/quiche/src/quic/core/quic_dispatcher.h" |
| #include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h" |
| #include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h" |
| #include "net/third_party/quiche/src/quic/tools/quic_simple_dispatcher.h" |
| #include "net/tools/quic/quic_simple_server.h" |
| #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| // This must match the certificate used (quic-chain.pem and quic-leaf-cert.key). |
| const char kTestServerHost[] = "test.example.com"; |
| // Used as a simple response from the server. |
| const char kHelloPath[] = "/hello.txt"; |
| const char kHelloBodyValue[] = "Hello from QUIC Server"; |
| const int kHelloStatus = 200; |
| |
| // Used as a simple pushed response from the server. |
| const char kKittenPath[] = "/kitten-1.jpg"; |
| const char kKittenBodyValue[] = "Kitten image"; |
| |
| // Used as a simple pushed response from the server. |
| const char kFaviconPath[] = "/favicon.ico"; |
| const char kFaviconBodyValue[] = "Favion"; |
| |
| // Used as a simple pushed response from the server. |
| const char kIndexPath[] = "/index2.html"; |
| const char kIndexBodyValue[] = "Hello from QUIC Server"; |
| const int kIndexStatus = 200; |
| |
| class MockCTPolicyEnforcerNonCompliant : public CTPolicyEnforcer { |
| public: |
| MockCTPolicyEnforcerNonCompliant() = default; |
| ~MockCTPolicyEnforcerNonCompliant() override = default; |
| |
| ct::CTPolicyCompliance CheckCompliance( |
| X509Certificate* cert, |
| const ct::SCTList& verified_scts, |
| const NetLogWithSource& net_log) override { |
| return ct::CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS; |
| } |
| }; |
| |
| // An ExpectCTReporter that records the number of times OnExpectCTFailed() was |
| // called. |
| class MockExpectCTReporter : public TransportSecurityState::ExpectCTReporter { |
| public: |
| MockExpectCTReporter() = default; |
| ~MockExpectCTReporter() override = default; |
| |
| void OnExpectCTFailed( |
| const HostPortPair& host_port_pair, |
| const GURL& report_uri, |
| base::Time expiration, |
| const X509Certificate* validated_certificate_chain, |
| const X509Certificate* served_certificate_chain, |
| const SignedCertificateTimestampAndStatusList& |
| signed_certificate_timestamps, |
| const NetworkIsolationKey& network_isolation_key) override { |
| num_failures_++; |
| report_uri_ = report_uri; |
| network_isolation_key_ = network_isolation_key; |
| } |
| |
| int num_failures() const { return num_failures_; } |
| const GURL& report_uri() const { return report_uri_; } |
| const NetworkIsolationKey& network_isolation_key() const { |
| return network_isolation_key_; |
| } |
| |
| private: |
| int num_failures_ = 0; |
| |
| GURL report_uri_; |
| NetworkIsolationKey network_isolation_key_; |
| }; |
| |
| class URLRequestQuicTest |
| : public TestWithTaskEnvironment, |
| public ::testing::WithParamInterface<quic::ParsedQuicVersion> { |
| protected: |
| URLRequestQuicTest() : context_(new TestURLRequestContext(true)) { |
| QuicEnableVersion(version()); |
| StartQuicServer(version()); |
| |
| std::unique_ptr<HttpNetworkSession::Params> params( |
| new HttpNetworkSession::Params); |
| CertVerifyResult verify_result; |
| verify_result.verified_cert = ImportCertFromFile( |
| GetTestCertsDirectory(), "quic-chain.pem"); |
| cert_verifier_.AddResultForCertAndHost(verify_result.verified_cert.get(), |
| kTestServerHost, verify_result, OK); |
| // To simplify the test, and avoid the race with the HTTP request, we force |
| // QUIC for these requests. |
| context_->set_quic_context(&quic_context_); |
| quic_context_.params()->supported_versions = {version()}; |
| quic_context_.params()->origins_to_force_quic_on.insert( |
| HostPortPair(kTestServerHost, 443)); |
| params->enable_quic = true; |
| params->enable_server_push_cancellation = true; |
| context_->set_host_resolver(host_resolver_.get()); |
| context_->set_http_network_session_params(std::move(params)); |
| context_->set_cert_verifier(&cert_verifier_); |
| context_->set_net_log(&net_log_); |
| transport_security_state_.SetExpectCTReporter(&expect_ct_reporter_); |
| context_->set_transport_security_state(&transport_security_state_); |
| } |
| |
| void TearDown() override { |
| if (server_) { |
| server_->Shutdown(); |
| // If possible, deliver the conncetion close packet to the client before |
| // destruct the TestURLRequestContext. |
| base::RunLoop().RunUntilIdle(); |
| } |
| } |
| |
| // Sets a NetworkDelegate to use for |context_|. Must be done before Init(). |
| void SetNetworkDelegate(NetworkDelegate* network_delegate) { |
| context_->set_network_delegate(network_delegate); |
| } |
| |
| // Can be used to modify |context_|. Only safe to modify before Init() is |
| // called. |
| TestURLRequestContext* context() { return context_.get(); } |
| |
| // Initializes the TestURLRequestContext |context_|. |
| void Init() { context_->Init(); } |
| |
| std::unique_ptr<URLRequest> CreateRequest(const GURL& url, |
| RequestPriority priority, |
| URLRequest::Delegate* delegate) { |
| return context_->CreateRequest(url, priority, delegate, |
| TRAFFIC_ANNOTATION_FOR_TESTS); |
| } |
| |
| unsigned int GetRstErrorCountReceivedByServer( |
| quic::QuicRstStreamErrorCode error_code) const { |
| return (static_cast<quic::QuicSimpleDispatcher*>(server_->dispatcher())) |
| ->GetRstErrorCount(error_code); |
| } |
| |
| static const NetLogSource FindPushUrlSource( |
| const std::vector<NetLogEntry>& entries, |
| const std::string& push_url) { |
| std::string entry_push_url; |
| for (const auto& entry : entries) { |
| if (entry.phase == NetLogEventPhase::BEGIN && |
| entry.source.type == |
| NetLogSourceType::SERVER_PUSH_LOOKUP_TRANSACTION) { |
| auto entry_push_url = |
| GetOptionalStringValueFromParams(entry, "push_url"); |
| if (entry_push_url && *entry_push_url == push_url) { |
| return entry.source; |
| } |
| } |
| } |
| return NetLogSource(); |
| } |
| |
| static const NetLogEntry* FindEndBySource( |
| const std::vector<NetLogEntry>& entries, |
| const NetLogSource& source) { |
| for (const auto& entry : entries) { |
| if (entry.phase == NetLogEventPhase::END && |
| entry.source.type == source.type && entry.source.id == source.id) |
| return &entry; |
| } |
| return nullptr; |
| } |
| |
| quic::ParsedQuicVersion version() { return GetParam(); } |
| |
| MockExpectCTReporter* expect_ct_reporter() { return &expect_ct_reporter_; } |
| |
| TransportSecurityState* transport_security_state() { |
| return &transport_security_state_; |
| } |
| |
| protected: |
| // Returns a fully-qualified URL for |path| on the test server. |
| std::string UrlFromPath(base::StringPiece path) { |
| return std::string("https://") + std::string(kTestServerHost) + |
| std::string(path); |
| } |
| |
| RecordingTestNetLog net_log_; |
| |
| private: |
| void StartQuicServer(quic::ParsedQuicVersion version) { |
| // Set up in-memory cache. |
| |
| // Add the simply hello response. |
| memory_cache_backend_.AddSimpleResponse(kTestServerHost, kHelloPath, |
| kHelloStatus, kHelloBodyValue); |
| |
| // Now set up index so that it pushes kitten and favicon. |
| quic::QuicBackendResponse::ServerPushInfo push_info1( |
| quic::QuicUrl(UrlFromPath(kKittenPath)), spdy::SpdyHeaderBlock(), |
| spdy::kV3LowestPriority, kKittenBodyValue); |
| quic::QuicBackendResponse::ServerPushInfo push_info2( |
| quic::QuicUrl(UrlFromPath(kFaviconPath)), spdy::SpdyHeaderBlock(), |
| spdy::kV3LowestPriority, kFaviconBodyValue); |
| memory_cache_backend_.AddSimpleResponseWithServerPushResources( |
| kTestServerHost, kIndexPath, kIndexStatus, kIndexBodyValue, |
| {push_info1, push_info2}); |
| quic::QuicConfig config; |
| // Set up server certs. |
| std::unique_ptr<net::ProofSourceChromium> proof_source( |
| new net::ProofSourceChromium()); |
| base::FilePath directory = GetTestCertsDirectory(); |
| CHECK(proof_source->Initialize( |
| directory.Append(FILE_PATH_LITERAL("quic-chain.pem")), |
| directory.Append(FILE_PATH_LITERAL("quic-leaf-cert.key")), |
| base::FilePath())); |
| server_.reset(new QuicSimpleServer( |
| quic::test::crypto_test_utils::ProofSourceForTesting(), config, |
| quic::QuicCryptoServerConfig::ConfigOptions(), {version}, |
| &memory_cache_backend_)); |
| int rv = |
| server_->Listen(net::IPEndPoint(net::IPAddress::IPv4AllZeros(), 0)); |
| EXPECT_GE(rv, 0) << "Quic server fails to start"; |
| |
| std::unique_ptr<MockHostResolver> resolver(new MockHostResolver()); |
| resolver->rules()->AddRule("test.example.com", "127.0.0.1"); |
| host_resolver_.reset(new MappedHostResolver(std::move(resolver))); |
| // Use a mapped host resolver so that request for test.example.com |
| // reach the server running on localhost. |
| std::string map_rule = |
| "MAP test.example.com test.example.com:" + |
| base::NumberToString(server_->server_address().port()); |
| EXPECT_TRUE(host_resolver_->AddRuleFromString(map_rule)); |
| } |
| |
| std::string ServerPushCacheDirectory() { |
| base::FilePath path; |
| base::PathService::Get(base::DIR_SOURCE_ROOT, &path); |
| path = path.AppendASCII("net").AppendASCII("data").AppendASCII( |
| "quic_http_response_cache_data_with_push"); |
| // The file path is known to be an ascii string. |
| return path.MaybeAsASCII(); |
| } |
| |
| MockExpectCTReporter expect_ct_reporter_; |
| TransportSecurityState transport_security_state_; |
| |
| std::unique_ptr<MappedHostResolver> host_resolver_; |
| std::unique_ptr<QuicSimpleServer> server_; |
| std::unique_ptr<TestURLRequestContext> context_; |
| QuicContext quic_context_; |
| quic::QuicMemoryCacheBackend memory_cache_backend_; |
| MockCertVerifier cert_verifier_; |
| QuicFlagSaver flags_; // Save/restore all QUIC flag values. |
| }; |
| |
| // A URLRequest::Delegate that checks LoadTimingInfo when response headers are |
| // received. |
| class CheckLoadTimingDelegate : public TestDelegate { |
| public: |
| CheckLoadTimingDelegate(bool session_reused) |
| : session_reused_(session_reused) {} |
| void OnResponseStarted(URLRequest* request, int error) override { |
| TestDelegate::OnResponseStarted(request, error); |
| LoadTimingInfo load_timing_info; |
| request->GetLoadTimingInfo(&load_timing_info); |
| assertLoadTimingValid(load_timing_info, session_reused_); |
| } |
| |
| private: |
| void assertLoadTimingValid(const LoadTimingInfo& load_timing_info, |
| bool session_reused) { |
| EXPECT_EQ(session_reused, load_timing_info.socket_reused); |
| |
| // If |session_reused| is true, these fields should all be null, non-null |
| // otherwise. |
| EXPECT_EQ(session_reused, |
| load_timing_info.connect_timing.connect_start.is_null()); |
| EXPECT_EQ(session_reused, |
| load_timing_info.connect_timing.connect_end.is_null()); |
| EXPECT_EQ(session_reused, |
| load_timing_info.connect_timing.ssl_start.is_null()); |
| EXPECT_EQ(session_reused, |
| load_timing_info.connect_timing.ssl_end.is_null()); |
| EXPECT_EQ(load_timing_info.connect_timing.connect_start, |
| load_timing_info.connect_timing.ssl_start); |
| EXPECT_EQ(load_timing_info.connect_timing.connect_end, |
| load_timing_info.connect_timing.ssl_end); |
| EXPECT_EQ(session_reused, |
| load_timing_info.connect_timing.dns_start.is_null()); |
| EXPECT_EQ(session_reused, |
| load_timing_info.connect_timing.dns_end.is_null()); |
| } |
| |
| bool session_reused_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CheckLoadTimingDelegate); |
| }; |
| |
| // A TestNetworkDelegate that invokes |all_requests_completed_callback| when |
| // |num_expected_requests| requests are completed. |
| class WaitForCompletionNetworkDelegate : public net::TestNetworkDelegate { |
| public: |
| WaitForCompletionNetworkDelegate( |
| const base::Closure& all_requests_completed_callback, |
| size_t num_expected_requests) |
| : all_requests_completed_callback_(all_requests_completed_callback), |
| num_expected_requests_(num_expected_requests) {} |
| |
| void OnCompleted(URLRequest* request, bool started, int net_error) override { |
| net::TestNetworkDelegate::OnCompleted(request, started, net_error); |
| num_expected_requests_--; |
| if (num_expected_requests_ == 0) |
| all_requests_completed_callback_.Run(); |
| } |
| |
| private: |
| const base::Closure all_requests_completed_callback_; |
| size_t num_expected_requests_; |
| DISALLOW_COPY_AND_ASSIGN(WaitForCompletionNetworkDelegate); |
| }; |
| |
| } // namespace |
| |
| // Used by ::testing::PrintToStringParamName(). |
| std::string PrintToString(const quic::ParsedQuicVersion& v) { |
| return quic::ParsedQuicVersionToString(v); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(Version, |
| URLRequestQuicTest, |
| ::testing::ValuesIn(quic::AllSupportedVersions()), |
| ::testing::PrintToStringParamName()); |
| |
| TEST_P(URLRequestQuicTest, TestGetRequest) { |
| Init(); |
| CheckLoadTimingDelegate delegate(false); |
| std::unique_ptr<URLRequest> request = |
| CreateRequest(GURL(UrlFromPath(kHelloPath)), DEFAULT_PRIORITY, &delegate); |
| |
| request->Start(); |
| ASSERT_TRUE(request->is_pending()); |
| delegate.RunUntilComplete(); |
| |
| EXPECT_EQ(OK, delegate.request_status()); |
| EXPECT_EQ(kHelloBodyValue, delegate.data_received()); |
| EXPECT_TRUE(request->ssl_info().is_valid()); |
| } |
| |
| TEST_P(URLRequestQuicTest, CancelPushIfCached_SomeCached) { |
| if (VersionUsesHttp3(version().transport_version)) { |
| Init(); |
| return; |
| } |
| |
| // Skip test if "split cache" is enabled while "partition connections" is |
| // disabled, as it breaks push. |
| if (base::FeatureList::IsEnabled( |
| net::features::kSplitCacheByNetworkIsolationKey) && |
| !base::FeatureList::IsEnabled( |
| net::features::kPartitionConnectionsByNetworkIsolationKey)) { |
| return; |
| } |
| |
| const url::Origin kOrigin1 = |
| url::Origin::Create(GURL("http://www.example.com")); |
| const IsolationInfo kTestIsolationInfo = |
| IsolationInfo::CreateForInternalRequest(kOrigin1); |
| |
| Init(); |
| |
| // Send a request to the pushed url: /kitten-1.jpg to pull the resource into |
| // cache. |
| CheckLoadTimingDelegate delegate_0(false); |
| std::unique_ptr<URLRequest> request_0 = CreateRequest( |
| GURL(UrlFromPath(kKittenPath)), DEFAULT_PRIORITY, &delegate_0); |
| |
| request_0->set_isolation_info(kTestIsolationInfo); |
| request_0->Start(); |
| ASSERT_TRUE(request_0->is_pending()); |
| |
| // Spin the message loop until the client receives the response for the first |
| // request. |
| delegate_0.RunUntilComplete(); |
| EXPECT_EQ(OK, delegate_0.request_status()); |
| |
| // Send a request to /index2.html which pushes /kitten-1.jpg and /favicon.ico. |
| // Should cancel push for /kitten-1.jpg. |
| CheckLoadTimingDelegate delegate(true); |
| std::unique_ptr<URLRequest> request = |
| CreateRequest(GURL(UrlFromPath(kIndexPath)), DEFAULT_PRIORITY, &delegate); |
| |
| request->set_isolation_info(kTestIsolationInfo); |
| request->Start(); |
| ASSERT_TRUE(request->is_pending()); |
| |
| // Spin the message loop until the client receives the response for the second |
| // request. |
| delegate.RunUntilComplete(); |
| EXPECT_EQ(OK, delegate.request_status()); |
| // Wait until all QUIC events are process, some of which happen |
| // asynchronously. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Extract net logs on client side to verify push lookup transactions. |
| auto entries = net_log_.GetEntriesWithType( |
| NetLogEventType::SERVER_PUSH_LOOKUP_TRANSACTION); |
| |
| ASSERT_EQ(4u, entries.size()); |
| |
| std::string value; |
| std::string push_url_1 = UrlFromPath(kKittenPath); |
| std::string push_url_2 = UrlFromPath(kFaviconPath); |
| |
| const NetLogSource source_1 = FindPushUrlSource(entries, push_url_1); |
| EXPECT_TRUE(source_1.IsValid()); |
| |
| // No net error code for this lookup transaction, the push is found. |
| const NetLogEntry* end_entry_1 = FindEndBySource(entries, source_1); |
| EXPECT_FALSE(end_entry_1->HasParams()); |
| EXPECT_FALSE(GetOptionalNetErrorCodeFromParams(*end_entry_1)); |
| |
| const NetLogSource source_2 = FindPushUrlSource(entries, push_url_2); |
| EXPECT_TRUE(source_2.IsValid()); |
| EXPECT_NE(source_1.id, source_2.id); |
| |
| // Net error code -400 is found for this lookup transaction, the push is not |
| // found in the cache. |
| const NetLogEntry* end_entry_2 = FindEndBySource(entries, source_2); |
| EXPECT_TRUE(end_entry_2->HasParams()); |
| EXPECT_EQ(-400, GetNetErrorCodeFromParams(*end_entry_2)); |
| |
| #if !defined(OS_FUCHSIA) && !defined(OS_IOS) |
| // TODO(crbug.com/813631): Make this work on Fuchsia. |
| // TODO(crbug.com/1032568): Make this work on iOS. |
| |
| // Wait until the server has processed all errors which is |
| // happening asynchronously |
| base::RunLoop().RunUntilIdle(); |
| // Verify the reset error count received on the server side. |
| EXPECT_LE(1u, GetRstErrorCountReceivedByServer(quic::QUIC_STREAM_CANCELLED)); |
| #endif |
| } |
| |
| TEST_P(URLRequestQuicTest, CancelPushIfCached_AllCached) { |
| if (VersionUsesHttp3(version().transport_version)) { |
| Init(); |
| return; |
| } |
| |
| // Skip test if "split cache" is enabled while "partition connections" is |
| // disabled, as it breaks push. |
| if (base::FeatureList::IsEnabled( |
| net::features::kSplitCacheByNetworkIsolationKey) && |
| !base::FeatureList::IsEnabled( |
| net::features::kPartitionConnectionsByNetworkIsolationKey)) { |
| return; |
| } |
| |
| const url::Origin kOrigin1 = |
| url::Origin::Create(GURL("http://www.example.com")); |
| const IsolationInfo kTestIsolationInfo = |
| IsolationInfo::CreateForInternalRequest(kOrigin1); |
| |
| Init(); |
| |
| // Send a request to the pushed url: /kitten-1.jpg to pull the resource into |
| // cache. |
| CheckLoadTimingDelegate delegate_0(false); |
| std::unique_ptr<URLRequest> request_0 = CreateRequest( |
| GURL(UrlFromPath(kKittenPath)), DEFAULT_PRIORITY, &delegate_0); |
| |
| request_0->set_isolation_info(kTestIsolationInfo); |
| request_0->Start(); |
| ASSERT_TRUE(request_0->is_pending()); |
| |
| // Spin the message loop until the client receives the response for the first |
| // request. |
| delegate_0.RunUntilComplete(); |
| EXPECT_EQ(OK, delegate_0.request_status()); |
| |
| // Send a request to the pushed url: /favicon.ico to pull the resource into |
| // cache. |
| CheckLoadTimingDelegate delegate_1(true); |
| std::unique_ptr<URLRequest> request_1 = CreateRequest( |
| GURL(UrlFromPath(kFaviconPath)), DEFAULT_PRIORITY, &delegate_1); |
| |
| request_1->set_isolation_info(kTestIsolationInfo); |
| request_1->Start(); |
| ASSERT_TRUE(request_1->is_pending()); |
| |
| // Spin the message loop until the client receives the response for the second |
| // request. |
| delegate_1.RunUntilComplete(); |
| EXPECT_EQ(OK, delegate_1.request_status()); |
| |
| // Send a request to /index2.html which pushes /kitten-1.jpg and /favicon.ico. |
| // Should cancel push for both pushed resources, since they're already cached. |
| CheckLoadTimingDelegate delegate(true); |
| std::unique_ptr<URLRequest> request = |
| CreateRequest(GURL(UrlFromPath(kIndexPath)), DEFAULT_PRIORITY, &delegate); |
| |
| request->set_isolation_info(kTestIsolationInfo); |
| request->Start(); |
| ASSERT_TRUE(request->is_pending()); |
| |
| // Spin the message loop until the client receives the response for the third |
| // request. |
| delegate.RunUntilComplete(); |
| EXPECT_EQ(OK, delegate.request_status()); |
| // Wait until all QUIC events are process, some of which happen |
| // asynchronously. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Extract net logs on client side to verify push lookup transactions. |
| auto entries = net_log_.GetEntriesWithType( |
| NetLogEventType::SERVER_PUSH_LOOKUP_TRANSACTION); |
| |
| ASSERT_EQ(4u, entries.size()); |
| |
| std::string value; |
| std::string push_url_1 = UrlFromPath(kKittenPath); |
| std::string push_url_2 = UrlFromPath(kFaviconPath); |
| |
| const NetLogSource source_1 = FindPushUrlSource(entries, push_url_1); |
| EXPECT_TRUE(source_1.IsValid()); |
| |
| // No net error code for this lookup transaction, the push is found. |
| const NetLogEntry* end_entry_1 = FindEndBySource(entries, source_1); |
| EXPECT_FALSE(end_entry_1->HasParams()); |
| EXPECT_FALSE(GetOptionalNetErrorCodeFromParams(*end_entry_1)); |
| |
| const NetLogSource source_2 = FindPushUrlSource(entries, push_url_2); |
| EXPECT_TRUE(source_1.IsValid()); |
| EXPECT_NE(source_1.id, source_2.id); |
| |
| // No net error code for this lookup transaction, the push is found. |
| const NetLogEntry* end_entry_2 = FindEndBySource(entries, source_2); |
| EXPECT_FALSE(end_entry_2->HasParams()); |
| EXPECT_FALSE(GetOptionalNetErrorCodeFromParams(*end_entry_2)); |
| |
| #if !defined(OS_FUCHSIA) && !defined(OS_IOS) && !defined(OS_MACOSX) |
| // TODO(crbug.com/813631): Make this work on Fuchsia. |
| // TODO(crbug.com/1032568): Make this work on iOS. |
| // TODO(crbug.com/1087378): Flaky on Mac. |
| // Verify the reset error count received on the server side. |
| EXPECT_LE(2u, GetRstErrorCountReceivedByServer(quic::QUIC_STREAM_CANCELLED)); |
| #endif |
| } |
| |
| TEST_P(URLRequestQuicTest, DoNotCancelPushIfNotFoundInCache) { |
| if (VersionUsesHttp3(version().transport_version)) { |
| Init(); |
| return; |
| } |
| |
| Init(); |
| |
| // Send a request to /index2.hmtl which pushes /kitten-1.jpg and /favicon.ico |
| // and shouldn't cancel any since neither is in cache. |
| CheckLoadTimingDelegate delegate(false); |
| std::string url = UrlFromPath(kIndexPath); |
| std::unique_ptr<URLRequest> request = |
| CreateRequest(GURL(url), DEFAULT_PRIORITY, &delegate); |
| |
| request->Start(); |
| ASSERT_TRUE(request->is_pending()); |
| |
| // Spin the message loop until the client receives response. |
| delegate.RunUntilComplete(); |
| EXPECT_EQ(OK, delegate.request_status()); |
| |
| // Extract net logs on client side to verify push lookup transactions. |
| auto entries = net_log_.GetEntriesWithType( |
| NetLogEventType::SERVER_PUSH_LOOKUP_TRANSACTION); |
| |
| ASSERT_EQ(4u, entries.size()); |
| |
| std::string value; |
| std::string push_url_1 = UrlFromPath(kKittenPath); |
| std::string push_url_2 = UrlFromPath(kFaviconPath); |
| |
| const NetLogSource source_1 = FindPushUrlSource(entries, push_url_1); |
| EXPECT_TRUE(source_1.IsValid()); |
| const NetLogEntry* end_entry_1 = FindEndBySource(entries, source_1); |
| EXPECT_TRUE(end_entry_1->HasParams()); |
| EXPECT_EQ(-400, GetNetErrorCodeFromParams(*end_entry_1)); |
| |
| const NetLogSource source_2 = FindPushUrlSource(entries, push_url_2); |
| EXPECT_TRUE(source_2.IsValid()); |
| EXPECT_NE(source_1.id, source_2.id); |
| const NetLogEntry* end_entry_2 = FindEndBySource(entries, source_2); |
| EXPECT_TRUE(end_entry_2->HasParams()); |
| EXPECT_EQ(-400, GetNetErrorCodeFromParams(*end_entry_2)); |
| |
| // Verify the reset error count received on the server side. |
| EXPECT_EQ(0u, GetRstErrorCountReceivedByServer(quic::QUIC_STREAM_CANCELLED)); |
| } |
| |
| // Tests that if two requests use the same QUIC session, the second request |
| // should not have |LoadTimingInfo::connect_timing|. |
| TEST_P(URLRequestQuicTest, TestTwoRequests) { |
| base::RunLoop run_loop; |
| WaitForCompletionNetworkDelegate network_delegate( |
| run_loop.QuitClosure(), /*num_expected_requests=*/2); |
| SetNetworkDelegate(&network_delegate); |
| Init(); |
| CheckLoadTimingDelegate delegate(false); |
| delegate.set_on_complete(base::DoNothing()); |
| std::unique_ptr<URLRequest> request = |
| CreateRequest(GURL(UrlFromPath(kHelloPath)), DEFAULT_PRIORITY, &delegate); |
| |
| CheckLoadTimingDelegate delegate2(true); |
| delegate2.set_on_complete(base::DoNothing()); |
| std::unique_ptr<URLRequest> request2 = CreateRequest( |
| GURL(UrlFromPath(kHelloPath)), DEFAULT_PRIORITY, &delegate2); |
| request->Start(); |
| request2->Start(); |
| ASSERT_TRUE(request->is_pending()); |
| ASSERT_TRUE(request2->is_pending()); |
| run_loop.Run(); |
| |
| EXPECT_EQ(OK, delegate.request_status()); |
| EXPECT_EQ(OK, delegate2.request_status()); |
| EXPECT_EQ(kHelloBodyValue, delegate.data_received()); |
| EXPECT_EQ(kHelloBodyValue, delegate2.data_received()); |
| } |
| |
| TEST_P(URLRequestQuicTest, RequestHeadersCallback) { |
| Init(); |
| HttpRawRequestHeaders raw_headers; |
| TestDelegate delegate; |
| TestURLRequestContext context; |
| HttpRequestHeaders extra_headers; |
| extra_headers.SetHeader("X-Foo", "bar"); |
| |
| std::unique_ptr<URLRequest> request = |
| CreateRequest(GURL(UrlFromPath(kHelloPath)), DEFAULT_PRIORITY, &delegate); |
| |
| request->SetExtraRequestHeaders(extra_headers); |
| request->SetRequestHeadersCallback( |
| base::BindLambdaForTesting([&](HttpRawRequestHeaders raw_headers) { |
| // This should be invoked before the request is completed, or any bytes |
| // are read. |
| EXPECT_FALSE(delegate.response_completed()); |
| EXPECT_FALSE(delegate.bytes_received()); |
| |
| EXPECT_FALSE(raw_headers.headers().empty()); |
| std::string value; |
| EXPECT_TRUE(raw_headers.FindHeaderForTest("x-foo", &value)); |
| EXPECT_EQ("bar", value); |
| EXPECT_TRUE(raw_headers.FindHeaderForTest("accept-encoding", &value)); |
| EXPECT_EQ("gzip, deflate", value); |
| EXPECT_TRUE(raw_headers.FindHeaderForTest(":path", &value)); |
| EXPECT_EQ("/hello.txt", value); |
| EXPECT_TRUE(raw_headers.FindHeaderForTest(":authority", &value)); |
| EXPECT_EQ("test.example.com", value); |
| EXPECT_TRUE(raw_headers.request_line().empty()); |
| })); |
| request->Start(); |
| ASSERT_TRUE(request->is_pending()); |
| delegate.RunUntilComplete(); |
| EXPECT_EQ(OK, delegate.request_status()); |
| } |
| |
| // Tests that if there's an Expect-CT failure at the QUIC layer, a report is |
| // generated. |
| TEST_P(URLRequestQuicTest, ExpectCT) { |
| // Expect-CT seems not to supported for quic::PROTOCOL_TLS1_3 handshakes. |
| // TODO(https://crbug.com/1090838): Remove this early exit once that is fixed. |
| if (GetParam().handshake_protocol == quic::PROTOCOL_TLS1_3) { |
| // Need this to avoid a DCHECK. |
| Init(); |
| return; |
| } |
| |
| TransportSecurityState::SetRequireCTForTesting(true); |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitWithFeatures( |
| // enabled_features |
| {features::kPartitionConnectionsByNetworkIsolationKey, |
| features::kPartitionHttpServerPropertiesByNetworkIsolationKey, |
| features::kPartitionSSLSessionsByNetworkIsolationKey}, |
| // disabled_features |
| {}); |
| |
| MockCTPolicyEnforcerNonCompliant ct_enforcer; |
| context()->set_ct_policy_enforcer(&ct_enforcer); |
| Init(); |
| |
| GURL report_uri("https://report.test/"); |
| IsolationInfo isolation_info = IsolationInfo::CreateTransient(); |
| transport_security_state()->AddExpectCT( |
| kTestServerHost, base::Time::Now() + base::TimeDelta::FromDays(1), |
| true /* enforce */, report_uri, isolation_info.network_isolation_key()); |
| |
| base::RunLoop run_loop; |
| TestDelegate delegate; |
| std::unique_ptr<URLRequest> request = |
| CreateRequest(GURL(UrlFromPath(kHelloPath)), DEFAULT_PRIORITY, &delegate); |
| request->set_isolation_info(isolation_info); |
| request->Start(); |
| delegate.RunUntilComplete(); |
| |
| EXPECT_EQ(ERR_QUIC_PROTOCOL_ERROR, delegate.request_status()); |
| ASSERT_EQ(1, expect_ct_reporter()->num_failures()); |
| EXPECT_EQ(report_uri, expect_ct_reporter()->report_uri()); |
| EXPECT_EQ(isolation_info.network_isolation_key(), |
| expect_ct_reporter()->network_isolation_key()); |
| } |
| |
| } // namespace net |