diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-13 15:05:36 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2019-02-14 10:33:47 +0000 |
commit | e684a3455bcc29a6e3e66a004e352dea4e1141e7 (patch) | |
tree | d55b4003bde34d7d05f558f02cfd82b2a66a7aac /chromium/services/network | |
parent | 2b94bfe47ccb6c08047959d1c26e392919550e86 (diff) | |
download | qtwebengine-chromium-e684a3455bcc29a6e3e66a004e352dea4e1141e7.tar.gz |
BASELINE: Update Chromium to 72.0.3626.110 and Ninja to 1.9.0
Change-Id: Ic57220b00ecc929a893c91f5cc552f5d3e99e922
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/services/network')
174 files changed, 10680 insertions, 2147 deletions
diff --git a/chromium/services/network/BUILD.gn b/chromium/services/network/BUILD.gn index 89977bd7320..308f93bc83c 100644 --- a/chromium/services/network/BUILD.gn +++ b/chromium/services/network/BUILD.gn @@ -4,10 +4,9 @@ import("//build/config/jumbo.gni") import("//mojo/public/tools/bindings/mojom.gni") -import("//services/catalog/public/tools/catalog.gni") -import("//services/service_manager/public/cpp/service.gni") +import("//net/features.gni") +import("//services/network/public/cpp/features.gni") import("//services/service_manager/public/service_manifest.gni") -import("//services/service_manager/public/tools/test/service_test.gni") jumbo_component("network_service") { sources = [ @@ -33,10 +32,10 @@ jumbo_component("network_service") { "cross_origin_read_blocking.h", "data_pipe_element_reader.cc", "data_pipe_element_reader.h", + "dns_config_change_manager.cc", + "dns_config_change_manager.h", "empty_url_loader_client.cc", "empty_url_loader_client.h", - "expect_ct_reporter.cc", - "expect_ct_reporter.h", "host_resolver.cc", "host_resolver.h", "http_cache_data_counter.cc", @@ -51,6 +50,8 @@ jumbo_component("network_service") { "keepalive_statistics_recorder.h", "loader_util.cc", "loader_util.h", + "mojo_host_resolver_impl.cc", + "mojo_host_resolver_impl.h", "net_log_capture_mode_type_converter.cc", "net_log_capture_mode_type_converter.h", "net_log_exporter.cc", @@ -59,6 +60,8 @@ jumbo_component("network_service") { "network_change_manager.h", "network_context.cc", "network_context.h", + "network_qualities_pref_delegate.cc", + "network_qualities_pref_delegate.h", "network_quality_estimator_manager.cc", "network_quality_estimator_manager.h", "network_sandbox_hook_linux.cc", @@ -85,10 +88,14 @@ jumbo_component("network_service") { "p2p/socket_throttler.h", "p2p/socket_udp.cc", "p2p/socket_udp.h", + "pending_callback_chain.cc", + "pending_callback_chain.h", "proxy_config_service_mojo.cc", "proxy_config_service_mojo.h", "proxy_lookup_request.cc", "proxy_lookup_request.h", + "proxy_resolver_factory_mojo.cc", + "proxy_resolver_factory_mojo.h", "proxy_resolving_client_socket.cc", "proxy_resolving_client_socket.h", "proxy_resolving_client_socket_factory.cc", @@ -97,6 +104,8 @@ jumbo_component("network_service") { "proxy_resolving_socket_factory_mojo.h", "proxy_resolving_socket_mojo.cc", "proxy_resolving_socket_mojo.h", + "proxy_service_mojo.cc", + "proxy_service_mojo.h", "resolve_host_request.cc", "resolve_host_request.h", "resource_scheduler.cc", @@ -159,6 +168,13 @@ jumbo_component("network_service") { "url_request_context_owner.h", ] + if (enable_mdns) { + sources += [ + "mdns_responder.cc", + "mdns_responder.h", + ] + } + if (!is_ios) { sources += [ "websocket.cc", @@ -170,13 +186,24 @@ jumbo_component("network_service") { ] } + if (is_chromeos) { + sources += [ + "cert_verifier_with_trust_anchors.cc", + "cert_verifier_with_trust_anchors.h", + "cert_verify_proc_chromeos.cc", + "cert_verify_proc_chromeos.h", + "nss_temp_certs_cache_chromeos.cc", + "nss_temp_certs_cache_chromeos.h", + ] + } + configs += [ "//build/config/compiler:wexit_time_destructors" ] deps = [ "//base", - "//components/certificate_transparency", "//components/content_settings/core/common", "//components/cookie_config", + "//components/domain_reliability", "//components/network_session_configurator/browser", "//components/network_session_configurator/common", "//components/os_crypt", @@ -199,6 +226,18 @@ jumbo_component("network_service") { "//url", ] + public_deps = [ + "//services/network/public/cpp:buildflags", + ] + + if (is_ct_supported) { + sources += [ + "expect_ct_reporter.cc", + "expect_ct_reporter.h", + ] + deps += [ "//components/certificate_transparency" ] + } + if (is_linux) { deps += [ "//sandbox/linux:sandbox_services", @@ -217,14 +256,6 @@ jumbo_component("network_service") { deps += [ "//sandbox/win:sandbox" ] } - sources += [ - "proxy_resolver_factory_mojo.cc", - "proxy_resolver_factory_mojo.h", - "proxy_service_mojo.cc", - "proxy_service_mojo.h", - ] - deps += [ "//net/dns:mojo_service" ] - defines = [ "IS_NETWORK_SERVICE_IMPL" ] if (is_chromecast) { @@ -239,18 +270,21 @@ source_set("tests") { "chunked_data_pipe_upload_data_stream_unittest.cc", "cookie_manager_unittest.cc", "cookie_settings_unittest.cc", + "cors/cors_url_loader_factory_unittest.cc", "cors/cors_url_loader_unittest.cc", "cors/preflight_controller_unittest.cc", "cross_origin_read_blocking_unittest.cc", "data_pipe_element_reader_unittest.cc", - "expect_ct_reporter_unittest.cc", + "dns_config_change_manager_unittest.cc", "host_resolver_unittest.cc", "http_cache_data_counter_unittest.cc", "http_cache_data_remover_unittest.cc", "ignore_errors_cert_verifier_unittest.cc", "keepalive_statistics_recorder_unittest.cc", + "mojo_host_resolver_impl_unittest.cc", "network_change_manager_unittest.cc", "network_context_unittest.cc", + "network_qualities_pref_delegate_unittest.cc", "network_quality_estimator_manager_unittest.cc", "network_service_proxy_delegate_unittest.cc", "network_service_unittest.cc", @@ -260,6 +294,7 @@ source_set("tests") { "p2p/socket_test_utils.cc", "p2p/socket_test_utils.h", "p2p/socket_udp_unittest.cc", + "pending_callback_chain_unittest.cc", "proxy_config_service_mojo_unittest.cc", "proxy_resolving_client_socket_unittest.cc", "proxy_resolving_socket_mojo_unittest.cc", @@ -283,9 +318,12 @@ source_set("tests") { "url_loader_unittest.cc", ] + if (enable_mdns) { + sources += [ "mdns_responder_unittest.cc" ] + } + if (!is_ios) { sources += [ - "network_context_cert_transparency_unittest.cc", "proxy_resolver_factory_mojo_unittest.cc", "websocket_throttler_unittest.cc", ] @@ -299,12 +337,21 @@ source_set("tests") { ] } + if (is_chromeos) { + sources += [ + "cert_verifier_with_trust_anchors_unittest.cc", + "cert_verify_proc_chromeos_unittest.cc", + "nss_temp_certs_cache_chromeos_unittest.cc", + ] + } + deps = [ ":network_service", ":test_support", "//base", - "//components/certificate_transparency", "//components/network_session_configurator/browser", + "//components/prefs:test_support", + "//crypto:test_support", "//jingle:jingle_fake_socket", "//mojo/core/embedder", "//mojo/public/cpp/bindings", @@ -312,12 +359,22 @@ source_set("tests") { "//net", "//net:extras", "//net:test_support", + "//net/http:transport_security_state_unittest_data_default", "//services/network/public/cpp", + "//services/network/public/cpp:buildflags", "//services/network/public/mojom", "//services/service_manager/public/cpp", - "//services/service_manager/public/cpp:service_test_support", + "//services/service_manager/public/cpp/test:test_support", "//testing/gtest", ] + + if (is_ct_supported) { + sources += [ + "expect_ct_reporter_unittest.cc", + "network_context_cert_transparency_unittest.cc", + ] + deps += [ "//components/certificate_transparency" ] + } } jumbo_source_set("test_support") { @@ -349,6 +406,7 @@ jumbo_source_set("test_support") { public_deps = [ "//services/network/public/cpp", + "//services/network/public/cpp:buildflags", "//services/network/public/mojom", ] @@ -366,14 +424,3 @@ service_manifest("manifest") { name = "network" source = "manifest.json" } - -service_manifest("unittest_manifest") { - name = "network_unittests" - source = "test/service_unittest_manifest.json" - packaged_services = [ ":manifest" ] -} - -catalog("tests_catalog") { - testonly = true - embedded_services = [ ":unittest_manifest" ] -} diff --git a/chromium/services/network/DEPS b/chromium/services/network/DEPS index 2e5b4b7a757..4d8cc1ac94d 100644 --- a/chromium/services/network/DEPS +++ b/chromium/services/network/DEPS @@ -2,6 +2,7 @@ include_rules = [ "+components/certificate_transparency", "+components/content_settings/core/common", "+components/cookie_config", + "+components/domain_reliability", "+components/network_session_configurator", "+components/os_crypt", # Prefs are used to create an independent file with a persisted key:value @@ -10,6 +11,8 @@ include_rules = [ "+components/prefs", "+crypto", "+ipc", + # FakeSSLClientSocket + "+jingle/glue", "+net", "+sandbox", "+services/proxy_resolver/public/mojom", diff --git a/chromium/services/network/OWNERS b/chromium/services/network/OWNERS index 418278801fa..98a98135709 100644 --- a/chromium/services/network/OWNERS +++ b/chromium/services/network/OWNERS @@ -12,6 +12,9 @@ per-file cross_origin_read_blocking*=lukasza@chromium.org per-file expect_ct_reporter*=estark@chromium.org +per-file cert_verifier_with_trust_anchors*=file://chromeos/policy/OWNERS +per-file cert_verify_proc_chromeos*=file://chromeos/policy/OWNERS + per-file manifest.json=set noparent per-file manifest.json=file://ipc/SECURITY_OWNERS per-file *_type_converter*.*=set noparent diff --git a/chromium/services/network/PRESUBMIT.py b/chromium/services/network/PRESUBMIT.py deleted file mode 100644 index 6d06e99bef2..00000000000 --- a/chromium/services/network/PRESUBMIT.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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. -"""Presubmit script for //services/network. - -See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts -for more details about the presubmit API built into depot_tools. -""" - -def PostUploadHook(cl, change, output_api): - """git cl upload will call this hook after the issue is created/modified. - - This hook adds extra try bots to the CL description in order to run network - service tests in addition to CQ try bots. - """ - return output_api.EnsureCQIncludeTrybotsAreAdded( - cl, - [ - 'luci.chromium.try:linux_mojo' - ], - 'Automatically added network service trybots to run tests on CQ.') - diff --git a/chromium/services/network/cert_verifier_with_trust_anchors.cc b/chromium/services/network/cert_verifier_with_trust_anchors.cc new file mode 100644 index 00000000000..0441894a57d --- /dev/null +++ b/chromium/services/network/cert_verifier_with_trust_anchors.cc @@ -0,0 +1,108 @@ +// Copyright (c) 2013 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 "services/network/cert_verifier_with_trust_anchors.h" + +#include <utility> + +#include "base/bind.h" +#include "base/logging.h" +#include "net/base/net_errors.h" +#include "net/cert/caching_cert_verifier.h" +#include "net/cert/cert_verify_proc.h" +#include "net/cert/multi_threaded_cert_verifier.h" + +namespace network { + +namespace { + +void MaybeSignalAnchorUse(int error, + const base::Closure& anchor_used_callback, + const net::CertVerifyResult& verify_result) { + if (error != net::OK || !verify_result.is_issued_by_additional_trust_anchor || + anchor_used_callback.is_null()) { + return; + } + anchor_used_callback.Run(); +} + +void CompleteAndSignalAnchorUse(const base::Closure& anchor_used_callback, + net::CompletionOnceCallback completion_callback, + const net::CertVerifyResult* verify_result, + int error) { + MaybeSignalAnchorUse(error, anchor_used_callback, *verify_result); + std::move(completion_callback).Run(error); +} + +net::CertVerifier::Config ExtendTrustAnchors( + const net::CertVerifier::Config& config, + const net::CertificateList& trust_anchors) { + net::CertVerifier::Config new_config = config; + new_config.additional_trust_anchors.insert( + new_config.additional_trust_anchors.begin(), trust_anchors.begin(), + trust_anchors.end()); + return new_config; +} + +} // namespace + +CertVerifierWithTrustAnchors::CertVerifierWithTrustAnchors( + const base::Closure& anchor_used_callback) + : anchor_used_callback_(anchor_used_callback) { + DETACH_FROM_THREAD(thread_checker_); +} + +CertVerifierWithTrustAnchors::~CertVerifierWithTrustAnchors() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); +} + +void CertVerifierWithTrustAnchors::InitializeOnIOThread( + const scoped_refptr<net::CertVerifyProc>& verify_proc) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + if (!verify_proc->SupportsAdditionalTrustAnchors()) { + LOG(WARNING) + << "Additional trust anchors not supported on the current platform!"; + } + delegate_ = std::make_unique<net::CachingCertVerifier>( + std::make_unique<net::MultiThreadedCertVerifier>(verify_proc.get())); + delegate_->SetConfig(ExtendTrustAnchors(orig_config_, trust_anchors_)); +} + +void CertVerifierWithTrustAnchors::SetTrustAnchors( + const net::CertificateList& trust_anchors) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + if (trust_anchors == trust_anchors_) + return; + trust_anchors_ = trust_anchors; + if (!delegate_) + return; + delegate_->SetConfig(ExtendTrustAnchors(orig_config_, trust_anchors_)); +} + +int CertVerifierWithTrustAnchors::Verify( + const RequestParams& params, + net::CertVerifyResult* verify_result, + net::CompletionOnceCallback completion_callback, + std::unique_ptr<Request>* out_req, + const net::NetLogWithSource& net_log) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + DCHECK(delegate_); + DCHECK(completion_callback); + net::CompletionOnceCallback wrapped_callback = + base::BindOnce(&CompleteAndSignalAnchorUse, anchor_used_callback_, + std::move(completion_callback), verify_result); + + int error = delegate_->Verify(params, verify_result, + std::move(wrapped_callback), out_req, net_log); + MaybeSignalAnchorUse(error, anchor_used_callback_, *verify_result); + return error; +} + +void CertVerifierWithTrustAnchors::SetConfig(const Config& config) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + orig_config_ = config; + delegate_->SetConfig(ExtendTrustAnchors(orig_config_, trust_anchors_)); +} + +} // namespace network diff --git a/chromium/services/network/cert_verifier_with_trust_anchors.h b/chromium/services/network/cert_verifier_with_trust_anchors.h new file mode 100644 index 00000000000..1104a36a487 --- /dev/null +++ b/chromium/services/network/cert_verifier_with_trust_anchors.h @@ -0,0 +1,70 @@ +// Copyright (c) 2013 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. + +#ifndef SERVICES_NETWORK_CERT_VERIFIER_WITH_TRUST_ANCHORS_H_ +#define SERVICES_NETWORK_CERT_VERIFIER_WITH_TRUST_ANCHORS_H_ + +#include <memory> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/component_export.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread_checker.h" +#include "net/base/completion_once_callback.h" +#include "net/cert/cert_verifier.h" + +namespace net { +class CertVerifyProc; +class CertVerifyResult; +class X509Certificate; +typedef std::vector<scoped_refptr<X509Certificate>> CertificateList; +} // namespace net + +namespace network { + +// Wraps a MultiThreadedCertVerifier to make it use the additional trust anchors +// configured by the ONC user policy. +class COMPONENT_EXPORT(NETWORK_SERVICE) CertVerifierWithTrustAnchors + : public net::CertVerifier { + public: + // Except of the constructor, all methods and the destructor must be called on + // the IO thread. Calls |anchor_used_callback| on the IO thread every time a + // certificate from the additional trust anchors (set with SetTrustAnchors) is + // used. + explicit CertVerifierWithTrustAnchors( + const base::Closure& anchor_used_callback); + ~CertVerifierWithTrustAnchors() override; + + // TODO(jam): once the network service is the only path, rename or get rid of + // this method. + void InitializeOnIOThread( + const scoped_refptr<net::CertVerifyProc>& verify_proc); + + // Sets the additional trust anchors. + void SetTrustAnchors(const net::CertificateList& trust_anchors); + + // CertVerifier: + int Verify(const RequestParams& params, + net::CertVerifyResult* verify_result, + net::CompletionOnceCallback callback, + std::unique_ptr<Request>* out_req, + const net::NetLogWithSource& net_log) override; + void SetConfig(const Config& config) override; + + private: + net::CertVerifier::Config orig_config_; + net::CertificateList trust_anchors_; + base::Closure anchor_used_callback_; + std::unique_ptr<CertVerifier> delegate_; + THREAD_CHECKER(thread_checker_); + + DISALLOW_COPY_AND_ASSIGN(CertVerifierWithTrustAnchors); +}; + +} // namespace network + +#endif // SERVICES_NETWORK_CERT_VERIFIER_WITH_TRUST_ANCHORS_H_ diff --git a/chromium/services/network/cert_verifier_with_trust_anchors_unittest.cc b/chromium/services/network/cert_verifier_with_trust_anchors_unittest.cc new file mode 100644 index 00000000000..618356d4390 --- /dev/null +++ b/chromium/services/network/cert_verifier_with_trust_anchors_unittest.cc @@ -0,0 +1,306 @@ +// Copyright 2014 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 "services/network/cert_verifier_with_trust_anchors.h" + +#include <memory> +#include <string> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/run_loop.h" +#include "base/test/scoped_task_environment.h" +#include "crypto/nss_util_internal.h" +#include "crypto/scoped_test_nss_chromeos_user.h" +#include "net/base/test_completion_callback.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/nss_cert_database_chromeos.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_util_nss.h" +#include "net/log/net_log_with_source.h" +#include "net/test/cert_test_util.h" +#include "net/test/test_data_directory.h" +#include "services/network/cert_verify_proc_chromeos.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { + +class CertVerifierWithTrustAnchorsTest : public testing::Test { + public: + CertVerifierWithTrustAnchorsTest() + : trust_anchor_used_(false), + test_nss_user_("user1"), + scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::IO) {} + + ~CertVerifierWithTrustAnchorsTest() override {} + + void SetUp() override { + ASSERT_TRUE(test_nss_user_.constructed_successfully()); + test_nss_user_.FinishInit(); + + test_cert_db_.reset(new net::NSSCertDatabaseChromeOS( + crypto::GetPublicSlotForChromeOSUser(test_nss_user_.username_hash()), + crypto::GetPrivateSlotForChromeOSUser( + test_nss_user_.username_hash(), + base::RepeatingCallback<void(crypto::ScopedPK11Slot)>()))); + + cert_verifier_.reset(new CertVerifierWithTrustAnchors(base::BindRepeating( + &CertVerifierWithTrustAnchorsTest::OnTrustAnchorUsed, + base::Unretained(this)))); + cert_verifier_->InitializeOnIOThread(new network::CertVerifyProcChromeOS( + crypto::GetPublicSlotForChromeOSUser(test_nss_user_.username_hash()))); + + test_ca_cert_ = LoadCertificate("root_ca_cert.pem", net::CA_CERT); + ASSERT_TRUE(test_ca_cert_); + test_ca_cert_list_.push_back( + net::x509_util::DupCERTCertificate(test_ca_cert_.get())); + + net::ScopedCERTCertificate test_server_cert = + LoadCertificate("ok_cert.pem", net::SERVER_CERT); + ASSERT_TRUE(test_server_cert); + test_server_cert_ = + net::x509_util::CreateX509CertificateFromCERTCertificate( + test_server_cert.get()); + ASSERT_TRUE(test_server_cert_); + } + + void TearDown() override { + // Destroy |cert_verifier_| before destroying the ThreadBundle, otherwise + // BrowserThread::CurrentlyOn checks fail. + cert_verifier_.reset(); + } + + protected: + int VerifyTestServerCert( + const net::TestCompletionCallback& test_callback, + net::CertVerifyResult* verify_result, + std::unique_ptr<net::CertVerifier::Request>* request) { + return cert_verifier_->Verify( + net::CertVerifier::RequestParams(test_server_cert_.get(), "127.0.0.1", + 0, std::string()), + verify_result, test_callback.callback(), request, + net::NetLogWithSource()); + } + + bool SupportsAdditionalTrustAnchors() { + scoped_refptr<net::CertVerifyProc> proc = + net::CertVerifyProc::CreateDefault(); + return proc->SupportsAdditionalTrustAnchors(); + } + + // Returns whether |cert_verifier| signalled usage of one of the additional + // trust anchors (i.e. of |test_ca_cert_|) for the first time or since the + // last call of this function. + bool WasTrustAnchorUsedAndReset() { + base::RunLoop().RunUntilIdle(); + bool result = trust_anchor_used_; + trust_anchor_used_ = false; + return result; + } + + // |test_ca_cert_| is the issuer of |test_server_cert_|. + net::ScopedCERTCertificate test_ca_cert_; + scoped_refptr<net::X509Certificate> test_server_cert_; + net::ScopedCERTCertificateList test_ca_cert_list_; + std::unique_ptr<net::NSSCertDatabaseChromeOS> test_cert_db_; + std::unique_ptr<CertVerifierWithTrustAnchors> cert_verifier_; + + private: + void OnTrustAnchorUsed() { trust_anchor_used_ = true; } + + net::ScopedCERTCertificate LoadCertificate(const std::string& name, + net::CertType type) { + net::ScopedCERTCertificate cert = + net::ImportCERTCertificateFromFile(net::GetTestCertsDirectory(), name); + if (!cert) + return cert; + + // No certificate is trusted right after it's loaded. + net::NSSCertDatabase::TrustBits trust = + test_cert_db_->GetCertTrust(cert.get(), type); + EXPECT_EQ(net::NSSCertDatabase::TRUST_DEFAULT, trust); + + return cert; + } + + bool trust_anchor_used_; + crypto::ScopedTestNSSChromeOSUser test_nss_user_; + base::test::ScopedTaskEnvironment scoped_task_environment_; +}; + +TEST_F(CertVerifierWithTrustAnchorsTest, VerifyUntrustedCert) { + // |test_server_cert_| is untrusted, so Verify() fails. + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + ASSERT_EQ(net::ERR_IO_PENDING, error); + EXPECT_TRUE(request); + error = callback.WaitForResult(); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, error); + } + + // Issuing the same request again hits the cache. This tests the synchronous + // path. + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, error); + } + + EXPECT_FALSE(WasTrustAnchorUsedAndReset()); +} + +TEST_F(CertVerifierWithTrustAnchorsTest, VerifyTrustedCert) { + // Make the database trust |test_ca_cert_|. + net::NSSCertDatabase::ImportCertFailureList failure_list; + ASSERT_TRUE(test_cert_db_->ImportCACerts( + test_ca_cert_list_, net::NSSCertDatabase::TRUSTED_SSL, &failure_list)); + ASSERT_TRUE(failure_list.empty()); + + // Verify that it is now trusted. + net::NSSCertDatabase::TrustBits trust = + test_cert_db_->GetCertTrust(test_ca_cert_.get(), net::CA_CERT); + EXPECT_EQ(net::NSSCertDatabase::TRUSTED_SSL, trust); + + // Verify() successfully verifies |test_server_cert_| after it was imported. + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + ASSERT_EQ(net::ERR_IO_PENDING, error); + EXPECT_TRUE(request); + error = callback.WaitForResult(); + EXPECT_EQ(net::OK, error); + + // The additional trust anchors were not used, since the certificate is + // trusted from the database. + EXPECT_FALSE(WasTrustAnchorUsedAndReset()); +} + +TEST_F(CertVerifierWithTrustAnchorsTest, VerifyUsingAdditionalTrustAnchor) { + ASSERT_TRUE(SupportsAdditionalTrustAnchors()); + + // |test_server_cert_| is untrusted, so Verify() fails. + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + ASSERT_EQ(net::ERR_IO_PENDING, error); + EXPECT_TRUE(request); + error = callback.WaitForResult(); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, error); + } + EXPECT_FALSE(WasTrustAnchorUsedAndReset()); + + net::CertificateList test_ca_x509cert_list = + net::x509_util::CreateX509CertificateListFromCERTCertificates( + test_ca_cert_list_); + ASSERT_FALSE(test_ca_x509cert_list.empty()); + + // Verify() again with the additional trust anchors. + cert_verifier_->SetTrustAnchors(test_ca_x509cert_list); + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + ASSERT_EQ(net::ERR_IO_PENDING, error); + EXPECT_TRUE(request); + error = callback.WaitForResult(); + EXPECT_EQ(net::OK, error); + } + EXPECT_TRUE(WasTrustAnchorUsedAndReset()); + + // Verify() again with the additional trust anchors will hit the cache. + cert_verifier_->SetTrustAnchors(test_ca_x509cert_list); + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + EXPECT_EQ(net::OK, error); + } + EXPECT_TRUE(WasTrustAnchorUsedAndReset()); + + // Verifying after removing the trust anchors should now fail. + cert_verifier_->SetTrustAnchors(net::CertificateList()); + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + // Note: Changing the trust anchors should flush the cache. + ASSERT_EQ(net::ERR_IO_PENDING, error); + EXPECT_TRUE(request); + error = callback.WaitForResult(); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, error); + } + // The additional trust anchors were reset, thus |cert_verifier_| should not + // signal it's usage anymore. + EXPECT_FALSE(WasTrustAnchorUsedAndReset()); +} + +TEST_F(CertVerifierWithTrustAnchorsTest, + VerifyUsesAdditionalTrustAnchorsAfterConfigChange) { + ASSERT_TRUE(SupportsAdditionalTrustAnchors()); + + // |test_server_cert_| is untrusted, so Verify() fails. + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + ASSERT_EQ(net::ERR_IO_PENDING, error); + EXPECT_TRUE(request); + error = callback.WaitForResult(); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, error); + } + EXPECT_FALSE(WasTrustAnchorUsedAndReset()); + + net::CertificateList test_ca_x509cert_list = + net::x509_util::CreateX509CertificateListFromCERTCertificates( + test_ca_cert_list_); + ASSERT_FALSE(test_ca_x509cert_list.empty()); + + // Verify() again with the additional trust anchors. + cert_verifier_->SetTrustAnchors(test_ca_x509cert_list); + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + ASSERT_EQ(net::ERR_IO_PENDING, error); + EXPECT_TRUE(request); + error = callback.WaitForResult(); + EXPECT_EQ(net::OK, error); + } + EXPECT_TRUE(WasTrustAnchorUsedAndReset()); + + // Change the configuration to enable SHA-1, which should still use the + // additional trust anchors. + net::CertVerifier::Config config; + config.enable_sha1_local_anchors = true; + cert_verifier_->SetConfig(config); + { + net::CertVerifyResult verify_result; + net::TestCompletionCallback callback; + std::unique_ptr<net::CertVerifier::Request> request; + int error = VerifyTestServerCert(callback, &verify_result, &request); + ASSERT_EQ(net::ERR_IO_PENDING, error); + EXPECT_TRUE(request); + error = callback.WaitForResult(); + EXPECT_EQ(net::OK, error); + } + EXPECT_TRUE(WasTrustAnchorUsedAndReset()); +} + +} // namespace network diff --git a/chromium/services/network/cert_verify_proc_chromeos.cc b/chromium/services/network/cert_verify_proc_chromeos.cc new file mode 100644 index 00000000000..5eac37cd9d0 --- /dev/null +++ b/chromium/services/network/cert_verify_proc_chromeos.cc @@ -0,0 +1,105 @@ +// Copyright 2014 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 "services/network/cert_verify_proc_chromeos.h" + +#include <utility> + +#include "net/cert/test_root_certs.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_util_nss.h" + +// NSS doesn't currently define CERT_LIST_TAIL. +// See https://bugzilla.mozilla.org/show_bug.cgi?id=962413 +// Can be removed once chrome requires NSS version 3.16 to build. +#ifndef CERT_LIST_TAIL +#define CERT_LIST_TAIL(l) ((CERTCertListNode*)PR_LIST_TAIL(&l->list)) +#endif + +namespace network { + +namespace { + +struct ChainVerifyArgs { + CertVerifyProcChromeOS* cert_verify_proc; + const net::CertificateList& additional_trust_anchors; +}; + +} // namespace + +CertVerifyProcChromeOS::CertVerifyProcChromeOS() {} + +CertVerifyProcChromeOS::CertVerifyProcChromeOS( + crypto::ScopedPK11Slot public_slot) { + // Only the software slot is passed, since that is the only one where user + // trust settings are stored. + profile_filter_.Init(std::move(public_slot), crypto::ScopedPK11Slot(), + crypto::ScopedPK11Slot()); +} + +CertVerifyProcChromeOS::~CertVerifyProcChromeOS() {} + +int CertVerifyProcChromeOS::VerifyInternal( + net::X509Certificate* cert, + const std::string& hostname, + const std::string& ocsp_response, + int flags, + net::CRLSet* crl_set, + const net::CertificateList& additional_trust_anchors, + net::CertVerifyResult* verify_result) { + ChainVerifyArgs chain_verify_args = {this, additional_trust_anchors}; + + CERTChainVerifyCallback chain_verify_callback; + chain_verify_callback.isChainValid = + &CertVerifyProcChromeOS::IsChainValidFunc; + chain_verify_callback.isChainValidArg = + static_cast<void*>(&chain_verify_args); + + return VerifyInternalImpl(cert, hostname, ocsp_response, flags, crl_set, + additional_trust_anchors, &chain_verify_callback, + verify_result); +} + +// static +SECStatus CertVerifyProcChromeOS::IsChainValidFunc( + void* is_chain_valid_arg, + const CERTCertList* current_chain, + PRBool* chain_ok) { + ChainVerifyArgs* args = static_cast<ChainVerifyArgs*>(is_chain_valid_arg); + CERTCertificate* cert = CERT_LIST_TAIL(current_chain)->cert; + + if (net::TestRootCerts::HasInstance()) { + if (net::TestRootCerts::GetInstance()->Contains(cert)) { + // Certs in the TestRootCerts are not stored in any slot, and thus would + // not be allowed by the profile_filter. This should only be hit in tests. + DVLOG(3) << cert->subjectName << " is a TestRootCert"; + *chain_ok = PR_TRUE; + return SECSuccess; + } + } + + for (net::CertificateList::const_iterator i = + args->additional_trust_anchors.begin(); + i != args->additional_trust_anchors.end(); ++i) { + if (net::x509_util::IsSameCertificate(cert, i->get())) { + // Certs in the additional_trust_anchors should always be allowed, even if + // they aren't stored in a slot that would be allowed by the + // profile_filter. + DVLOG(3) << cert->subjectName << " is an additional_trust_anchor"; + *chain_ok = PR_TRUE; + return SECSuccess; + } + } + + // TODO(mattm): If crbug.com/334384 is fixed to allow setting trust + // properly when the same cert is in multiple slots, this would also need + // updating to check the per-slot trust values. + *chain_ok = args->cert_verify_proc->profile_filter_.IsCertAllowed(cert) + ? PR_TRUE + : PR_FALSE; + DVLOG(3) << cert->subjectName << " is " << (*chain_ok ? "ok" : "not ok"); + return SECSuccess; +} + +} // namespace network diff --git a/chromium/services/network/cert_verify_proc_chromeos.h b/chromium/services/network/cert_verify_proc_chromeos.h new file mode 100644 index 00000000000..45ee9fb455a --- /dev/null +++ b/chromium/services/network/cert_verify_proc_chromeos.h @@ -0,0 +1,61 @@ +// Copyright 2014 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. + +#ifndef SERVICES_NETWORK_CERT_VERIFY_PROC_CHROMEOS_H_ +#define SERVICES_NETWORK_CERT_VERIFY_PROC_CHROMEOS_H_ + +#include "base/component_export.h" +#include "crypto/scoped_nss_types.h" +#include "net/cert/cert_verify_proc_nss.h" +#include "net/cert/nss_profile_filter_chromeos.h" + +namespace network { + +// Wrapper around CertVerifyProcNSS which allows filtering trust decisions on a +// per-slot basis. +// +// Note that only the simple case is currently handled (if a slot contains a new +// trust root, that root should not be trusted by CertVerifyProcChromeOS +// instances using other slots). More complicated cases are not handled (like +// two slots adding the same root cert but with different trust values). +class COMPONENT_EXPORT(NETWORK_SERVICE) CertVerifyProcChromeOS + : public net::CertVerifyProcNSS { + public: + // Creates a CertVerifyProc that doesn't allow any user-provided trust roots. + CertVerifyProcChromeOS(); + + // Creates a CertVerifyProc that doesn't allow trust roots provided by + // users other than the specified slot. + explicit CertVerifyProcChromeOS(crypto::ScopedPK11Slot public_slot); + + protected: + ~CertVerifyProcChromeOS() override; + + private: + // net::CertVerifyProcNSS implementation: + int VerifyInternal(net::X509Certificate* cert, + const std::string& hostname, + const std::string& ocsp_response, + int flags, + net::CRLSet* crl_set, + const net::CertificateList& additional_trust_anchors, + net::CertVerifyResult* verify_result) override; + + // Check if the trust root of |current_chain| is allowed. + // |is_chain_valid_arg| is actually a ChainVerifyArgs*, which is used to pass + // state through the NSS CERTChainVerifyCallback.isChainValidArg parameter. + // If the chain is allowed, |*chain_ok| will be set to PR_TRUE. + // If the chain is not allowed, |*chain_ok| is set to PR_FALSE, and this + // function may be called again during a single certificate verification if + // there are multiple possible valid chains. + static SECStatus IsChainValidFunc(void* is_chain_valid_arg, + const CERTCertList* current_chain, + PRBool* chain_ok); + + net::NSSProfileFilterChromeOS profile_filter_; +}; + +} // namespace network + +#endif // SERVICES_NETWORK_CERT_VERIFY_PROC_CHROMEOS_H_ diff --git a/chromium/services/network/cert_verify_proc_chromeos_unittest.cc b/chromium/services/network/cert_verify_proc_chromeos_unittest.cc new file mode 100644 index 00000000000..d0e2061fa0b --- /dev/null +++ b/chromium/services/network/cert_verify_proc_chromeos_unittest.cc @@ -0,0 +1,387 @@ +// Copyright 2014 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 "services/network/cert_verify_proc_chromeos.h" + +#include <stddef.h> + +#include "crypto/nss_util_internal.h" +#include "crypto/scoped_test_nss_chromeos_user.h" +#include "net/base/net_errors.h" +#include "net/cert/cert_verify_proc.h" +#include "net/cert/cert_verify_result.h" +#include "net/cert/nss_cert_database_chromeos.h" +#include "net/cert/x509_util.h" +#include "net/cert/x509_util_nss.h" +#include "net/test/cert_test_util.h" +#include "net/test/test_data_directory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { + +namespace { + +std::string GetSubjectCN(CRYPTO_BUFFER* cert_handle) { + scoped_refptr<net::X509Certificate> cert = + net::X509Certificate::CreateFromBuffer(bssl::UpRef(cert_handle), {}); + if (!cert) + return std::string(); + return "CN=" + cert->subject().common_name; +} + +} // namespace + +class CertVerifyProcChromeOSTest : public testing::Test { + public: + CertVerifyProcChromeOSTest() : user_1_("user1"), user_2_("user2") {} + + void SetUp() override { + // Initialize nss_util slots. + ASSERT_TRUE(user_1_.constructed_successfully()); + ASSERT_TRUE(user_2_.constructed_successfully()); + user_1_.FinishInit(); + user_2_.FinishInit(); + + // Create NSSCertDatabaseChromeOS for each user. + db_1_.reset(new net::NSSCertDatabaseChromeOS( + crypto::GetPublicSlotForChromeOSUser(user_1_.username_hash()), + crypto::GetPrivateSlotForChromeOSUser( + user_1_.username_hash(), + base::Callback<void(crypto::ScopedPK11Slot)>()))); + db_2_.reset(new net::NSSCertDatabaseChromeOS( + crypto::GetPublicSlotForChromeOSUser(user_2_.username_hash()), + crypto::GetPrivateSlotForChromeOSUser( + user_2_.username_hash(), + base::Callback<void(crypto::ScopedPK11Slot)>()))); + + // Create default verifier and for each user. + verify_proc_default_ = new CertVerifyProcChromeOS(); + verify_proc_1_ = new CertVerifyProcChromeOS(db_1_->GetPublicSlot()); + verify_proc_2_ = new CertVerifyProcChromeOS(db_2_->GetPublicSlot()); + + // Load test cert chains from disk. + certs_1_ = net::CreateCERTCertificateListFromFile( + net::GetTestCertsDirectory(), "multi-root-chain1.pem", + net::X509Certificate::FORMAT_AUTO); + ASSERT_EQ(4U, certs_1_.size()); + + certs_2_ = net::CreateCERTCertificateListFromFile( + net::GetTestCertsDirectory(), "multi-root-chain2.pem", + net::X509Certificate::FORMAT_AUTO); + ASSERT_EQ(4U, certs_2_.size()); + + // The chains: + // 1. A (end-entity) -> B -> C -> D (self-signed root) + // 2. A (end-entity) -> B -> C2 -> E (self-signed root) + ASSERT_TRUE(net::x509_util::IsSameCertificate(certs_1_[0].get(), + certs_2_[0].get())); + ASSERT_TRUE(net::x509_util::IsSameCertificate(certs_1_[1].get(), + certs_2_[1].get())); + ASSERT_FALSE(net::x509_util::IsSameCertificate(certs_1_[2].get(), + certs_2_[2].get())); + ASSERT_STREQ("CN=C CA - Multi-root", certs_1_[2]->subjectName); + ASSERT_STREQ("CN=C CA - Multi-root", certs_2_[2]->subjectName); + + ASSERT_STREQ("CN=D Root CA - Multi-root", certs_1_.back()->subjectName); + ASSERT_STREQ("CN=E Root CA - Multi-root", certs_2_.back()->subjectName); + root_1_.push_back( + net::x509_util::DupCERTCertificate(certs_1_.back().get())); + ASSERT_TRUE(root_1_.back()); + root_2_.push_back( + net::x509_util::DupCERTCertificate(certs_2_.back().get())); + ASSERT_TRUE(root_2_.back()); + ASSERT_STREQ("CN=D Root CA - Multi-root", root_1_.back()->subjectName); + ASSERT_STREQ("CN=E Root CA - Multi-root", root_2_.back()->subjectName); + + server_ = net::x509_util::CreateX509CertificateFromCERTCertificate( + certs_1_[0].get()); + ASSERT_TRUE(server_); + } + + int VerifyWithAdditionalTrustAnchors( + net::CertVerifyProc* verify_proc, + const net::CertificateList& additional_trust_anchors, + net::X509Certificate* cert, + std::string* root_subject_name) { + int flags = 0; + net::CertVerifyResult verify_result; + int error = + verify_proc->Verify(cert, "127.0.0.1", std::string(), flags, NULL, + additional_trust_anchors, &verify_result); + if (!verify_result.verified_cert->intermediate_buffers().empty()) { + root_subject_name->assign(GetSubjectCN( + verify_result.verified_cert->intermediate_buffers().back().get())); + } else { + root_subject_name->clear(); + } + return error; + } + + int Verify(net::CertVerifyProc* verify_proc, + net::X509Certificate* cert, + std::string* root_subject_name) { + net::CertificateList additional_trust_anchors; + return VerifyWithAdditionalTrustAnchors( + verify_proc, additional_trust_anchors, cert, root_subject_name); + } + + protected: + crypto::ScopedTestNSSChromeOSUser user_1_; + crypto::ScopedTestNSSChromeOSUser user_2_; + std::unique_ptr<net::NSSCertDatabaseChromeOS> db_1_; + std::unique_ptr<net::NSSCertDatabaseChromeOS> db_2_; + scoped_refptr<net::CertVerifyProc> verify_proc_default_; + scoped_refptr<net::CertVerifyProc> verify_proc_1_; + scoped_refptr<net::CertVerifyProc> verify_proc_2_; + net::ScopedCERTCertificateList certs_1_; + net::ScopedCERTCertificateList certs_2_; + net::ScopedCERTCertificateList root_1_; + net::ScopedCERTCertificateList root_2_; + scoped_refptr<net::X509Certificate> server_; +}; + +// Test that the CertVerifyProcChromeOS doesn't trusts roots that are in other +// user's slots or that have been deleted, and that verifying done by one user +// doesn't affect verifications done by others. +TEST_F(CertVerifyProcChromeOSTest, TestChainVerify) { + std::string verify_root; + // Before either of the root certs have been trusted, all verifications should + // fail with CERT_AUTHORITY_INVALID. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_default_.get(), server_.get(), &verify_root)); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_1_.get(), server_.get(), &verify_root)); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_2_.get(), server_.get(), &verify_root)); + + // Import and trust the D root for user 1. + net::NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(db_1_->ImportCACerts(root_1_, net::NSSCertDatabase::TRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + + // Imported CA certs are not trusted by default verifier. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_default_.get(), server_.get(), &verify_root)); + // User 1 should now verify successfully through the D root. + EXPECT_EQ(net::OK, Verify(verify_proc_1_.get(), server_.get(), &verify_root)); + EXPECT_EQ("CN=D Root CA - Multi-root", verify_root); + // User 2 should still fail. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_2_.get(), server_.get(), &verify_root)); + + // Import and trust the E root for user 2. + failed.clear(); + EXPECT_TRUE(db_2_->ImportCACerts(root_2_, net::NSSCertDatabase::TRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + + // Imported CA certs are not trusted by default verifier. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_default_.get(), server_.get(), &verify_root)); + // User 1 should still verify successfully through the D root. + EXPECT_EQ(net::OK, Verify(verify_proc_1_.get(), server_.get(), &verify_root)); + EXPECT_EQ("CN=D Root CA - Multi-root", verify_root); + // User 2 should now verify successfully through the E root. + EXPECT_EQ(net::OK, Verify(verify_proc_2_.get(), server_.get(), &verify_root)); + EXPECT_EQ("CN=E Root CA - Multi-root", verify_root); + + // Delete D root. + EXPECT_TRUE(db_1_->DeleteCertAndKey(root_1_[0].get())); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_default_.get(), server_.get(), &verify_root)); + // User 1 should now fail to verify. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_1_.get(), server_.get(), &verify_root)); + // User 2 should still verify successfully through the E root. + EXPECT_EQ(net::OK, Verify(verify_proc_2_.get(), server_.get(), &verify_root)); + EXPECT_EQ("CN=E Root CA - Multi-root", verify_root); + + // Delete E root. + EXPECT_TRUE(db_2_->DeleteCertAndKey(root_2_[0].get())); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_default_.get(), server_.get(), &verify_root)); + // User 1 should still fail to verify. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_1_.get(), server_.get(), &verify_root)); + // User 2 should now fail to verify. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_2_.get(), server_.get(), &verify_root)); +} + +// Test that roots specified through additional_trust_anchors are trusted for +// that verification, and that there is not any caching that affects later +// verifications. +TEST_F(CertVerifyProcChromeOSTest, TestAdditionalTrustAnchors) { + EXPECT_TRUE(verify_proc_default_->SupportsAdditionalTrustAnchors()); + EXPECT_TRUE(verify_proc_1_->SupportsAdditionalTrustAnchors()); + + std::string verify_root; + net::CertificateList additional_trust_anchors; + scoped_refptr<net::X509Certificate> d_root_ca = + net::x509_util::CreateX509CertificateFromCERTCertificate( + certs_1_.back().get()); + ASSERT_TRUE(d_root_ca); + + // Before either of the root certs have been trusted, all verifications should + // fail with CERT_AUTHORITY_INVALID. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + VerifyWithAdditionalTrustAnchors(verify_proc_default_.get(), + additional_trust_anchors, + server_.get(), &verify_root)); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + VerifyWithAdditionalTrustAnchors(verify_proc_1_.get(), + additional_trust_anchors, + server_.get(), &verify_root)); + + // Use D Root CA as additional trust anchor. Verifications should succeed now. + additional_trust_anchors.push_back(d_root_ca); + EXPECT_EQ(net::OK, VerifyWithAdditionalTrustAnchors( + verify_proc_default_.get(), additional_trust_anchors, + server_.get(), &verify_root)); + EXPECT_EQ("CN=D Root CA - Multi-root", verify_root); + EXPECT_EQ(net::OK, VerifyWithAdditionalTrustAnchors( + verify_proc_1_.get(), additional_trust_anchors, + server_.get(), &verify_root)); + EXPECT_EQ("CN=D Root CA - Multi-root", verify_root); + // User 2 should still fail. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + VerifyWithAdditionalTrustAnchors(verify_proc_2_.get(), + net::CertificateList(), + server_.get(), &verify_root)); + + // Without additional trust anchors, verification should fail again. + additional_trust_anchors.clear(); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + VerifyWithAdditionalTrustAnchors(verify_proc_default_.get(), + additional_trust_anchors, + server_.get(), &verify_root)); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + VerifyWithAdditionalTrustAnchors(verify_proc_1_.get(), + additional_trust_anchors, + server_.get(), &verify_root)); + + // Import and trust the D Root CA for user 2. + net::NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(db_2_->ImportCACerts(root_1_, net::NSSCertDatabase::TRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + + // Use D Root CA as additional trust anchor. Verifications should still + // succeed even if the cert is trusted by a different profile. + additional_trust_anchors.push_back(d_root_ca); + EXPECT_EQ(net::OK, VerifyWithAdditionalTrustAnchors( + verify_proc_default_.get(), additional_trust_anchors, + server_.get(), &verify_root)); + EXPECT_EQ("CN=D Root CA - Multi-root", verify_root); + EXPECT_EQ(net::OK, VerifyWithAdditionalTrustAnchors( + verify_proc_1_.get(), additional_trust_anchors, + server_.get(), &verify_root)); + EXPECT_EQ("CN=D Root CA - Multi-root", verify_root); + EXPECT_EQ(net::OK, VerifyWithAdditionalTrustAnchors( + verify_proc_2_.get(), additional_trust_anchors, + server_.get(), &verify_root)); + EXPECT_EQ("CN=D Root CA - Multi-root", verify_root); +} + +class CertVerifyProcChromeOSOrderingTest + : public CertVerifyProcChromeOSTest, + public ::testing::WithParamInterface<std::tuple<bool, int, std::string>> { +}; + +// Test a variety of different combinations of (maybe) verifying / (maybe) +// importing / verifying again, to try to find any cases where caching might +// affect the results. +// http://crbug.com/396501 +TEST_P(CertVerifyProcChromeOSOrderingTest, DISABLED_TrustThenVerify) { + const ParamType& param = GetParam(); + const bool verify_first = std::get<0>(param); + const int trust_bitmask = std::get<1>(param); + const std::string test_order = std::get<2>(param); + DVLOG(1) << "verify_first: " << verify_first + << " trust_bitmask: " << trust_bitmask + << " test_order: " << test_order; + + std::string verify_root; + + if (verify_first) { + // Before either of the root certs have been trusted, all verifications + // should fail with CERT_AUTHORITY_INVALID. + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_default_.get(), server_.get(), &verify_root)); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_1_.get(), server_.get(), &verify_root)); + EXPECT_EQ(net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_2_.get(), server_.get(), &verify_root)); + } + + int expected_user1_result = net::ERR_CERT_AUTHORITY_INVALID; + int expected_user2_result = net::ERR_CERT_AUTHORITY_INVALID; + + if (trust_bitmask & 1) { + expected_user1_result = net::OK; + // Import and trust the D root for user 1. + net::NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(db_1_->ImportCACerts(root_1_, net::NSSCertDatabase::TRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + for (size_t i = 0; i < failed.size(); ++i) { + LOG(ERROR) << "import fail " << failed[i].net_error << " for " + << failed[i].certificate->subjectName; + } + } + + if (trust_bitmask & 2) { + expected_user2_result = net::OK; + // Import and trust the E root for user 2. + net::NSSCertDatabase::ImportCertFailureList failed; + EXPECT_TRUE(db_2_->ImportCACerts(root_2_, net::NSSCertDatabase::TRUSTED_SSL, + &failed)); + EXPECT_EQ(0U, failed.size()); + for (size_t i = 0; i < failed.size(); ++i) { + LOG(ERROR) << "import fail " << failed[i].net_error << " for " + << failed[i].certificate->subjectName; + } + } + + // Repeat the tests twice, they should return the same each time. + for (int i = 0; i < 2; ++i) { + SCOPED_TRACE(i); + for (std::string::const_iterator j = test_order.begin(); + j != test_order.end(); ++j) { + switch (*j) { + case 'd': + // Default verifier should always fail. + EXPECT_EQ( + net::ERR_CERT_AUTHORITY_INVALID, + Verify(verify_proc_default_.get(), server_.get(), &verify_root)); + break; + case '1': + EXPECT_EQ(expected_user1_result, + Verify(verify_proc_1_.get(), server_.get(), &verify_root)); + if (expected_user1_result == net::OK) + EXPECT_EQ("CN=D Root CA - Multi-root", verify_root); + break; + case '2': + EXPECT_EQ(expected_user2_result, + Verify(verify_proc_2_.get(), server_.get(), &verify_root)); + if (expected_user2_result == net::OK) + EXPECT_EQ("CN=E Root CA - Multi-root", verify_root); + break; + default: + FAIL(); + } + } + } +} + +INSTANTIATE_TEST_CASE_P( + Variations, + CertVerifyProcChromeOSOrderingTest, + ::testing::Combine( + ::testing::Bool(), + ::testing::Range(0, 1 << 2), + ::testing::Values("d12", "d21", "1d2", "12d", "2d1", "21d"))); + +} // namespace network diff --git a/chromium/services/network/chunked_data_pipe_upload_data_stream.cc b/chromium/services/network/chunked_data_pipe_upload_data_stream.cc index 2aec26e2add..4f59ea5746a 100644 --- a/chromium/services/network/chunked_data_pipe_upload_data_stream.cc +++ b/chromium/services/network/chunked_data_pipe_upload_data_stream.cc @@ -48,11 +48,6 @@ int ChunkedDataPipeUploadDataStream::InitInternal( mojo::DataPipe data_pipe; chunked_data_pipe_getter_->StartReading(std::move(data_pipe.producer_handle)); data_pipe_ = std::move(data_pipe.consumer_handle); - handle_watcher_.Watch( - data_pipe_.get(), - MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, - base::BindRepeating(&ChunkedDataPipeUploadDataStream::OnHandleReadable, - base::Unretained(this))); return net::OK; } @@ -77,6 +72,16 @@ int ChunkedDataPipeUploadDataStream::ReadInternal(net::IOBuffer* buf, return net::OK; } + // Only start watching once a read starts. This is because OnHandleReadable() + // uses |buf_| implicitly assuming that this method has already been called. + if (!handle_watcher_.IsWatching()) { + handle_watcher_.Watch( + data_pipe_.get(), + MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED, + base::BindRepeating(&ChunkedDataPipeUploadDataStream::OnHandleReadable, + base::Unretained(this))); + } + uint32_t num_bytes = buf_len; if (size_ && num_bytes > *size_ - bytes_read_) num_bytes = *size_ - bytes_read_; @@ -164,6 +169,7 @@ void ChunkedDataPipeUploadDataStream::OnSizeReceived(int32_t status, data_pipe_.reset(); // Clear |buf_| as well, so it's only non-null while there's a pending read. buf_ = nullptr; + buf_len_ = 0; OnReadCompleted(status_); diff --git a/chromium/services/network/cors/cors_url_loader.cc b/chromium/services/network/cors/cors_url_loader.cc index b7b56a5e4f9..714fa96a77b 100644 --- a/chromium/services/network/cors/cors_url_loader.cc +++ b/chromium/services/network/cors/cors_url_loader.cc @@ -18,33 +18,33 @@ namespace cors { namespace { bool NeedsPreflight(const ResourceRequest& request) { - if (!IsCORSEnabledRequestMode(request.fetch_request_mode)) + if (!IsCorsEnabledRequestMode(request.fetch_request_mode)) return false; if (request.is_external_request) return true; if (request.fetch_request_mode == - mojom::FetchRequestMode::kCORSWithForcedPreflight) { + mojom::FetchRequestMode::kCorsWithForcedPreflight) { return true; } if (request.cors_preflight_policy == - mojom::CORSPreflightPolicy::kPreventPreflight) { + mojom::CorsPreflightPolicy::kPreventPreflight) { return false; } - if (!IsCORSSafelistedMethod(request.method)) + if (!IsCorsSafelistedMethod(request.method)) return true; - return !CORSUnsafeNotForbiddenRequestHeaderNames( + return !CorsUnsafeNotForbiddenRequestHeaderNames( request.headers.GetHeaderVector(), request.is_revalidating) .empty(); } } // namespace -CORSURLLoader::CORSURLLoader( +CorsURLLoader::CorsURLLoader( mojom::URLLoaderRequest loader_request, int32_t routing_id, int32_t request_id, @@ -55,7 +55,8 @@ CORSURLLoader::CORSURLLoader( const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojom::URLLoaderFactory* network_loader_factory, const base::RepeatingCallback<void(int)>& request_finalizer, - const OriginAccessList* origin_access_list) + const OriginAccessList* origin_access_list, + PreflightController* preflight_controller) : binding_(this, std::move(loader_request)), routing_id_(routing_id), request_id_(request_id), @@ -68,23 +69,25 @@ CORSURLLoader::CORSURLLoader( request_finalizer_(request_finalizer), traffic_annotation_(traffic_annotation), origin_access_list_(origin_access_list), + preflight_controller_(preflight_controller), weak_factory_(this) { binding_.set_connection_error_handler(base::BindOnce( - &CORSURLLoader::OnConnectionError, base::Unretained(this))); + &CorsURLLoader::OnConnectionError, base::Unretained(this))); DCHECK(network_loader_factory_); DCHECK(origin_access_list_); - SetCORSFlagIfNeeded(); + DCHECK(preflight_controller_); + SetCorsFlagIfNeeded(); } -CORSURLLoader::~CORSURLLoader() { +CorsURLLoader::~CorsURLLoader() { // Close pipes first to ignore possible subsequent callback invocations // cased by |network_loader_| network_client_binding_.Close(); } -void CORSURLLoader::Start() { +void CorsURLLoader::Start() { if (fetch_cors_flag_ && - IsCORSEnabledRequestMode(request_.fetch_request_mode)) { + IsCorsEnabledRequestMode(request_.fetch_request_mode)) { // Username and password should be stripped in a CORS-enabled request. if (request_.url.has_username() || request_.url.has_password()) { GURL::Replacements replacements; @@ -96,15 +99,24 @@ void CORSURLLoader::Start() { StartRequest(); } -void CORSURLLoader::FollowRedirect( +void CorsURLLoader::FollowRedirect( const base::Optional<std::vector<std::string>>& to_be_removed_request_headers, - const base::Optional<net::HttpRequestHeaders>& modified_request_headers) { - if (!network_loader_ || !is_waiting_follow_redirect_call_) { + const base::Optional<net::HttpRequestHeaders>& modified_request_headers, + const base::Optional<GURL>& new_url) { + if (!network_loader_ || !deferred_redirect_url_) { HandleComplete(URLLoaderCompletionStatus(net::ERR_FAILED)); return; } - is_waiting_follow_redirect_call_ = false; + + if (new_url && + (new_url->GetOrigin() != deferred_redirect_url_->GetOrigin())) { + NOTREACHED() << "Can only change the URL within the same origin."; + HandleComplete(URLLoaderCompletionStatus(net::ERR_FAILED)); + return; + } + + deferred_redirect_url_.reset(); // When the redirect mode is "error", the client is not expected to // call this function. Let's abort the request. @@ -131,7 +143,7 @@ void CORSURLLoader::FollowRedirect( request_.request_body = nullptr; const bool original_fetch_cors_flag = fetch_cors_flag_; - SetCORSFlagIfNeeded(); + SetCorsFlagIfNeeded(); // We cannot use FollowRedirect for a request with preflight (i.e., when both // |fetch_cors_flag_| and |NeedsPreflight(request_)| are true). @@ -149,10 +161,10 @@ void CORSURLLoader::FollowRedirect( CalculateResponseTainting(request_.url, request_.fetch_request_mode, request_.request_initiator, fetch_cors_flag_); network_loader_->FollowRedirect(to_be_removed_request_headers, - modified_request_headers); + modified_request_headers, new_url); return; } - DCHECK_NE(request_.fetch_request_mode, mojom::FetchRequestMode::kNoCORS); + DCHECK_NE(request_.fetch_request_mode, mojom::FetchRequestMode::kNoCors); if (request_finalizer_) request_finalizer_.Run(request_id_); @@ -161,37 +173,40 @@ void CORSURLLoader::FollowRedirect( StartRequest(); } -void CORSURLLoader::ProceedWithResponse() { +void CorsURLLoader::ProceedWithResponse() { NOTREACHED(); } -void CORSURLLoader::SetPriority(net::RequestPriority priority, +void CorsURLLoader::SetPriority(net::RequestPriority priority, int32_t intra_priority_value) { if (network_loader_) network_loader_->SetPriority(priority, intra_priority_value); } -void CORSURLLoader::PauseReadingBodyFromNet() { +void CorsURLLoader::PauseReadingBodyFromNet() { if (network_loader_) network_loader_->PauseReadingBodyFromNet(); } -void CORSURLLoader::ResumeReadingBodyFromNet() { +void CorsURLLoader::ResumeReadingBodyFromNet() { if (network_loader_) network_loader_->ResumeReadingBodyFromNet(); } -void CORSURLLoader::OnReceiveResponse( +void CorsURLLoader::OnReceiveResponse( const ResourceResponseHead& response_head) { DCHECK(network_loader_); DCHECK(forwarding_client_); - DCHECK(!is_waiting_follow_redirect_call_); + DCHECK(!deferred_redirect_url_); + + int response_status_code = + response_head.headers ? response_head.headers->response_code() : 0; const bool is_304_for_revalidation = - request_.is_revalidating && response_head.headers->response_code() == 304; + request_.is_revalidating && response_status_code == 304; if (fetch_cors_flag_ && !is_304_for_revalidation) { const auto error_status = CheckAccess( - request_.url, response_head.headers->response_code(), + request_.url, response_status_code, GetHeaderString(response_head, header_names::kAccessControlAllowOrigin), GetHeaderString(response_head, header_names::kAccessControlAllowCredentials), @@ -208,15 +223,15 @@ void CORSURLLoader::OnReceiveResponse( forwarding_client_->OnReceiveResponse(response_head_to_pass); } -void CORSURLLoader::OnReceiveRedirect( +void CorsURLLoader::OnReceiveRedirect( const net::RedirectInfo& redirect_info, const ResourceResponseHead& response_head) { DCHECK(network_loader_); DCHECK(forwarding_client_); - DCHECK(!is_waiting_follow_redirect_call_); + DCHECK(!deferred_redirect_url_); if (request_.fetch_redirect_mode == mojom::FetchRedirectMode::kManual) { - is_waiting_follow_redirect_call_ = true; + deferred_redirect_url_ = std::make_unique<GURL>(redirect_info.new_url); forwarding_client_->OnReceiveRedirect(redirect_info, response_head); return; } @@ -224,7 +239,7 @@ void CORSURLLoader::OnReceiveRedirect( // If |CORS flag| is set and a CORS check for |request| and |response| returns // failure, then return a network error. if (fetch_cors_flag_ && - IsCORSEnabledRequestMode(request_.fetch_request_mode)) { + IsCorsEnabledRequestMode(request_.fetch_request_mode)) { const auto error_status = CheckAccess( request_.url, response_head.headers->response_code(), GetHeaderString(response_head, header_names::kAccessControlAllowOrigin), @@ -286,7 +301,7 @@ void CORSURLLoader::OnReceiveRedirect( redirect_info_ = redirect_info; - is_waiting_follow_redirect_call_ = true; + deferred_redirect_url_ = std::make_unique<GURL>(redirect_info.new_url); auto response_head_to_pass = response_head; if (request_.fetch_redirect_mode == mojom::FetchRedirectMode::kManual) { @@ -298,42 +313,42 @@ void CORSURLLoader::OnReceiveRedirect( forwarding_client_->OnReceiveRedirect(redirect_info, response_head_to_pass); } -void CORSURLLoader::OnUploadProgress(int64_t current_position, +void CorsURLLoader::OnUploadProgress(int64_t current_position, int64_t total_size, OnUploadProgressCallback ack_callback) { DCHECK(network_loader_); DCHECK(forwarding_client_); - DCHECK(!is_waiting_follow_redirect_call_); + DCHECK(!deferred_redirect_url_); forwarding_client_->OnUploadProgress(current_position, total_size, std::move(ack_callback)); } -void CORSURLLoader::OnReceiveCachedMetadata(const std::vector<uint8_t>& data) { +void CorsURLLoader::OnReceiveCachedMetadata(const std::vector<uint8_t>& data) { DCHECK(network_loader_); DCHECK(forwarding_client_); - DCHECK(!is_waiting_follow_redirect_call_); + DCHECK(!deferred_redirect_url_); forwarding_client_->OnReceiveCachedMetadata(data); } -void CORSURLLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) { +void CorsURLLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) { DCHECK(network_loader_); DCHECK(forwarding_client_); - DCHECK(!is_waiting_follow_redirect_call_); + DCHECK(!deferred_redirect_url_); forwarding_client_->OnTransferSizeUpdated(transfer_size_diff); } -void CORSURLLoader::OnStartLoadingResponseBody( +void CorsURLLoader::OnStartLoadingResponseBody( mojo::ScopedDataPipeConsumerHandle body) { DCHECK(network_loader_); DCHECK(forwarding_client_); - DCHECK(!is_waiting_follow_redirect_call_); + DCHECK(!deferred_redirect_url_); forwarding_client_->OnStartLoadingResponseBody(std::move(body)); } -void CORSURLLoader::OnComplete(const URLLoaderCompletionStatus& status) { +void CorsURLLoader::OnComplete(const URLLoaderCompletionStatus& status) { DCHECK(network_loader_); DCHECK(forwarding_client_); - DCHECK(!is_waiting_follow_redirect_call_); + DCHECK(!deferred_redirect_url_); URLLoaderCompletionStatus modified_status(status); if (status.error_code == net::OK) @@ -341,15 +356,15 @@ void CORSURLLoader::OnComplete(const URLLoaderCompletionStatus& status) { HandleComplete(modified_status); } -void CORSURLLoader::StartRequest() { - if (fetch_cors_flag_ && !base::ContainsValue(url::GetCORSEnabledSchemes(), +void CorsURLLoader::StartRequest() { + if (fetch_cors_flag_ && !base::ContainsValue(url::GetCorsEnabledSchemes(), request_.url.scheme())) { HandleComplete(URLLoaderCompletionStatus( - CORSErrorStatus(mojom::CORSError::kCORSDisabledScheme))); + CorsErrorStatus(mojom::CorsError::kCorsDisabledScheme))); return; } - // If the CORS flag is set, |httpRequest|’s method is neither `GET` nor + // If the |CORS flag| is set, |httpRequest|’s method is neither `GET` nor // `HEAD`, or |httpRequest|’s mode is "websocket", then append // `Origin`/the result of serializing a request origin with |httpRequest|, to // |httpRequest|’s header list. @@ -369,7 +384,7 @@ void CORSURLLoader::StartRequest() { request_.fetch_request_mode == mojom::FetchRequestMode::kSameOrigin) { DCHECK(request_.request_initiator); HandleComplete(URLLoaderCompletionStatus( - CORSErrorStatus(mojom::CORSError::kDisallowedByMode))); + CorsErrorStatus(mojom::CorsError::kDisallowedByMode))); return; } @@ -396,17 +411,17 @@ void CORSURLLoader::StartRequest() { if (request_finalizer_) preflight_finalizer = base::BindOnce(request_finalizer_, request_id_); - PreflightController::GetDefaultController()->PerformPreflightCheck( - base::BindOnce(&CORSURLLoader::StartNetworkRequest, + preflight_controller_->PerformPreflightCheck( + base::BindOnce(&CorsURLLoader::StartNetworkRequest, weak_factory_.GetWeakPtr()), request_id_, request_, tainted_, net::NetworkTrafficAnnotationTag(traffic_annotation_), network_loader_factory_, std::move(preflight_finalizer)); } -void CORSURLLoader::StartNetworkRequest( +void CorsURLLoader::StartNetworkRequest( int error_code, - base::Optional<CORSErrorStatus> status, + base::Optional<CorsErrorStatus> status, base::Optional<PreflightTimingInfo> preflight_timing_info) { if (error_code != net::OK) { HandleComplete(status ? URLLoaderCompletionStatus(*status) @@ -423,30 +438,30 @@ void CORSURLLoader::StartNetworkRequest( // Binding |this| as an unretained pointer is safe because // |network_client_binding_| shares this object's lifetime. network_client_binding_.set_connection_error_handler(base::BindOnce( - &CORSURLLoader::OnConnectionError, base::Unretained(this))); + &CorsURLLoader::OnConnectionError, base::Unretained(this))); network_loader_factory_->CreateLoaderAndStart( mojo::MakeRequest(&network_loader_), routing_id_, request_id_, options_, request_, std::move(network_client), traffic_annotation_); } -void CORSURLLoader::HandleComplete(const URLLoaderCompletionStatus& status) { +void CorsURLLoader::HandleComplete(const URLLoaderCompletionStatus& status) { forwarding_client_->OnComplete(status); std::move(delete_callback_).Run(this); // |this| is deleted here. } -void CORSURLLoader::OnConnectionError() { +void CorsURLLoader::OnConnectionError() { HandleComplete(URLLoaderCompletionStatus(net::ERR_ABORTED)); } -// This should be identical to CalculateCORSFlag defined in +// This should be identical to CalculateCorsFlag defined in // //third_party/blink/renderer/platform/loader/cors/cors.cc. -void CORSURLLoader::SetCORSFlagIfNeeded() { +void CorsURLLoader::SetCorsFlagIfNeeded() { if (fetch_cors_flag_) return; if (request_.fetch_request_mode == mojom::FetchRequestMode::kNavigate || - request_.fetch_request_mode == mojom::FetchRequestMode::kNoCORS) { + request_.fetch_request_mode == mojom::FetchRequestMode::kNoCors) { return; } @@ -487,9 +502,11 @@ void CORSURLLoader::SetCORSFlagIfNeeded() { fetch_cors_flag_ = true; } -base::Optional<std::string> CORSURLLoader::GetHeaderString( +base::Optional<std::string> CorsURLLoader::GetHeaderString( const ResourceResponseHead& response, const std::string& header_name) { + if (!response.headers) + return base::nullopt; std::string header_value; if (!response.headers->GetNormalizedHeader(header_name, &header_value)) return base::nullopt; diff --git a/chromium/services/network/cors/cors_url_loader.h b/chromium/services/network/cors/cors_url_loader.h index 5d67f8401a8..beb9617c27b 100644 --- a/chromium/services/network/cors/cors_url_loader.h +++ b/chromium/services/network/cors/cors_url_loader.h @@ -9,6 +9,7 @@ #include "base/optional.h" #include "mojo/public/cpp/bindings/binding.h" #include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/cors/preflight_controller.h" #include "services/network/public/cpp/cors/cors_error_status.h" #include "services/network/public/cpp/cors/preflight_timing_info.h" #include "services/network/public/mojom/fetch_api.mojom.h" @@ -25,9 +26,9 @@ class OriginAccessList; // Wrapper class that adds cross-origin resource sharing capabilities // (https://fetch.spec.whatwg.org/#http-cors-protocol), delegating requests as // well as potential preflight requests to the supplied -// |network_loader_factory|. It is owned by the CORSURLLoaderFactory that +// |network_loader_factory|. It is owned by the CorsURLLoaderFactory that // created it. -class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoader +class COMPONENT_EXPORT(NETWORK_SERVICE) CorsURLLoader : public mojom::URLLoader, public mojom::URLLoaderClient { public: @@ -36,7 +37,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoader // Assumes network_loader_factory outlives this loader. // TODO(yhirano): Remove |request_finalizer| when the network service is // fully enabled. - CORSURLLoader( + CorsURLLoader( mojom::URLLoaderRequest loader_request, int32_t routing_id, int32_t request_id, @@ -47,19 +48,21 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoader const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojom::URLLoaderFactory* network_loader_factory, const base::RepeatingCallback<void(int)>& request_finalizer, - const OriginAccessList* origin_access_list); + const OriginAccessList* origin_access_list, + PreflightController* preflight_controller); - ~CORSURLLoader() override; + ~CorsURLLoader() override; // Starts processing the request. This is expected to be called right after // the constructor. void Start(); // mojom::URLLoader overrides: - void FollowRedirect(const base::Optional<std::vector<std::string>>& - to_be_removed_request_headers, - const base::Optional<net::HttpRequestHeaders>& - modified_request_headers) override; + void FollowRedirect( + const base::Optional<std::vector<std::string>>& + to_be_removed_request_headers, + const base::Optional<net::HttpRequestHeaders>& modified_request_headers, + const base::Optional<GURL>& new_url) override; void ProceedWithResponse() override; void SetPriority(net::RequestPriority priority, int intra_priority_value) override; @@ -83,7 +86,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoader void StartRequest(); void StartNetworkRequest( int net_error, - base::Optional<CORSErrorStatus> status, + base::Optional<CorsErrorStatus> status, base::Optional<PreflightTimingInfo> preflight_timing_info); // Called when there is a connection error on the upstream pipe used for the @@ -95,7 +98,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoader void OnConnectionError(); - void SetCORSFlagIfNeeded(); + void SetCorsFlagIfNeeded(); static base::Optional<std::string> GetHeaderString( const ResourceResponseHead& response, @@ -110,7 +113,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoader DeleteCallback delete_callback_; - // This raw URLLoaderFactory pointer is shared with the CORSURLLoaderFactory + // This raw URLLoaderFactory pointer is shared with the CorsURLLoaderFactory // that created and owns this object. mojom::URLLoaderFactory* network_loader_factory_; @@ -132,9 +135,9 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoader mojom::FetchResponseType response_tainting_ = mojom::FetchResponseType::kBasic; - // A flag to indicate that the instance is waiting for that forwarding_client_ - // calls FollowRedirect. - bool is_waiting_follow_redirect_call_ = false; + // Holds the URL of a redirect if it's currently deferred, waiting for + // forwarding_client_ to call FollowRedirect. + std::unique_ptr<GURL> deferred_redirect_url_; // Corresponds to the Fetch spec, https://fetch.spec.whatwg.org/. bool fetch_cors_flag_ = false; @@ -159,11 +162,12 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoader // Outlives |this|. const OriginAccessList* const origin_access_list_; + PreflightController* preflight_controller_; // Used to run asynchronous class instance bound callbacks safely. - base::WeakPtrFactory<CORSURLLoader> weak_factory_; + base::WeakPtrFactory<CorsURLLoader> weak_factory_; - DISALLOW_COPY_AND_ASSIGN(CORSURLLoader); + DISALLOW_COPY_AND_ASSIGN(CorsURLLoader); }; } // namespace cors diff --git a/chromium/services/network/cors/cors_url_loader_factory.cc b/chromium/services/network/cors/cors_url_loader_factory.cc index 5786ab2c56e..f1d341b662d 100644 --- a/chromium/services/network/cors/cors_url_loader_factory.cc +++ b/chromium/services/network/cors/cors_url_loader_factory.cc @@ -19,7 +19,7 @@ namespace network { namespace cors { -CORSURLLoaderFactory::CORSURLLoaderFactory( +CorsURLLoaderFactory::CorsURLLoaderFactory( NetworkContext* context, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr<ResourceSchedulerClient> resource_scheduler_client, @@ -37,10 +37,11 @@ CORSURLLoaderFactory::CORSURLLoaderFactory( DCHECK(origin_access_list_); bindings_.AddBinding(this, std::move(request)); bindings_.set_connection_error_handler(base::BindRepeating( - &CORSURLLoaderFactory::DeleteIfNeeded, base::Unretained(this))); + &CorsURLLoaderFactory::DeleteIfNeeded, base::Unretained(this))); + preflight_controller_ = context_->cors_preflight_controller(); } -CORSURLLoaderFactory::CORSURLLoaderFactory( +CorsURLLoaderFactory::CorsURLLoaderFactory( bool disable_web_security, std::unique_ptr<mojom::URLLoaderFactory> network_loader_factory, const base::RepeatingCallback<void(int)>& preflight_finalizer, @@ -50,16 +51,20 @@ CORSURLLoaderFactory::CORSURLLoaderFactory( preflight_finalizer_(preflight_finalizer), origin_access_list_(origin_access_list) { DCHECK(origin_access_list_); + // Ideally this should be per-profile, but per-factory would be enough for + // this code path that is eventually removed. + owned_preflight_controller_ = std::make_unique<PreflightController>(); + preflight_controller_ = owned_preflight_controller_.get(); } -CORSURLLoaderFactory::~CORSURLLoaderFactory() = default; +CorsURLLoaderFactory::~CorsURLLoaderFactory() = default; -void CORSURLLoaderFactory::OnLoaderCreated( +void CorsURLLoaderFactory::OnLoaderCreated( std::unique_ptr<mojom::URLLoader> loader) { loaders_.insert(std::move(loader)); } -void CORSURLLoaderFactory::DestroyURLLoader(mojom::URLLoader* loader) { +void CorsURLLoaderFactory::DestroyURLLoader(mojom::URLLoader* loader) { auto it = loaders_.find(loader); DCHECK(it != loaders_.end()); loaders_.erase(it); @@ -67,7 +72,7 @@ void CORSURLLoaderFactory::DestroyURLLoader(mojom::URLLoader* loader) { DeleteIfNeeded(); } -void CORSURLLoaderFactory::CreateLoaderAndStart( +void CorsURLLoaderFactory::CreateLoaderAndStart( mojom::URLLoaderRequest request, int32_t routing_id, int32_t request_id, @@ -80,15 +85,15 @@ void CORSURLLoaderFactory::CreateLoaderAndStart( return; } - if (base::FeatureList::IsEnabled(features::kOutOfBlinkCORS) && + if (base::FeatureList::IsEnabled(features::kOutOfBlinkCors) && !disable_web_security_) { - auto loader = std::make_unique<CORSURLLoader>( + auto loader = std::make_unique<CorsURLLoader>( std::move(request), routing_id, request_id, options, - base::BindOnce(&CORSURLLoaderFactory::DestroyURLLoader, + base::BindOnce(&CorsURLLoaderFactory::DestroyURLLoader, base::Unretained(this)), resource_request, std::move(client), traffic_annotation, network_loader_factory_.get(), preflight_finalizer_, - origin_access_list_); + origin_access_list_, preflight_controller_); auto* raw_loader = loader.get(); OnLoaderCreated(std::move(loader)); raw_loader->Start(); @@ -99,28 +104,28 @@ void CORSURLLoaderFactory::CreateLoaderAndStart( } } -void CORSURLLoaderFactory::Clone(mojom::URLLoaderFactoryRequest request) { +void CorsURLLoaderFactory::Clone(mojom::URLLoaderFactoryRequest request) { // The cloned factories stop working when this factory is destructed. bindings_.AddBinding(this, std::move(request)); } -void CORSURLLoaderFactory::ClearBindings() { +void CorsURLLoaderFactory::ClearBindings() { bindings_.CloseAllBindings(); } -void CORSURLLoaderFactory::DeleteIfNeeded() { +void CorsURLLoaderFactory::DeleteIfNeeded() { if (!context_) return; if (bindings_.empty() && loaders_.empty()) context_->DestroyURLLoaderFactory(this); } -bool CORSURLLoaderFactory::IsSane(const ResourceRequest& request) { +bool CorsURLLoaderFactory::IsSane(const ResourceRequest& request) { // CORS needs a proper origin (including a unique opaque origin). If the // request doesn't have one, CORS cannot work. if (!request.request_initiator && request.fetch_request_mode != mojom::FetchRequestMode::kNavigate && - request.fetch_request_mode != mojom::FetchRequestMode::kNoCORS) { + request.fetch_request_mode != mojom::FetchRequestMode::kNoCors) { LOG(WARNING) << "|fetch_request_mode| is " << request.fetch_request_mode << ", but |request_initiator| is not set."; return false; diff --git a/chromium/services/network/cors/cors_url_loader_factory.h b/chromium/services/network/cors/cors_url_loader_factory.h index 0e5d71d4f64..41aa48ef74c 100644 --- a/chromium/services/network/cors/cors_url_loader_factory.h +++ b/chromium/services/network/cors/cors_url_loader_factory.h @@ -12,6 +12,7 @@ #include "base/macros.h" #include "mojo/public/cpp/bindings/strong_binding_set.h" #include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/cors/preflight_controller.h" #include "services/network/public/cpp/cors/origin_access_list.h" #include "services/network/public/mojom/network_context.mojom.h" #include "services/network/public/mojom/url_loader_factory.mojom.h" @@ -27,14 +28,14 @@ namespace cors { // A factory class to create a URLLoader that supports CORS. // This class takes a network::mojom::URLLoaderFactory instance in the -// constructor and owns it to make network requests for CORS preflight, and +// constructor and owns it to make network requests for CORS-preflight, and // actual network request. -class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoaderFactory final +class COMPONENT_EXPORT(NETWORK_SERVICE) CorsURLLoaderFactory final : public mojom::URLLoaderFactory { public: // |origin_access_list| should always outlive this factory instance. // Used by network::NetworkContext. - CORSURLLoaderFactory( + CorsURLLoaderFactory( NetworkContext* context, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr<ResourceSchedulerClient> resource_scheduler_client, @@ -42,12 +43,12 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoaderFactory final const OriginAccessList* origin_access_list); // Used by content::ResourceMessageFilter. // TODO(yhirano): Remove this once when the network service is fully enabled. - CORSURLLoaderFactory( + CorsURLLoaderFactory( bool disable_web_security, std::unique_ptr<mojom::URLLoaderFactory> network_loader_factory, const base::RepeatingCallback<void(int)>& preflight_finalizer, const OriginAccessList* origin_access_list); - ~CORSURLLoaderFactory() override; + ~CorsURLLoaderFactory() override; void OnLoaderCreated(std::unique_ptr<mojom::URLLoader> loader); void DestroyURLLoader(mojom::URLLoader* loader); @@ -78,11 +79,15 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoaderFactory final // The NetworkContext owns |this|. NetworkContext* const context_ = nullptr; scoped_refptr<ResourceSchedulerClient> resource_scheduler_client_; - std::set<std::unique_ptr<mojom::URLLoader>, base::UniquePtrComparator> - loaders_; const bool disable_web_security_; + + // Relative order of |network_loader_factory_| and |loaders_| matters - + // URLLoaderFactory needs to live longer than URLLoaders created using the + // factory. See also https://crbug.com/906305. std::unique_ptr<mojom::URLLoaderFactory> network_loader_factory_; + std::set<std::unique_ptr<mojom::URLLoader>, base::UniquePtrComparator> + loaders_; // Used when constructed by ResourceMessageFilter. base::RepeatingCallback<void(int)> preflight_finalizer_; @@ -91,7 +96,14 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CORSURLLoaderFactory final // it's safe. const OriginAccessList* const origin_access_list_; - DISALLOW_COPY_AND_ASSIGN(CORSURLLoaderFactory); + // Usually |preflight_controoler_| is owned by NetworkContext, but we create + // own one if NetworkContext is not provided, e.g. for legacy code path. + // TODO(toyoshim): Remove owned controller once the network service is fully + // enabled. + PreflightController* preflight_controller_; + std::unique_ptr<PreflightController> owned_preflight_controller_; + + DISALLOW_COPY_AND_ASSIGN(CorsURLLoaderFactory); }; } // namespace cors diff --git a/chromium/services/network/cors/cors_url_loader_factory_unittest.cc b/chromium/services/network/cors/cors_url_loader_factory_unittest.cc new file mode 100644 index 00000000000..869c71b3181 --- /dev/null +++ b/chromium/services/network/cors/cors_url_loader_factory_unittest.cc @@ -0,0 +1,146 @@ +// Copyright 2018 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 <memory> + +#include "base/macros.h" +#include "base/message_loop/message_loop.h" +#include "base/test/scoped_feature_list.h" +#include "base/test/scoped_task_environment.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/public/cpp/features.h" +#include "services/network/public/cpp/resource_request.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.h" +#include "services/network/resource_scheduler_client.h" +#include "services/network/test/test_url_loader_client.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace network { +namespace cors { + +namespace { + +constexpr int kProcessId = 123; +constexpr int kRequestId = 456; +constexpr int kRouteId = 789; + +} // namespace + +class CorsURLLoaderFactoryTest : public testing::Test { + public: + CorsURLLoaderFactoryTest() + : scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::IO), + resource_scheduler_(true) { + net::URLRequestContextBuilder context_builder; + context_builder.set_proxy_resolution_service( + net::ProxyResolutionService::CreateDirect()); + url_request_context_ = context_builder.Build(); + } + + protected: + // testing::Test implementation. + void SetUp() override { + feature_list_.InitAndEnableFeature(features::kOutOfBlinkCors); + + network_service_ = NetworkService::CreateForTesting(); + + auto context_params = mojom::NetworkContextParams::New(); + // Use a fixed proxy config, to avoid dependencies on local network + // configuration. + context_params->initial_proxy_config = + net::ProxyConfigWithAnnotation::CreateDirect(); + network_context_ = std::make_unique<NetworkContext>( + network_service_.get(), mojo::MakeRequest(&network_context_ptr_), + std::move(context_params)); + + auto factory_params = network::mojom::URLLoaderFactoryParams::New(); + factory_params->process_id = kProcessId; + auto resource_scheduler_client = + base::MakeRefCounted<ResourceSchedulerClient>( + kProcessId, kRouteId, &resource_scheduler_, + url_request_context_->network_quality_estimator()); + cors_url_loader_factory_ = std::make_unique<CorsURLLoaderFactory>( + network_context_.get(), std::move(factory_params), + resource_scheduler_client, + mojo::MakeRequest(&cors_url_loader_factory_ptr_), &origin_access_list_); + } + + void CreateLoaderAndStart(const ResourceRequest& request) { + cors_url_loader_factory_->CreateLoaderAndStart( + mojo::MakeRequest(&url_loader_), kRouteId, kRequestId, + mojom::kURLLoadOptionNone, request, + test_cors_loader_client_.CreateInterfacePtr(), + net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); + } + + void ResetFactory() { cors_url_loader_factory_.reset(); } + + private: + // Testing instance to enable kOutOfBlinkCors feature. + base::test::ScopedFeatureList feature_list_; + + // Test environment. + base::test::ScopedTaskEnvironment scoped_task_environment_; + std::unique_ptr<net::URLRequestContext> url_request_context_; + ResourceScheduler resource_scheduler_; + std::unique_ptr<NetworkService> network_service_; + std::unique_ptr<NetworkContext> network_context_; + mojom::NetworkContextPtr network_context_ptr_; + + // CorsURLLoaderFactory instance under tests. + std::unique_ptr<mojom::URLLoaderFactory> cors_url_loader_factory_; + mojom::URLLoaderFactoryPtr cors_url_loader_factory_ptr_; + + // Holds URLLoaderPtr that CreateLoaderAndStart() creates. + mojom::URLLoaderPtr url_loader_; + + // TestURLLoaderClient that records callback activities. + TestURLLoaderClient test_cors_loader_client_; + + // Holds for allowed origin access lists. + OriginAccessList origin_access_list_; + + DISALLOW_COPY_AND_ASSIGN(CorsURLLoaderFactoryTest); +}; + +// Regression test for https://crbug.com/906305. +TEST_F(CorsURLLoaderFactoryTest, DestructionOrder) { + ResourceRequest request; + GURL url("http://localhost"); + request.fetch_request_mode = mojom::FetchRequestMode::kNoCors; + request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; + request.load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; + request.load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; + request.load_flags |= net::LOAD_DO_NOT_SEND_AUTH_DATA; + request.method = net::HttpRequestHeaders::kGetMethod; + request.url = url; + request.request_initiator = url::Origin::Create(url); + + // As of r609458 setting |keepalive| to true was triggerring a dereference of + // |factory_params_| in the destructor of network::URLLoader. This + // dereference assumes that the network::URLLoaderFactory (which keeps + // |factory_params_| alive) lives longer than the network::URLLoaders created + // via the factory (which necessitates being careful with the destruction + // order of fields of network::cors::CorsURLLoaderFactory which owns both + // network::URLLoaderFactory and the network::URLLoaders it creates). + request.keepalive = true; + + // Create a loader and immediately (while the loader is still stored in + // CorsURLLoaderFactory::loaders_ / not released via test_cors_loader_client_) + // destroy the factory. If ASAN doesn't complain then the test passes. + CreateLoaderAndStart(request); + ResetFactory(); +} + +} // namespace cors +} // namespace network diff --git a/chromium/services/network/cors/cors_url_loader_unittest.cc b/chromium/services/network/cors/cors_url_loader_unittest.cc index bc0164b312e..6c2526af8e4 100644 --- a/chromium/services/network/cors/cors_url_loader_unittest.cc +++ b/chromium/services/network/cors/cors_url_loader_unittest.cc @@ -76,7 +76,7 @@ class TestURLLoaderFactory : public mojom::URLLoaderFactory { on_create_loader_and_start_ = closure; } - const network::ResourceRequest& request() const { return request_; } + const ResourceRequest& request() const { return request_; } const GURL& GetRequestedURL() const { return request_.url; } int num_created_loaders() const { return num_created_loaders_; } @@ -114,15 +114,15 @@ class TestURLLoaderFactory : public mojom::URLLoaderFactory { DISALLOW_COPY_AND_ASSIGN(TestURLLoaderFactory); }; -class CORSURLLoaderTest : public testing::Test { +class CorsURLLoaderTest : public testing::Test { public: using ReferrerPolicy = net::URLRequest::ReferrerPolicy; - CORSURLLoaderTest() { + CorsURLLoaderTest() { std::unique_ptr<TestURLLoaderFactory> factory = std::make_unique<TestURLLoaderFactory>(); test_url_loader_factory_ = factory->GetWeakPtr(); - cors_url_loader_factory_ = std::make_unique<CORSURLLoaderFactory>( + cors_url_loader_factory_ = std::make_unique<CorsURLLoaderFactory>( false, std::move(factory), base::RepeatingCallback<void(int)>(), &origin_access_list_); } @@ -130,7 +130,7 @@ class CORSURLLoaderTest : public testing::Test { protected: // testing::Test implementation. void SetUp() override { - feature_list_.InitAndEnableFeature(features::kOutOfBlinkCORS); + feature_list_.InitAndEnableFeature(features::kOutOfBlinkCors); } void CreateLoaderAndStart(const GURL& origin, @@ -190,7 +190,7 @@ class CORSURLLoaderTest : public testing::Test { void FollowRedirect() { DCHECK(url_loader_); - url_loader_->FollowRedirect(base::nullopt, base::nullopt); + url_loader_->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); } const ResourceRequest& GetRequest() const { @@ -228,10 +228,10 @@ class CORSURLLoaderTest : public testing::Test { void AddAllowListEntryForOrigin(const url::Origin& source_origin, const std::string& protocol, const std::string& domain, - bool allow_subdomains) { + const mojom::CorsOriginAccessMatchMode mode) { origin_access_list_.AddAllowListEntryForOrigin( - source_origin, protocol, domain, allow_subdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + source_origin, protocol, domain, mode, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); } static net::RedirectInfo CreateRedirectInfo( @@ -253,13 +253,13 @@ class CORSURLLoaderTest : public testing::Test { // Be the first member so it is destroyed last. base::MessageLoop message_loop_; - // Testing instance to enable kOutOfBlinkCORS feature. + // Testing instance to enable kOutOfBlinkCors feature. base::test::ScopedFeatureList feature_list_; - // CORSURLLoaderFactory instance under tests. + // CorsURLLoaderFactory instance under tests. std::unique_ptr<mojom::URLLoaderFactory> cors_url_loader_factory_; - // TestURLLoaderFactory instance owned by CORSURLLoaderFactory. + // TestURLLoaderFactory instance owned by CorsURLLoaderFactory. base::WeakPtr<TestURLLoaderFactory> test_url_loader_factory_; // Holds URLLoaderPtr that CreateLoaderAndStart() creates. @@ -271,10 +271,10 @@ class CORSURLLoaderTest : public testing::Test { // Holds for allowed origin access lists. OriginAccessList origin_access_list_; - DISALLOW_COPY_AND_ASSIGN(CORSURLLoaderTest); + DISALLOW_COPY_AND_ASSIGN(CorsURLLoaderTest); }; -TEST_F(CORSURLLoaderTest, SameOriginWithoutInitiator) { +TEST_F(CorsURLLoaderTest, SameOriginWithoutInitiator) { ResourceRequest request; request.fetch_request_mode = mojom::FetchRequestMode::kSameOrigin; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kInclude; @@ -291,9 +291,9 @@ TEST_F(CORSURLLoaderTest, SameOriginWithoutInitiator) { EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, NoCORSWithoutInitiator) { +TEST_F(CorsURLLoaderTest, NoCorsWithoutInitiator) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kNoCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kNoCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kInclude; request.url = GURL("http://example.com/"); request.request_initiator = base::nullopt; @@ -310,9 +310,9 @@ TEST_F(CORSURLLoaderTest, NoCORSWithoutInitiator) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, CORSWithoutInitiator) { +TEST_F(CorsURLLoaderTest, CorsWithoutInitiator) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kInclude; request.url = GURL("http://example.com/"); request.request_initiator = base::nullopt; @@ -327,7 +327,7 @@ TEST_F(CORSURLLoaderTest, CORSWithoutInitiator) { EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, NavigateWithoutInitiator) { +TEST_F(CorsURLLoaderTest, NavigateWithoutInitiator) { ResourceRequest request; request.fetch_request_mode = mojom::FetchRequestMode::kNavigate; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kInclude; @@ -346,7 +346,7 @@ TEST_F(CORSURLLoaderTest, NavigateWithoutInitiator) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther1) { +TEST_F(CorsURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther1) { ResourceRequest request; request.fetch_request_mode = mojom::FetchRequestMode::kNavigate; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; @@ -365,7 +365,7 @@ TEST_F(CORSURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther1) { EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther2) { +TEST_F(CorsURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther2) { ResourceRequest request; request.fetch_request_mode = mojom::FetchRequestMode::kNavigate; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; @@ -384,7 +384,7 @@ TEST_F(CORSURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther2) { EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther3) { +TEST_F(CorsURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther3) { ResourceRequest request; request.fetch_request_mode = mojom::FetchRequestMode::kNavigate; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; @@ -403,7 +403,7 @@ TEST_F(CORSURLLoaderTest, CredentialsModeAndLoadFlagsContradictEachOther3) { EXPECT_EQ(net::ERR_INVALID_ARGUMENT, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, SameOriginRequest) { +TEST_F(CorsURLLoaderTest, SameOriginRequest) { const GURL url("http://example.com/foo.png"); CreateLoaderAndStart(url.GetOrigin(), url, mojom::FetchRequestMode::kSameOrigin); @@ -420,10 +420,10 @@ TEST_F(CORSURLLoaderTest, SameOriginRequest) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, CrossOriginRequestWithNoCORSMode) { +TEST_F(CorsURLLoaderTest, CrossOriginRequestWithNoCorsMode) { const GURL origin("http://example.com"); const GURL url("http://other.com/foo.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kNoCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kNoCors); NotifyLoaderClientOnReceiveResponse(); NotifyLoaderClientOnComplete(net::OK); @@ -439,11 +439,11 @@ TEST_F(CORSURLLoaderTest, CrossOriginRequestWithNoCORSMode) { GetRequest().headers.HasHeader(net::HttpRequestHeaders::kOrigin)); } -TEST_F(CORSURLLoaderTest, CrossOriginRequestWithNoCORSModeAndPatchMethod) { +TEST_F(CorsURLLoaderTest, CrossOriginRequestWithNoCorsModeAndPatchMethod) { const GURL origin("http://example.com"); const GURL url("http://other.com/foo.png"); ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kNoCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kNoCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kInclude; request.method = "PATCH"; request.url = url; @@ -466,7 +466,7 @@ TEST_F(CORSURLLoaderTest, CrossOriginRequestWithNoCORSModeAndPatchMethod) { EXPECT_EQ(origin_header, "http://example.com"); } -TEST_F(CORSURLLoaderTest, CrossOriginRequestFetchRequestModeSameOrigin) { +TEST_F(CorsURLLoaderTest, CrossOriginRequestFetchRequestModeSameOrigin) { const GURL origin("http://example.com"); const GURL url("http://other.com/foo.png"); CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kSameOrigin); @@ -480,14 +480,14 @@ TEST_F(CORSURLLoaderTest, CrossOriginRequestFetchRequestModeSameOrigin) { EXPECT_FALSE(client().has_received_response()); EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code); ASSERT_TRUE(client().completion_status().cors_error_status); - EXPECT_EQ(mojom::CORSError::kDisallowedByMode, + EXPECT_EQ(mojom::CorsError::kDisallowedByMode, client().completion_status().cors_error_status->cors_error); } -TEST_F(CORSURLLoaderTest, CrossOriginRequestWithCORSModeButMissingCORSHeader) { +TEST_F(CorsURLLoaderTest, CrossOriginRequestWithCorsModeButMissingCorsHeader) { const GURL origin("http://example.com"); const GURL url("http://other.com/foo.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); NotifyLoaderClientOnReceiveResponse(); NotifyLoaderClientOnComplete(net::OK); @@ -503,14 +503,14 @@ TEST_F(CORSURLLoaderTest, CrossOriginRequestWithCORSModeButMissingCORSHeader) { EXPECT_FALSE(client().has_received_response()); EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code); ASSERT_TRUE(client().completion_status().cors_error_status); - EXPECT_EQ(mojom::CORSError::kMissingAllowOriginHeader, + EXPECT_EQ(mojom::CorsError::kMissingAllowOriginHeader, client().completion_status().cors_error_status->cors_error); } -TEST_F(CORSURLLoaderTest, CrossOriginRequestWithCORSMode) { +TEST_F(CorsURLLoaderTest, CrossOriginRequestWithCorsMode) { const GURL origin("http://example.com"); const GURL url("http://other.com/foo.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); NotifyLoaderClientOnReceiveResponse( {"Access-Control-Allow-Origin: http://example.com"}); @@ -525,11 +525,11 @@ TEST_F(CORSURLLoaderTest, CrossOriginRequestWithCORSMode) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, - CrossOriginRequestFetchRequestWithCORSModeButMismatchedCORSHeader) { +TEST_F(CorsURLLoaderTest, + CrossOriginRequestFetchRequestWithCorsModeButMismatchedCorsHeader) { const GURL origin("http://example.com"); const GURL url("http://other.com/foo.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); NotifyLoaderClientOnReceiveResponse( {"Access-Control-Allow-Origin: http://some-other-domain.com"}); @@ -542,15 +542,15 @@ TEST_F(CORSURLLoaderTest, EXPECT_FALSE(client().has_received_response()); EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code); ASSERT_TRUE(client().completion_status().cors_error_status); - EXPECT_EQ(mojom::CORSError::kAllowOriginMismatch, + EXPECT_EQ(mojom::CorsError::kAllowOriginMismatch, client().completion_status().cors_error_status->cors_error); } -TEST_F(CORSURLLoaderTest, StripUsernameAndPassword) { +TEST_F(CorsURLLoaderTest, StripUsernameAndPassword) { const GURL origin("http://example.com"); const GURL url("http://foo:bar@other.com/foo.png"); std::string stripped_url = "http://other.com/foo.png"; - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); NotifyLoaderClientOnReceiveResponse( {"Access-Control-Allow-Origin: http://example.com"}); @@ -566,12 +566,12 @@ TEST_F(CORSURLLoaderTest, StripUsernameAndPassword) { EXPECT_EQ(stripped_url, GetRequestedURL().spec()); } -TEST_F(CORSURLLoaderTest, CORSCheckPassOnRedirect) { +TEST_F(CorsURLLoaderTest, CorsCheckPassOnRedirect) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other2.example.com/bar.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); EXPECT_EQ(1, num_created_loaders()); EXPECT_EQ(GetRequest().url, url); @@ -588,12 +588,12 @@ TEST_F(CORSURLLoaderTest, CORSCheckPassOnRedirect) { EXPECT_TRUE(client().has_received_redirect()); } -TEST_F(CORSURLLoaderTest, CORSCheckFailOnRedirect) { +TEST_F(CorsURLLoaderTest, CorsCheckFailOnRedirect) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other2.example.com/bar.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); EXPECT_EQ(1, num_created_loaders()); EXPECT_EQ(GetRequest().url, url); @@ -609,15 +609,15 @@ TEST_F(CORSURLLoaderTest, CORSCheckFailOnRedirect) { EXPECT_EQ(client().completion_status().error_code, net::ERR_FAILED); ASSERT_TRUE(client().completion_status().cors_error_status); EXPECT_EQ(client().completion_status().cors_error_status->cors_error, - mojom::CORSError::kMissingAllowOriginHeader); + mojom::CorsError::kMissingAllowOriginHeader); } -TEST_F(CORSURLLoaderTest, SameOriginToSameOriginRedirect) { +TEST_F(CorsURLLoaderTest, SameOriginToSameOriginRedirect) { const GURL origin("https://example.com"); const GURL url("https://example.com/foo.png"); const GURL new_url("https://example.com/bar.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); EXPECT_EQ(1, num_created_loaders()); EXPECT_EQ(GetRequest().url, url); @@ -648,12 +648,12 @@ TEST_F(CORSURLLoaderTest, SameOriginToSameOriginRedirect) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, SameOriginToCrossOriginRedirect) { +TEST_F(CorsURLLoaderTest, SameOriginToCrossOriginRedirect) { const GURL origin("https://example.com"); const GURL url("https://example.com/foo.png"); const GURL new_url("https://other.example.com/bar.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); EXPECT_EQ(1, num_created_loaders()); EXPECT_EQ(GetRequest().url, url); @@ -687,12 +687,12 @@ TEST_F(CORSURLLoaderTest, SameOriginToCrossOriginRedirect) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, CrossOriginToCrossOriginRedirect) { +TEST_F(CorsURLLoaderTest, CrossOriginToCrossOriginRedirect) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other.example.com/bar.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); EXPECT_EQ(1, num_created_loaders()); EXPECT_EQ(GetRequest().url, url); @@ -727,12 +727,12 @@ TEST_F(CORSURLLoaderTest, CrossOriginToCrossOriginRedirect) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, CrossOriginToOriginalOriginRedirect) { +TEST_F(CorsURLLoaderTest, CrossOriginToOriginalOriginRedirect) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://example.com/bar.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); EXPECT_EQ(1, num_created_loaders()); EXPECT_EQ(GetRequest().url, url); @@ -768,15 +768,15 @@ TEST_F(CORSURLLoaderTest, CrossOriginToOriginalOriginRedirect) { EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code); ASSERT_TRUE(client().completion_status().cors_error_status); EXPECT_EQ(client().completion_status().cors_error_status->cors_error, - mojom::CORSError::kMissingAllowOriginHeader); + mojom::CorsError::kMissingAllowOriginHeader); } -TEST_F(CORSURLLoaderTest, CrossOriginToAnotherCrossOriginRedirect) { +TEST_F(CorsURLLoaderTest, CrossOriginToAnotherCrossOriginRedirect) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other2.example.com/bar.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); EXPECT_EQ(1, num_created_loaders()); EXPECT_EQ(GetRequest().url, url); @@ -811,14 +811,14 @@ TEST_F(CORSURLLoaderTest, CrossOriginToAnotherCrossOriginRedirect) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, +TEST_F(CorsURLLoaderTest, CrossOriginToAnotherCrossOriginRedirectWithPreflight) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other2.example.com/bar.png"); ResourceRequest original_request; - original_request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + original_request.fetch_request_mode = mojom::FetchRequestMode::kCors; original_request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; original_request.load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; original_request.load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; @@ -885,13 +885,13 @@ TEST_F(CORSURLLoaderTest, EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, RedirectInfoShouldBeUsed) { +TEST_F(CorsURLLoaderTest, RedirectInfoShouldBeUsed) { const GURL origin("https://example.com"); const GURL url("https://example.com/foo.png"); const GURL new_url("https://other.example.com/foo.png"); ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; request.load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; @@ -939,11 +939,11 @@ TEST_F(CORSURLLoaderTest, RedirectInfoShouldBeUsed) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, TooManyRedirects) { +TEST_F(CorsURLLoaderTest, TooManyRedirects) { const GURL origin("https://example.com"); const GURL url("https://example.com/foo.png"); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); for (int i = 0; i < 20; ++i) { EXPECT_EQ(1, num_created_loaders()); @@ -970,13 +970,13 @@ TEST_F(CORSURLLoaderTest, TooManyRedirects) { client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, FollowErrorRedirect) { +TEST_F(CorsURLLoaderTest, FollowErrorRedirect) { const GURL origin("https://example.com"); const GURL url("https://example.com/foo.png"); const GURL new_url("https://example.com/bar.png"); ResourceRequest original_request; - original_request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + original_request.fetch_request_mode = mojom::FetchRequestMode::kCors; original_request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; original_request.load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; original_request.load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; @@ -1006,16 +1006,17 @@ TEST_F(CORSURLLoaderTest, FollowErrorRedirect) { // Tests if OriginAccessList is actually used to decide the cors flag. // Does not verify detailed functionalities that are verified in // OriginAccessListTest. -TEST_F(CORSURLLoaderTest, OriginAccessList) { +TEST_F(CorsURLLoaderTest, OriginAccessList) { const GURL origin("http://example.com"); const GURL url("http://other.com/foo.png"); // Adds an entry to allow the cross origin request beyond the CORS // rules. - AddAllowListEntryForOrigin(url::Origin::Create(origin), url.scheme(), - url.host(), false); + AddAllowListEntryForOrigin( + url::Origin::Create(origin), url.scheme(), url.host(), + mojom::CorsOriginAccessMatchMode::kDisallowSubdomains); - CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCORS); + CreateLoaderAndStart(origin, url, mojom::FetchRequestMode::kCors); NotifyLoaderClientOnReceiveResponse(); NotifyLoaderClientOnComplete(net::OK); @@ -1029,13 +1030,13 @@ TEST_F(CORSURLLoaderTest, OriginAccessList) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, 304ForSimpleRevalidation) { +TEST_F(CorsURLLoaderTest, 304ForSimpleRevalidation) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other2.example.com/bar.png"); ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; request.load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; @@ -1061,13 +1062,13 @@ TEST_F(CORSURLLoaderTest, 304ForSimpleRevalidation) { EXPECT_EQ(net::OK, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, 304ForSimpleGet) { +TEST_F(CorsURLLoaderTest, 304ForSimpleGet) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other2.example.com/bar.png"); ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; request.load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; @@ -1089,13 +1090,13 @@ TEST_F(CORSURLLoaderTest, 304ForSimpleGet) { EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, 200ForSimpleRevalidation) { +TEST_F(CorsURLLoaderTest, 200ForSimpleRevalidation) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other2.example.com/bar.png"); ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; request.load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; @@ -1121,13 +1122,13 @@ TEST_F(CORSURLLoaderTest, 200ForSimpleRevalidation) { EXPECT_EQ(net::ERR_FAILED, client().completion_status().error_code); } -TEST_F(CORSURLLoaderTest, RevalidationAndPreflight) { +TEST_F(CorsURLLoaderTest, RevalidationAndPreflight) { const GURL origin("https://example.com"); const GURL url("https://other.example.com/foo.png"); const GURL new_url("https://other2.example.com/bar.png"); ResourceRequest original_request; - original_request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + original_request.fetch_request_mode = mojom::FetchRequestMode::kCors; original_request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; original_request.load_flags |= net::LOAD_DO_NOT_SAVE_COOKIES; original_request.load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; diff --git a/chromium/services/network/cors/preflight_controller.cc b/chromium/services/network/cors/preflight_controller.cc index 37712042364..de1fcf62b50 100644 --- a/chromium/services/network/cors/preflight_controller.cc +++ b/chromium/services/network/cors/preflight_controller.cc @@ -48,7 +48,7 @@ std::string CreateAccessControlRequestHeadersHeader( // agent. They must be checked separately and rejected for // JavaScript-initiated requests. std::vector<std::string> filtered_headers = - CORSUnsafeNotForbiddenRequestHeaderNames(headers.GetHeaderVector(), + CorsUnsafeNotForbiddenRequestHeaderNames(headers.GetHeaderVector(), is_revalidating); if (filtered_headers.empty()) return std::string(); @@ -121,7 +121,7 @@ std::unique_ptr<PreflightResult> CreatePreflightResult( const ResourceResponseHead& head, const ResourceRequest& original_request, bool tainted, - base::Optional<CORSErrorStatus>* detected_error_status) { + base::Optional<CorsErrorStatus>* detected_error_status) { DCHECK(detected_error_status); *detected_error_status = CheckPreflightAccess( @@ -134,10 +134,10 @@ std::unique_ptr<PreflightResult> CreatePreflightResult( if (*detected_error_status) return nullptr; - base::Optional<mojom::CORSError> error; + base::Optional<mojom::CorsError> error; error = CheckPreflight(head.headers->response_code()); if (error) { - *detected_error_status = CORSErrorStatus(*error); + *detected_error_status = CorsErrorStatus(*error); return nullptr; } @@ -156,14 +156,14 @@ std::unique_ptr<PreflightResult> CreatePreflightResult( &error); if (error) - *detected_error_status = CORSErrorStatus(*error); + *detected_error_status = CorsErrorStatus(*error); return result; } -base::Optional<CORSErrorStatus> CheckPreflightResult( +base::Optional<CorsErrorStatus> CheckPreflightResult( PreflightResult* result, const ResourceRequest& original_request) { - base::Optional<CORSErrorStatus> status = + base::Optional<CorsErrorStatus> status = result->EnsureAllowedCrossOriginMethod(original_request.method); if (status) return status; @@ -272,7 +272,7 @@ class PreflightController::PreflightLoader final { std::move(completion_callback_) .Run(net::ERR_FAILED, - CORSErrorStatus(mojom::CORSError::kPreflightDisallowedRedirect), + CorsErrorStatus(mojom::CorsError::kPreflightDisallowedRedirect), base::nullopt); RemoveFromController(); @@ -291,7 +291,7 @@ class PreflightController::PreflightLoader final { &timing_info_.timing_allow_origin); timing_info_.transfer_size = head.encoded_data_length; - base::Optional<CORSErrorStatus> detected_error_status; + base::Optional<CorsErrorStatus> detected_error_status; std::unique_ptr<PreflightResult> result = CreatePreflightResult( final_url, head, original_request_, tainted_, &detected_error_status); @@ -374,12 +374,6 @@ PreflightController::CreatePreflightRequestForTesting( return CreatePreflightRequest(request, tainted); } -// static -PreflightController* PreflightController::GetDefaultController() { - static base::NoDestructor<PreflightController> controller; - return &*controller; -} - PreflightController::PreflightController() = default; PreflightController::~PreflightController() = default; diff --git a/chromium/services/network/cors/preflight_controller.h b/chromium/services/network/cors/preflight_controller.h index 779c8f11342..defe11b9160 100644 --- a/chromium/services/network/cors/preflight_controller.h +++ b/chromium/services/network/cors/preflight_controller.h @@ -36,7 +36,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) PreflightController final { // PreflightTimingInfo is provided only when a preflight request was made. using CompletionCallback = base::OnceCallback<void(int net_error, - base::Optional<CORSErrorStatus>, + base::Optional<CorsErrorStatus>, base::Optional<PreflightTimingInfo>)>; // Creates a CORS-preflight ResourceRequest for a specified |request| for a // URL that is originally requested. @@ -44,10 +44,6 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) PreflightController final { const ResourceRequest& request, bool tainted = false); - // Obtains the shared default controller instance. - // TODO(toyoshim): Find a right owner rather than a single design. - static PreflightController* GetDefaultController(); - PreflightController(); ~PreflightController(); diff --git a/chromium/services/network/cors/preflight_controller_unittest.cc b/chromium/services/network/cors/preflight_controller_unittest.cc index a3002222863..f30e825f9f6 100644 --- a/chromium/services/network/cors/preflight_controller_unittest.cc +++ b/chromium/services/network/cors/preflight_controller_unittest.cc @@ -32,7 +32,7 @@ namespace { TEST(PreflightControllerCreatePreflightRequestTest, LexicographicalOrder) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.request_initiator = url::Origin(); request.headers.SetHeader("Orange", "Orange"); @@ -57,7 +57,7 @@ TEST(PreflightControllerCreatePreflightRequestTest, LexicographicalOrder) { TEST(PreflightControllerCreatePreflightRequestTest, ExcludeSimpleHeaders) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.request_initiator = url::Origin(); request.headers.SetHeader("Accept", "everything"); @@ -79,7 +79,7 @@ TEST(PreflightControllerCreatePreflightRequestTest, ExcludeSimpleHeaders) { TEST(PreflightControllerCreatePreflightRequestTest, Credentials) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kInclude; request.request_initiator = url::Origin(); request.headers.SetHeader("Orange", "Orange"); @@ -97,7 +97,7 @@ TEST(PreflightControllerCreatePreflightRequestTest, Credentials) { TEST(PreflightControllerCreatePreflightRequestTest, ExcludeSimpleContentTypeHeader) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.request_initiator = url::Origin(); request.headers.SetHeader(net::HttpRequestHeaders::kContentType, @@ -114,7 +114,7 @@ TEST(PreflightControllerCreatePreflightRequestTest, TEST(PreflightControllerCreatePreflightRequestTest, IncludeNonSimpleHeader) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.request_initiator = url::Origin(); request.headers.SetHeader("X-Custom-Header", "foobar"); @@ -131,7 +131,7 @@ TEST(PreflightControllerCreatePreflightRequestTest, IncludeNonSimpleHeader) { TEST(PreflightControllerCreatePreflightRequestTest, IncludeNonSimpleContentTypeHeader) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.request_initiator = url::Origin(); request.headers.SetHeader(net::HttpRequestHeaders::kContentType, @@ -148,7 +148,7 @@ TEST(PreflightControllerCreatePreflightRequestTest, TEST(PreflightControllerCreatePreflightRequestTest, ExcludeForbiddenHeaders) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.request_initiator = url::Origin(); request.headers.SetHeader("referer", "https://www.google.com/"); @@ -163,7 +163,7 @@ TEST(PreflightControllerCreatePreflightRequestTest, ExcludeForbiddenHeaders) { TEST(PreflightControllerCreatePreflightRequestTest, Tainted) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.request_initiator = url::Origin::Create(GURL("https://example.com")); @@ -202,7 +202,7 @@ class PreflightControllerTest : public testing::Test { protected: void HandleRequestCompletion( int net_error, - base::Optional<CORSErrorStatus> status, + base::Optional<CorsErrorStatus> status, base::Optional<PreflightTimingInfo> timing_info) { net_error_ = net_error; status_ = status; @@ -226,8 +226,8 @@ class PreflightControllerTest : public testing::Test { } int net_error() const { return net_error_; } - base::Optional<CORSErrorStatus> status() { return status_; } - base::Optional<CORSErrorStatus> success() { return base::nullopt; } + base::Optional<CorsErrorStatus> status() { return status_; } + base::Optional<CorsErrorStatus> success() { return base::nullopt; } size_t access_count() { return access_count_; } bool cancel_preflight_called() const { return cancel_preflight_called_; } @@ -286,12 +286,12 @@ class PreflightControllerTest : public testing::Test { std::unique_ptr<PreflightController> preflight_controller_; int net_error_ = net::OK; - base::Optional<CORSErrorStatus> status_; + base::Optional<CorsErrorStatus> status_; }; TEST_F(PreflightControllerTest, CheckInvalidRequest) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.url = GetURL("/404"); request.request_initiator = url::Origin::Create(request.url); @@ -299,13 +299,13 @@ TEST_F(PreflightControllerTest, CheckInvalidRequest) { PerformPreflightCheck(request); EXPECT_EQ(net::ERR_FAILED, net_error()); ASSERT_TRUE(status()); - EXPECT_EQ(mojom::CORSError::kPreflightInvalidStatus, status()->cors_error); + EXPECT_EQ(mojom::CorsError::kPreflightInvalidStatus, status()->cors_error); EXPECT_EQ(1u, access_count()); } TEST_F(PreflightControllerTest, CheckValidRequest) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.url = GetURL("/allow"); request.request_initiator = url::Origin::Create(request.url); @@ -323,7 +323,7 @@ TEST_F(PreflightControllerTest, CheckValidRequest) { TEST_F(PreflightControllerTest, CheckTaintedRequest) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.url = GetURL("/tainted"); request.request_initiator = url::Origin::Create(request.url); @@ -338,7 +338,7 @@ TEST_F(PreflightControllerTest, CheckTaintedRequest) { // enabled. TEST_F(PreflightControllerTest, CancelPreflightIsCalled) { ResourceRequest request; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.fetch_credentials_mode = mojom::FetchCredentialsMode::kOmit; request.url = GetURL("/allow"); request.request_initiator = url::Origin::Create(request.url); diff --git a/chromium/services/network/cross_origin_read_blocking.cc b/chromium/services/network/cross_origin_read_blocking.cc index 02b7e5f6ecc..b508cf9438e 100644 --- a/chromium/services/network/cross_origin_read_blocking.cc +++ b/chromium/services/network/cross_origin_read_blocking.cc @@ -609,7 +609,7 @@ CrossOriginReadBlocking::ResponseAnalyzer::ShouldBlockBasedOnHeaders( if (response.head.was_fetched_via_service_worker) { switch (response.head.response_type) { case network::mojom::FetchResponseType::kBasic: - case network::mojom::FetchResponseType::kCORS: + case network::mojom::FetchResponseType::kCors: case network::mojom::FetchResponseType::kDefault: case network::mojom::FetchResponseType::kError: // Non-opaque responses shouldn't be blocked. diff --git a/chromium/services/network/cross_origin_read_blocking.h b/chromium/services/network/cross_origin_read_blocking.h index 6344ecab626..6378978e996 100644 --- a/chromium/services/network/cross_origin_read_blocking.h +++ b/chromium/services/network/cross_origin_read_blocking.h @@ -234,7 +234,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) CrossOriginReadBlocking { // not allowed by actual CORS rules by ignoring 1) credentials and 2) // methods. Preflight requests don't matter here since they are not used to // decide whether to block a response or not on the client side. - // TODO(crbug.com/736308) Remove this check once the kOutOfBlinkCORS feature + // TODO(crbug.com/736308) Remove this check once the kOutOfBlinkCors feature // is shipped. static bool IsValidCorsHeaderSet(const url::Origin& frame_origin, const std::string& access_control_origin); diff --git a/chromium/services/network/dns_config_change_manager.cc b/chromium/services/network/dns_config_change_manager.cc new file mode 100644 index 00000000000..5a95b7ab21d --- /dev/null +++ b/chromium/services/network/dns_config_change_manager.cc @@ -0,0 +1,40 @@ +// Copyright 2018 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 "services/network/dns_config_change_manager.h" + +#include <utility> + +namespace network { + +DnsConfigChangeManager::DnsConfigChangeManager() { + net::NetworkChangeNotifier::AddDNSObserver(this); +} + +DnsConfigChangeManager::~DnsConfigChangeManager() { + net::NetworkChangeNotifier::RemoveDNSObserver(this); +} + +void DnsConfigChangeManager::AddBinding( + mojom::DnsConfigChangeManagerRequest request) { + bindings_.AddBinding(this, std::move(request)); +} + +void DnsConfigChangeManager::RequestNotifications( + mojom::DnsConfigChangeManagerClientPtr client) { + clients_.AddPtr(std::move(client)); +} + +void DnsConfigChangeManager::OnDNSChanged() { + clients_.ForAllPtrs([](mojom::DnsConfigChangeManagerClient* client) { + client->OnSystemDnsConfigChanged(); + }); +} + +void DnsConfigChangeManager::OnInitialDNSConfigRead() { + // Service API makes no distinction between initial read and change. + OnDNSChanged(); +} + +} // namespace network diff --git a/chromium/services/network/dns_config_change_manager.h b/chromium/services/network/dns_config_change_manager.h new file mode 100644 index 00000000000..6d92f02f221 --- /dev/null +++ b/chromium/services/network/dns_config_change_manager.h @@ -0,0 +1,46 @@ +// Copyright 2018 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. + +#ifndef SERVICES_NETWORK_DNS_CONFIG_CHANGE_MANAGER_H_ +#define SERVICES_NETWORK_DNS_CONFIG_CHANGE_MANAGER_H_ + +#include <memory> +#include <set> + +#include "base/component_export.h" +#include "base/macros.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "mojo/public/cpp/bindings/interface_ptr_set.h" +#include "net/base/network_change_notifier.h" +#include "services/network/public/mojom/host_resolver.mojom.h" + +namespace network { + +class COMPONENT_EXPORT(NETWORK_SERVICE) DnsConfigChangeManager + : public mojom::DnsConfigChangeManager, + public net::NetworkChangeNotifier::DNSObserver { + public: + DnsConfigChangeManager(); + ~DnsConfigChangeManager() override; + + void AddBinding(mojom::DnsConfigChangeManagerRequest request); + + // mojom::DnsConfigChangeManager implementation: + void RequestNotifications( + mojom::DnsConfigChangeManagerClientPtr client) override; + + private: + // net::NetworkChangeNotifier::DNSObserver implementation: + void OnDNSChanged() override; + void OnInitialDNSConfigRead() override; + + mojo::BindingSet<mojom::DnsConfigChangeManager> bindings_; + mojo::InterfacePtrSet<mojom::DnsConfigChangeManagerClient> clients_; + + DISALLOW_COPY_AND_ASSIGN(DnsConfigChangeManager); +}; + +} // namespace network + +#endif // SERVICES_NETWORK_DNS_CONFIG_CHANGE_MANAGER_H_ diff --git a/chromium/services/network/dns_config_change_manager_unittest.cc b/chromium/services/network/dns_config_change_manager_unittest.cc new file mode 100644 index 00000000000..5925cc54297 --- /dev/null +++ b/chromium/services/network/dns_config_change_manager_unittest.cc @@ -0,0 +1,126 @@ +// Copyright 2018 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 "services/network/dns_config_change_manager.h" + +#include <climits> +#include <utility> + +#include "base/run_loop.h" +#include "base/test/scoped_task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { +namespace { + +class TestDnsConfigChangeManagerClient + : public mojom::DnsConfigChangeManagerClient { + public: + explicit TestDnsConfigChangeManagerClient(DnsConfigChangeManager* manager) { + mojom::DnsConfigChangeManagerPtr manager_ptr; + mojom::DnsConfigChangeManagerRequest manager_request( + mojo::MakeRequest(&manager_ptr)); + manager->AddBinding(std::move(manager_request)); + + mojom::DnsConfigChangeManagerClientPtr client_ptr; + mojom::DnsConfigChangeManagerClientRequest client_request( + mojo::MakeRequest(&client_ptr)); + binding_.Bind(std::move(client_request)); + + manager_ptr->RequestNotifications(std::move(client_ptr)); + } + + void OnSystemDnsConfigChanged() override { + num_notifications_++; + if (num_notifications_ >= num_notifications_expected_) + run_loop_.Quit(); + } + + int num_notifications() { return num_notifications_; } + + void WaitForNotification(int num_notifications_expected) { + num_notifications_expected_ = num_notifications_expected; + if (num_notifications_ < num_notifications_expected_) + run_loop_.Run(); + } + + private: + int num_notifications_ = 0; + int num_notifications_expected_ = INT_MAX; + base::RunLoop run_loop_; + mojo::Binding<mojom::DnsConfigChangeManagerClient> binding_{this}; + + DISALLOW_COPY_AND_ASSIGN(TestDnsConfigChangeManagerClient); +}; + +class DnsConfigChangeManagerTest : public testing::Test { + public: + DnsConfigChangeManagerTest() {} + + DnsConfigChangeManager* manager() { return &manager_; } + TestDnsConfigChangeManagerClient* client() { return &client_; } + + private: + base::test::ScopedTaskEnvironment scoped_task_environment_; + std::unique_ptr<net::NetworkChangeNotifier> notifier_mock_{ + net::NetworkChangeNotifier::CreateMock()}; + DnsConfigChangeManager manager_; + TestDnsConfigChangeManagerClient client_{&manager_}; + + DISALLOW_COPY_AND_ASSIGN(DnsConfigChangeManagerTest); +}; + +TEST_F(DnsConfigChangeManagerTest, Notification) { + EXPECT_EQ(0, client()->num_notifications()); + + net::NetworkChangeNotifier::NotifyObserversOfDNSChangeForTests(); + client()->WaitForNotification(1); + EXPECT_EQ(1, client()->num_notifications()); + + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, client()->num_notifications()); +} + +TEST_F(DnsConfigChangeManagerTest, Notification_InitialRead) { + EXPECT_EQ(0, client()->num_notifications()); + + net::NetworkChangeNotifier::NotifyObserversOfInitialDNSConfigReadForTests(); + client()->WaitForNotification(1); + EXPECT_EQ(1, client()->num_notifications()); + + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, client()->num_notifications()); +} + +TEST_F(DnsConfigChangeManagerTest, MultipleNotification) { + EXPECT_EQ(0, client()->num_notifications()); + + net::NetworkChangeNotifier::NotifyObserversOfDNSChangeForTests(); + net::NetworkChangeNotifier::NotifyObserversOfDNSChangeForTests(); + client()->WaitForNotification(2); + EXPECT_EQ(2, client()->num_notifications()); + + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(2, client()->num_notifications()); +} + +TEST_F(DnsConfigChangeManagerTest, MultipleClients) { + TestDnsConfigChangeManagerClient client2(manager()); + + EXPECT_EQ(0, client()->num_notifications()); + EXPECT_EQ(0, client2.num_notifications()); + + net::NetworkChangeNotifier::NotifyObserversOfDNSChangeForTests(); + client()->WaitForNotification(1); + client2.WaitForNotification(1); + EXPECT_EQ(1, client()->num_notifications()); + EXPECT_EQ(1, client2.num_notifications()); + + base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, client()->num_notifications()); + EXPECT_EQ(1, client2.num_notifications()); +} + +} // namespace +} // namespace network diff --git a/chromium/services/network/expect_ct_reporter.h b/chromium/services/network/expect_ct_reporter.h index e8220a3c5b7..b7f1b60f888 100644 --- a/chromium/services/network/expect_ct_reporter.h +++ b/chromium/services/network/expect_ct_reporter.h @@ -76,11 +76,11 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ExpectCTReporter FRIEND_TEST_ALL_PREFIXES(ExpectCTReporterTest, SendReport); FRIEND_TEST_ALL_PREFIXES(ExpectCTReporterTest, PreflightContainsWhitespace); FRIEND_TEST_ALL_PREFIXES(ExpectCTReporterTest, - BadCORSPreflightResponseOrigin); + BadCorsPreflightResponseOrigin); FRIEND_TEST_ALL_PREFIXES(ExpectCTReporterTest, - BadCORSPreflightResponseMethods); + BadCorsPreflightResponseMethods); FRIEND_TEST_ALL_PREFIXES(ExpectCTReporterTest, - BadCORSPreflightResponseHeaders); + BadCorsPreflightResponseHeaders); // Starts a CORS preflight request to obtain permission from the server to // send a report with Content-Type: application/expect-ct-report+json. The diff --git a/chromium/services/network/expect_ct_reporter_unittest.cc b/chromium/services/network/expect_ct_reporter_unittest.cc index 191dc5e88a3..e10264a8e5f 100644 --- a/chromium/services/network/expect_ct_reporter_unittest.cc +++ b/chromium/services/network/expect_ct_reporter_unittest.cc @@ -407,7 +407,7 @@ class ExpectCTReporterTest : public ::testing::Test { EXPECT_EQ(successful_report_uri, sender->latest_report_uri()); } - void SetCORSHeaderWithWhitespace() { + void SetCorsHeaderWithWhitespace() { cors_headers_["Access-Control-Allow-Methods"] = "GET, POST"; } @@ -688,7 +688,7 @@ TEST_F(ExpectCTReporterTest, SendReportSuccessCallback) { // Test that report preflight responses can contain whitespace. TEST_F(ExpectCTReporterTest, PreflightContainsWhitespace) { - SetCORSHeaderWithWhitespace(); + SetCorsHeaderWithWhitespace(); TestCertificateReportSender* sender = new TestCertificateReportSender(); net::TestURLRequestContext context; @@ -719,7 +719,7 @@ TEST_F(ExpectCTReporterTest, PreflightContainsWhitespace) { // Test that no report is sent when the CORS preflight returns an invalid // Access-Control-Allow-Origin. -TEST_F(ExpectCTReporterTest, BadCORSPreflightResponseOrigin) { +TEST_F(ExpectCTReporterTest, BadCorsPreflightResponseOrigin) { TestCertificateReportSender* sender = new TestCertificateReportSender(); net::TestURLRequestContext context; ExpectCTReporter reporter(&context, base::Closure(), base::Closure()); @@ -743,7 +743,7 @@ TEST_F(ExpectCTReporterTest, BadCORSPreflightResponseOrigin) { // Test that no report is sent when the CORS preflight returns an invalid // Access-Control-Allow-Methods. -TEST_F(ExpectCTReporterTest, BadCORSPreflightResponseMethods) { +TEST_F(ExpectCTReporterTest, BadCorsPreflightResponseMethods) { TestCertificateReportSender* sender = new TestCertificateReportSender(); net::TestURLRequestContext context; ExpectCTReporter reporter(&context, base::Closure(), base::Closure()); @@ -767,7 +767,7 @@ TEST_F(ExpectCTReporterTest, BadCORSPreflightResponseMethods) { // Test that no report is sent when the CORS preflight returns an invalid // Access-Control-Allow-Headers. -TEST_F(ExpectCTReporterTest, BadCORSPreflightResponseHeaders) { +TEST_F(ExpectCTReporterTest, BadCorsPreflightResponseHeaders) { TestCertificateReportSender* sender = new TestCertificateReportSender(); net::TestURLRequestContext context; ExpectCTReporter reporter(&context, base::Closure(), base::Closure()); diff --git a/chromium/services/network/host_resolver_unittest.cc b/chromium/services/network/host_resolver_unittest.cc index 501a0429dd8..a402ccbcf5e 100644 --- a/chromium/services/network/host_resolver_unittest.cc +++ b/chromium/services/network/host_resolver_unittest.cc @@ -19,6 +19,7 @@ #include "net/base/ip_address.h" #include "net/base/net_errors.h" #include "net/dns/mock_host_resolver.h" +#include "net/dns/public/dns_query_type.h" #include "net/log/net_log.h" #include "services/network/host_resolver.h" #include "services/network/public/mojom/host_resolver.mojom.h" @@ -158,7 +159,7 @@ TEST_F(HostResolverTest, DnsQueryType) { mojom::ResolveHostParametersPtr optional_parameters = mojom::ResolveHostParameters::New(); - optional_parameters->dns_query_type = net::HostResolver::DnsQueryType::AAAA; + optional_parameters->dns_query_type = net::DnsQueryType::AAAA; base::RunLoop run_loop; mojom::ResolveHostClientPtr response_client_ptr; diff --git a/chromium/services/network/mdns_responder.cc b/chromium/services/network/mdns_responder.cc new file mode 100644 index 00000000000..f1d4e9bdab2 --- /dev/null +++ b/chromium/services/network/mdns_responder.cc @@ -0,0 +1,960 @@ +// Copyright 2018 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 <algorithm> +#include <numeric> +#include <utility> + +#include "services/network/mdns_responder.h" + +#include "base/bind.h" +#include "base/guid.h" +#include "base/logging.h" +#include "base/optional.h" +#include "base/single_thread_task_runner.h" +#include "base/stl_util.h" +#include "base/strings/string_piece.h" +#include "base/sys_byteorder.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/default_tick_clock.h" +#include "net/base/address_family.h" +#include "net/base/io_buffer.h" +#include "net/base/ip_address.h" +#include "net/base/net_errors.h" +#include "net/dns/dns_response.h" +#include "net/dns/dns_util.h" +#include "net/dns/mdns_client.h" +#include "net/dns/public/dns_protocol.h" +#include "net/dns/public/util.h" +#include "net/dns/record_parsed.h" +#include "net/dns/record_rdata.h" +#include "net/socket/datagram_server_socket.h" +#include "net/socket/udp_server_socket.h" + +// TODO(qingsi): Several features to implement: +// +// 1) Support parsing a query with multiple questions in the wire format to a +// DnsQuery, and bundle answers to questions in a single DnsResponse with proper +// rate limiting. +// +// 2) Support detecting queries for the same record within the minimal interval +// between responses and allow at most one response queued by the scheduler at a +// time for each name. +// +// 3) Support parsing the authority section of a query in the wire format to +// correctly implement the detection of probe queries. +namespace { + +// RFC 6762, Section 6. +// +// The multicast of responses of the same record on an interface must be at +// least one second apart on that particular interface. +const base::TimeDelta kMinIntervalBetweenSameRecord = + base::TimeDelta::FromSeconds(1); + +const base::TimeDelta kMinIntervalBetweenMdnsResponses = + base::TimeDelta::FromSeconds(1); + +// RFC 6762, Section 10. +const base::TimeDelta kDefaultTtlForRecordWithHostname = + base::TimeDelta::FromSeconds(120); + +// RFC 6762, Section 8.3. +const int kMinNumAnnouncementsToSend = 2; + +// RFC 6762, Section 10.2. +// +// The top bit of the class field in a resource record is repurposed to the +// cache-flush bit. +const uint16_t kFlagCacheFlush = 0x8000; + +// Maximum number of retries for the same response due to send failure. +const uint8_t kMaxMdnsResponseRetries = 2; +// Maximum delay allowed for per-response rate-limited responses. +const base::TimeDelta kMaxScheduledDelay = base::TimeDelta::FromSeconds(10); + +class RandomUuidNameGenerator + : public network::MdnsResponderManager::NameGenerator { + public: + std::string CreateName() override { return base::GenerateGUID(); } +}; + +bool QueryTypeAndAddressFamilyAreCompatible(uint16_t qtype, + net::AddressFamily af) { + switch (qtype) { + case net::dns_protocol::kTypeA: + return af == net::ADDRESS_FAMILY_IPV4; + case net::dns_protocol::kTypeAAAA: + return af == net::ADDRESS_FAMILY_IPV6; + case net::dns_protocol::kTypeANY: + return af == net::ADDRESS_FAMILY_IPV4 || af == net::ADDRESS_FAMILY_IPV6; + default: + return false; + } +} + +// Creates a vector of A or AAAA records, where the name field of each record is +// given by the name in |name_addr_map|, and its mapped address is used to +// construct the RDATA stored in |DnsResourceRecord::owned_rdata|. |ttl| +// specifies the TTL of each record. With the owned RDATA, the returned records +// can be later used to construct a DnsResponse. +std::vector<net::DnsResourceRecord> CreateAddressResourceRecords( + const std::map<std::string, net::IPAddress>& name_addr_map, + const base::TimeDelta& ttl) { + std::vector<net::DnsResourceRecord> address_records; + for (const auto& name_addr_pair : name_addr_map) { + const auto& ip = name_addr_pair.second; + DCHECK(ip.IsIPv4() || ip.IsIPv6()); + net::DnsResourceRecord record; + record.name = name_addr_pair.first; + record.type = (ip.IsIPv4() ? net::dns_protocol::kTypeA + : net::dns_protocol::kTypeAAAA); + // Set the cache-flush bit to assert that this information is the truth and + // the whole truth. + record.klass = net::dns_protocol::kClassIN | kFlagCacheFlush; + int64_t ttl_seconds = ttl.InSeconds(); + // TTL in a resource record is 32-bit. + DCHECK(ttl_seconds >= 0 && ttl_seconds <= 0x0ffffffff); + record.ttl = ttl_seconds; + record.SetOwnedRdata(net::IPAddressToPackedString(ip)); + address_records.push_back(std::move(record)); + } + return address_records; +} + +// Creates an NSEC record RDATA in the wire format for the resource record type +// that corresponds to the address family of |addr|. The type bit map in the +// RDATA asserts the existence of only the address record that matches |addr|. +// Per RFC 3845 Section 2.1 and RFC 6762 Section 6, each RDATA has its Next +// Domain Name as a two-octet pointer to the name field of the NSEC resource +// record. |containing_nsec_rr_offset| defines the offset in the message of the +// NSEC resource record that would contain the returned RDATA, and its value is +// used to generate the correct pointer for Next Domain Name. +std::string CreateNsecRdata(const net::IPAddress& addr, + uint16_t containing_nsec_rr_offset) { + DCHECK(addr.IsIPv4() || addr.IsIPv6()); + // Each NSEC rdata in our negative response is given by 5 octets and 8 + // octets for type A and type AAAA records, respectively: + // + // 2 octets for Next Domain Name as a pointer to the name field + // (DnsResourceRecord::name) of the NSEC record that will contain this RDATA; + // 1 octet for Window Block, which is always 0; + // 1 octet for Bitmap Length with value X, where X=1 for type A and X=4 for + // type AAAA; + // X octet(s) for Bitmap, 0x40 for type A and 0x00000008 for type AAAA. + std::string next_domain_name = + net::CreateNamePointer(containing_nsec_rr_offset); + DCHECK_EQ(2u, next_domain_name.size()); + if (addr.IsIPv4()) + return next_domain_name + std::string("\x00\x01\x40", 3); + + return next_domain_name + std::string("\x00\x04\x00\x00\x00\x08", 6); +} + +// Creates a vector of NSEC records, where the name field of each record is +// given by the name in |name_addr_map|, and its mapped address is used to +// construct the RDATA stored in |DnsResourceRecord::owned_rdata| via +// CreateNsecRdata above. With the owned RDATA, the returned records can be +// later used to construct a DnsResponse. +std::vector<net::DnsResourceRecord> CreateNsecResourceRecords( + const std::map<std::string, net::IPAddress>& name_addr_map, + uint16_t first_nsec_rr_offset) { + std::vector<net::DnsResourceRecord> nsec_records; + uint16_t cur_rr_offset = first_nsec_rr_offset; + for (const auto& name_addr_pair : name_addr_map) { + net::DnsResourceRecord record; + record.name = name_addr_pair.first; + record.type = net::dns_protocol::kTypeNSEC; + // Set the cache-flush bit to assert that this information is the truth and + // the whole truth. + record.klass = net::dns_protocol::kClassIN | kFlagCacheFlush; + // RFC 6762, Section 6.1. TTL should be the same as that of what the record + // would have. + record.ttl = kDefaultTtlForRecordWithHostname.InSeconds(); + record.SetOwnedRdata(CreateNsecRdata(name_addr_pair.second, cur_rr_offset)); + cur_rr_offset += record.CalculateRecordSize(); + nsec_records.push_back(std::move(record)); + } + return nsec_records; +} + +bool IsProbeQuery(const net::DnsQuery& query) { + // TODO(qingsi): RFC 6762, the proper way to detect a probe query is + // to check if + // + // 1) its qtype is ANY (Section 8.1) and + // 2) it "contains a proposed record in the Authority Section that + // answers the question in the Question Section" (Section 6). + // + // Currently DnsQuery does not support the Authority section. Fix it. + return query.qtype() == net::dns_protocol::kTypeANY; +} + +} // namespace + +namespace network { + +namespace mdns_helper { + +scoped_refptr<net::IOBufferWithSize> CreateResolutionResponse( + const base::TimeDelta& ttl, + const std::map<std::string, net::IPAddress>& name_addr_map) { + DCHECK(!name_addr_map.empty()); + std::vector<net::DnsResourceRecord> answers = + CreateAddressResourceRecords(name_addr_map, ttl); + std::vector<net::DnsResourceRecord> additional_records; + if (!ttl.is_zero()) { + uint16_t cur_size = std::accumulate( + answers.begin(), answers.end(), sizeof(net::dns_protocol::Header), + [](size_t cur_size, const net::DnsResourceRecord& answer) { + return cur_size + answer.CalculateRecordSize(); + }); + additional_records = CreateNsecResourceRecords(name_addr_map, cur_size); + } + + // RFC 6762. + // + // Section 6. mDNS responses MUST NOT contain any questions. + // Section 18.1. In mDNS responses, ID MUST be set to zero. + net::DnsResponse response(0 /* id */, true /* is_authoritative */, answers, + additional_records, base::nullopt /* query */); + DCHECK(response.io_buffer() != nullptr); + auto buf = + base::MakeRefCounted<net::IOBufferWithSize>(response.io_buffer_size()); + memcpy(buf->data(), response.io_buffer()->data(), response.io_buffer_size()); + return buf; +} + +scoped_refptr<net::IOBufferWithSize> CreateNegativeResponse( + const std::map<std::string, net::IPAddress>& name_addr_map) { + DCHECK(!name_addr_map.empty()); + std::vector<net::DnsResourceRecord> nsec_records = CreateNsecResourceRecords( + name_addr_map, sizeof(net::dns_protocol::Header)); + std::vector<net::DnsResourceRecord> additional_records = + CreateAddressResourceRecords(name_addr_map, + kDefaultTtlForRecordWithHostname); + net::DnsResponse response(0 /* id */, true /* is_authoritative */, + nsec_records, additional_records, + base::nullopt /* query */); + DCHECK(response.io_buffer() != nullptr); + auto buf = + base::MakeRefCounted<net::IOBufferWithSize>(response.io_buffer_size()); + memcpy(buf->data(), response.io_buffer()->data(), response.io_buffer_size()); + return buf; +} + +} // namespace mdns_helper + +class MdnsResponderManager::SocketHandler { + public: + SocketHandler(uint16_t id, + std::unique_ptr<net::DatagramServerSocket> socket, + MdnsResponderManager* responder_manager) + : id_(id), + scheduler_(std::make_unique<ResponseScheduler>(this)), + socket_(std::move(socket)), + responder_manager_(responder_manager), + io_buffer_(base::MakeRefCounted<net::IOBufferWithSize>( + net::dns_protocol::kMaxUDPSize + 1)), + weak_factory_(this) {} + ~SocketHandler() = default; + + int Start() { + net::IPEndPoint end_point; + int rv = socket_->GetLocalAddress(&end_point); + if (rv != net::OK) { + return rv; + } + DCHECK(end_point.GetFamily() == net::ADDRESS_FAMILY_IPV4 || + end_point.GetFamily() == net::ADDRESS_FAMILY_IPV6); + multicast_addr_ = + net::dns_util::GetMdnsGroupEndPoint(end_point.GetFamily()); + int result = DoReadLoop(); + if (result == net::ERR_IO_PENDING) { + // An in-progress read loop is considered a completed start. + return net::OK; + } + return result; + } + + // Returns true if the send is successfully scheduled after rate limiting on + // the underlying interface, and false otherwise. + bool Send(scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option); + + void DoSend(scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option); + + uint16_t id() const { return id_; } + + void SetTickClockForTesting(const base::TickClock* tick_clock); + + base::WeakPtr<SocketHandler> GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + private: + class ResponseScheduler; + + int DoReadLoop() { + int result; + do { + // Using base::Unretained(this) is safe because the CompletionOnceCallback + // is automatically cancelled when |socket_| is destroyed, and the latter + // is owned by |this|. + result = socket_->RecvFrom( + io_buffer_.get(), io_buffer_->size(), &recv_addr_, + base::BindOnce(&MdnsResponderManager::SocketHandler::OnRead, + base::Unretained(this))); + // Process synchronous return from RecvFrom. + HandlePacket(result); + } while (result >= 0); + + return result; + } + + // For the methods below, |result| indicates the number of bytes read if + // positive, or a network stack error code if negative. Zero indicates either + // net::OK or zero bytes read. + void OnRead(int result) { + if (result >= 0) { + HandlePacket(result); + DoReadLoop(); + } else { + responder_manager_->OnSocketHandlerReadError(id_, result); + } + } + void HandlePacket(int result); + + uint16_t id_; + std::unique_ptr<ResponseScheduler> scheduler_; + std::unique_ptr<net::DatagramServerSocket> socket_; + // A back pointer to the responder manager that owns this socket handler. The + // handler should be destroyed before |responder_manager_| becomes invalid or + // a weak reference should be used to access the manager when there is no such + // guarantee in an operation. + MdnsResponderManager* const responder_manager_; + scoped_refptr<net::IOBufferWithSize> io_buffer_; + net::IPEndPoint recv_addr_; + net::IPEndPoint multicast_addr_; + + base::WeakPtrFactory<SocketHandler> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(SocketHandler); +}; + +// Implements the rate limiting schemes for sending responses as defined by +// RateLimitScheme. Specifically: +// +// 1. Announcements for new names (RFC 6762, Section 8.3) and goodbyes (RFC +// 6762, Section 10.1) are rate limited per response on each interface, so that +// the interval between sending the above responses is no less than one second +// on the given interface. +// +// 2. Responses containing resource records for name resolution, and also +// negative responses to queries for non-existing records of generated names, +// are rate limited per record. The delay of such a response from the last +// per-record rate limited response is computed as the maximum delay of all +// records (names) contained. Per RFC 6762, Section 6, records are sent at a +// maximum rate of one per each second. +// +// 3. Responses to probing queries (RFC 6762, Section 8.1) are not rate +// limited. +// +// Also, if the projected delay of a response exceeds the maximum scheduled +// delay given by kMaxScheduledDelay, the response is NOT scheduled. +class MdnsResponderManager::SocketHandler::ResponseScheduler { + public: + enum class RateLimitScheme { + // The next response will be sent at least after + // kMinIntervalBetweenResponses since the last response that is rate limited + // by the per-response scheme. + PER_RESPONSE, + // The delay of the response from the last one that is rate limited by the + // per-record scheme, is computed as the maximum delay of all its records + // (identified by names). The multicast of each record is separated by at + // least kMinIntervalBetweenSameRecord. + PER_RECORD, + // The response is sent immediately. + NO_LIMIT, + }; + + ResponseScheduler(MdnsResponderManager::SocketHandler* handler) + : handler_(handler), + task_runner_(base::SequencedTaskRunnerHandle::Get()), + tick_clock_(base::DefaultTickClock::GetInstance()), + next_available_time_per_resp_sched_(tick_clock_->NowTicks()), + weak_factory_(this) {} + ~ResponseScheduler() = default; + + // Implements the rate limit scheme on the underlying interface managed by + // |handler_|. Returns true if the send is scheduled on this interface. + // + // Pending sends scheduled are cancelled after |handler_| becomes invalid; + bool ScheduleNextSend(scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option); + void OnResponseSent(scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option, + int result) { + if (result < 0) { + VLOG(1) << "Socket send error, socket=" << handler_->id() + << ", error=" << result; + if (CanBeRetriedAfterSendFailure(*option)) { + ++option->num_send_retries_done; + handler_->DoSend(std::move(buf), std::move(option)); + } else + VLOG(1) << "Response cannot be sent after " << kMaxMdnsResponseRetries + << " retries."; + } + } + + // Also resets the scheduler. + void SetTickClockForTesting(const base::TickClock* tick_clock) { + tick_clock_ = tick_clock; + next_available_time_per_resp_sched_ = tick_clock_->NowTicks(); + next_available_time_for_name_.clear(); + } + + base::WeakPtr<ResponseScheduler> GetWeakPtr() { + return weak_factory_.GetWeakPtr(); + } + + private: + RateLimitScheme GetRateLimitSchemeForClass( + MdnsResponseSendOption::ResponseClass klass) { + switch (klass) { + case MdnsResponseSendOption::ResponseClass::ANNOUNCEMENT: + case MdnsResponseSendOption::ResponseClass::GOODBYE: + return RateLimitScheme::PER_RESPONSE; + case MdnsResponseSendOption::ResponseClass::NEGATIVE: + case MdnsResponseSendOption::ResponseClass::REGULAR_RESOLUTION: + return RateLimitScheme::PER_RECORD; + case MdnsResponseSendOption::ResponseClass::PROBE_RESOLUTION: + return RateLimitScheme::NO_LIMIT; + case MdnsResponseSendOption::ResponseClass::UNSPECIFIED: + NOTREACHED(); + return RateLimitScheme::PER_RESPONSE; + } + } + // Returns null if the computed delay exceeds kMaxScheduledDelay and the next + // available time is not updated. + base::Optional<base::TimeDelta> + ComputeResponseDelayAndUpdateNextAvailableTime( + RateLimitScheme rate_limit_scheme, + const MdnsResponseSendOption& option); + // Determines if a response can be retried after send failure. + bool CanBeRetriedAfterSendFailure(const MdnsResponseSendOption& option) { + if (option.num_send_retries_done >= kMaxMdnsResponseRetries) + return false; + + if (option.klass == MdnsResponseSendOption::ResponseClass::ANNOUNCEMENT || + option.klass == MdnsResponseSendOption::ResponseClass::GOODBYE || + option.klass == MdnsResponseSendOption::ResponseClass::PROBE_RESOLUTION) + return true; + + return false; + } + + // A back pointer to the socket handler that owns this scheduler. The + // scheduler should be destroyed before |handler_| becomes invalid or a weak + // reference should be used to access the handler when there is no such + // guarantee in an operation. + MdnsResponderManager::SocketHandler* const handler_; + scoped_refptr<base::SequencedTaskRunner> task_runner_; + const base::TickClock* tick_clock_; + std::map<std::string, base::TimeTicks> next_available_time_for_name_; + base::TimeTicks next_available_time_per_resp_sched_; + + base::WeakPtrFactory<ResponseScheduler> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(ResponseScheduler); +}; + +bool MdnsResponderManager::SocketHandler::Send( + scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option) { + return scheduler_->ScheduleNextSend(std::move(buf), std::move(option)); +} + +void MdnsResponderManager::SocketHandler::DoSend( + scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option) { + auto* buf_data = buf.get(); + size_t buf_size = buf->size(); + socket_->SendTo(buf_data, buf_size, multicast_addr_, + base::BindOnce(&ResponseScheduler::OnResponseSent, + scheduler_->GetWeakPtr(), std::move(buf), + std::move(option))); +} + +void MdnsResponderManager::SocketHandler::SetTickClockForTesting( + const base::TickClock* tick_clock) { + scheduler_->SetTickClockForTesting(tick_clock); +} + +bool MdnsResponderManager::SocketHandler::ResponseScheduler::ScheduleNextSend( + scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option) { + auto rate_limit_scheme = GetRateLimitSchemeForClass(option->klass); + if (rate_limit_scheme == RateLimitScheme::NO_LIMIT) { + // Skip the scheduling for this response. Currently the zero delay is only + // used for negative responses generated by the responder itself. Responses + // with positive name resolution generated by the responder and also those + // triggered via the Mojo connection (i.e. announcements and goodbye + // packets) are rate limited via the scheduled delay below. + handler_->DoSend(std::move(buf), std::move(option)); + return true; + } + const base::Optional<base::TimeDelta> delay = + ComputeResponseDelayAndUpdateNextAvailableTime(rate_limit_scheme, + *option); + if (!delay) + return false; + + // Note that the owning handler of this scheduler may be removed if it + // encounters read error as we process in OnSocketHandlerReadError. We should + // guarantee any posted task can be cancelled if the handler goes away, which + // we do via the weak pointer. + task_runner_->PostDelayedTask( + FROM_HERE, + base::BindOnce(&MdnsResponderManager::SocketHandler::DoSend, + handler_->GetWeakPtr(), std::move(buf), std::move(option)), + delay.value()); + return true; +} + +base::Optional<base::TimeDelta> MdnsResponderManager::SocketHandler:: + ResponseScheduler::ComputeResponseDelayAndUpdateNextAvailableTime( + RateLimitScheme rate_limit_scheme, + const MdnsResponseSendOption& option) { + auto now = tick_clock_->NowTicks(); + // RFC 6762 requires the rate limiting applied on a per-record basis. When a + // response contains multiple records, each identified by the name, we + // compute the delay as the maximum delay of records contained. See the + // definition of RateLimitScheme::PER_RECORD. + // + // For responses that are triggered via the Mojo connection, we perform more + // restrictive rate limiting on a per-response basis. See the + // definition of RateLimitScheme::PER_RESPONSE. + if (rate_limit_scheme == RateLimitScheme::PER_RESPONSE) { + auto delay = + std::max(next_available_time_per_resp_sched_ - now, base::TimeDelta()); + if (delay > kMaxScheduledDelay) + return base::nullopt; + + next_available_time_per_resp_sched_ = + now + delay + kMinIntervalBetweenMdnsResponses; + return delay; + } + + DCHECK(rate_limit_scheme == RateLimitScheme::PER_RECORD); + DCHECK(!option.names_for_rate_limit.empty()); + auto next_available_time_for_response = now; + // TODO(qingsi): There are a couple of issues with computing the delay of a + // response as the maximum of each name contained and updating the next + // available time for each name accordingly. + // + // 1) It can unnecessarily delay the records with the names that are not + // backlogged in the schedule. + // + // 2) The update of the next available time following 1) further delays the + // future responses for these victim names, which could escalate the + // congestion until we start to drop the response after exceeding + // kMaxScheduledDelay. + // + // The root cause is we currently maintain a one-to-one mapping between + // queries and responses, such that a response answers the questions in the + // corresponding query entirely (note however that DnsQuery currently supports + // only a single question). We could mitigate this issue by splitting or + // merging responses. See the comment block at the beginning of this file + // about features to implement. + for (const auto& name : option.names_for_rate_limit) { + // The following computation assumes that we always send the address record + // and the negative record at the same time (as we do) for any given name. + next_available_time_for_response = std::max( + next_available_time_for_response, next_available_time_for_name_[name]); + } + base::TimeDelta delay = + std::max(next_available_time_for_response - now, base::TimeDelta()); + if (delay > kMaxScheduledDelay) + return base::nullopt; + + for (const auto& name : option.names_for_rate_limit) { + next_available_time_for_name_[name] = + next_available_time_for_response + kMinIntervalBetweenSameRecord; + } + return delay; +} + +MdnsResponseSendOption::MdnsResponseSendOption() = default; +MdnsResponseSendOption::~MdnsResponseSendOption() = default; + +MdnsResponderManager::MdnsResponderManager() : MdnsResponderManager(nullptr) {} + +MdnsResponderManager::MdnsResponderManager( + net::MDnsSocketFactory* socket_factory) + : socket_factory_(socket_factory), + name_generator_(std::make_unique<RandomUuidNameGenerator>()) { + if (!socket_factory_) { + owned_socket_factory_ = net::MDnsSocketFactory::CreateDefault(); + socket_factory_ = owned_socket_factory_.get(); + } + Start(); +} + +MdnsResponderManager::~MdnsResponderManager() { + // When destroyed, each responder will send out Goodbye messages for owned + // names via the back pointer to the manager. As a result, we should destroy + // the remaining responders before the manager is destroyed. + responders_.clear(); +} + +void MdnsResponderManager::Start() { + VLOG(1) << "Starting mDNS responder manager."; + DCHECK(start_result_ == SocketHandlerStartResult::UNSPECIFIED); + DCHECK(socket_handler_by_id_.empty()); + std::vector<std::unique_ptr<net::DatagramServerSocket>> sockets; + // Create and return only bound sockets. + socket_factory_->CreateSockets(&sockets); + + uint16_t next_available_id = 1; + for (std::unique_ptr<net::DatagramServerSocket>& socket : sockets) { + socket_handler_by_id_.emplace( + next_available_id, + std::make_unique<MdnsResponderManager::SocketHandler>( + next_available_id, std::move(socket), this)); + ++next_available_id; + } + + for (auto it = socket_handler_by_id_.begin(); + it != socket_handler_by_id_.end();) { + // Start to process untrusted input. + int rv = it->second->Start(); + if (rv == net::OK) { + ++it; + } else { + VLOG(1) << "Start failed, socket=" << it->second->id() + << ", error=" << rv; + it = socket_handler_by_id_.erase(it); + } + } + size_t num_started_socket_handlers = socket_handler_by_id_.size(); + if (socket_handler_by_id_.empty()) { + start_result_ = SocketHandlerStartResult::ALL_FAILURE; + LOG(ERROR) << "mDNS responder manager failed to started."; + return; + } + + if (num_started_socket_handlers == next_available_id) { + start_result_ = SocketHandlerStartResult::ALL_SUCCESS; + return; + } + + start_result_ = SocketHandlerStartResult::PARTIAL_SUCCESS; +} + +void MdnsResponderManager::CreateMdnsResponder( + mojom::MdnsResponderRequest request) { + if (start_result_ == SocketHandlerStartResult::UNSPECIFIED || + start_result_ == SocketHandlerStartResult::ALL_FAILURE) { + LOG(ERROR) << "The mDNS responder manager is not started yet."; + request = nullptr; + return; + } + auto responder = std::make_unique<MdnsResponder>(std::move(request), this); + responders_.insert(std::move(responder)); +} + +bool MdnsResponderManager::Send(scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option) { + DCHECK(buf != nullptr); + bool all_success = true; + if (option->send_socket_handler_ids.empty()) { + for (auto& id_handler_pair : socket_handler_by_id_) + all_success &= id_handler_pair.second->Send(buf, option); + + return all_success; + } + for (auto id : option->send_socket_handler_ids) { + DCHECK(socket_handler_by_id_.find(id) != socket_handler_by_id_.end()); + all_success &= socket_handler_by_id_[id]->Send(buf, option); + } + return all_success; +} + +void MdnsResponderManager::OnMojoConnectionError(MdnsResponder* responder) { + auto it = responders_.find(responder); + DCHECK(it != responders_.end()); + responders_.erase(it); +} + +void MdnsResponderManager::SetNameGeneratorForTesting( + std::unique_ptr<MdnsResponderManager::NameGenerator> name_generator) { + name_generator_ = std::move(name_generator); + for (auto& responder : responders_) + responder->SetNameGeneratorForTesting(name_generator_.get()); +} + +void MdnsResponderManager::SetTickClockForTesting( + const base::TickClock* tick_clock) { + for (auto& id_handler_pair : socket_handler_by_id_) { + id_handler_pair.second->SetTickClockForTesting(tick_clock); + } +} + +void MdnsResponderManager::HandleNameConflictIfAny( + const std::map<std::string, std::set<net::IPAddress>>& external_maps) { + for (const auto& name_to_addresses : external_maps) { + for (auto& responder : responders_) { + if (responder->HasConflictWithExternalResolution( + name_to_addresses.first, {name_to_addresses.second})) { + // In the rare case when we encounter conflicting resolutions for a + // randomly generated name, We close the connection and let the other + // side of the pipe observe and handle the error, which could possibly + // rebind to a responder and generate new names. + OnMojoConnectionError(responder.get()); + // Since each name is uniquely owned by one instance of responders, we + // can stop searching for this name once we find one conflict. + break; + } + } + } +} + +void MdnsResponderManager::OnMdnsQueryReceived( + const net::DnsQuery& query, + uint16_t recv_socket_handler_id) { + for (auto& responder : responders_) + responder->OnMdnsQueryReceived(query, recv_socket_handler_id); +} + +void MdnsResponderManager::OnSocketHandlerReadError(uint16_t socket_handler_id, + int result) { + auto it = socket_handler_by_id_.find(socket_handler_id); + DCHECK(it != socket_handler_by_id_.end()); + // It is safe to remove the handler in error since this error handler is + // invoked by the callback after the asynchronous return of RecvFrom, when the + // handler has exited the read loop. + socket_handler_by_id_.erase(it); + VLOG(1) << "Socket read error, socket=" << socket_handler_id + << ", error=" << result; + if (socket_handler_by_id_.empty()) { + LOG(ERROR) + << "All socket handlers failed. Restarting the mDNS responder manager."; + start_result_ = MdnsResponderManager::SocketHandlerStartResult::UNSPECIFIED; + Start(); + } +} + +void MdnsResponderManager::SocketHandler::HandlePacket(int result) { + if (result <= 0) + return; + + net::DnsQuery query(io_buffer_.get()); + bool parsed_as_query = query.Parse(result); + if (parsed_as_query) { + responder_manager_->OnMdnsQueryReceived(query, id_); + } else { + net::DnsResponse response(io_buffer_.get(), io_buffer_->size()); + if (response.InitParseWithoutQuery(io_buffer_->size()) && + response.answer_count() > 0) { + // There could be multiple records for the same name in the response. + std::map<std::string, std::set<net::IPAddress>> external_maps; + auto parser = response.Parser(); + for (size_t i = 0; i < response.answer_count(); ++i) { + auto parsed_record = + net::RecordParsed::CreateFrom(&parser, base::Time::Now()); + if (!parsed_record || !parsed_record->ttl()) + continue; + + switch (parsed_record->type()) { + case net::ARecordRdata::kType: + external_maps[parsed_record->name()].insert( + parsed_record->rdata<net::ARecordRdata>()->address()); + break; + case net::AAAARecordRdata::kType: + external_maps[parsed_record->name()].insert( + parsed_record->rdata<net::AAAARecordRdata>()->address()); + break; + default: + break; + } + } + responder_manager_->HandleNameConflictIfAny(external_maps); + } + } +} + +MdnsResponder::MdnsResponder(mojom::MdnsResponderRequest request, + MdnsResponderManager* manager) + : binding_(this, std::move(request)), + manager_(manager), + name_generator_(manager_->name_generator()) { + binding_.set_connection_error_handler( + base::BindOnce(&MdnsResponderManager::OnMojoConnectionError, + base::Unretained(manager_), this)); +} + +MdnsResponder::~MdnsResponder() { + SendGoodbyePacketForNameAddressMap(name_addr_map_); +} + +void MdnsResponder::CreateNameForAddress( + const net::IPAddress& address, + mojom::MdnsResponder::CreateNameForAddressCallback callback) { + std::string name; + auto it = FindNameCreatedForAddress(address); + bool announcement_sched_at_least_once = false; + if (it == name_addr_map_.end()) { + name = name_generator_->CreateName() + ".local"; +#ifdef DEBUG + // The name should be uniquely owned by one instance of responders. + DCHECK(manager_->AddName(name)); +#endif + name_addr_map_[name] = address; + DCHECK(name_refcount_map_.find(name) == name_refcount_map_.end()); + name_refcount_map_[name] = 1; + // RFC 6762, Section 8.3. + // + // Send mDNS announcements, one second apart, for the newly created + // name-address association. The scheduler will pace the announcements. + std::map<std::string, net::IPAddress> map_to_announce({{name, address}}); + auto option = base::MakeRefCounted<MdnsResponseSendOption>(); + // Send on all interfaces. + option->klass = MdnsResponseSendOption::ResponseClass::ANNOUNCEMENT; + for (int i = 0; i < kMinNumAnnouncementsToSend; ++i) { + bool announcement_scheduled = SendMdnsResponse( + mdns_helper::CreateResolutionResponse( + kDefaultTtlForRecordWithHostname, map_to_announce), + option); + announcement_sched_at_least_once |= announcement_scheduled; + if (!announcement_scheduled) + break; + } + } else { + name = it->first; + DCHECK(name_refcount_map_.find(name) != name_refcount_map_.end()); + ++name_refcount_map_[name]; + } + std::move(callback).Run(name, announcement_sched_at_least_once); +} + +void MdnsResponder::RemoveNameForAddress( + const net::IPAddress& address, + mojom::MdnsResponder::RemoveNameForAddressCallback callback) { + auto it = FindNameCreatedForAddress(address); + if (it == name_addr_map_.end()) { + std::move(callback).Run(false /* removed */, false /* goodbye_scheduled */); + return; + } + std::string name = it->first; + DCHECK(name_refcount_map_.find(name) != name_refcount_map_.end()); + auto refcount = --name_refcount_map_[name]; + bool goodbye_scheduled = false; + if (refcount == 0) { + goodbye_scheduled = SendGoodbyePacketForNameAddressMap({*it}); +#ifdef DEBUG + // The name removed should be previously owned by one instance of + // responders. + DCHECK(manager_->RemoveName(name)); +#endif + name_refcount_map_.erase(name); + name_addr_map_.erase(it); + } + DCHECK(refcount == 0 || !goodbye_scheduled); + std::move(callback).Run(refcount == 0, goodbye_scheduled); +} + +void MdnsResponder::OnMdnsQueryReceived(const net::DnsQuery& query, + uint16_t recv_socket_handler_id) { + // Currently we only support a single question in DnsQuery. + std::string dotted_name_to_resolve = net::DNSDomainToString(query.qname()); + auto it = name_addr_map_.find(dotted_name_to_resolve); + if (it == name_addr_map_.end()) + return; + + std::map<std::string, net::IPAddress> map_to_respond({*it}); + auto option = base::MakeRefCounted<MdnsResponseSendOption>(); + option->send_socket_handler_ids.insert(recv_socket_handler_id); + option->names_for_rate_limit.insert(it->first); + if (!QueryTypeAndAddressFamilyAreCompatible(query.qtype(), + GetAddressFamily(it->second))) { + // The query asks for a record that does not exist for the name and we send + // a negative response. + option->klass = MdnsResponseSendOption::ResponseClass::NEGATIVE; + + SendMdnsResponse(mdns_helper::CreateNegativeResponse(map_to_respond), + std::move(option)); + return; + } + // TODO(qingsi): Once we update DnsQuery and IsProbeQuery to properly detect + // probe queries (see the comment inside IsProbeQuery), we should check the + // probe queries first for conflicting records of names we own, and send the + // negative responses without rate limiting. In other words, the check above + // with QueryTypeAndAddressFamilyAreCompatible that results in the per-record + // rate limiting should not apply to negative responses to probe queries. + if (IsProbeQuery(query)) + option->klass = MdnsResponseSendOption::ResponseClass::PROBE_RESOLUTION; + else + option->klass = MdnsResponseSendOption::ResponseClass::REGULAR_RESOLUTION; + + // Send the name resolution for the received query. + SendMdnsResponse(mdns_helper::CreateResolutionResponse( + kDefaultTtlForRecordWithHostname, map_to_respond), + std::move(option)); +} + +bool MdnsResponder::HasConflictWithExternalResolution( + const std::string& name, + const std::set<net::IPAddress>& external_mapped_addreses) { + DCHECK(!external_mapped_addreses.empty()); + auto matching_record_it = name_addr_map_.find(name); + if (matching_record_it == name_addr_map_.end()) + return false; + + if (external_mapped_addreses.size() == 1 && + *external_mapped_addreses.begin() == matching_record_it->second) { + VLOG(1) << "Received an external response for an owned record."; + return false; + } + + LOG(ERROR) << "Received conflicting resolution for name: " << name; + return true; +} + +bool MdnsResponder::SendMdnsResponse( + scoped_refptr<net::IOBufferWithSize> response, + scoped_refptr<MdnsResponseSendOption> option) { + DCHECK_NE(MdnsResponseSendOption::ResponseClass::UNSPECIFIED, option->klass); + return manager_->Send(std::move(response), std::move(option)); +} + +bool MdnsResponder::SendGoodbyePacketForNameAddressMap( + const std::map<std::string, net::IPAddress>& name_addr_map) { + if (name_addr_map.empty()) + return false; + + auto option = base::MakeRefCounted<MdnsResponseSendOption>(); + // Send on all interfaces. + option->klass = MdnsResponseSendOption::ResponseClass::GOODBYE; + return SendMdnsResponse(mdns_helper::CreateResolutionResponse( + base::TimeDelta() /* ttl */, name_addr_map), + std::move(option)); +} + +std::map<std::string, net::IPAddress>::iterator +MdnsResponder::FindNameCreatedForAddress(const net::IPAddress& address) { + auto ret = name_addr_map_.end(); + size_t count = 0; + for (auto it = name_addr_map_.begin(); it != name_addr_map_.end(); ++it) { + if (it->second == address) { + ret = it; + ++count; + DCHECK(count <= 1); + } + } + return ret; +} + +} // namespace network diff --git a/chromium/services/network/mdns_responder.h b/chromium/services/network/mdns_responder.h new file mode 100644 index 00000000000..af5f4c3f68c --- /dev/null +++ b/chromium/services/network/mdns_responder.h @@ -0,0 +1,268 @@ +// Copyright 2018 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. + +#ifndef SERVICES_NETWORK_MDNS_RESPONDER_H_ +#define SERVICES_NETWORK_MDNS_RESPONDER_H_ + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "base/containers/flat_set.h" +#include "base/containers/unique_ptr_adapters.h" +#include "mojo/public/cpp/bindings/binding_set.h" +#include "net/dns/dns_query.h" +#include "net/dns/dns_response.h" +#include "services/network/public/mojom/mdns_responder.mojom.h" + +namespace base { +class TickClock; +} // namespace base + +namespace net { +class IOBufferWithSize; +class IPAddress; +class MDnsSocketFactory; +} // namespace net + +namespace network { + +class MdnsResponder; + +namespace mdns_helper { + +// Creates an mDNS response, of which the Answer section contains the address +// records for |name_addr_map|, and the Additional section contains the +// corresponding NSEC records that assert the existence of only address +// records in the Answer section. +COMPONENT_EXPORT(NETWORK_SERVICE) +scoped_refptr<net::IOBufferWithSize> CreateResolutionResponse( + const base::TimeDelta& ttl, + const std::map<std::string, net::IPAddress>& name_addr_map); +// Creates an mDNS response, of which the Answer section contains NSEC records +// that assert the existence of only address records for |name_addr_map|, and +// the Additional section contains the corresponding address records. +COMPONENT_EXPORT(NETWORK_SERVICE) +scoped_refptr<net::IOBufferWithSize> CreateNegativeResponse( + const std::map<std::string, net::IPAddress>& name_addr_map); + +} // namespace mdns_helper + +// Options to configure the transmission of mDNS responses. +struct COMPONENT_EXPORT(NETWORK_SERVICE) MdnsResponseSendOption + : public base::RefCounted<MdnsResponseSendOption> { + public: + enum class ResponseClass { + UNSPECIFIED, + ANNOUNCEMENT, + PROBE_RESOLUTION, + REGULAR_RESOLUTION, + NEGATIVE, + GOODBYE, + }; + + MdnsResponseSendOption(); + // As a shorthand, an empty set denotes all interfaces. + std::set<uint16_t> send_socket_handler_ids; + // Used for rate limiting. + base::flat_set<std::string> names_for_rate_limit; + // Used for retry after send failure. + ResponseClass klass = ResponseClass::UNSPECIFIED; + // The number of retries done for the same response due to send failure. + uint8_t num_send_retries_done = 0; + + private: + friend class RefCounted<MdnsResponseSendOption>; + + ~MdnsResponseSendOption(); +}; + +// The responder manager creates and manages responder instances spawned for +// each Mojo binding. It also manages the underlying network IO by delegating +// the IO task to socket handlers of each interface. When there is a network +// stack error or a Mojo binding error, the manager also offers the +// corresponding error handling. +class COMPONENT_EXPORT(NETWORK_SERVICE) MdnsResponderManager { + public: + // Wraps a name generation method that can be configured in tests via + // SetNameGeneratorForTesting below. + class NameGenerator { + public: + virtual ~NameGenerator() = default; + virtual std::string CreateName() = 0; + }; + + MdnsResponderManager(); + explicit MdnsResponderManager(net::MDnsSocketFactory* socket_factory); + ~MdnsResponderManager(); + + // Creates an instance of MdnsResponder for the binding request from an + // InterfacePtr. + void CreateMdnsResponder(mojom::MdnsResponderRequest request); +#ifdef DEBUG + // The methods below are only used for extra uniqueness validation of names + // owned by responders. By default, we use the RandomUuidNameGenerator (see + // mdns_responder.cc), which probabilistically guarantees the uniqueness of + // generated names. + // + // Adds a name to the set of all existing names generated by all responders + // (i.e., names owned by an instance of responder). Return true if the name is + // not in the set before the addition; false otherwise. + bool AddName(const std::string& name) { + auto result = names_.insert(name); + return result.second; + } + // Removes a name from the set of all existing names generated by all + // responders. Return true if the name exists in the set before the removal; + // false otherwise. + bool RemoveName(const std::string& name) { return names_.erase(name) == 1; } +#endif + // Sends an mDNS response in the wire format given by |buf|. See + // MdnsResponseSendOption for configurable options in |option|. + // + // Sending responses is rate-limited, and this method returns true if the + // response is successfully scheduled to send on all successfully bound + // interfaces specified in |option.send_socket_handler_ids|, and false + // otherwise. + bool Send(scoped_refptr<net::IOBufferWithSize> buf, + scoped_refptr<MdnsResponseSendOption> option); + // The error handler that is invoked when the Mojo binding of |responder| is + // closed (e.g. the InterfacePtr on the client side is destroyed) or + // encounters an error. It removes this responder instance, which further + // clears the existing name-address associations owned by this responder in + // the local network. + void OnMojoConnectionError(MdnsResponder* responder); + + // Called when an external mDNS response is received and it contains + // records of names generated by an owned MdnsResponder instance. + void HandleNameConflictIfAny( + const std::map<std::string, std::set<net::IPAddress>>& external_maps); + + NameGenerator* name_generator() const { return name_generator_.get(); } + // Sets the name generator that is shared by all MdnsResponder instances. + // Changing the name generator affects all existing responder instances and + // also the ones spawned in the future. + // + // Used for tests only. + void SetNameGeneratorForTesting( + std::unique_ptr<NameGenerator> name_generator); + + // Sets the tick clock that is used for rate limiting of mDNS responses, and + // also resets the internal schedule for rate limiting. + // + // Used for tests only. + void SetTickClockForTesting(const base::TickClock* tick_clock); + + private: + enum class SocketHandlerStartResult { + UNSPECIFIED, + // Handlers started for all interfaces. + ALL_SUCCESS, + // Handlers started for a subset of interfaces. + PARTIAL_SUCCESS, + // No handler started. + ALL_FAILURE, + }; + // Handles the underlying sending and receiving of mDNS messages on each + // interface available. The implementation mostly resembles + // net::MdnsConnection::SocketHandler. + class SocketHandler; + + // Initializes socket handlers and sets |start_result_|; + void Start(); + // Dispatches a parsed query from a socket handler to each responder instance. + void OnMdnsQueryReceived(const net::DnsQuery& query, + uint16_t recv_socket_handler_id); + void OnSocketHandlerReadError(uint16_t socket_handler_id, int result); + + std::unique_ptr<net::MDnsSocketFactory> owned_socket_factory_; + net::MDnsSocketFactory* socket_factory_; + // Only the socket handlers that have successfully bound and started are kept. + std::map<uint16_t, std::unique_ptr<SocketHandler>> socket_handler_by_id_; + SocketHandlerStartResult start_result_ = + SocketHandlerStartResult::UNSPECIFIED; +#ifdef DEBUG + // Used in debug only for the extra uniqueness validation of names generated + // by responders. + std::set<std::string> names_; +#endif + std::unique_ptr<NameGenerator> name_generator_; + std::set<std::unique_ptr<MdnsResponder>, base::UniquePtrComparator> + responders_; + + DISALLOW_COPY_AND_ASSIGN(MdnsResponderManager); +}; + +// Implementation of the mDNS service that can provide utilities of an mDNS +// responder. +class COMPONENT_EXPORT(NETWORK_SERVICE) MdnsResponder + : public mojom::MdnsResponder { + public: + MdnsResponder(mojom::MdnsResponderRequest request, + MdnsResponderManager* manager); + // When destroyed, clears all existing name-address associations owned by this + // responder in the local network by sending out goodbye packets. See + // SendGoodbyePacketForNameAddressMap below. + ~MdnsResponder() override; + + // mojom::MdnsResponder overrides. + void CreateNameForAddress( + const net::IPAddress& address, + mojom::MdnsResponder::CreateNameForAddressCallback callback) override; + void RemoveNameForAddress( + const net::IPAddress& address, + mojom::MdnsResponder::RemoveNameForAddressCallback callback) override; + + // Processes the given query and generates a response if the query contains an + // mDNS name that this responder has a mapped IP address. + void OnMdnsQueryReceived(const net::DnsQuery& query, + uint16_t recv_socket_handler_id); + + bool HasConflictWithExternalResolution( + const std::string& name, + const std::set<net::IPAddress>& external_mapped_addreses); + + void SetNameGeneratorForTesting( + MdnsResponderManager::NameGenerator* name_generator) { + name_generator_ = name_generator; + } + + private: + // Returns true if the response is successfully scheduled to send on all + // successfully bound interfaces after rate limiting, and false otherwise. See + // also MdnsResponderManager::Send. + bool SendMdnsResponse(scoped_refptr<net::IOBufferWithSize> response, + scoped_refptr<MdnsResponseSendOption> option); + // RFC 6761, Section 10.1 "Goodbye Packets". + // + // The responder should send out an unsolicited mDNS response with an resource + // record of zero TTL to clear the name-to-address mapping in neighboring + // hosts, when the mapping is no longer valid. + // + // Returns true if the goodbye message is successfully scheduled to send on + // all interfaces after rate limiting, and false otherwise. See also + // MdnsResponderManager::Send. + bool SendGoodbyePacketForNameAddressMap( + const std::map<std::string, net::IPAddress>& name_addr_map); + + std::map<std::string, net::IPAddress>::iterator FindNameCreatedForAddress( + const net::IPAddress& address); + + mojo::Binding<network::mojom::MdnsResponder> binding_; + // A back pointer to the responder manager that owns this responder. The + // responder should be destroyed before |manager_| becomes invalid or a weak + // reference should be used to access the manager when there is no such + // guarantee in an operation. + MdnsResponderManager* const manager_; + std::map<std::string, net::IPAddress> name_addr_map_; + std::map<std::string, uint16_t> name_refcount_map_; + MdnsResponderManager::NameGenerator* name_generator_; + + DISALLOW_COPY_AND_ASSIGN(MdnsResponder); +}; + +} // namespace network + +#endif // SERVICES_NETWORK_MDNS_RESPONDER_H_ diff --git a/chromium/services/network/mdns_responder_unittest.cc b/chromium/services/network/mdns_responder_unittest.cc new file mode 100644 index 00000000000..cb3a0515707 --- /dev/null +++ b/chromium/services/network/mdns_responder_unittest.cc @@ -0,0 +1,974 @@ +// Copyright 2018 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 <map> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "services/network/mdns_responder.h" + +#include "base/bind.h" +#include "base/logging.h" +#include "base/memory/scoped_refptr.h" +#include "base/strings/string_piece.h" +#include "base/test/test_mock_time_task_runner.h" +#include "net/base/ip_address.h" +#include "net/dns/dns_query.h" +#include "net/dns/dns_response.h" +#include "net/dns/dns_util.h" +#include "net/dns/mock_mdns_socket_factory.h" +#include "net/dns/public/dns_protocol.h" +#include "services/network/public/mojom/mdns_responder.mojom.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::_; + +namespace { + +const net::IPAddress kPublicAddrs[2] = {net::IPAddress(11, 11, 11, 11), + net::IPAddress(22, 22, 22, 22)}; +const net::IPAddress kPublicAddrsIpv6[2] = { + net::IPAddress(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), + net::IPAddress(16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)}; + +const base::TimeDelta kDefaultTtl = base::TimeDelta::FromSeconds(120); + +const int kNumAnnouncementsPerInterface = 2; +const int kNumMaxRetriesPerResponse = 2; + +std::string CreateMdnsQuery(uint16_t query_id, + const std::string& dotted_name, + uint16_t qtype = net::dns_protocol::kTypeA) { + std::string qname; + net::DNSDomainFromDot(dotted_name, &qname); + net::DnsQuery query(query_id, qname, qtype); + return std::string(query.io_buffer()->data(), query.io_buffer()->size()); +} + +// Creates an mDNS response as announcement, resolution for queries or goodbye. +std::string CreateResolutionResponse( + const base::TimeDelta& ttl, + const std::map<std::string, net::IPAddress>& name_addr_map) { + auto buf = network::mdns_helper::CreateResolutionResponse(ttl, name_addr_map); + DCHECK(buf != nullptr); + return std::string(buf->data(), buf->size()); +} + +std::string CreateNegativeResponse( + const std::map<std::string, net::IPAddress>& name_addr_map) { + auto buf = network::mdns_helper::CreateNegativeResponse(name_addr_map); + DCHECK(buf != nullptr); + return std::string(buf->data(), buf->size()); +} + +// A mock mDNS socket factory to create sockets that can fail sending or +// receiving packets. +class MockFailingMdnsSocketFactory : public net::MDnsSocketFactory { + public: + MockFailingMdnsSocketFactory( + scoped_refptr<base::TestMockTimeTaskRunner> task_runner) + : task_runner_(std::move(task_runner)) {} + + ~MockFailingMdnsSocketFactory() override = default; + + MOCK_METHOD1(CreateSockets, + void(std::vector<std::unique_ptr<net::DatagramServerSocket>>*)); + + MOCK_METHOD1(OnSendTo, void(const std::string&)); + + // Emulates the asynchronous contract of invoking |callback| in the SendTo + // primitive but failed sending; + int FailToSend(const std::string& packet, + const std::string& address, + net::CompletionRepeatingCallback callback) { + OnSendTo(packet); + task_runner_->PostTask( + FROM_HERE, + base::BindOnce( + [](net::CompletionRepeatingCallback callback) { callback.Run(-1); }, + callback)); + return -1; + } + + // Emulates the asynchronous contract of invoking |callback| in the RecvFrom + // primitive but failed receiving; + int FailToRecv(net::IOBuffer* buffer, + int size, + net::IPEndPoint* address, + net::CompletionRepeatingCallback callback) { + task_runner_->PostTask( + FROM_HERE, + base::BindOnce( + [](net::CompletionRepeatingCallback callback) { callback.Run(-1); }, + callback)); + return -1; + } + + private: + scoped_refptr<base::TestMockTimeTaskRunner> task_runner_; +}; + +} // namespace + +namespace network { + +// Tests of the response creation helpers. For positive responses, we have the +// address records in the Answer section and, if TTL is nonzero, the +// corresponding NSEC records in the Additional section. For negative responses, +// the NSEC records are placed in the Answer section with the address records in +// the Answer section. +TEST(CreateMdnsResponseTest, SingleARecordAnswer) { + const char response_data[]{ + 0x00, 0x00, // mDNS response ID mus be zero. + 0x84, 0x00, // flags, response with authoritative answer + 0x00, 0x00, // number of questions + 0x00, 0x01, // number of answer rr + 0x00, 0x00, // number of name server rr + 0x00, 0x01, // number of additional rr + 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', + 'o', 'm', + 0x00, // null label + 0x00, 0x01, // type A Record + 0x80, 0x01, // class IN, cache-flush bit set + 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds + 0x00, 0x04, // rdlength, 32 bits + 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 + // Additional section + 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', + 'o', 'm', + 0x00, // null label + 0x00, 0x2f, // type NSEC Record + 0x80, 0x01, // class IN, cache-flush bit set + 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds + 0x00, 0x05, // rdlength, 5 bytes + 0xc0, 0x2b, // pointer to the previous "www.example.com" + 0x00, 0x01, 0x40, // type bit map of type A: window block 0, bitmap + // length 1, bitmap with bit 1 set + }; + + std::string expected_response(response_data, sizeof(response_data)); + std::string actual_response = CreateResolutionResponse( + kDefaultTtl, + {{"www.example.com", net::IPAddress(0xc0, 0xa8, 0x00, 0x01)}}); + EXPECT_EQ(expected_response, actual_response); +} + +TEST(CreateMdnsResponseTest, SingleARecordGoodbye) { + const char response_data[]{ + 0x00, 0x00, // mDNS response ID mus be zero. + 0x84, 0x00, // flags, response with authoritative answer + 0x00, 0x00, // number of questions + 0x00, 0x01, // number of answer rr + 0x00, 0x00, // number of name server rr + 0x00, 0x00, // number of additional rr + 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', + 'm', 'p', 'l', 'e', 0x03, 'c', 'o', 'm', + 0x00, // null label + 0x00, 0x01, // type A Record + 0x80, 0x01, // class IN, cache-flush bit set + 0x00, 0x00, 0x00, 0x00, // zero TTL + 0x00, 0x04, // rdlength, 32 bits + 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 + }; + + std::string expected_response(response_data, sizeof(response_data)); + std::string actual_response = CreateResolutionResponse( + base::TimeDelta(), + {{"www.example.com", net::IPAddress(0xc0, 0xa8, 0x00, 0x01)}}); + EXPECT_EQ(expected_response, actual_response); +} + +TEST(CreateMdnsResponseTest, SingleQuadARecordAnswer) { + const char response_data[] = { + 0x00, 0x00, // mDNS response ID mus be zero. + 0x84, 0x00, // flags, response with authoritative answer + 0x00, 0x00, // number of questions + 0x00, 0x01, // number of answer rr + 0x00, 0x00, // number of name server rr + 0x00, 0x01, // number of additional rr + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'o', 'r', 'g', + 0x00, // null label + 0x00, 0x1c, // type AAAA Record + 0x80, 0x01, // class IN, cache-flush bit set + 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds + 0x00, 0x10, // rdlength, 128 bits + 0xfd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0x00, 0x01, // fd12:3456:789a:1::1 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // Additional section + 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'o', 'r', 'g', + 0x00, // null label + 0x00, 0x2f, // type NSEC Record + 0x80, 0x01, // class IN, cache-flush bit set + 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds + 0x00, 0x08, // rdlength, 8 bytes + 0xc0, 0x33, // pointer to the previous "example.org" + 0x00, 0x04, 0x00, 0x00, 0x00, + 0x08, // type bit map of type AAAA: window block 0, bitmap + // length 4, bitmap with bit 28 set + }; + std::string expected_response(response_data, sizeof(response_data)); + std::string actual_response = CreateResolutionResponse( + kDefaultTtl, + {{"example.org", + net::IPAddress(0xfd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01)}}); + EXPECT_EQ(expected_response, actual_response); +} + +TEST(CreateMdnsResponseTest, SingleNsecRecordAnswer) { + const char response_data[] = { + 0x00, 0x00, // mDNS response ID mus be zero. + 0x84, 0x00, // flags, response with authoritative answer + 0x00, 0x00, // number of questions + 0x00, 0x01, // number of answer rr + 0x00, 0x00, // number of name server rr + 0x00, 0x01, // number of additional rr + 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', + 'o', 'm', + 0x00, // null label + 0x00, 0x2f, // type NSEC Record + 0x80, 0x01, // class IN, cache-flush bit set + 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds + 0x00, 0x05, // rdlength, 5 bytes + 0xc0, 0x0c, // pointer to the previous "www.example.com" + 0x00, 0x01, 0x40, // type bit map of type A: window block 0, bitmap + // length 1, bitmap with bit 1 set + // Additional section + 0x03, 'w', 'w', 'w', 0x07, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0x03, 'c', + 'o', 'm', + 0x00, // null label + 0x00, 0x01, // type A Record + 0x80, 0x01, // class IN, cache-flush bit set + 0x00, 0x00, 0x00, 0x78, // TTL, 120 seconds + 0x00, 0x04, // rdlength, 32 bits + 0xc0, 0xa8, 0x00, 0x01, // 192.168.0.1 + }; + std::string expected_response(response_data, sizeof(response_data)); + std::string actual_response = CreateNegativeResponse( + {{"www.example.com", net::IPAddress(0xc0, 0xa8, 0x00, 0x01)}}); + EXPECT_EQ(expected_response, actual_response); +} + +class SimpleNameGenerator : public MdnsResponderManager::NameGenerator { + public: + std::string CreateName() override { + return std::to_string(next_available_id_++); + } + + private: + uint32_t next_available_id_ = 0; +}; + +// Test suite for the mDNS responder utilities provided by the service. +class MdnsResponderTest : public testing::Test { + public: + MdnsResponderTest() + : test_task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>()), + task_runner_override_scoped_cleanup_( + base::ThreadTaskRunnerHandle::OverrideForTesting( + test_task_runner_)), + failing_socket_factory_(test_task_runner_) { + Reset(); + } + + void Reset(bool use_failing_socket_factory = false) { + client_[0].reset(); + client_[1].reset(); + if (use_failing_socket_factory) + host_manager_ = + std::make_unique<MdnsResponderManager>(&failing_socket_factory_); + else + host_manager_ = std::make_unique<MdnsResponderManager>(&socket_factory_); + + host_manager_->SetNameGeneratorForTesting( + std::make_unique<SimpleNameGenerator>()); + host_manager_->SetTickClockForTesting( + test_task_runner_->GetMockTickClock()); + CreateMdnsResponders(); + } + + void CreateMdnsResponders() { + auto request1 = mojo::MakeRequest(&client_[0]); + client_[0].set_connection_error_handler(base::BindOnce( + &MdnsResponderTest::OnMojoConnectionError, base::Unretained(this), 0)); + host_manager_->CreateMdnsResponder(std::move(request1)); + auto request2 = mojo::MakeRequest(&client_[1]); + client_[1].set_connection_error_handler(base::BindOnce( + &MdnsResponderTest::OnMojoConnectionError, base::Unretained(this), 1)); + host_manager_->CreateMdnsResponder(std::move(request2)); + } + + // The following method is synchronous for testing by waiting on running the + // task runner. + std::string CreateNameForAddress(int client_id, const net::IPAddress& addr) { + client_[client_id]->CreateNameForAddress( + addr, base::BindOnce(&MdnsResponderTest::OnNameCreatedForAddress, + base::Unretained(this), addr)); + RunUntilNoTasksRemain(); + return last_name_created_; + } + + void RemoveNameForAddress(int client_id, + const net::IPAddress& addr, + bool expected_removed, + bool expected_goodbye_sched) { + client_[client_id]->RemoveNameForAddress( + addr, base::BindOnce(&MdnsResponderTest::OnNameRemovedForAddress, + base::Unretained(this), expected_removed, + expected_goodbye_sched)); + } + + void RemoveNameForAddressAndExpectDone(int client_id, + const net::IPAddress& addr) { + RemoveNameForAddress(client_id, addr, true, true); + } + + void CloseConnectionToResponderHost(int client_id) { + client_[client_id].reset(); + } + + void OnMojoConnectionError(int client_id) { client_[client_id].reset(); } + + protected: + void OnNameCreatedForAddress(const net::IPAddress& addr, + const std::string& name, + bool announcement_scheduled) { + last_name_created_ = name; + } + + void OnNameRemovedForAddress(bool expected_removed, + bool expected_goodbye_scheduled, + bool actual_removed, + bool actual_goodbye_scheduled) { + EXPECT_EQ(expected_removed, actual_removed); + EXPECT_EQ(expected_goodbye_scheduled, actual_goodbye_scheduled); + } + + void RunUntilNoTasksRemain() { + test_task_runner_->FastForwardUntilNoTasksRemain(); + } + void RunFor(base::TimeDelta duration) { + test_task_runner_->FastForwardBy(duration); + } + + mojom::MdnsResponderPtr client_[2]; + std::unique_ptr<MdnsResponderManager> host_manager_; + // Overrides the current thread task runner, so we can simulate the passage + // of time and avoid any actual sleeps. + scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_; + base::ScopedClosureRunner task_runner_override_scoped_cleanup_; + NiceMock<net::MockMDnsSocketFactory> socket_factory_; + NiceMock<MockFailingMdnsSocketFactory> failing_socket_factory_; + std::string last_name_created_; +}; + +// Test that a name-to-address map does not change for the same client after +// it is created. +TEST_F(MdnsResponderTest, PersistentNameAddressMapForTheSameClient) { + const auto& addr1 = kPublicAddrs[0]; + const auto& addr2 = kPublicAddrs[1]; + const auto name1 = CreateNameForAddress(0, addr1); + const auto name2 = CreateNameForAddress(0, addr2); + EXPECT_NE(name1, name2); + EXPECT_EQ(name1, CreateNameForAddress(0, addr1)); +} + +// Test that a name-to-address map can be removed when reaching zero refcount +// and can be updated afterwards. +TEST_F(MdnsResponderTest, NameAddressMapCanBeRemovedByOwningClient) { + const auto& addr = kPublicAddrs[0]; + const auto prev_name = CreateNameForAddress(0, addr); + RemoveNameForAddressAndExpectDone(0, addr); + RunUntilNoTasksRemain(); + EXPECT_NE(prev_name, CreateNameForAddress(0, addr)); +} + +// Test that a name-to-address map is not removed with a positive refcount. +TEST_F(MdnsResponderTest, + NameAddressMapCanOnlyBeRemovedWhenReachingZeroRefcount) { + const auto& addr = kPublicAddrs[0]; + const auto prev_name = CreateNameForAddress(0, addr); + EXPECT_EQ(prev_name, CreateNameForAddress(0, addr)); + RemoveNameForAddress(0, addr, false /* expected removed */, + false /* expected goodbye scheduled */); + RunUntilNoTasksRemain(); + EXPECT_EQ(prev_name, CreateNameForAddress(0, addr)); +} + +// Test that different clients have isolated space of name-to-address maps. +TEST_F(MdnsResponderTest, ClientsHaveIsolatedNameSpaceForAddresses) { + const net::IPAddress& addr = kPublicAddrs[0]; + // The same address is mapped to different names for different clients. + const auto name_client1 = CreateNameForAddress(0, addr); + const auto name_client2 = CreateNameForAddress(1, addr); + EXPECT_NE(name_client1, name_client2); + // Removing a name-address association by client 2 does not change the + // mapping for client 1. + RemoveNameForAddressAndExpectDone(1, addr); + EXPECT_EQ(name_client1, CreateNameForAddress(0, addr)); +} + +// Test that the mDNS responder sends an mDNS response to announce the +// ownership of an address and its newly mapped name, but not for a previously +// announced name-to-address map. +TEST_F(MdnsResponderTest, + CreatingNameForAddressOnlySendsAnnouncementForNewName) { + const net::IPAddress& addr = kPublicAddrs[0]; + + std::string expected_announcement = + CreateResolutionResponse(kDefaultTtl, {{"0.local", addr}}); + + // MockMdnsSocketFactory binds sockets to two interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement)) + .Times(2 * kNumAnnouncementsPerInterface); + EXPECT_EQ("0.local", CreateNameForAddress(0, addr)); // SimpleNameGenerator. + // Sends no announcement for a name that is already mapped to an address. + CreateNameForAddress(0, addr); + RunUntilNoTasksRemain(); +} + +// Test that the announcements are sent for isolated spaces of name-to-address +// maps owned by different clients. +TEST_F(MdnsResponderTest, + CreatingNamesForSameAddressButTwoClientsSendsDistinctAnnouncements) { + const auto& addr = kPublicAddrs[0]; + + std::string expected_announcement1 = + CreateResolutionResponse(kDefaultTtl, {{"0.local", addr}}); + std::string expected_announcement2 = + CreateResolutionResponse(kDefaultTtl, {{"1.local", addr}}); + + // MockMdnsSocketFactory binds sockets to two interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement1)) + .Times(2 * kNumAnnouncementsPerInterface); + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement2)) + .Times(2 * kNumAnnouncementsPerInterface); + CreateNameForAddress(0, addr); + CreateNameForAddress(1, addr); + RunUntilNoTasksRemain(); +} + +// Test that the goodbye message with zero TTL for a name is sent only +// when we remove a name in an existing name-to-address map. +TEST_F(MdnsResponderTest, + RemovingNameForAddressOnlySendsResponseWithZeroTtlForExistingName) { + const auto& addr = kPublicAddrs[0]; + + std::string expected_announcement = + CreateResolutionResponse(kDefaultTtl, {{"0.local", addr}}); + std::string expected_goodbye = + CreateResolutionResponse(base::TimeDelta(), {{"0.local", addr}}); + + // MockMdnsSocketFactory binds sockets to two interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement)) + .Times(2 * kNumAnnouncementsPerInterface); + EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye)).Times(2); + + CreateNameForAddress(0, addr); + RemoveNameForAddressAndExpectDone(0, addr); + // Sends no goodbye message for a name that is already removed. + RemoveNameForAddress(0, addr, false /* expected removed */, + false /* expected goodbye scheduled */); + RunUntilNoTasksRemain(); +} + +// Test that the responder can reply to an incoming query about a name it +// knows. +TEST_F(MdnsResponderTest, SendResponseToQueryForOwnedName) { + const auto& addr = kPublicAddrs[0]; + const auto name = CreateNameForAddress(0, addr); + + std::string query1 = CreateMdnsQuery(0, name); + std::string query2 = CreateMdnsQuery(0, "unknown_name"); + + std::string expected_response = + CreateResolutionResponse(kDefaultTtl, {{name, addr}}); + + // SimulateReceive only lets the last created socket receive. + EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(1); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query1.data()), query1.size()); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query2.data()), query2.size()); + RunUntilNoTasksRemain(); +} + +// Test that the responder does not respond to any query about a name that is +// unknown to it. +TEST_F(MdnsResponderTest, SendNoResponseToQueryForRemovedName) { + const auto& addr = kPublicAddrs[0]; + const auto name = CreateNameForAddress(0, addr); + RemoveNameForAddressAndExpectDone(0, addr); + RunUntilNoTasksRemain(); + + std::string query = CreateMdnsQuery(0, {name}); + + EXPECT_CALL(socket_factory_, OnSendTo(_)).Times(0); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query.data()), query.size()); + RunUntilNoTasksRemain(); +} + +// Test that the responder sends a negative response to any query that is not +// of type A, AAAA, or ANY. +TEST_F(MdnsResponderTest, SendNegativeResponseToQueryForNonAddressRecord) { + const auto& addr = kPublicAddrs[0]; + const auto name = CreateNameForAddress(0, addr); + const std::set<uint16_t> non_address_qtypes = { + net::dns_protocol::kTypeCNAME, net::dns_protocol::kTypeSOA, + net::dns_protocol::kTypePTR, net::dns_protocol::kTypeTXT, + net::dns_protocol::kTypeSRV, net::dns_protocol::kTypeOPT, + net::dns_protocol::kTypeNSEC, + }; + + std::string expected_negative_response = + CreateNegativeResponse({{name, addr}}); + for (auto qtype : non_address_qtypes) { + std::string query = CreateMdnsQuery(0, {name}, qtype); + EXPECT_CALL(socket_factory_, OnSendTo(expected_negative_response)).Times(1); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query.data()), query.size()); + RunUntilNoTasksRemain(); + } +} + +// Test that the responder manager closes the connection after observing +// conflicting name resolution in the network. +TEST_F(MdnsResponderTest, HostClosesMojoConnectionAfterObservingNameConflict) { + const auto& addr1 = kPublicAddrs[0]; + const auto& addr2 = kPublicAddrs[1]; + const auto name1 = CreateNameForAddress(0, addr1); + const auto name2 = CreateNameForAddress(0, addr2); + + std::string query1 = CreateMdnsQuery(0, {name1}); + std::string query2 = CreateMdnsQuery(0, {name2}); + + std::string conflicting_response = + CreateResolutionResponse(kDefaultTtl, {{name1, addr2}}); + + std::string expected_goodbye = CreateResolutionResponse( + base::TimeDelta(), {{name1, addr1}, {name2, addr2}}); + + // MockMdnsSocketFactory binds sockets to two interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye)).Times(2); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(conflicting_response.data()), + conflicting_response.size()); + RunUntilNoTasksRemain(); + // The responder should have observed the conflict and the responder manager + // should have closed the Mojo connection and sent out the goodbye messages + // for owned names. + EXPECT_FALSE(client_[0].is_bound()); + // Also, as a result, we should have stopped responding to the following + // queries. + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query1.data()), query1.size()); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query2.data()), query2.size()); + RunUntilNoTasksRemain(); +} + +// Test that the responder host clears all name-address maps in one goodbye +// message with zero TTL for a client after the Mojo connection between them is +// lost. +TEST_F(MdnsResponderTest, ResponderHostDoesCleanUpAfterMojoConnectionError) { + const auto& addr1 = kPublicAddrs[0]; + const auto& addr2 = kPublicAddrs[1]; + const auto name1 = CreateNameForAddress(0, addr1); + const auto name2 = CreateNameForAddress(0, addr2); + + std::string expected_goodbye = CreateResolutionResponse( + base::TimeDelta(), {{name1, addr1}, {name2, addr2}}); + // MockMdnsSocketFactory binds sockets to two interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye)).Times(2); + + CloseConnectionToResponderHost(0); + RunUntilNoTasksRemain(); +} + +// Test that the host generates a Mojo connection error when no socket handler +// is successfully started. +TEST_F(MdnsResponderTest, ClosesBindingWhenNoSocketHanlderStarted) { + EXPECT_CALL(failing_socket_factory_, CreateSockets(_)).WillOnce(Return()); + Reset(true /* use_failing_socket_factory */); + RunUntilNoTasksRemain(); + // MdnsResponderTest::OnMojoConnectionError. + EXPECT_FALSE(client_[0].is_bound()); + EXPECT_FALSE(client_[1].is_bound()); +} + +// Test that an announcement is retried after send failure. +TEST_F(MdnsResponderTest, AnnouncementRetriedAfterSendFailure) { + auto create_send_failing_socket = + [this](std::vector<std::unique_ptr<net::DatagramServerSocket>>* sockets) { + auto socket = + std::make_unique<NiceMock<net::MockMDnsDatagramServerSocket>>( + net::ADDRESS_FAMILY_IPV4); + + ON_CALL(*socket, SendToInternal(_, _, _)) + .WillByDefault(Invoke(&failing_socket_factory_, + &MockFailingMdnsSocketFactory::FailToSend)); + ON_CALL(*socket, RecvFromInternal(_, _, _, _)) + .WillByDefault(Return(-1)); + + sockets->push_back(std::move(socket)); + }; + EXPECT_CALL(failing_socket_factory_, CreateSockets(_)) + .WillOnce(Invoke(create_send_failing_socket)); + Reset(true /* use_failing_socket_factory */); + const auto& addr = kPublicAddrs[0]; + std::string expected_announcement = + CreateResolutionResponse(kDefaultTtl, {{"0.local", addr}}); + // Mocked CreateSockets above only creates one socket. + EXPECT_CALL(failing_socket_factory_, OnSendTo(expected_announcement)) + .Times(kNumAnnouncementsPerInterface + kNumMaxRetriesPerResponse); + const auto name = CreateNameForAddress(0, addr); + RunUntilNoTasksRemain(); +} + +// Test that responses as announcements are sent following the per-response rate +// limit. +TEST_F(MdnsResponderTest, AnnouncementsAreRateLimitedPerResponse) { + const auto& addr1 = kPublicAddrs[0]; + const auto& addr2 = kPublicAddrs[1]; + std::string expected_announcement1 = + CreateResolutionResponse(kDefaultTtl, {{"0.local", addr1}}); + std::string expected_announcement2 = + CreateResolutionResponse(kDefaultTtl, {{"1.local", addr2}}); + // First announcement for 0.local. + // MockMdnsSocketFactory binds sockets to two interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement1)).Times(2); + // We need the async call from the client. + client_[0]->CreateNameForAddress(addr1, base::DoNothing()); + client_[0]->CreateNameForAddress(addr2, base::DoNothing()); + + RunFor(base::TimeDelta::FromMilliseconds(900)); + // Second announcement for 0.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement1)).Times(2); + RunFor(base::TimeDelta::FromSeconds(1)); + // First announcement for 1.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement2)).Times(2); + RunFor(base::TimeDelta::FromSeconds(1)); + // Second announcement for 1.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement2)).Times(2); + RunUntilNoTasksRemain(); +} + +// Test that responses as goodbyes are sent following the per-response rate +// limit. +TEST_F(MdnsResponderTest, GoodbyesAreRateLimitedPerResponse) { + const auto& addr1 = kPublicAddrs[0]; + const auto& addr2 = kPublicAddrs[1]; + // Note that the wrapper method calls below are sync and announcements are + // sent after they return. + auto name1 = CreateNameForAddress(0, addr1); + auto name2 = CreateNameForAddress(0, addr2); + std::string expected_goodbye1 = + CreateResolutionResponse(base::TimeDelta(), {{name1, addr1}}); + std::string expected_goodbye2 = + CreateResolutionResponse(base::TimeDelta(), {{name2, addr2}}); + + // Goodbye for 0.local. + // MockMdnsSocketFactory binds sockets to two interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye1)).Times(2); + // Note that the wrapper method calls below are async. + RemoveNameForAddressAndExpectDone(0, addr1); + RemoveNameForAddressAndExpectDone(0, addr2); + + RunFor(base::TimeDelta::FromMilliseconds(900)); + // Goodbye for 1.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye2)).Times(2); + RunUntilNoTasksRemain(); +} + +// Test that the mixture of announcements and goodbyes are sent following the +// per-response rate limit. +TEST_F(MdnsResponderTest, AnnouncementsAndGoodbyesAreRateLimitedPerResponse) { + const auto& addr1 = kPublicAddrs[0]; + const auto& addr2 = kPublicAddrs[1]; + std::string expected_announcement1 = + CreateResolutionResponse(kDefaultTtl, {{"0.local", addr1}}); + std::string expected_announcement2 = + CreateResolutionResponse(kDefaultTtl, {{"1.local", addr2}}); + std::string expected_goodbye1 = + CreateResolutionResponse(base::TimeDelta(), {{"0.local", addr1}}); + std::string expected_goodbye2 = + CreateResolutionResponse(base::TimeDelta(), {{"1.local", addr2}}); + + // First announcement for 0.local. + // MockMdnsSocketFactory binds sockets to two interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement1)).Times(2); + // We need the async call from the client. + client_[0]->CreateNameForAddress(addr1, base::DoNothing()); + RemoveNameForAddressAndExpectDone(0, addr1); + + client_[0]->CreateNameForAddress(addr2, base::DoNothing()); + RemoveNameForAddressAndExpectDone(0, addr2); + + RunFor(base::TimeDelta::FromMilliseconds(900)); + // Second announcement for 0.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement1)).Times(2); + RunFor(base::TimeDelta::FromSeconds(1)); + // Goodbye for 0.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye1)).Times(2); + RunFor(base::TimeDelta::FromSeconds(1)); + // First announcement for 1.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement2)).Times(2); + RunFor(base::TimeDelta::FromSeconds(1)); + // Second announcement for 1.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_announcement2)).Times(2); + RunFor(base::TimeDelta::FromSeconds(1)); + // Goodbye for 1.local. + EXPECT_CALL(socket_factory_, OnSendTo(expected_goodbye2)).Times(2); + RunUntilNoTasksRemain(); +} + +// Test that responses with resource records for name resolution are sent based +// on a per-record rate limit. +TEST_F(MdnsResponderTest, ResolutionResponsesAreRateLimitedPerRecord) { + const auto& addr1 = kPublicAddrs[0]; + const auto& addr2 = kPublicAddrs[1]; + auto name1 = CreateNameForAddress(0, addr1); + auto name2 = CreateNameForAddress(0, addr2); + // kPublicAddrs are IPv4. + std::string query1 = CreateMdnsQuery(0, {name1}, net::dns_protocol::kTypeA); + std::string query2 = CreateMdnsQuery(0, {name2}, net::dns_protocol::kTypeA); + std::string expected_response1 = + CreateResolutionResponse(kDefaultTtl, {{name1, addr1}}); + std::string expected_response2 = + CreateResolutionResponse(kDefaultTtl, {{name2, addr2}}); + + // Resolution for name1. + EXPECT_CALL(socket_factory_, OnSendTo(expected_response1)).Times(1); + // Resolution for name2. + EXPECT_CALL(socket_factory_, OnSendTo(expected_response2)).Times(1); + // SimulateReceive only lets the last created socket receive. + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query1.data()), query1.size()); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query2.data()), query2.size()); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query1.data()), query1.size()); + RunFor(base::TimeDelta::FromMilliseconds(900)); + // Resolution for name1 for the second query about it. + EXPECT_CALL(socket_factory_, OnSendTo(expected_response1)).Times(1); + RunUntilNoTasksRemain(); +} + +// Test that negative responses to queries for non-existing records are sent +// based on a per-record rate limit. +TEST_F(MdnsResponderTest, NegativeResponsesAreRateLimitedPerRecord) { + const auto& addr1 = kPublicAddrs[0]; + const auto& addr2 = kPublicAddrs[1]; + auto name1 = CreateNameForAddress(0, addr1); + auto name2 = CreateNameForAddress(0, addr2); + // kPublicAddrs are IPv4 and type AAAA records do not exist. + std::string query1 = + CreateMdnsQuery(0, {name1}, net::dns_protocol::kTypeAAAA); + std::string query2 = + CreateMdnsQuery(0, {name2}, net::dns_protocol::kTypeAAAA); + std::string expected_response1 = CreateNegativeResponse({{name1, addr1}}); + std::string expected_response2 = CreateNegativeResponse({{name2, addr2}}); + + // Negative response for name1. + EXPECT_CALL(socket_factory_, OnSendTo(expected_response1)).Times(1); + // Negative response for name2. + EXPECT_CALL(socket_factory_, OnSendTo(expected_response2)).Times(1); + // SimulateReceive only lets the last created socket receive. + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query1.data()), query1.size()); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query2.data()), query2.size()); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query1.data()), query1.size()); + RunFor(base::TimeDelta::FromMilliseconds(900)); + // Negative response for name1 for the second query about it. + EXPECT_CALL(socket_factory_, OnSendTo(expected_response1)).Times(1); + RunUntilNoTasksRemain(); +} + +// Test that the mixture of resolution and negative responses are sent following +// the per-record rate limit. +TEST_F(MdnsResponderTest, + ResolutionAndNegativeResponsesAreRateLimitedPerRecord) { + const auto& addr = kPublicAddrs[0]; + auto name = CreateNameForAddress(0, addr); + // kPublicAddrs are IPv4 and type AAAA records do not exist. + std::string query_a = CreateMdnsQuery(0, {name}, net::dns_protocol::kTypeA); + std::string query_aaaa = + CreateMdnsQuery(0, {name}, net::dns_protocol::kTypeAAAA); + std::string expected_resolution = + CreateResolutionResponse(kDefaultTtl, {{name, addr}}); + std::string expected_negative_resp = CreateNegativeResponse({{name, addr}}); + + EXPECT_CALL(socket_factory_, OnSendTo(expected_resolution)).Times(1); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query_a.data()), query_a.size()); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query_aaaa.data()), query_aaaa.size()); + RunFor(base::TimeDelta::FromMilliseconds(900)); + + EXPECT_CALL(socket_factory_, OnSendTo(expected_negative_resp)).Times(1); + RunUntilNoTasksRemain(); +} + +// Test that we send responses immediately to probing queries with qtype ANY. +TEST_F(MdnsResponderTest, ResponsesToProbesAreNotRateLimited) { + const auto& addr = kPublicAddrs[0]; + auto name = CreateNameForAddress(0, addr); + // Type ANY for probing queries. + // + // TODO(qingsi): Setting the type to kTypeAny is not sufficient to construct a + // proper probe query. We also need to include a record in the Authority + // section. See the comment inside IsProbeQuery in mdns_responder.cc. After we + // support parsing the Authority section of a query in DnsQuery, we should + // create the following probe query by the standard definition. + std::string query = CreateMdnsQuery(0, {name}, net::dns_protocol::kTypeANY); + std::string expected_response = + CreateResolutionResponse(kDefaultTtl, {{name, addr}}); + + EXPECT_CALL(socket_factory_, OnSendTo(expected_response)).Times(2); + // SimulateReceive only lets the last created socket receive. + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query.data()), query.size()); + socket_factory_.SimulateReceive( + reinterpret_cast<const uint8_t*>(query.data()), query.size()); + RunFor(base::TimeDelta::FromMilliseconds(500)); +} + +// Test that different rate limit schemes effectively form different queues of +// responses that do not interfere with each other. +TEST_F(MdnsResponderTest, RateLimitSchemesDoNotInterfere) { + const auto& addr1 = kPublicAddrsIpv6[0]; + const auto& addr2 = kPublicAddrsIpv6[1]; + // SimpleNameGenerator. + std::string name1 = "0.local"; + std::string name2 = "1.local"; + std::string query1_a = CreateMdnsQuery(0, {name1}, net::dns_protocol::kTypeA); + std::string query1_aaaa = + CreateMdnsQuery(0, {name1}, net::dns_protocol::kTypeAAAA); + std::string query1_any = + CreateMdnsQuery(0, {name1}, net::dns_protocol::kTypeANY); + std::string query2_a = CreateMdnsQuery(0, {name2}, net::dns_protocol::kTypeA); + std::string query2_aaaa = + CreateMdnsQuery(0, {name2}, net::dns_protocol::kTypeAAAA); + std::string query2_any = + CreateMdnsQuery(0, {name2}, net::dns_protocol::kTypeANY); + std::string expected_resolution1 = + CreateResolutionResponse(kDefaultTtl, {{name1, addr1}}); + std::string expected_resolution2 = + CreateResolutionResponse(kDefaultTtl, {{name2, addr2}}); + std::string expected_negative_resp1 = + CreateNegativeResponse({{name1, addr1}}); + std::string expected_negative_resp2 = + CreateNegativeResponse({{name2, addr2}}); + + auto do_sequence_after_name_created = + [](net::MockMDnsSocketFactory* socket_factory, const std::string& query_a, + const std::string& query_aaaa, const std::string& query_any, + const std::string& /* name */, bool /* announcement_scheduled */) { + socket_factory->SimulateReceive( + reinterpret_cast<const uint8_t*>(query_a.data()), query_a.size()); + socket_factory->SimulateReceive( + reinterpret_cast<const uint8_t*>(query_aaaa.data()), + query_aaaa.size()); + socket_factory->SimulateReceive( + reinterpret_cast<const uint8_t*>(query_any.data()), + query_any.size()); + }; + // 2 first announcements for name1 from 2 interfaces (per-response limit) and + // 1 response to the probing query1_any (no limit). + EXPECT_CALL(socket_factory_, OnSendTo(expected_resolution1)).Times(3); + // 1 negative response to query1_a (per-record limit). + EXPECT_CALL(socket_factory_, OnSendTo(expected_negative_resp1)).Times(1); + // 1 response to the probing query2_any (no limit). + EXPECT_CALL(socket_factory_, OnSendTo(expected_resolution2)).Times(1); + // 1 negative response to query2_a (per-record limit). + EXPECT_CALL(socket_factory_, OnSendTo(expected_negative_resp2)).Times(1); + client_[0]->CreateNameForAddress( + addr1, base::BindOnce(do_sequence_after_name_created, &socket_factory_, + query1_a, query1_aaaa, query1_any)); + client_[0]->CreateNameForAddress( + addr2, base::BindOnce(do_sequence_after_name_created, &socket_factory_, + query2_a, query2_aaaa, query2_any)); + RunFor(base::TimeDelta::FromMilliseconds(900)); + + // 2 second announcements for name1 from 2 interfaces, and 1 response to + // query1_aaaa (per-record limit). + EXPECT_CALL(socket_factory_, OnSendTo(expected_resolution1)).Times(3); + // 1 response to query2_aaaa (per-record limit). + EXPECT_CALL(socket_factory_, OnSendTo(expected_resolution2)).Times(1); + RunFor(base::TimeDelta::FromSeconds(1)); + + // 2 first announcements for name2 from 2 interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_resolution2)).Times(2); + RunFor(base::TimeDelta::FromSeconds(1)); + + // 2 second announcements for name2 from 2 interfaces. + EXPECT_CALL(socket_factory_, OnSendTo(expected_resolution2)).Times(2); + RunUntilNoTasksRemain(); +} + +// Test that the responder does not send an announcement if the current +// scheduled delay exceeds the maximum delay allowed, and the client side gets +// notified of the result. +TEST_F(MdnsResponderTest, LongDelayedAnnouncementIsNotScheduled) { + const auto& addr = kPublicAddrs[0]; + // Enqueue announcements and delays so that we reach the maximum delay + // allowed of the per-response rate limit. Our current implementation defines + // a 10-second maximum scheduled delay (see kMaxScheduledDelay in + // mdns_responder.cc). + for (int i = 0; i < 5; ++i) { + // Use the async call from the client. + client_[0]->CreateNameForAddress(addr, base::DoNothing()); + client_[0]->RemoveNameForAddress(addr, base::DoNothing()); + } + client_[0]->CreateNameForAddress( + addr, base::BindOnce([](const std::string&, bool announcement_scheduled) { + EXPECT_FALSE(announcement_scheduled); + })); + RunUntilNoTasksRemain(); +} + +// Test that all pending sends scheduled are cancelled if the responder manager +// is destroyed. +TEST_F(MdnsResponderTest, ScheduledSendsAreCancelledAfterManagerDestroyed) { + const auto& addr = kPublicAddrs[0]; + EXPECT_CALL(socket_factory_, OnSendTo(_)).Times(0); + // Use the async call from the client. + client_[0]->CreateNameForAddress(addr, base::DoNothing()); + client_[0]->RemoveNameForAddress(addr, base::DoNothing()); + host_manager_.reset(); + RunUntilNoTasksRemain(); +} + +// Test that if all socket handlers fail to read, the manager restarts itself. +TEST_F(MdnsResponderTest, ManagerCanRestartAfterAllSocketHandlersFailToRead) { + auto create_read_failing_socket = + [this](std::vector<std::unique_ptr<net::DatagramServerSocket>>* sockets) { + auto socket = + std::make_unique<NiceMock<net::MockMDnsDatagramServerSocket>>( + net::ADDRESS_FAMILY_IPV4); + + ON_CALL(*socket, SendToInternal(_, _, _)).WillByDefault(Return(0)); + ON_CALL(*socket, RecvFromInternal(_, _, _, _)) + .WillByDefault(Invoke(&failing_socket_factory_, + &MockFailingMdnsSocketFactory::FailToRecv)); + + sockets->push_back(std::move(socket)); + }; + EXPECT_CALL(failing_socket_factory_, CreateSockets(_)) + .WillOnce(Invoke(create_read_failing_socket)); + Reset(true /* use_failing_socket_factory */); + EXPECT_CALL(failing_socket_factory_, CreateSockets(_)).Times(1); + RunUntilNoTasksRemain(); +} + +} // namespace network diff --git a/chromium/services/network/mojo_host_resolver_impl.cc b/chromium/services/network/mojo_host_resolver_impl.cc new file mode 100644 index 00000000000..21623f45d0b --- /dev/null +++ b/chromium/services/network/mojo_host_resolver_impl.cc @@ -0,0 +1,137 @@ +// Copyright 2015 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 "services/network/mojo_host_resolver_impl.h" + +#include <utility> + +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/base/network_interfaces.h" +#include "net/dns/host_resolver.h" + +namespace network { + +// Handles host resolution for a single request and sends a response when done. +// Also detects connection errors for HostResolverRequestClient and cancels the +// outstanding resolve request. Owned by MojoHostResolverImpl. +class MojoHostResolverImpl::Job { + public: + Job(MojoHostResolverImpl* resolver_service, + net::HostResolver* resolver, + const net::HostResolver::RequestInfo& request_info, + const net::NetLogWithSource& net_log, + proxy_resolver::mojom::HostResolverRequestClientPtr client); + ~Job(); + + void set_iter(std::list<Job>::iterator iter) { iter_ = iter; } + + void Start(); + + private: + // Completion callback for the HostResolver::Resolve request. + void OnResolveDone(int result); + + // Mojo error handler. + void OnConnectionError(); + + MojoHostResolverImpl* resolver_service_; + // This Job's iterator in |resolver_service_|, so the Job may be removed on + // completion. + std::list<Job>::iterator iter_; + net::HostResolver* resolver_; + net::HostResolver::RequestInfo request_info_; + const net::NetLogWithSource net_log_; + proxy_resolver::mojom::HostResolverRequestClientPtr client_; + std::unique_ptr<net::HostResolver::Request> request_; + net::AddressList result_; + base::ThreadChecker thread_checker_; +}; + +MojoHostResolverImpl::MojoHostResolverImpl(net::HostResolver* resolver, + const net::NetLogWithSource& net_log) + : resolver_(resolver), net_log_(net_log) {} + +MojoHostResolverImpl::~MojoHostResolverImpl() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void MojoHostResolverImpl::Resolve( + std::unique_ptr<net::HostResolver::RequestInfo> request_info, + proxy_resolver::mojom::HostResolverRequestClientPtr client) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (request_info->is_my_ip_address()) { + // The proxy resolver running inside a sandbox may not be able to get the + // correct host name. Instead, fill it ourself if the request is for our own + // IP address. + request_info->set_host_port_pair(net::HostPortPair(net::GetHostName(), 80)); + } + + pending_jobs_.emplace_front(this, resolver_, *request_info, net_log_, + std::move(client)); + auto job = pending_jobs_.begin(); + job->set_iter(job); + job->Start(); +} + +void MojoHostResolverImpl::DeleteJob(std::list<Job>::iterator job) { + DCHECK(thread_checker_.CalledOnValidThread()); + pending_jobs_.erase(job); +} + +MojoHostResolverImpl::Job::Job( + MojoHostResolverImpl* resolver_service, + net::HostResolver* resolver, + const net::HostResolver::RequestInfo& request_info, + const net::NetLogWithSource& net_log, + proxy_resolver::mojom::HostResolverRequestClientPtr client) + : resolver_service_(resolver_service), + resolver_(resolver), + request_info_(request_info), + net_log_(net_log), + client_(std::move(client)) { + client_.set_connection_error_handler(base::Bind( + &MojoHostResolverImpl::Job::OnConnectionError, base::Unretained(this))); +} + +void MojoHostResolverImpl::Job::Start() { + // The caller is responsible for setting up |iter_|. + DCHECK_EQ(this, &*iter_); + + DVLOG(1) << "Resolve " << request_info_.host_port_pair().ToString(); + int result = + resolver_->Resolve(request_info_, net::DEFAULT_PRIORITY, &result_, + base::Bind(&MojoHostResolverImpl::Job::OnResolveDone, + base::Unretained(this)), + &request_, net_log_); + + if (result != net::ERR_IO_PENDING) + OnResolveDone(result); +} + +MojoHostResolverImpl::Job::~Job() = default; + +void MojoHostResolverImpl::Job::OnResolveDone(int result) { + DCHECK(thread_checker_.CalledOnValidThread()); + request_.reset(); + DVLOG(1) << "Resolved " << request_info_.host_port_pair().ToString() + << " with error " << result << " and " << result_.size() + << " results!"; + for (const auto& address : result_) { + DVLOG(1) << address.ToString(); + } + client_->ReportResult(result, result_); + resolver_service_->DeleteJob(iter_); +} + +void MojoHostResolverImpl::Job::OnConnectionError() { + DCHECK(thread_checker_.CalledOnValidThread()); + // |resolver_service_| should always outlive us. + DCHECK(resolver_service_); + DVLOG(1) << "Connection error on request for " + << request_info_.host_port_pair().ToString(); + resolver_service_->DeleteJob(iter_); +} + +} // namespace network diff --git a/chromium/services/network/mojo_host_resolver_impl.h b/chromium/services/network/mojo_host_resolver_impl.h new file mode 100644 index 00000000000..33016bc3403 --- /dev/null +++ b/chromium/services/network/mojo_host_resolver_impl.h @@ -0,0 +1,66 @@ +// Copyright 2015 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. + +#ifndef SERVICES_NETWORK_MOJO_HOST_RESOLVER_IMPL_H_ +#define SERVICES_NETWORK_MOJO_HOST_RESOLVER_IMPL_H_ + +#include <list> +#include <memory> + +#include "base/component_export.h" +#include "base/macros.h" +#include "base/threading/thread_checker.h" +#include "net/log/net_log_with_source.h" +#include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h" + +namespace net { +class HostResolver; +} // namespace net + +namespace network { + +// MojoHostResolverImpl handles mojo host resolution requests from the Proxy +// Resolver Service. Inbound Mojo requests are sent to the HostResolver passed +// into the constructor. When destroyed, any outstanding resolver requests are +// cancelled. If a request's HostResolverRequestClient is shut down, the +// associated resolver request is cancelled. +// +// TODO(mmenke): Rename this to something that makes it clearer that this is +// just for use by the ProxyResolverFactoryMojo, or merge it into +// ProxyResolverFactoryMojo. +class COMPONENT_EXPORT(NETWORK_SERVICE) MojoHostResolverImpl { + public: + // |resolver| is expected to outlive |this|. + MojoHostResolverImpl(net::HostResolver* resolver, + const net::NetLogWithSource& net_log); + ~MojoHostResolverImpl(); + + void Resolve(std::unique_ptr<net::HostResolver::RequestInfo> request_info, + proxy_resolver::mojom::HostResolverRequestClientPtr client); + + bool request_in_progress() { return !pending_jobs_.empty(); } + + private: + class Job; + + // Removes |job| from the set of pending jobs. + void DeleteJob(std::list<Job>::iterator job); + + // Resolver for resolving incoming requests. Not owned. + net::HostResolver* resolver_; + + // The NetLogWithSource to be passed to |resolver_| for all requests. + const net::NetLogWithSource net_log_; + + // All pending jobs, so they can be cancelled when this service is destroyed. + std::list<Job> pending_jobs_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(MojoHostResolverImpl); +}; + +} // namespace network + +#endif // SERVICES_NETWORK_MOJO_HOST_RESOLVER_IMPL_H_ diff --git a/chromium/services/network/mojo_host_resolver_impl_unittest.cc b/chromium/services/network/mojo_host_resolver_impl_unittest.cc new file mode 100644 index 00000000000..01b3230961c --- /dev/null +++ b/chromium/services/network/mojo_host_resolver_impl_unittest.cc @@ -0,0 +1,284 @@ +// Copyright 2015 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 "services/network/mojo_host_resolver_impl.h" + +#include <string> +#include <utility> + +#include "base/callback_helpers.h" +#include "base/run_loop.h" +#include "base/test/scoped_task_environment.h" +#include "base/time/time.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "mojo/public/cpp/bindings/interface_request.h" +#include "net/base/address_list.h" +#include "net/base/net_errors.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/gtest_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using net::test::IsError; +using net::test::IsOk; + +namespace network { + +namespace { + +class TestRequestClient + : public proxy_resolver::mojom::HostResolverRequestClient { + public: + explicit TestRequestClient( + mojo::InterfaceRequest<proxy_resolver::mojom::HostResolverRequestClient> + req) + : done_(false), binding_(this, std::move(req)) { + binding_.set_connection_error_handler(base::Bind( + &TestRequestClient::OnConnectionError, base::Unretained(this))); + } + + void WaitForResult(); + void WaitForConnectionError(); + + int32_t error_; + net::AddressList results_; + + private: + // Overridden from proxy_resolver::mojom::HostResolverRequestClient. + void ReportResult(int32_t error, const net::AddressList& results) override; + + // Mojo error handler. + void OnConnectionError(); + + bool done_; + base::Closure run_loop_quit_closure_; + base::Closure connection_error_quit_closure_; + + mojo::Binding<proxy_resolver::mojom::HostResolverRequestClient> binding_; +}; + +void TestRequestClient::WaitForResult() { + if (done_) + return; + + base::RunLoop run_loop; + run_loop_quit_closure_ = run_loop.QuitClosure(); + run_loop.Run(); + ASSERT_TRUE(done_); +} + +void TestRequestClient::WaitForConnectionError() { + base::RunLoop run_loop; + connection_error_quit_closure_ = run_loop.QuitClosure(); + run_loop.Run(); +} + +void TestRequestClient::ReportResult(int32_t error, + const net::AddressList& results) { + if (!run_loop_quit_closure_.is_null()) { + run_loop_quit_closure_.Run(); + } + ASSERT_FALSE(done_); + error_ = error; + results_ = results; + done_ = true; +} + +void TestRequestClient::OnConnectionError() { + if (!connection_error_quit_closure_.is_null()) + connection_error_quit_closure_.Run(); +} + +class CallbackMockHostResolver : public net::MockHostResolver { + public: + CallbackMockHostResolver() = default; + ~CallbackMockHostResolver() override = default; + + // Set a callback to run whenever Resolve is called. Callback is cleared after + // every run. + void SetResolveCallback(base::Closure callback) { + resolve_callback_ = callback; + } + + // Overridden from MockHostResolver. + int Resolve(const RequestInfo& info, + net::RequestPriority priority, + net::AddressList* addresses, + net::CompletionOnceCallback callback, + std::unique_ptr<Request>* request, + const net::NetLogWithSource& net_log) override; + + private: + base::Closure resolve_callback_; +}; + +int CallbackMockHostResolver::Resolve(const RequestInfo& info, + net::RequestPriority priority, + net::AddressList* addresses, + net::CompletionOnceCallback callback, + std::unique_ptr<Request>* request, + const net::NetLogWithSource& net_log) { + int result = MockHostResolver::Resolve(info, priority, addresses, + std::move(callback), request, net_log); + if (!resolve_callback_.is_null()) { + std::move(resolve_callback_).Run(); + } + return result; +} + +} // namespace + +class MojoHostResolverImplTest : public testing::Test { + protected: + void SetUp() override { + mock_host_resolver_.rules()->AddRule("example.com", "1.2.3.4"); + mock_host_resolver_.rules()->AddRule("chromium.org", "8.8.8.8"); + mock_host_resolver_.rules()->AddSimulatedFailure("failure.fail"); + + resolver_service_.reset(new MojoHostResolverImpl(&mock_host_resolver_, + net::NetLogWithSource())); + } + + std::unique_ptr<net::HostResolver::RequestInfo> + CreateRequest(const std::string& host, uint16_t port, bool is_my_ip_address) { + std::unique_ptr<net::HostResolver::RequestInfo> request = + std::make_unique<net::HostResolver::RequestInfo>( + net::HostPortPair(host, port)); + request->set_is_my_ip_address(is_my_ip_address); + request->set_address_family(net::ADDRESS_FAMILY_IPV4); + return request; + } + + // Wait until the mock resolver has received |num| resolve requests. + void WaitForRequests(size_t num) { + while (mock_host_resolver_.num_resolve() < num) { + base::RunLoop run_loop; + mock_host_resolver_.SetResolveCallback(run_loop.QuitClosure()); + run_loop.Run(); + } + } + + base::test::ScopedTaskEnvironment scoped_task_environment_; + + CallbackMockHostResolver mock_host_resolver_; + std::unique_ptr<MojoHostResolverImpl> resolver_service_; +}; + +TEST_F(MojoHostResolverImplTest, Resolve) { + proxy_resolver::mojom::HostResolverRequestClientPtr client_ptr; + TestRequestClient client(mojo::MakeRequest(&client_ptr)); + + resolver_service_->Resolve(CreateRequest("example.com", 80, false), + std::move(client_ptr)); + client.WaitForResult(); + + EXPECT_THAT(client.error_, IsOk()); + net::AddressList& address_list = client.results_; + EXPECT_EQ(1U, address_list.size()); + EXPECT_EQ("1.2.3.4:80", address_list[0].ToString()); +} + +TEST_F(MojoHostResolverImplTest, ResolveSynchronous) { + proxy_resolver::mojom::HostResolverRequestClientPtr client_ptr; + TestRequestClient client(mojo::MakeRequest(&client_ptr)); + + mock_host_resolver_.set_synchronous_mode(true); + + resolver_service_->Resolve(CreateRequest("example.com", 80, false), + std::move(client_ptr)); + client.WaitForResult(); + + EXPECT_THAT(client.error_, IsOk()); + net::AddressList& address_list = client.results_; + EXPECT_EQ(1U, address_list.size()); + EXPECT_EQ("1.2.3.4:80", address_list[0].ToString()); +} + +TEST_F(MojoHostResolverImplTest, ResolveMultiple) { + proxy_resolver::mojom::HostResolverRequestClientPtr client1_ptr; + TestRequestClient client1(mojo::MakeRequest(&client1_ptr)); + proxy_resolver::mojom::HostResolverRequestClientPtr client2_ptr; + TestRequestClient client2(mojo::MakeRequest(&client2_ptr)); + + mock_host_resolver_.set_ondemand_mode(true); + + resolver_service_->Resolve(CreateRequest("example.com", 80, false), + std::move(client1_ptr)); + resolver_service_->Resolve(CreateRequest("chromium.org", 80, false), + std::move(client2_ptr)); + WaitForRequests(2); + mock_host_resolver_.ResolveAllPending(); + + client1.WaitForResult(); + client2.WaitForResult(); + + EXPECT_THAT(client1.error_, IsOk()); + net::AddressList& address_list1 = client1.results_; + EXPECT_EQ(1U, address_list1.size()); + EXPECT_EQ("1.2.3.4:80", address_list1[0].ToString()); + EXPECT_THAT(client2.error_, IsOk()); + net::AddressList& address_list2 = client2.results_; + EXPECT_EQ(1U, address_list2.size()); + EXPECT_EQ("8.8.8.8:80", address_list2[0].ToString()); +} + +TEST_F(MojoHostResolverImplTest, ResolveDuplicate) { + proxy_resolver::mojom::HostResolverRequestClientPtr client1_ptr; + TestRequestClient client1(mojo::MakeRequest(&client1_ptr)); + proxy_resolver::mojom::HostResolverRequestClientPtr client2_ptr; + TestRequestClient client2(mojo::MakeRequest(&client2_ptr)); + + mock_host_resolver_.set_ondemand_mode(true); + + resolver_service_->Resolve(CreateRequest("example.com", 80, false), + std::move(client1_ptr)); + resolver_service_->Resolve(CreateRequest("example.com", 80, false), + std::move(client2_ptr)); + WaitForRequests(2); + mock_host_resolver_.ResolveAllPending(); + + client1.WaitForResult(); + client2.WaitForResult(); + + EXPECT_THAT(client1.error_, IsOk()); + net::AddressList& address_list1 = client1.results_; + EXPECT_EQ(1U, address_list1.size()); + EXPECT_EQ("1.2.3.4:80", address_list1[0].ToString()); + EXPECT_THAT(client2.error_, IsOk()); + net::AddressList& address_list2 = client2.results_; + EXPECT_EQ(1U, address_list2.size()); + EXPECT_EQ("1.2.3.4:80", address_list2[0].ToString()); +} + +TEST_F(MojoHostResolverImplTest, ResolveFailure) { + proxy_resolver::mojom::HostResolverRequestClientPtr client_ptr; + TestRequestClient client(mojo::MakeRequest(&client_ptr)); + + resolver_service_->Resolve(CreateRequest("failure.fail", 80, false), + std::move(client_ptr)); + client.WaitForResult(); + + EXPECT_THAT(client.error_, IsError(net::ERR_NAME_NOT_RESOLVED)); + EXPECT_TRUE(client.results_.empty()); +} + +TEST_F(MojoHostResolverImplTest, DestroyClient) { + proxy_resolver::mojom::HostResolverRequestClientPtr client_ptr; + std::unique_ptr<TestRequestClient> client( + new TestRequestClient(mojo::MakeRequest(&client_ptr))); + + mock_host_resolver_.set_ondemand_mode(true); + + resolver_service_->Resolve(CreateRequest("example.com", 80, false), + std::move(client_ptr)); + WaitForRequests(1); + + client.reset(); + base::RunLoop().RunUntilIdle(); + + mock_host_resolver_.ResolveAllPending(); + base::RunLoop().RunUntilIdle(); +} + +} // namespace network diff --git a/chromium/services/network/network_change_manager.cc b/chromium/services/network/network_change_manager.cc index 00532f60199..a1ed97863de 100644 --- a/chromium/services/network/network_change_manager.cc +++ b/chromium/services/network/network_change_manager.cc @@ -9,6 +9,7 @@ #include "base/logging.h" #include "net/base/network_change_notifier.h" +#include "net/base/network_change_notifier_chromeos.h" namespace network { @@ -43,6 +44,34 @@ void NetworkChangeManager::RequestNotifications( clients_.push_back(std::move(client_ptr)); } +#if defined(OS_CHROMEOS) +void NetworkChangeManager::OnNetworkChanged( + bool dns_changed, + bool ip_address_changed, + bool connection_type_changed, + mojom::ConnectionType new_connection_type, + bool connection_subtype_changed, + mojom::ConnectionSubtype new_connection_subtype) { + DCHECK(network_change_notifier_); + net::NetworkChangeNotifierChromeos* notifier = + static_cast<net::NetworkChangeNotifierChromeos*>( + network_change_notifier_.get()); + if (dns_changed) + notifier->OnDNSChanged(); + if (ip_address_changed) + notifier->OnIPAddressChanged(); + if (connection_type_changed) { + notifier->OnConnectionChanged( + net::NetworkChangeNotifier::ConnectionType(new_connection_type)); + } + if (connection_type_changed || connection_subtype_changed) { + notifier->OnConnectionSubtypeChanged( + net::NetworkChangeNotifier::ConnectionType(new_connection_type), + net::NetworkChangeNotifier::ConnectionSubtype(new_connection_subtype)); + } +} +#endif + size_t NetworkChangeManager::GetNumClientsForTesting() const { return clients_.size(); } diff --git a/chromium/services/network/network_change_manager.h b/chromium/services/network/network_change_manager.h index 0800582fff2..6dd4dc9feee 100644 --- a/chromium/services/network/network_change_manager.h +++ b/chromium/services/network/network_change_manager.h @@ -41,6 +41,16 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkChangeManager void RequestNotifications( mojom::NetworkChangeManagerClientPtr client_ptr) override; +#if defined(OS_CHROMEOS) + void OnNetworkChanged( + bool dns_changed, + bool ip_address_changed, + bool connection_type_changed, + mojom::ConnectionType new_connection_type, + bool connection_subtype_changed, + mojom::ConnectionSubtype new_connection_subtype) override; +#endif + size_t GetNumClientsForTesting() const; private: diff --git a/chromium/services/network/network_context.cc b/chromium/services/network/network_context.cc index 8934556a91d..bf75d0997e2 100644 --- a/chromium/services/network/network_context.cc +++ b/chromium/services/network/network_context.cc @@ -7,6 +7,7 @@ #include <memory> #include <utility> +#include "base/barrier_closure.h" #include "base/base64.h" #include "base/command_line.h" #include "base/containers/unique_ptr_adapters.h" @@ -23,13 +24,8 @@ #include "base/task/post_task.h" #include "base/task/task_traits.h" #include "build/build_config.h" -#include "components/certificate_transparency/chrome_ct_policy_enforcer.h" -#include "components/certificate_transparency/chrome_require_ct_delegate.h" -#include "components/certificate_transparency/features.h" -#include "components/certificate_transparency/sth_distributor.h" -#include "components/certificate_transparency/sth_reporter.h" -#include "components/certificate_transparency/tree_state_tracker.h" #include "components/cookie_config/cookie_store_util.h" +#include "components/domain_reliability/monitor.h" #include "components/network_session_configurator/browser/network_session_configurator.h" #include "components/network_session_configurator/common/network_switches.h" #include "components/prefs/json_pref_store.h" @@ -41,12 +37,11 @@ #include "net/base/load_flags.h" #include "net/base/net_errors.h" #include "net/base/network_delegate.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/cert/cert_verifier.h" -#include "net/cert/ct_log_verifier.h" -#include "net/cert/multi_log_ct_verifier.h" +#include "net/cert/ct_verify_result.h" #include "net/cookies/cookie_monster.h" #include "net/dns/host_cache.h" -#include "net/dns/host_resolver.h" #include "net/dns/mapped_host_resolver.h" #include "net/extras/sqlite/sqlite_channel_id_store.h" #include "net/extras/sqlite/sqlite_persistent_cookie_store.h" @@ -68,7 +63,6 @@ #include "net/url_request/url_request_context_builder.h" #include "services/network/cookie_manager.h" #include "services/network/cors/cors_url_loader_factory.h" -#include "services/network/expect_ct_reporter.h" #include "services/network/host_resolver.h" #include "services/network/http_server_properties_pref_delegate.h" #include "services/network/ignore_errors_cert_verifier.h" @@ -91,20 +85,47 @@ #include "services/network/throttling/network_conditions.h" #include "services/network/throttling/throttling_controller.h" #include "services/network/throttling/throttling_network_transaction_factory.h" +#include "services/network/url_loader.h" #include "services/network/url_request_context_builder_mojo.h" +#if BUILDFLAG(IS_CT_SUPPORTED) +#include "components/certificate_transparency/chrome_ct_policy_enforcer.h" +#include "components/certificate_transparency/chrome_require_ct_delegate.h" +#include "components/certificate_transparency/features.h" +#include "components/certificate_transparency/sth_distributor.h" +#include "components/certificate_transparency/sth_reporter.h" +#include "components/certificate_transparency/tree_state_tracker.h" +#include "net/cert/ct_log_verifier.h" +#include "net/cert/multi_log_ct_verifier.h" +#include "services/network/expect_ct_reporter.h" +#endif // BUILDFLAG(IS_CT_SUPPORTED) + +#if defined(OS_CHROMEOS) +#include "crypto/nss_util_internal.h" +#include "net/cert/caching_cert_verifier.h" +#include "net/cert/multi_threaded_cert_verifier.h" +#include "services/network/cert_verifier_with_trust_anchors.h" +#include "services/network/cert_verify_proc_chromeos.h" +#include "services/network/nss_temp_certs_cache_chromeos.h" +#endif + #if !defined(OS_IOS) #include "services/network/websocket_factory.h" #endif // !defined(OS_IOS) #if BUILDFLAG(ENABLE_REPORTING) -#include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/network_error_logging/network_error_logging_service.h" #include "net/reporting/reporting_browsing_data_remover.h" #include "net/reporting/reporting_policy.h" +#include "net/reporting/reporting_report.h" #include "net/reporting/reporting_service.h" +#include "net/url_request/http_user_agent_settings.h" #endif // BUILDFLAG(ENABLE_REPORTING) +#if BUILDFLAG(ENABLE_MDNS) +#include "services/network/mdns_responder.h" +#endif // BUILDFLAG(ENABLE_MDNS) + #if defined(USE_NSS_CERTS) #include "net/cert_net/nss_ocsp.h" #endif // defined(USE_NSS_CERTS) @@ -123,6 +144,7 @@ namespace network { namespace { +#if BUILDFLAG(IS_CT_SUPPORTED) // A Base-64 encoded DER certificate for use in test Expect-CT reports. The // contents of the certificate don't matter. const char kTestReportCert[] = @@ -147,6 +169,7 @@ const char kTestReportCert[] = "EfELR8Hn6WjZ8wAbvO4p7RTrzu1c/RZ0M+NLkID56Brbl70GC2h5681LPwAOaZ7/" "mWQ5kekSyJjmLfF12b+h9RVAt5MrXZgk2vNujssgGf4nbWh4KZyQ6qrs778ZdDLm" "yfUn"; +#endif // BUILDFLAG(IS_CT_SUPPORTED) net::CertVerifier* g_cert_verifier_for_testing = nullptr; @@ -203,8 +226,6 @@ base::RepeatingCallback<bool(const std::string& host_name)> MakeDomainFilter( std::move(filter_domains)); } -// Generic functions but currently only used for reporting. -#if BUILDFLAG(ENABLE_REPORTING) // Predicate function to determine if the given |url| matches the |filter_type|, // |filter_domains| and |filter_origins| from a |mojom::ClearDataFilter|. bool MatchesUrlFilter(mojom::ClearDataFilter_Type filter_type, @@ -244,7 +265,6 @@ base::RepeatingCallback<bool(const GURL&)> BuildUrlFilter( std::move(filter_domains), std::move(filter_origins)); } -#endif // BUILDFLAG(ENABLE_REPORTING) void OnClearedChannelIds(net::SSLConfigService* ssl_config_service, base::OnceClosure callback) { @@ -277,6 +297,18 @@ class NetworkContextApplicationStatusListener }; #endif +struct TestVerifyCertState { + net::CertVerifyResult result; + std::unique_ptr<net::CertVerifier::Request> request; +}; + +void TestVerifyCertCallback( + std::unique_ptr<TestVerifyCertState> request, + NetworkContext::VerifyCertificateForTestingCallback callback, + int result) { + std::move(callback).Run(result); +} + std::string HashesToBase64String(const net::HashValueVector& hashes) { std::string str; for (size_t i = 0; i != hashes.size(); ++i) { @@ -324,8 +356,30 @@ class NetworkContext::ContextNetworkDelegate GURL* new_url) override { if (!enable_referrers_) request->SetReferrer(std::string()); - if (network_context_->network_service()) - network_context_->network_service()->OnBeforeURLRequest(); + NetworkService* network_service = network_context_->network_service(); + if (network_service) + network_service->OnBeforeURLRequest(); + + auto* loader = URLLoader::ForRequest(*request); + if (!loader) + return; + const GURL* effective_url = nullptr; + if (loader->new_redirect_url()) { + *new_url = loader->new_redirect_url().value(); + effective_url = new_url; + } else { + effective_url = &request->url(); + } + if (network_service) { + loader->SetAllowReportingRawHeaders(network_service->HasRawHeadersAccess( + loader->GetProcessId(), *effective_url)); + } + } + + void OnBeforeRedirectInternal(net::URLRequest* request, + const GURL& new_location) override { + if (network_context_->domain_reliability_monitor_) + network_context_->domain_reliability_monitor_->OnBeforeRedirect(request); } void OnCompletedInternal(net::URLRequest* request, @@ -335,6 +389,11 @@ class NetworkContext::ContextNetworkDelegate // this logic into URLLoader's completion method. DCHECK_NE(net::ERR_IO_PENDING, net_error); + if (network_context_->domain_reliability_monitor_) { + network_context_->domain_reliability_monitor_->OnCompleted(request, + started); + } + // Record network errors that HTTP requests complete with, including OK and // ABORTED. // TODO(mmenke): Seems like this really should be looking at HTTPS requests, @@ -399,9 +458,8 @@ class NetworkContext::ContextNetworkDelegate request.site_for_cookies()); } - bool OnCanEnablePrivacyModeInternal( - const GURL& url, - const GURL& site_for_cookies) const override { + bool OnForcePrivacyModeInternal(const GURL& url, + const GURL& site_for_cookies) const override { return !network_context_->cookie_manager() ->cookie_settings() .IsCookieAccessAllowed(url, site_for_cookies); @@ -462,7 +520,8 @@ NetworkContext::NetworkContext( app_status_listener_( std::make_unique<NetworkContextApplicationStatusListener>()), #endif - binding_(this, std::move(request)) { + binding_(this, std::move(request)), + host_resolver_factory_(std::make_unique<net::HostResolver::Factory>()) { url_request_context_owner_ = MakeURLRequestContext(); url_request_context_ = url_request_context_owner_.url_request_context.get(); @@ -479,6 +538,8 @@ NetworkContext::NetworkContext( url_request_context_->net_log(), url_request_context_); resource_scheduler_ = std::make_unique<ResourceScheduler>(enable_resource_scheduler_); + + InitializeCorsOriginAccessList(); } // TODO(mmenke): Share URLRequestContextBulder configuration between two @@ -496,7 +557,8 @@ NetworkContext::NetworkContext( app_status_listener_( std::make_unique<NetworkContextApplicationStatusListener>()), #endif - binding_(this, std::move(request)) { + binding_(this, std::move(request)), + host_resolver_factory_(std::make_unique<net::HostResolver::Factory>()) { url_request_context_owner_ = ApplyContextParamsToBuilder(builder.get()); url_request_context_ = url_request_context_owner_.url_request_context.get(); @@ -505,6 +567,8 @@ NetworkContext::NetworkContext( url_request_context_->net_log(), url_request_context_); resource_scheduler_ = std::make_unique<ResourceScheduler>(enable_resource_scheduler_); + + InitializeCorsOriginAccessList(); } NetworkContext::NetworkContext(NetworkService* network_service, @@ -524,7 +588,8 @@ NetworkContext::NetworkContext(NetworkService* network_service, nullptr)), socket_factory_( std::make_unique<SocketFactory>(url_request_context_->net_log(), - url_request_context)) { + url_request_context)), + host_resolver_factory_(std::make_unique<net::HostResolver::Factory>()) { // May be nullptr in tests. if (network_service_) network_service_->RegisterNetworkContext(this); @@ -548,6 +613,17 @@ NetworkContext::~NetworkContext() { #endif } + if (domain_reliability_monitor_) + domain_reliability_monitor_->Shutdown(); + // Because of the order of declaration in the class, + // domain_reliability_monitor_ will be destroyed before + // |url_loader_factories_| which could own URLLoader's whose destructor call + // back into this class and might use domain_reliability_monitor_. So we reset + // |domain_reliability_monitor_| here expliclity, instead of changing the + // order, because any work calling into |domain_reliability_monitor_| at + // shutdown would be unnecessary as the reports would be thrown out. + domain_reliability_monitor_.reset(); + if (url_request_context_ && url_request_context_->transport_security_state()) { if (certificate_report_sender_) { @@ -558,6 +634,7 @@ NetworkContext::~NetworkContext() { certificate_report_sender_.reset(); } +#if BUILDFLAG(IS_CT_SUPPORTED) if (expect_ct_reporter_) { url_request_context_->transport_security_state()->SetExpectCTReporter( nullptr); @@ -568,8 +645,10 @@ NetworkContext::~NetworkContext() { url_request_context_->transport_security_state()->SetRequireCTDelegate( nullptr); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) } +#if BUILDFLAG(IS_CT_SUPPORTED) if (url_request_context_ && url_request_context_->cert_transparency_verifier()) { url_request_context_->cert_transparency_verifier()->SetObserver(nullptr); @@ -580,6 +659,7 @@ NetworkContext::~NetworkContext() { network_service_->sth_reporter()->UnregisterObserver( ct_tree_tracker_.get()); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) } void NetworkContext::SetCertVerifierForTesting( @@ -595,7 +675,7 @@ void NetworkContext::CreateURLLoaderFactory( mojom::URLLoaderFactoryRequest request, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr<ResourceSchedulerClient> resource_scheduler_client) { - url_loader_factories_.emplace(std::make_unique<cors::CORSURLLoaderFactory>( + url_loader_factories_.emplace(std::make_unique<cors::CorsURLLoaderFactory>( this, std::move(params), std::move(resource_scheduler_client), std::move(request), &cors_origin_access_list_)); } @@ -620,6 +700,11 @@ void NetworkContext::CreateURLLoaderFactory( std::move(resource_scheduler_client)); } +void NetworkContext::ResetURLLoaderFactories() { + for (const auto& factory : url_loader_factories_) + factory->ClearBindings(); +} + void NetworkContext::GetCookieManager(mojom::CookieManagerRequest request) { cookie_manager_->AddRequest(std::move(request)); } @@ -645,7 +730,7 @@ void NetworkContext::DisableQuic() { } void NetworkContext::DestroyURLLoaderFactory( - cors::CORSURLLoaderFactory* url_loader_factory) { + cors::CorsURLLoaderFactory* url_loader_factory) { auto it = url_loader_factories_.find(url_loader_factory); DCHECK(it != url_loader_factories_.end()); url_loader_factories_.erase(it); @@ -656,23 +741,26 @@ size_t NetworkContext::GetNumOutstandingResolveHostRequestsForTesting() const { if (internal_host_resolver_) sum += internal_host_resolver_->GetNumOutstandingRequestsForTesting(); for (const auto& host_resolver : host_resolvers_) - sum += host_resolver->GetNumOutstandingRequestsForTesting(); + sum += host_resolver.first->GetNumOutstandingRequestsForTesting(); return sum; } void NetworkContext::ClearNetworkingHistorySince( base::Time time, base::OnceClosure completion_callback) { + auto barrier = base::BarrierClosure(2, std::move(completion_callback)); + + url_request_context_->transport_security_state()->DeleteAllDynamicDataSince( + time, barrier); + // TODO(mmenke): Neither of these methods waits until the changes have been // commited to disk. They probably should, as most similar methods net/ // exposes do. + // May not be set in all tests. + if (network_qualities_pref_delegate_) + network_qualities_pref_delegate_->ClearPrefs(); - // Completes synchronously. - url_request_context_->transport_security_state()->DeleteAllDynamicDataSince( - time); - - url_request_context_->http_server_properties()->Clear( - std::move(completion_callback)); + url_request_context_->http_server_properties()->Clear(barrier); } void NetworkContext::ClearHttpCache(base::Time start_time, @@ -798,6 +886,38 @@ void NetworkContext::ClearNetworkErrorLogging( std::move(callback).Run(); } + +void NetworkContext::QueueReport(const std::string& type, + const std::string& group, + const GURL& url, + const base::Optional<std::string>& user_agent, + base::Value body) { + DCHECK(body.is_dict()); + if (!body.is_dict()) + return; + + // Get the ReportingService. + net::URLRequestContext* request_context = url_request_context(); + net::ReportingService* reporting_service = + request_context->reporting_service(); + // TODO(paulmeyer): Remove this once the network service ships everywhere. + if (!reporting_service) { + net::ReportingReport::RecordReportDiscardedForNoReportingService(); + return; + } + + std::string reported_user_agent = user_agent.value_or(""); + if (reported_user_agent.empty() && + request_context->http_user_agent_settings() != nullptr) { + reported_user_agent = + request_context->http_user_agent_settings()->GetUserAgent(); + } + + // Send the crash report to the Reporting API. + reporting_service->QueueReport(url, reported_user_agent, group, type, + base::Value::ToUniquePtrValue(std::move(body)), + 0 /* depth */); +} #else // BUILDFLAG(ENABLE_REPORTING) void NetworkContext::ClearReportingCacheReports( mojom::ClearDataFilterPtr filter, @@ -816,8 +936,48 @@ void NetworkContext::ClearNetworkErrorLogging( ClearNetworkErrorLoggingCallback callback) { NOTREACHED(); } + +void NetworkContext::QueueReport(const std::string& type, + const std::string& group, + const GURL& url, + const base::Optional<std::string>& user_agent, + base::Value body) { + NOTREACHED(); +} #endif // BUILDFLAG(ENABLE_REPORTING) +void NetworkContext::ClearDomainReliability( + mojom::ClearDataFilterPtr filter, + DomainReliabilityClearMode mode, + ClearDomainReliabilityCallback callback) { + if (domain_reliability_monitor_) { + domain_reliability::DomainReliabilityClearMode dr_mode; + if (mode == + mojom::NetworkContext::DomainReliabilityClearMode::CLEAR_CONTEXTS) { + dr_mode = domain_reliability::CLEAR_CONTEXTS; + } else { + dr_mode = domain_reliability::CLEAR_BEACONS; + } + + domain_reliability_monitor_->ClearBrowsingData( + dr_mode, BuildUrlFilter(std::move(filter))); + } + std::move(callback).Run(); +} + +void NetworkContext::GetDomainReliabilityJSON( + GetDomainReliabilityJSONCallback callback) { + if (!domain_reliability_monitor_) { + base::DictionaryValue data; + data.SetString("error", "no_service"); + std::move(callback).Run(std::move(data)); + return; + } + + std::move(callback).Run( + std::move(*domain_reliability_monitor_->GetWebUIData())); +} + void NetworkContext::CloseAllConnections(CloseAllConnectionsCallback callback) { net::HttpNetworkSession* http_session = url_request_context_->http_transaction_factory()->GetSession(); @@ -866,6 +1026,22 @@ void NetworkContext::SetEnableReferrers(bool enable_referrers) { context_network_delegate_->set_enable_referrers(enable_referrers); } +#if defined(OS_CHROMEOS) +void NetworkContext::UpdateAdditionalCertificates( + mojom::AdditionalCertificatesPtr additional_certificates) { + if (!additional_certificates) { + nss_temp_certs_cache_.reset(); + cert_verifier_with_trust_anchors_->SetTrustAnchors(net::CertificateList()); + return; + } + nss_temp_certs_cache_ = std::make_unique<network::NSSTempCertsCacheChromeOS>( + additional_certificates->all_certificates); + cert_verifier_with_trust_anchors_->SetTrustAnchors( + additional_certificates->trust_anchors); +} +#endif + +#if BUILDFLAG(IS_CT_SUPPORTED) void NetworkContext::SetCTPolicy( const std::vector<std::string>& required_hosts, const std::vector<std::string>& excluded_hosts, @@ -982,6 +1158,7 @@ void NetworkContext::GetExpectCTState(const std::string& domain, std::move(callback).Run(std::move(result)); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) void NetworkContext::CreateUDPSocket(mojom::UDPSocketRequest request, mojom::UDPSocketReceiverPtr receiver) { @@ -1032,6 +1209,30 @@ void NetworkContext::CreateProxyResolvingSocketFactory( std::move(request)); } +void NetworkContext::LookUpProxyForURL( + const GURL& url, + mojom::ProxyLookupClientPtr proxy_lookup_client) { + DCHECK(proxy_lookup_client); + std::unique_ptr<ProxyLookupRequest> proxy_lookup_request( + std::make_unique<ProxyLookupRequest>(std::move(proxy_lookup_client), + this)); + ProxyLookupRequest* proxy_lookup_request_ptr = proxy_lookup_request.get(); + proxy_lookup_requests_.insert(std::move(proxy_lookup_request)); + proxy_lookup_request_ptr->Start(url); +} + +void NetworkContext::ForceReloadProxyConfig( + ForceReloadProxyConfigCallback callback) { + url_request_context()->proxy_resolution_service()->ForceReloadProxyConfig(); + std::move(callback).Run(); +} + +void NetworkContext::ClearBadProxiesCache( + ClearBadProxiesCacheCallback callback) { + url_request_context()->proxy_resolution_service()->ClearBadProxiesCache(); + std::move(callback).Run(); +} + void NetworkContext::CreateWebSocket( mojom::WebSocketRequest request, int32_t process_id, @@ -1047,18 +1248,6 @@ void NetworkContext::CreateWebSocket( #endif // !defined(OS_IOS) } -void NetworkContext::LookUpProxyForURL( - const GURL& url, - mojom::ProxyLookupClientPtr proxy_lookup_client) { - DCHECK(proxy_lookup_client); - std::unique_ptr<ProxyLookupRequest> proxy_lookup_request( - std::make_unique<ProxyLookupRequest>(std::move(proxy_lookup_client), - this)); - ProxyLookupRequest* proxy_lookup_request_ptr = proxy_lookup_request.get(); - proxy_lookup_requests_.insert(std::move(proxy_lookup_request)); - proxy_lookup_request_ptr->Start(url); -} - void NetworkContext::CreateNetLogExporter( mojom::NetLogExporterRequest request) { net_log_exporter_bindings_.AddBinding(std::make_unique<NetLogExporter>(this), @@ -1078,12 +1267,40 @@ void NetworkContext::ResolveHost( std::move(response_client)); } -void NetworkContext::CreateHostResolver(mojom::HostResolverRequest request) { - host_resolvers_.emplace(std::make_unique<HostResolver>( - std::move(request), - base::BindOnce(&NetworkContext::OnHostResolverShutdown, - base::Unretained(this)), - url_request_context_->host_resolver(), url_request_context_->net_log())); +void NetworkContext::CreateHostResolver( + const base::Optional<net::DnsConfigOverrides>& config_overrides, + mojom::HostResolverRequest request) { + net::HostResolver* internal_resolver = url_request_context_->host_resolver(); + std::unique_ptr<net::HostResolver> private_internal_resolver; + + if (config_overrides && + config_overrides.value() != net::DnsConfigOverrides()) { + // If custom configuration is needed, create a separate internal resolver + // with the specified configuration overrides. Because we are using a non- + // standard resolver, disable the cache. + // + // TODO(crbug.com/846423): Consider allowing per-resolve overrides, so the + // same net::HostResolver with the same scheduler and cache can be used with + // different overrides. But since this is only used for special cases for + // now, much easier to create entirely separate net::HostResolver instances. + net::HostResolver::Options options; + options.enable_caching = false; + + private_internal_resolver = host_resolver_factory_->CreateResolver( + options, url_request_context_->net_log()); + internal_resolver = private_internal_resolver.get(); + + internal_resolver->SetDnsClientEnabled(true); + internal_resolver->SetDnsConfigOverrides(config_overrides.value()); + } + + host_resolvers_.emplace( + std::make_unique<HostResolver>( + std::move(request), + base::BindOnce(&NetworkContext::OnHostResolverShutdown, + base::Unretained(this)), + internal_resolver, url_request_context_->net_log()), + std::move(private_internal_resolver)); } void NetworkContext::VerifyCertForSignedExchange( @@ -1133,19 +1350,6 @@ void NetworkContext::WriteCacheMetadata(const GURL& url, data.size()); } -void NetworkContext::IsHSTSActiveForHost(const std::string& host, - IsHSTSActiveForHostCallback callback) { - net::TransportSecurityState* security_state = - url_request_context_->transport_security_state(); - - if (!security_state) { - std::move(callback).Run(false); - return; - } - - std::move(callback).Run(security_state->ShouldUpgradeToSSL(host)); -} - void NetworkContext::SetCorsOriginAccessListsForOrigin( const url::Origin& source_origin, std::vector<mojom::CorsOriginPatternPtr> allow_patterns, @@ -1166,6 +1370,19 @@ void NetworkContext::AddHSTS(const std::string& host, std::move(callback).Run(); } +void NetworkContext::IsHSTSActiveForHost(const std::string& host, + IsHSTSActiveForHostCallback callback) { + net::TransportSecurityState* security_state = + url_request_context_->transport_security_state(); + + if (!security_state) { + std::move(callback).Run(false); + return; + } + + std::move(callback).Run(security_state->ShouldUpgradeToSSL(host)); +} + void NetworkContext::GetHSTSState(const std::string& domain, GetHSTSStateCallback callback) { base::DictionaryValue result; @@ -1256,6 +1473,14 @@ void NetworkContext::DeleteDynamicDataForHost( transport_security_state->DeleteDynamicDataForHost(host)); } +void NetworkContext::EnableStaticKeyPinningForTesting( + EnableStaticKeyPinningForTestingCallback callback) { + net::TransportSecurityState* state = + url_request_context_->transport_security_state(); + state->EnableStaticPinsForTesting(); + std::move(callback).Run(); +} + void NetworkContext::SetFailingHttpTransactionForTesting( int32_t error_code, SetFailingHttpTransactionForTestingCallback callback) { @@ -1272,6 +1497,25 @@ void NetworkContext::SetFailingHttpTransactionForTesting( std::move(callback).Run(); } +void NetworkContext::VerifyCertificateForTesting( + const scoped_refptr<net::X509Certificate>& certificate, + const std::string& hostname, + const std::string& ocsp_response, + VerifyCertificateForTestingCallback callback) { + net::CertVerifier* cert_verifier = url_request_context_->cert_verifier(); + + auto state = std::make_unique<TestVerifyCertState>(); + auto* request = &state->request; + auto* result = &state->result; + + cert_verifier->Verify(net::CertVerifier::RequestParams( + certificate.get(), hostname, 0, ocsp_response), + result, + base::BindOnce(TestVerifyCertCallback, std::move(state), + std::move(callback)), + request, net::NetLogWithSource()); +} + void NetworkContext::PreconnectSockets(uint32_t num_streams, const GURL& original_url, int32_t load_flags, @@ -1320,9 +1564,51 @@ void NetworkContext::CreateP2PSocketManager( socket_managers_[socket_manager.get()] = std::move(socket_manager); } -void NetworkContext::ResetURLLoaderFactories() { - for (const auto& factory : url_loader_factories_) - factory->ClearBindings(); +void NetworkContext::CreateMdnsResponder( + mojom::MdnsResponderRequest responder_request) { +#if BUILDFLAG(ENABLE_MDNS) + if (!mdns_responder_manager_) + mdns_responder_manager_ = std::make_unique<MdnsResponderManager>(); + + mdns_responder_manager_->CreateMdnsResponder(std::move(responder_request)); +#else + NOTREACHED(); +#endif // BUILDFLAG(ENABLE_MDNS) +} + +void NetworkContext::AddDomainReliabilityContextForTesting( + const GURL& origin, + const GURL& upload_url, + AddDomainReliabilityContextForTestingCallback callback) { + auto config = std::make_unique<domain_reliability::DomainReliabilityConfig>(); + config->origin = origin; + config->include_subdomains = false; + config->collectors.push_back(std::make_unique<GURL>(upload_url)); + config->success_sample_rate = 1.0; + config->failure_sample_rate = 1.0; + domain_reliability_monitor_->AddContextForTesting(std::move(config)); + std::move(callback).Run(); +} + +void NetworkContext::ForceDomainReliabilityUploadsForTesting( + ForceDomainReliabilityUploadsForTestingCallback callback) { + domain_reliability_monitor_->ForceUploadsForTesting(); + std::move(callback).Run(); +} + +void NetworkContext::LookupBasicAuthCredentials( + const GURL& url, + LookupBasicAuthCredentialsCallback callback) { + net::HttpAuthCache* http_auth_cache = + url_request_context_->http_transaction_factory() + ->GetSession() + ->http_auth_cache(); + net::HttpAuthCache::Entry* entry = + http_auth_cache->LookupByPath(url.GetOrigin(), url.path()); + if (entry && entry->scheme() == net::HttpAuth::AUTH_SCHEME_BASIC) + std::move(callback).Run(entry->credentials()); + else + std::move(callback).Run(base::nullopt); } // ApplyContextParamsToBuilder represents the core configuration for @@ -1471,6 +1757,7 @@ URLRequestContextOwner NetworkContext::ApplyContextParamsToBuilder( pref_service_factory.set_async(true); scoped_refptr<PrefRegistrySimple> pref_registry(new PrefRegistrySimple()); HttpServerPropertiesPrefDelegate::RegisterPrefs(pref_registry.get()); + NetworkQualitiesPrefDelegate::RegisterPrefs(pref_registry.get()); pref_service = pref_service_factory.Create(pref_registry.get()); builder->SetHttpServerProperties( @@ -1478,6 +1765,10 @@ URLRequestContextOwner NetworkContext::ApplyContextParamsToBuilder( std::make_unique<HttpServerPropertiesPrefDelegate>( pref_service.get()), net_log)); + + network_qualities_pref_delegate_ = + std::make_unique<NetworkQualitiesPrefDelegate>( + pref_service.get(), network_service_->network_quality_estimator()); } if (params_->transport_security_persister_path) { @@ -1507,10 +1798,12 @@ URLRequestContextOwner NetworkContext::ApplyContextParamsToBuilder( base::FeatureList::IsEnabled(features::kNetworkErrorLogging)); #endif // BUILDFLAG(ENABLE_REPORTING) +#if BUILDFLAG(IS_CT_SUPPORTED) if (params_->enforce_chrome_ct_policy) { builder->set_ct_policy_enforcer( std::make_unique<certificate_transparency::ChromeCTPolicyEnforcer>()); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) net::HttpNetworkSession::Params session_params; bool is_quic_force_disabled = false; @@ -1557,6 +1850,7 @@ URLRequestContextOwner NetworkContext::ApplyContextParamsToBuilder( }, params_.get(), &context_network_delegate_, this)); +#if BUILDFLAG(IS_CT_SUPPORTED) std::vector<scoped_refptr<const net::CTLogVerifier>> ct_logs; if (!params_->ct_logs.empty()) { for (const auto& log : params_->ct_logs) { @@ -1573,6 +1867,7 @@ URLRequestContextOwner NetworkContext::ApplyContextParamsToBuilder( ct_verifier->AddLogs(ct_logs); builder->set_ct_verifier(std::move(ct_verifier)); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) auto result = URLRequestContextOwner(std::move(pref_service), builder->Build()); @@ -1622,13 +1917,13 @@ URLRequestContextOwner NetworkContext::ApplyContextParamsToBuilder( certificate_report_sender_.get()); } +#if BUILDFLAG(IS_CT_SUPPORTED) if (params_->enable_expect_ct_reporting) { LazyCreateExpectCTReporter(result.url_request_context.get()); result.url_request_context->transport_security_state()->SetExpectCTReporter( expect_ct_reporter_.get()); } -#if !defined(OS_IOS) if (base::FeatureList::IsEnabled(certificate_transparency::kCTLogAuditing) && network_service_ && !ct_logs.empty()) { net::URLRequestContext* context = result.url_request_context.get(); @@ -1638,7 +1933,6 @@ URLRequestContextOwner NetworkContext::ApplyContextParamsToBuilder( context->cert_transparency_verifier()->SetObserver(ct_tree_tracker_.get()); network_service_->sth_reporter()->RegisterObserver(ct_tree_tracker_.get()); } -#endif if (params_->enforce_chrome_ct_policy) { require_ct_delegate_ = @@ -1646,6 +1940,25 @@ URLRequestContextOwner NetworkContext::ApplyContextParamsToBuilder( result.url_request_context->transport_security_state() ->SetRequireCTDelegate(require_ct_delegate_.get()); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) + + if (params_->enable_domain_reliability) { + domain_reliability_monitor_ = + std::make_unique<domain_reliability::DomainReliabilityMonitor>( + params_->domain_reliability_upload_reporter, + base::BindRepeating(&NetworkContext::CanUploadDomainReliability, + base::Unretained(this))); + domain_reliability_monitor_->InitURLRequestContext( + result.url_request_context.get()); + domain_reliability_monitor_->AddBakedInConfigs(); + domain_reliability_monitor_->SetDiscardUploads( + params_->discard_domain_reliablity_uploads); + } + + if (proxy_delegate_) { + proxy_delegate_->SetProxyResolutionService( + result.url_request_context->proxy_resolution_service()); + } // These must be matched by cleanup code just before the URLRequestContext is // destroyed. @@ -1710,21 +2023,48 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext() { const base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); + std::unique_ptr<net::CertVerifier> cert_verifier; if (g_cert_verifier_for_testing) { - builder.SetCertVerifier(std::make_unique<WrappedTestingCertVerifier>()); + cert_verifier = std::make_unique<WrappedTestingCertVerifier>(); } else { - std::unique_ptr<net::CertVerifier> cert_verifier = - net::CertVerifier::CreateDefault(); - builder.SetCertVerifier(IgnoreErrorsCertVerifier::MaybeWrapCertVerifier( - *command_line, nullptr, std::move(cert_verifier))); +#if defined(OS_CHROMEOS) + if (params_->username_hash.empty()) { + cert_verifier = std::make_unique<net::CachingCertVerifier>( + std::make_unique<net::MultiThreadedCertVerifier>( + base::MakeRefCounted<CertVerifyProcChromeOS>())); + } else { + // Make sure NSS is initialized for the user. + crypto::InitializeNSSForChromeOSUser(params_->username_hash, + params_->nss_path.value()); + + crypto::ScopedPK11Slot public_slot = + crypto::GetPublicSlotForChromeOSUser(params_->username_hash); + scoped_refptr<net::CertVerifyProc> verify_proc( + new CertVerifyProcChromeOS(std::move(public_slot))); + + cert_verifier_with_trust_anchors_ = new CertVerifierWithTrustAnchors( + base::Bind(&NetworkContext::TrustAnchorUsed, base::Unretained(this))); + UpdateAdditionalCertificates( + std::move(params_->initial_additional_certificates)); + cert_verifier_with_trust_anchors_->InitializeOnIOThread(verify_proc); + cert_verifier = base::WrapUnique(cert_verifier_with_trust_anchors_); + } +#else + cert_verifier = net::CertVerifier::CreateDefault(); +#endif } + builder.SetCertVerifier(IgnoreErrorsCertVerifier::MaybeWrapCertVerifier( + *command_line, nullptr, std::move(cert_verifier))); + std::unique_ptr<net::NetworkDelegate> network_delegate = std::make_unique<NetworkServiceNetworkDelegate>(this); builder.set_network_delegate(std::move(network_delegate)); - if (params_->custom_proxy_config_client_request) { + if (params_->initial_custom_proxy_config || + params_->custom_proxy_config_client_request) { proxy_delegate_ = std::make_unique<NetworkServiceProxyDelegate>( + std::move(params_->initial_custom_proxy_config), std::move(params_->custom_proxy_config_client_request)); builder.set_shared_proxy_delegate(proxy_delegate_.get()); } @@ -1757,6 +2097,16 @@ void NetworkContext::DestroySocketManager(P2PSocketManager* socket_manager) { socket_managers_.erase(iter); } +void NetworkContext::CanUploadDomainReliability( + const GURL& origin, + base::OnceCallback<void(bool)> callback) { + client_->OnCanSendDomainReliabilityUpload( + origin, + base::BindOnce([](base::OnceCallback<void(bool)> callback, + bool allowed) { std::move(callback).Run(allowed); }, + std::move(callback))); +} + void NetworkContext::OnCertVerifyForSignedExchangeComplete(int cert_verify_id, int result) { auto iter = cert_verifier_requests_.find(cert_verify_id); @@ -1766,6 +2116,7 @@ void NetworkContext::OnCertVerifyForSignedExchangeComplete(int cert_verify_id, cert_verifier_requests_.erase(iter); net::ct::CTVerifyResult ct_verify_result; +#if BUILDFLAG(IS_CT_SUPPORTED) if (result == net::OK) { net::X509Certificate* verified_cert = pending_cert_verify->result->verified_cert.get(); @@ -1840,21 +2191,26 @@ void NetworkContext::OnCertVerifyForSignedExchangeComplete(int cert_verify_id, result = net::ERR_CERTIFICATE_TRANSPARENCY_REQUIRED; } } +#endif // BUILDFLAG(IS_CT_SUPPORTED) std::move(pending_cert_verify->callback) .Run(result, *pending_cert_verify->result.get(), ct_verify_result); } -void NetworkContext::ForceReloadProxyConfig( - ForceReloadProxyConfigCallback callback) { - url_request_context()->proxy_resolution_service()->ForceReloadProxyConfig(); - std::move(callback).Run(); +#if defined(OS_CHROMEOS) +void NetworkContext::TrustAnchorUsed() { + network_service_->client()->OnTrustAnchorUsed(params_->username_hash); } +#endif -void NetworkContext::ClearBadProxiesCache( - ClearBadProxiesCacheCallback callback) { - url_request_context()->proxy_resolution_service()->ClearBadProxiesCache(); - std::move(callback).Run(); +void NetworkContext::InitializeCorsOriginAccessList() { + for (const auto& pattern : params_->cors_origin_access_list) { + url::Origin origin = url::Origin::Create(GURL(pattern->source_origin)); + cors_origin_access_list_.SetAllowListForOrigin(origin, + pattern->allow_patterns); + cors_origin_access_list_.SetBlockListForOrigin(origin, + pattern->block_patterns); + } } } // namespace network diff --git a/chromium/services/network/network_context.h b/chromium/services/network/network_context.h index e25f3045425..0b424f69dff 100644 --- a/chromium/services/network/network_context.h +++ b/chromium/services/network/network_context.h @@ -7,9 +7,11 @@ #include <stdint.h> +#include <map> #include <memory> #include <set> #include <string> +#include <utility> #include <vector> #include "base/callback.h" @@ -18,15 +20,21 @@ #include "base/containers/unique_ptr_adapters.h" #include "base/files/file.h" #include "base/macros.h" +#include "base/optional.h" #include "base/time/time.h" #include "build/build_config.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/strong_binding_set.h" #include "net/cert/cert_verifier.h" #include "net/cert/cert_verify_result.h" +#include "net/dns/dns_config_overrides.h" +#include "net/dns/host_resolver.h" +#include "services/network/cors/preflight_controller.h" #include "services/network/http_cache_data_counter.h" #include "services/network/http_cache_data_remover.h" +#include "services/network/network_qualities_pref_delegate.h" #include "services/network/public/cpp/cors/origin_access_list.h" +#include "services/network/public/cpp/network_service_buildflags.h" #include "services/network/public/mojom/host_resolver.mojom.h" #include "services/network/public/mojom/network_context.mojom.h" #include "services/network/public/mojom/proxy_lookup_client.mojom.h" @@ -56,12 +64,19 @@ class ChromeRequireCTDelegate; class TreeStateTracker; } // namespace certificate_transparency +namespace domain_reliability { +class DomainReliabilityMonitor; +} // namespace domain_reliability + namespace network { +class CertVerifierWithTrustAnchors; class CookieManager; class ExpectCTReporter; class HostResolver; class NetworkService; class NetworkServiceProxyDelegate; +class MdnsResponderManager; +class NSSTempCertsCacheChromeOS; class P2PSocketManager; class ProxyLookupRequest; class ResourceScheduler; @@ -70,7 +85,7 @@ class URLRequestContextBuilderMojo; class WebSocketFactory; namespace cors { -class CORSURLLoaderFactory; +class CorsURLLoaderFactory; } // namespace cors // A NetworkContext creates and manages access to a URLRequestContext. @@ -154,6 +169,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext void SetClient(mojom::NetworkContextClientPtr client) override; void CreateURLLoaderFactory(mojom::URLLoaderFactoryRequest request, mojom::URLLoaderFactoryParamsPtr params) override; + void ResetURLLoaderFactories() override; void GetCookieManager(mojom::CookieManagerRequest request) override; void GetRestrictedCookieManager(mojom::RestrictedCookieManagerRequest request, const url::Origin& origin) override; @@ -167,6 +183,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext void ComputeHttpCacheSize(base::Time start_time, base::Time end_time, ComputeHttpCacheSizeCallback callback) override; + void WriteCacheMetadata(const GURL& url, + net::RequestPriority priority, + base::Time expected_response_time, + const std::vector<uint8_t>& data) override; void ClearChannelIds(base::Time start_time, base::Time end_time, mojom::ClearDataFilterPtr filter, @@ -184,12 +204,22 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext void ClearNetworkErrorLogging( mojom::ClearDataFilterPtr filter, ClearNetworkErrorLoggingCallback callback) override; + void ClearDomainReliability(mojom::ClearDataFilterPtr filter, + DomainReliabilityClearMode mode, + ClearDomainReliabilityCallback callback) override; + void GetDomainReliabilityJSON( + GetDomainReliabilityJSONCallback callback) override; void CloseAllConnections(CloseAllConnectionsCallback callback) override; void CloseIdleConnections(CloseIdleConnectionsCallback callback) override; void SetNetworkConditions(const base::UnguessableToken& throttling_profile_id, mojom::NetworkConditionsPtr conditions) override; void SetAcceptLanguage(const std::string& new_accept_language) override; void SetEnableReferrers(bool enable_referrers) override; +#if defined(OS_CHROMEOS) + void UpdateAdditionalCertificates( + mojom::AdditionalCertificatesPtr additional_certificates) override; +#endif +#if BUILDFLAG(IS_CT_SUPPORTED) void SetCTPolicy( const std::vector<std::string>& required_hosts, const std::vector<std::string>& excluded_hosts, @@ -204,6 +234,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext SetExpectCTTestReportCallback callback) override; void GetExpectCTState(const std::string& domain, GetExpectCTStateCallback callback) override; +#endif // BUILDFLAG(IS_CT_SUPPORTED) void CreateUDPSocket(mojom::UDPSocketRequest request, mojom::UDPSocketReceiverPtr receiver) override; void CreateTCPServerSocket( @@ -227,37 +258,35 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext CreateTCPBoundSocketCallback callback) override; void CreateProxyResolvingSocketFactory( mojom::ProxyResolvingSocketFactoryRequest request) override; - void CreateWebSocket(mojom::WebSocketRequest request, - int32_t process_id, - int32_t render_frame_id, - const url::Origin& origin, - mojom::AuthenticationHandlerPtr auth_handler) override; void LookUpProxyForURL( const GURL& url, mojom::ProxyLookupClientPtr proxy_lookup_client) override; void ForceReloadProxyConfig(ForceReloadProxyConfigCallback callback) override; void ClearBadProxiesCache(ClearBadProxiesCacheCallback callback) override; + void CreateWebSocket(mojom::WebSocketRequest request, + int32_t process_id, + int32_t render_frame_id, + const url::Origin& origin, + mojom::AuthenticationHandlerPtr auth_handler) override; void CreateNetLogExporter(mojom::NetLogExporterRequest request) override; void ResolveHost(const net::HostPortPair& host, mojom::ResolveHostParametersPtr optional_parameters, mojom::ResolveHostClientPtr response_client) override; - void CreateHostResolver(mojom::HostResolverRequest request) override; - void WriteCacheMetadata(const GURL& url, - net::RequestPriority priority, - base::Time expected_response_time, - const std::vector<uint8_t>& data) override; + void CreateHostResolver( + const base::Optional<net::DnsConfigOverrides>& config_overrides, + mojom::HostResolverRequest request) override; void VerifyCertForSignedExchange( const scoped_refptr<net::X509Certificate>& certificate, const GURL& url, const std::string& ocsp_result, const std::string& sct_list, VerifyCertForSignedExchangeCallback callback) override; - void IsHSTSActiveForHost(const std::string& host, - IsHSTSActiveForHostCallback callback) override; void AddHSTS(const std::string& host, base::Time expiry, bool include_subdomains, AddHSTSCallback callback) override; + void IsHSTSActiveForHost(const std::string& host, + IsHSTSActiveForHostCallback callback) override; void GetHSTSState(const std::string& domain, GetHSTSStateCallback callback) override; void DeleteDynamicDataForHost( @@ -268,9 +297,16 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext std::vector<mojom::CorsOriginPatternPtr> allow_patterns, std::vector<mojom::CorsOriginPatternPtr> block_patterns, SetCorsOriginAccessListsForOriginCallback callback) override; + void EnableStaticKeyPinningForTesting( + EnableStaticKeyPinningForTestingCallback callback) override; void SetFailingHttpTransactionForTesting( int32_t rv, SetFailingHttpTransactionForTestingCallback callback) override; + void VerifyCertificateForTesting( + const scoped_refptr<net::X509Certificate>& certificate, + const std::string& hostname, + const std::string& ocsp_response, + VerifyCertificateForTestingCallback callback) override; void PreconnectSockets(uint32_t num_streams, const GURL& url, int32_t load_flags, @@ -279,7 +315,22 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext mojom::P2PTrustedSocketManagerClientPtr client, mojom::P2PTrustedSocketManagerRequest trusted_socket_manager, mojom::P2PSocketManagerRequest socket_manager_request) override; - void ResetURLLoaderFactories() override; + void CreateMdnsResponder( + mojom::MdnsResponderRequest responder_request) override; + void QueueReport(const std::string& type, + const std::string& group, + const GURL& url, + const base::Optional<std::string>& user_agent, + base::Value body) override; + void AddDomainReliabilityContextForTesting( + const GURL& origin, + const GURL& upload_url, + AddDomainReliabilityContextForTestingCallback callback) override; + void ForceDomainReliabilityUploadsForTesting( + ForceDomainReliabilityUploadsForTestingCallback callback) override; + void LookupBasicAuthCredentials( + const GURL& url, + LookupBasicAuthCredentialsCallback callback) override; // Destroys |request| when a proxy lookup completes. void OnProxyLookupComplete(ProxyLookupRequest* proxy_lookup_request); @@ -289,7 +340,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext // Destroys the specified factory. Called by the factory itself when it has // no open pipes. - void DestroyURLLoaderFactory(cors::CORSURLLoaderFactory* url_loader_factory); + void DestroyURLLoaderFactory(cors::CorsURLLoaderFactory* url_loader_factory); size_t GetNumOutstandingResolveHostRequestsForTesting() const; @@ -301,6 +352,22 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext return proxy_delegate_.get(); } + void set_host_resolver_factory_for_testing( + std::unique_ptr<net::HostResolver::Factory> factory) { + host_resolver_factory_ = std::move(factory); + } + + void set_network_qualities_pref_delegate_for_testing( + std::unique_ptr<NetworkQualitiesPrefDelegate> + network_qualities_pref_delegate) { + network_qualities_pref_delegate_ = + std::move(network_qualities_pref_delegate); + } + + cors::PreflightController* cors_preflight_controller() { + return &cors_preflight_controller_; + } + private: class ContextNetworkDelegate; @@ -331,13 +398,24 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext void DestroySocketManager(P2PSocketManager* socket_manager); + void CanUploadDomainReliability(const GURL& origin, + base::OnceCallback<void(bool)> callback); + void OnCertVerifyForSignedExchangeComplete(int cert_verify_id, int result); +#if defined(OS_CHROMEOS) + void TrustAnchorUsed(); +#endif + +#if BUILDFLAG(IS_CT_SUPPORTED) void OnSetExpectCTTestReportSuccess(); void LazyCreateExpectCTReporter(net::URLRequestContext* url_request_context); void OnSetExpectCTTestReportFailure(); +#endif // BUILDFLAG(IS_CT_SUPPORTED) + + void InitializeCorsOriginAccessList(); NetworkService* const network_service_; @@ -387,13 +465,17 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext // This must be below |url_request_context_| so that the URLRequestContext // outlives all the URLLoaderFactories and URLLoaders that depend on it. - std::set<std::unique_ptr<cors::CORSURLLoaderFactory>, + std::set<std::unique_ptr<cors::CorsURLLoaderFactory>, base::UniquePtrComparator> url_loader_factories_; base::flat_map<P2PSocketManager*, std::unique_ptr<P2PSocketManager>> socket_managers_; +#if BUILDFLAG(ENABLE_MDNS) + std::unique_ptr<MdnsResponderManager> mdns_responder_manager_; +#endif // BUILDFLAG(ENABLE_MDNS) + mojo::StrongBindingSet<mojom::NetLogExporter> net_log_exporter_bindings_; mojo::StrongBindingSet<mojom::RestrictedCookieManager> @@ -411,16 +493,34 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext // URLRequestContext), and must be disconnected from it before it's destroyed. std::unique_ptr<net::ReportSender> certificate_report_sender_; +#if BUILDFLAG(IS_CT_SUPPORTED) std::unique_ptr<ExpectCTReporter> expect_ct_reporter_; std::unique_ptr<certificate_transparency::ChromeRequireCTDelegate> require_ct_delegate_; + + std::queue<SetExpectCTTestReportCallback> + outstanding_set_expect_ct_callbacks_; std::unique_ptr<certificate_transparency::TreeStateTracker> ct_tree_tracker_; +#endif // BUILDFLAG(IS_CT_SUPPORTED) + +#if defined(OS_CHROMEOS) + CertVerifierWithTrustAnchors* cert_verifier_with_trust_anchors_ = nullptr; + // Additional certificates made available to NSS cert validation as temporary + // certificates. + std::unique_ptr<network::NSSTempCertsCacheChromeOS> nss_temp_certs_cache_; +#endif // Created on-demand. Null if unused. std::unique_ptr<HostResolver> internal_host_resolver_; - std::set<std::unique_ptr<HostResolver>, base::UniquePtrComparator> + // Map values set to non-null only if that HostResolver has its own private + // internal net::HostResolver. + std::map<std::unique_ptr<HostResolver>, + std::unique_ptr<net::HostResolver>, + base::UniquePtrComparator> host_resolvers_; + // Factory used to create any needed private internal net::HostResolvers. + std::unique_ptr<net::HostResolver::Factory> host_resolver_factory_; std::unique_ptr<NetworkServiceProxyDelegate> proxy_delegate_; @@ -444,8 +544,14 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext // Manages allowed origin access lists. cors::OriginAccessList cors_origin_access_list_; - std::queue<SetExpectCTTestReportCallback> - outstanding_set_expect_ct_callbacks_; + // Manages CORS preflight requests and its cache. + cors::PreflightController cors_preflight_controller_; + + std::unique_ptr<NetworkQualitiesPrefDelegate> + network_qualities_pref_delegate_; + + std::unique_ptr<domain_reliability::DomainReliabilityMonitor> + domain_reliability_monitor_; DISALLOW_COPY_AND_ASSIGN(NetworkContext); }; diff --git a/chromium/services/network/network_context_unittest.cc b/chromium/services/network/network_context_unittest.cc index d22064b2fec..e451c3ff6ac 100644 --- a/chromium/services/network/network_context_unittest.cc +++ b/chromium/services/network/network_context_unittest.cc @@ -38,6 +38,7 @@ #include "build/build_config.h" #include "components/network_session_configurator/browser/network_session_configurator.h" #include "components/network_session_configurator/common/network_switches.h" +#include "components/prefs/testing_pref_service.h" #include "mojo/public/cpp/bindings/interface_request.h" #include "mojo/public/cpp/bindings/strong_binding.h" #include "mojo/public/cpp/system/data_pipe_utils.h" @@ -46,6 +47,7 @@ #include "net/base/host_port_pair.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" +#include "net/base/network_change_notifier.h" #include "net/base/test_completion_callback.h" #include "net/cert/cert_verify_result.h" #include "net/cert/mock_cert_verifier.h" @@ -53,13 +55,19 @@ #include "net/cookies/cookie_options.h" #include "net/cookies/cookie_store.h" #include "net/disk_cache/disk_cache.h" +#include "net/dns/dns_test_util.h" +#include "net/dns/host_resolver_impl.h" +#include "net/dns/host_resolver_source.h" #include "net/dns/mock_host_resolver.h" +#include "net/dns/public/dns_query_type.h" #include "net/http/http_auth.h" #include "net/http/http_cache.h" #include "net/http/http_network_session.h" #include "net/http/http_server_properties_manager.h" #include "net/http/http_transaction_factory.h" #include "net/http/http_transaction_test_util.h" +#include "net/http/transport_security_state_test_util.h" +#include "net/nqe/network_quality_estimator_test_util.h" #include "net/proxy_resolution/proxy_config.h" #include "net/proxy_resolution/proxy_info.h" #include "net/proxy_resolution/proxy_resolution_service.h" @@ -83,8 +91,10 @@ #include "services/network/cookie_manager.h" #include "services/network/net_log_exporter.h" #include "services/network/network_context.h" +#include "services/network/network_qualities_pref_delegate.h" #include "services/network/network_service.h" #include "services/network/public/cpp/features.h" +#include "services/network/public/cpp/network_service_buildflags.h" #include "services/network/public/mojom/host_resolver.mojom.h" #include "services/network/public/mojom/net_log.mojom.h" #include "services/network/public/mojom/network_service.mojom.h" @@ -113,32 +123,12 @@ const GURL kURL("http://foo.com"); const GURL kOtherURL("http://other.com"); constexpr char kMockHost[] = "mock.host"; -// Sends an HttpResponse for requests for "/" that result in sending an HPKP -// report. Ignores other paths to avoid catching the subsequent favicon -// request. -std::unique_ptr<net::test_server::HttpResponse> SendReportHttpResponse( - const GURL& report_url, - const net::test_server::HttpRequest& request) { - if (request.relative_url == "/") { - std::unique_ptr<net::test_server::BasicHttpResponse> response( - new net::test_server::BasicHttpResponse()); - std::string header_value = base::StringPrintf( - "max-age=50000;" - "pin-sha256=\"9999999999999999999999999999999999999999999=\";" - "pin-sha256=\"9999999999999999999999999999999999999999998=\";" - "report-uri=\"%s\"", - report_url.spec().c_str()); - response->AddCustomHeader("Public-Key-Pins-Report-Only", header_value); - return std::move(response); - } - - return nullptr; -} - +#if BUILDFLAG(IS_CT_SUPPORTED) void StoreBool(bool* result, const base::Closure& callback, bool value) { *result = value; callback.Run(); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) void StoreValue(base::Value* result, const base::Closure& callback, @@ -249,6 +239,7 @@ class NetworkContextTest : public testing::Test, NetworkContextTest() : scoped_task_environment_( base::test::ScopedTaskEnvironment::MainThreadType::IO), + network_change_notifier_(net::NetworkChangeNotifier::CreateMock()), network_service_(NetworkService::CreateForTesting()) {} ~NetworkContextTest() override {} @@ -335,6 +326,7 @@ class NetworkContextTest : public testing::Test, protected: base::test::ScopedTaskEnvironment scoped_task_environment_; + std::unique_ptr<net::NetworkChangeNotifier> network_change_notifier_; std::unique_ptr<NetworkService> network_service_; // Stores the NetworkContextPtr of the most recently created NetworkContext. // Not strictly needed, but seems best to mimic real-world usage. @@ -842,6 +834,45 @@ TEST_F(NetworkContextTest, ClearHttpServerPropertiesInMemory) { ->GetSupportsSpdy(kSchemeHostPort)); } +// Checks that ClearNetworkingHistorySince() clears network quality prefs. +TEST_F(NetworkContextTest, ClearingNetworkingHistoryClearNetworkQualityPrefs) { + const url::SchemeHostPort kSchemeHostPort("https", "foo", 443); + net::TestNetworkQualityEstimator estimator; + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(mojom::NetworkContextParams::New()); + TestingPrefServiceSimple pref_service_simple; + NetworkQualitiesPrefDelegate::RegisterPrefs(pref_service_simple.registry()); + + std::unique_ptr<NetworkQualitiesPrefDelegate> + network_qualities_pref_delegate = + std::make_unique<NetworkQualitiesPrefDelegate>(&pref_service_simple, + &estimator); + NetworkQualitiesPrefDelegate* network_qualities_pref_delegate_ptr = + network_qualities_pref_delegate.get(); + network_context->set_network_qualities_pref_delegate_for_testing( + std::move(network_qualities_pref_delegate)); + + // Running the loop allows prefs to be set. + base::RunLoop().RunUntilIdle(); + EXPECT_FALSE( + network_qualities_pref_delegate_ptr->ForceReadPrefsForTesting().empty()); + + // Clear the networking history. + base::RunLoop run_loop; + base::HistogramTester histogram_tester; + network_context->ClearNetworkingHistorySince( + base::Time::Now() - base::TimeDelta::FromHours(1), + run_loop.QuitClosure()); + run_loop.Run(); + + // Running the loop should clear the network quality prefs. + base::RunLoop().RunUntilIdle(); + // Prefs should be empty now. + EXPECT_TRUE( + network_qualities_pref_delegate_ptr->ForceReadPrefsForTesting().empty()); + histogram_tester.ExpectTotalCount("NQE.PrefsSizeOnClearing", 1); +} + // Test that TransportSecurity state is persisted (or not) as expected. TEST_F(NetworkContextTest, TransportSecurityStatePersisted) { const char kDomain[] = "foo.test"; @@ -897,28 +928,33 @@ TEST_F(NetworkContextTest, TransportSecurityStatePersisted) { } } -// Test that HPKP failures are reported if and only if certificate reporting is +// Test that PKP failures are reported if and only if certificate reporting is // enabled. TEST_F(NetworkContextTest, CertReporting) { - const char kReportPath[] = "/report"; + const char kPreloadedPKPHost[] = "with-report-uri-pkp.preloaded.test"; + const char kReportHost[] = "report-uri.preloaded.test"; + const char kReportPath[] = "/pkp"; for (bool reporting_enabled : {false, true}) { - // Server that HPKP reports are sent to. + // Server that PKP reports are sent to. net::test_server::EmbeddedTestServer report_test_server; net::test_server::ControllableHttpResponse controllable_response( &report_test_server, kReportPath); ASSERT_TRUE(report_test_server.Start()); - // Server that sends an HPKP report when its root document is fetched. - net::test_server::EmbeddedTestServer hpkp_test_server( + // Configure the TransportSecurityStateSource so that kPreloadedPKPHost will + // have static PKP pins set, with a report URI on kReportHost. + net::ScopedTransportSecurityStateSource scoped_security_state_source( + report_test_server.port()); + + // Configure a test HTTPS server. + net::test_server::EmbeddedTestServer pkp_test_server( net::test_server::EmbeddedTestServer::TYPE_HTTPS); - hpkp_test_server.SetSSLConfig( + pkp_test_server.SetSSLConfig( net::test_server::EmbeddedTestServer::CERT_COMMON_NAME_IS_DOMAIN); - hpkp_test_server.RegisterRequestHandler(base::BindRepeating( - &SendReportHttpResponse, report_test_server.GetURL(kReportPath))); - ASSERT_TRUE(hpkp_test_server.Start()); + ASSERT_TRUE(pkp_test_server.Start()); - // Configure mock cert verifier to cause the HPKP check to fail. + // Configure mock cert verifier to cause the PKP check to fail. net::CertVerifyResult result; result.verified_cert = net::CreateCertificateChainFromFile( net::GetTestCertsDirectory(), "ok_cert.pem", @@ -928,18 +964,35 @@ TEST_F(NetworkContextTest, CertReporting) { result.public_key_hashes.push_back(net::HashValue(hash)); result.is_issued_by_known_root = true; net::MockCertVerifier mock_verifier; - mock_verifier.AddResultForCert(hpkp_test_server.GetCertificate(), result, + mock_verifier.AddResultForCert(pkp_test_server.GetCertificate(), result, net::OK); NetworkContext::SetCertVerifierForTesting(&mock_verifier); + // Configure a MockHostResolver to map requests to kPreloadedPKPHost and + // kReportHost to the test servers: + scoped_refptr<net::RuleBasedHostResolverProc> mock_resolver_proc = + base::MakeRefCounted<net::RuleBasedHostResolverProc>(nullptr); + mock_resolver_proc->AddIPLiteralRule( + kPreloadedPKPHost, pkp_test_server.GetIPLiteralString(), std::string()); + mock_resolver_proc->AddIPLiteralRule( + kReportHost, report_test_server.GetIPLiteralString(), std::string()); + net::ScopedDefaultHostResolverProc scoped_default_host_resolver( + mock_resolver_proc.get()); + mojom::NetworkContextParamsPtr context_params = CreateContextParams(); EXPECT_FALSE(context_params->enable_certificate_reporting); context_params->enable_certificate_reporting = reporting_enabled; std::unique_ptr<NetworkContext> network_context = CreateContextWithParams(std::move(context_params)); + // Enable static pins so that requests made to kPreloadedPKPHost will check + // the pins, and send a report if the pinning check fails. + network_context->url_request_context() + ->transport_security_state() + ->EnableStaticPinsForTesting(); + ResourceRequest request; - request.url = hpkp_test_server.base_url(); + request.url = pkp_test_server.GetURL(kPreloadedPKPHost, "/"); mojom::URLLoaderFactoryPtr loader_factory; mojom::URLLoaderFactoryParamsPtr params = @@ -958,7 +1011,8 @@ TEST_F(NetworkContextTest, CertReporting) { client.RunUntilComplete(); EXPECT_TRUE(client.has_received_completion()); - EXPECT_EQ(net::OK, client.completion_status().error_code); + EXPECT_EQ(net::ERR_INSECURE_RESPONSE, + client.completion_status().error_code); if (reporting_enabled) { // If reporting is enabled, wait to see the request from the ReportSender. @@ -1659,6 +1713,47 @@ TEST_F(NetworkContextTest, ClearEmptyHttpAuthCache) { EXPECT_EQ(0u, cache->GetEntriesSizeForTesting()); } +TEST_F(NetworkContextTest, LookupBasicAuthCredentials) { + GURL origin("http://google.com"); + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(CreateContextParams()); + net::HttpAuthCache* cache = network_context->url_request_context() + ->http_transaction_factory() + ->GetSession() + ->http_auth_cache(); + + base::string16 user = base::ASCIIToUTF16("user"); + base::string16 password = base::ASCIIToUTF16("pass"); + cache->Add(origin, "Realm", net::HttpAuth::AUTH_SCHEME_BASIC, + "basic realm=Realm", net::AuthCredentials(user, password), "/"); + + base::RunLoop run_loop1; + base::Optional<net::AuthCredentials> result; + network_context->LookupBasicAuthCredentials( + origin, base::BindLambdaForTesting( + [&](const base::Optional<net::AuthCredentials>& credentials) { + result = credentials; + run_loop1.Quit(); + })); + run_loop1.Run(); + + EXPECT_TRUE(result.has_value()); + EXPECT_EQ(user, result->username()); + EXPECT_EQ(password, result->password()); + + base::RunLoop run_loop2; + result = base::nullopt; + network_context->LookupBasicAuthCredentials( + GURL("http://foo.com"), + base::BindLambdaForTesting( + [&](const base::Optional<net::AuthCredentials>& credentials) { + result = credentials; + run_loop2.Quit(); + })); + run_loop2.Run(); + EXPECT_FALSE(result.has_value()); +} + #if BUILDFLAG(ENABLE_REPORTING) TEST_F(NetworkContextTest, ClearReportingCacheReports) { std::unique_ptr<NetworkContext> network_context = @@ -2922,12 +3017,48 @@ TEST_F(NetworkContextTest, ResolveHost_CloseClient) { network_context->GetNumOutstandingResolveHostRequestsForTesting()); } +// Test factory of net::HostResolvers. Creates standard net::HostResolverImpl. +// Keeps pointers to all created resolvers. +class TestResolverFactory : public net::HostResolver::Factory { + public: + static TestResolverFactory* CreateAndSetFactory(NetworkContext* context) { + auto factory = std::make_unique<TestResolverFactory>(); + auto* factory_ptr = factory.get(); + context->set_host_resolver_factory_for_testing(std::move(factory)); + return factory_ptr; + } + + std::unique_ptr<net::HostResolver> CreateResolver( + const net::HostResolver::Options& options, + net::NetLog* net_log) override { + std::unique_ptr<net::HostResolverImpl> resolver = + net::HostResolver::CreateSystemResolverImpl(options, net_log); + resolvers_.push_back(resolver.get()); + return resolver; + } + + const std::vector<net::HostResolverImpl*>& resolvers() const { + return resolvers_; + } + + private: + std::vector<net::HostResolverImpl*> resolvers_; +}; + TEST_F(NetworkContextTest, CreateHostResolver) { std::unique_ptr<NetworkContext> network_context = CreateContextWithParams(CreateContextParams()); + // Inject a factory to control and capture created net::HostResolvers. + TestResolverFactory* factory = + TestResolverFactory::CreateAndSetFactory(network_context.get()); + mojom::HostResolverPtr resolver; - network_context->CreateHostResolver(mojo::MakeRequest(&resolver)); + network_context->CreateHostResolver(base::nullopt, + mojo::MakeRequest(&resolver)); + + // Expected to use shared internal HostResolver. + EXPECT_TRUE(factory->resolvers().empty()); base::RunLoop run_loop; mojom::ResolveHostClientPtr response_client_ptr; @@ -2957,7 +3088,8 @@ TEST_F(NetworkContextTest, CreateHostResolver_CloseResolver) { internal_resolver.get()); mojom::HostResolverPtr resolver; - network_context->CreateHostResolver(mojo::MakeRequest(&resolver)); + network_context->CreateHostResolver(base::nullopt, + mojo::MakeRequest(&resolver)); ASSERT_EQ(0, internal_resolver->num_cancellations()); @@ -2999,7 +3131,8 @@ TEST_F(NetworkContextTest, CreateHostResolver_CloseContext) { internal_resolver.get()); mojom::HostResolverPtr resolver; - network_context->CreateHostResolver(mojo::MakeRequest(&resolver)); + network_context->CreateHostResolver(base::nullopt, + mojo::MakeRequest(&resolver)); ASSERT_EQ(0, internal_resolver->num_cancellations()); @@ -3040,13 +3173,81 @@ TEST_F(NetworkContextTest, CreateHostResolver_CloseContext) { EXPECT_TRUE(resolver_closed); } +TEST_F(NetworkContextTest, CreateHostResolverWithConfigOverrides) { + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(CreateContextParams()); + + // Inject a factory to control and capture created net::HostResolvers. + TestResolverFactory* factory = + TestResolverFactory::CreateAndSetFactory(network_context.get()); + + net::DnsConfigOverrides overrides; + overrides.nameservers = std::vector<net::IPEndPoint>{ + CreateExpectedEndPoint("100.100.100.100", 22)}; + + mojom::HostResolverPtr resolver; + network_context->CreateHostResolver(overrides, mojo::MakeRequest(&resolver)); + + // Should create 1 private resolver with a DnsClient (if DnsClient is + // enablable for the build config). + ASSERT_EQ(1u, factory->resolvers().size()); + net::HostResolverImpl* internal_resolver = factory->resolvers().front(); +#if defined(ENABLE_BUILT_IN_DNS) + EXPECT_TRUE(internal_resolver->GetDnsConfigAsValue()); +#endif + + // Override DnsClient with a basic mock. + const std::string kQueryHostname = "example.com"; + const std::string kResult = "1.2.3.4"; + net::IPAddress result; + CHECK(result.AssignFromIPLiteral(kResult)); + net::MockDnsClientRuleList rules{ + net::MockDnsClientRule(kQueryHostname, net::dns_protocol::kTypeA, + net::MockDnsClientRule::Result(result), false), + net::MockDnsClientRule(kQueryHostname, net::dns_protocol::kTypeAAAA, + net::MockDnsClientRule::Result( + net::MockDnsClientRule::ResultType::EMPTY), + false)}; + auto mock_dns_client = + std::make_unique<net::MockDnsClient>(net::DnsConfig(), rules); + auto* mock_dns_client_ptr = mock_dns_client.get(); + internal_resolver->SetDnsClient(std::move(mock_dns_client)); + + // Force the base configuration to ensure consistent overriding. + net::DnsConfig base_configuration; + base_configuration.nameservers = {CreateExpectedEndPoint("12.12.12.12", 53)}; + internal_resolver->SetBaseDnsConfigForTesting(base_configuration); + + // Test that the DnsClient is getting the overridden configuration. + EXPECT_TRUE(overrides.ApplyOverrides(base_configuration) + .Equals(*mock_dns_client_ptr->GetConfig())); + + // Ensure we are using the private resolver by testing that we get results + // from the overridden DnsClient. + base::RunLoop run_loop; + mojom::ResolveHostParametersPtr optional_parameters = + mojom::ResolveHostParameters::New(); + optional_parameters->dns_query_type = net::DnsQueryType::A; + optional_parameters->source = net::HostResolverSource::DNS; + mojom::ResolveHostClientPtr response_client_ptr; + TestResolveHostClient response_client(&response_client_ptr, &run_loop); + resolver->ResolveHost(net::HostPortPair(kQueryHostname, 80), + std::move(optional_parameters), + std::move(response_client_ptr)); + run_loop.Run(); + + EXPECT_EQ(net::OK, response_client.result_error()); + EXPECT_THAT(response_client.result_addresses().value().endpoints(), + testing::ElementsAre(CreateExpectedEndPoint(kResult, 80))); +} + TEST_F(NetworkContextTest, PrivacyModeDisabledByDefault) { std::unique_ptr<NetworkContext> network_context = CreateContextWithParams(CreateContextParams()); EXPECT_FALSE(network_context->url_request_context() ->network_delegate() - ->CanEnablePrivacyMode(kURL, kOtherURL)); + ->ForcePrivacyMode(kURL, kOtherURL)); } TEST_F(NetworkContextTest, PrivacyModeEnabledIfCookiesBlocked) { @@ -3057,10 +3258,10 @@ TEST_F(NetworkContextTest, PrivacyModeEnabledIfCookiesBlocked) { network_context.get()); EXPECT_TRUE(network_context->url_request_context() ->network_delegate() - ->CanEnablePrivacyMode(kURL, kOtherURL)); + ->ForcePrivacyMode(kURL, kOtherURL)); EXPECT_FALSE(network_context->url_request_context() ->network_delegate() - ->CanEnablePrivacyMode(kOtherURL, kURL)); + ->ForcePrivacyMode(kOtherURL, kURL)); } TEST_F(NetworkContextTest, PrivacyModeDisabledIfCookiesAllowed) { @@ -3071,7 +3272,7 @@ TEST_F(NetworkContextTest, PrivacyModeDisabledIfCookiesAllowed) { network_context.get()); EXPECT_FALSE(network_context->url_request_context() ->network_delegate() - ->CanEnablePrivacyMode(kURL, kOtherURL)); + ->ForcePrivacyMode(kURL, kOtherURL)); } TEST_F(NetworkContextTest, PrivacyModeDisabledIfCookiesSettingForOtherURL) { @@ -3083,7 +3284,7 @@ TEST_F(NetworkContextTest, PrivacyModeDisabledIfCookiesSettingForOtherURL) { network_context.get()); EXPECT_FALSE(network_context->url_request_context() ->network_delegate() - ->CanEnablePrivacyMode(kURL, kOtherURL)); + ->ForcePrivacyMode(kURL, kOtherURL)); } TEST_F(NetworkContextTest, PrivacyModeEnabledIfThirdPartyCookiesBlocked) { @@ -3093,12 +3294,12 @@ TEST_F(NetworkContextTest, PrivacyModeEnabledIfThirdPartyCookiesBlocked) { network_context->url_request_context()->network_delegate(); network_context->cookie_manager()->BlockThirdPartyCookies(true); - EXPECT_TRUE(delegate->CanEnablePrivacyMode(kURL, kOtherURL)); - EXPECT_FALSE(delegate->CanEnablePrivacyMode(kURL, kURL)); + EXPECT_TRUE(delegate->ForcePrivacyMode(kURL, kOtherURL)); + EXPECT_FALSE(delegate->ForcePrivacyMode(kURL, kURL)); network_context->cookie_manager()->BlockThirdPartyCookies(false); - EXPECT_FALSE(delegate->CanEnablePrivacyMode(kURL, kOtherURL)); - EXPECT_FALSE(delegate->CanEnablePrivacyMode(kURL, kURL)); + EXPECT_FALSE(delegate->ForcePrivacyMode(kURL, kOtherURL)); + EXPECT_FALSE(delegate->ForcePrivacyMode(kURL, kURL)); } TEST_F(NetworkContextTest, CanSetCookieFalseIfCookiesBlocked) { @@ -3438,7 +3639,8 @@ TEST_F(NetworkContextTest, CloseAllConnections) { EXPECT_EQ(num_sockets, 0); } -TEST_F(NetworkContextTest, CloseIdleConnections) { +// Flaky; see http://crbug.com/905423 +TEST_F(NetworkContextTest, DISABLED_CloseIdleConnections) { std::unique_ptr<NetworkContext> network_context = CreateContextWithParams(CreateContextParams()); @@ -3484,6 +3686,7 @@ TEST_F(NetworkContextTest, CloseIdleConnections) { 1, GetSocketPoolInfo(network_context.get(), "handed_out_socket_count")); } +#if BUILDFLAG(IS_CT_SUPPORTED) TEST_F(NetworkContextTest, ExpectCT) { std::unique_ptr<NetworkContext> network_context = CreateContextWithParams(CreateContextParams()); @@ -3603,6 +3806,7 @@ TEST_F(NetworkContextTest, SetExpectCTTestReport) { EXPECT_TRUE(base::ContainsKey(requested_urls, kReportURL)); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) TEST_F(NetworkContextTest, QueryHSTS) { const char kTestDomain[] = "example.com"; @@ -4125,15 +4329,16 @@ TEST_F(NetworkContextTest, EnsureProperProxyServerIsUsed) { } proxy_config_set[2]; proxy_config_set[0].proxy_config.proxy_rules().ParseFromString( - base::StringPrintf("http=%s", - test_server.host_port_pair().ToString().c_str())); - // The domain here is irrelevant, and it is the path that matters. + "http=" + test_server.host_port_pair().ToString()); proxy_config_set[0].url = GURL("http://does.not.matter/echo"); proxy_config_set[0].expected_proxy_config_scheme = net::ProxyServer::SCHEME_HTTP; proxy_config_set[1].proxy_config.proxy_rules().ParseFromString( "http=direct://"); + proxy_config_set[1] + .proxy_config.proxy_rules() + .bypass_rules.AddRulesToSubtractImplicit(); proxy_config_set[1].url = test_server.GetURL("/echo"); proxy_config_set[1].expected_proxy_config_scheme = net::ProxyServer::SCHEME_DIRECT; @@ -4156,8 +4361,7 @@ TEST_F(NetworkContextTest, EnsureProperProxyServerIsUsed) { std::move(params)); ResourceRequest request; - // The domain here is irrelevant, and it is the path that matters. - request.url = proxy_data.url; // test_server.GetURL("/echo"); + request.url = proxy_data.url; mojom::URLLoaderPtr loader; TestURLLoaderClient client; @@ -4174,6 +4378,152 @@ TEST_F(NetworkContextTest, EnsureProperProxyServerIsUsed) { } } +class TestHeaderClient : public mojom::TrustedURLLoaderHeaderClient { + public: + // network::mojom::TrustedURLLoaderHeaderClient: + void OnBeforeSendHeaders(int32_t request_id, + const net::HttpRequestHeaders& headers, + OnBeforeSendHeadersCallback callback) override { + auto new_headers = headers; + new_headers.SetHeader("foo", "bar"); + std::move(callback).Run(on_before_send_headers_result, new_headers); + } + void OnHeadersReceived(int32_t request_id, + const std::string& headers, + OnHeadersReceivedCallback callback) override { + auto new_headers = base::MakeRefCounted<net::HttpResponseHeaders>(headers); + new_headers->AddHeader("baz: qux"); + std::move(callback).Run(on_headers_received_result, + new_headers->raw_headers(), GURL()); + } + + int on_before_send_headers_result = net::OK; + int on_headers_received_result = net::OK; +}; + +TEST_F(NetworkContextTest, HeaderClientModifiesHeaders) { + net::EmbeddedTestServer test_server; + net::test_server::RegisterDefaultHandlers(&test_server); + ASSERT_TRUE(test_server.Start()); + + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(CreateContextParams()); + + ResourceRequest request; + request.url = test_server.GetURL("/echoheader?foo"); + + mojom::URLLoaderFactoryPtr loader_factory; + mojom::URLLoaderFactoryParamsPtr params = + mojom::URLLoaderFactoryParams::New(); + params->process_id = mojom::kBrowserProcessId; + params->is_corb_enabled = false; + mojo::MakeStrongBinding(std::make_unique<TestHeaderClient>(), + mojo::MakeRequest(¶ms->header_client)); + network_context->CreateURLLoaderFactory(mojo::MakeRequest(&loader_factory), + std::move(params)); + + // First, do a request with kURLLoadOptionUseHeaderClient set. + { + mojom::URLLoaderPtr loader; + TestURLLoaderClient client; + loader_factory->CreateLoaderAndStart( + mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */, + mojom::kURLLoadOptionUseHeaderClient, request, + client.CreateInterfacePtr(), + net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); + + client.RunUntilComplete(); + + // Make sure request header was modified. The value will be in the body + // since we used the /echoheader endpoint. + std::string response; + EXPECT_TRUE( + mojo::BlockingCopyToString(client.response_body_release(), &response)); + EXPECT_EQ(response, "bar"); + + // Make sure response header was modified. + EXPECT_TRUE(client.response_head().headers->HasHeaderValue("baz", "qux")); + } + + // Next, do a request without kURLLoadOptionUseHeaderClient set, headers + // should not be modified. + { + mojom::URLLoaderPtr loader; + TestURLLoaderClient client; + loader_factory->CreateLoaderAndStart( + mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */, + 0 /* options */, request, client.CreateInterfacePtr(), + net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); + + client.RunUntilComplete(); + + // Make sure request header was not set. + std::string response; + EXPECT_TRUE( + mojo::BlockingCopyToString(client.response_body_release(), &response)); + EXPECT_EQ(response, "None"); + + // Make sure response header was not set. + EXPECT_FALSE(client.response_head().headers->HasHeaderValue("foo", "bar")); + } +} + +TEST_F(NetworkContextTest, HeaderClientFailsRequest) { + net::EmbeddedTestServer test_server; + net::test_server::RegisterDefaultHandlers(&test_server); + ASSERT_TRUE(test_server.Start()); + + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(CreateContextParams()); + + ResourceRequest request; + request.url = test_server.GetURL("/echo"); + + auto header_client = std::make_unique<TestHeaderClient>(); + auto* raw_header_client = header_client.get(); + + mojom::URLLoaderFactoryPtr loader_factory; + mojom::URLLoaderFactoryParamsPtr params = + mojom::URLLoaderFactoryParams::New(); + params->process_id = mojom::kBrowserProcessId; + params->is_corb_enabled = false; + mojo::MakeStrongBinding(std::move(header_client), + mojo::MakeRequest(¶ms->header_client)); + network_context->CreateURLLoaderFactory(mojo::MakeRequest(&loader_factory), + std::move(params)); + + // First, fail request on OnBeforeSendHeaders. + { + raw_header_client->on_before_send_headers_result = net::ERR_FAILED; + mojom::URLLoaderPtr loader; + TestURLLoaderClient client; + loader_factory->CreateLoaderAndStart( + mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */, + mojom::kURLLoadOptionUseHeaderClient, request, + client.CreateInterfacePtr(), + net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); + + client.RunUntilComplete(); + EXPECT_EQ(client.completion_status().error_code, net::ERR_FAILED); + } + + // Next, fail request on OnHeadersReceived. + { + raw_header_client->on_before_send_headers_result = net::OK; + raw_header_client->on_headers_received_result = net::ERR_FAILED; + mojom::URLLoaderPtr loader; + TestURLLoaderClient client; + loader_factory->CreateLoaderAndStart( + mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */, + mojom::kURLLoadOptionUseHeaderClient, request, + client.CreateInterfacePtr(), + net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); + + client.RunUntilComplete(); + EXPECT_EQ(client.completion_status().error_code, net::ERR_FAILED); + } +} + // Custom proxy does not apply to localhost, so resolve kMockHost to localhost, // and use that instead. class NetworkContextMockHostTest : public NetworkContextTest { @@ -4194,6 +4544,16 @@ class NetworkContextMockHostTest : public NetworkContextTest { EXPECT_TRUE(base_url.is_valid()) << base_url.possibly_invalid_spec(); return base_url.Resolve(relative_url); } + + net::ProxyServer ConvertToProxyServer(const net::EmbeddedTestServer& server) { + std::string base_url = server.base_url().spec(); + // Remove slash from URL. + base_url.pop_back(); + auto proxy_server = + net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP); + EXPECT_TRUE(proxy_server.is_valid()) << base_url; + return proxy_server; + } }; TEST_F(NetworkContextMockHostTest, CustomProxyAddsHeaders) { @@ -4212,10 +4572,8 @@ TEST_F(NetworkContextMockHostTest, CustomProxyAddsHeaders) { CreateContextWithParams(std::move(context_params)); auto config = mojom::CustomProxyConfig::New(); - std::string base_url = proxy_test_server.base_url().spec(); - // Remove slash from URL. - base_url.pop_back(); - config->rules.ParseFromString("http=" + base_url); + net::ProxyServer proxy_server = ConvertToProxyServer(proxy_test_server); + config->rules.ParseFromString("http=" + proxy_server.ToURI()); config->pre_cache_headers.SetHeader("pre_foo", "pre_foo_value"); config->post_cache_headers.SetHeader("post_foo", "post_foo_value"); proxy_config_client->OnCustomProxyConfigUpdated(std::move(config)); @@ -4236,12 +4594,10 @@ TEST_F(NetworkContextMockHostTest, CustomProxyAddsHeaders) { EXPECT_EQ(response, base::JoinString({"post_bar_value", "post_foo_value", "pre_bar_value", "pre_foo_value"}, "\n")); - EXPECT_EQ(client->response_head().proxy_server, - net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP)); + EXPECT_EQ(client->response_head().proxy_server, proxy_server); } -TEST_F(NetworkContextMockHostTest, - CustomProxyRequestHeadersOverrideConfigHeaders) { +TEST_F(NetworkContextMockHostTest, CustomProxyHeadersAreMerged) { net::EmbeddedTestServer test_server; ASSERT_TRUE(test_server.Start()); @@ -4257,18 +4613,18 @@ TEST_F(NetworkContextMockHostTest, CreateContextWithParams(std::move(context_params)); auto config = mojom::CustomProxyConfig::New(); - std::string base_url = proxy_test_server.base_url().spec(); - // Remove slash from URL. - base_url.pop_back(); - config->rules.ParseFromString("http=" + base_url); - config->pre_cache_headers.SetHeader("foo", "bad"); - config->post_cache_headers.SetHeader("bar", "bad"); + net::ProxyServer proxy_server = ConvertToProxyServer(proxy_test_server); + config->rules.ParseFromString("http=" + proxy_server.ToURI()); + config->pre_cache_headers.SetHeader("foo", "first_foo_key=value1"); + config->post_cache_headers.SetHeader("bar", "first_bar_key=value2"); proxy_config_client->OnCustomProxyConfigUpdated(std::move(config)); scoped_task_environment_.RunUntilIdle(); ResourceRequest request; - request.custom_proxy_pre_cache_headers.SetHeader("foo", "foo_value"); - request.custom_proxy_post_cache_headers.SetHeader("bar", "bar_value"); + request.custom_proxy_pre_cache_headers.SetHeader("foo", + "foo_next_key=value3"); + request.custom_proxy_post_cache_headers.SetHeader("bar", + "bar_next_key=value4"); request.url = GetURLWithMockHost(test_server, "/echoheader?foo&bar"); std::unique_ptr<TestURLLoaderClient> client = FetchRequest(request, network_context.get()); @@ -4276,9 +4632,11 @@ TEST_F(NetworkContextMockHostTest, EXPECT_TRUE( mojo::BlockingCopyToString(client->response_body_release(), &response)); - EXPECT_EQ(response, base::JoinString({"bar_value", "foo_value"}, "\n")); - EXPECT_EQ(client->response_head().proxy_server, - net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP)); + EXPECT_EQ(response, + base::JoinString({"first_bar_key=value2, bar_next_key=value4", + "first_foo_key=value1, foo_next_key=value3"}, + "\n")); + EXPECT_EQ(client->response_head().proxy_server, proxy_server); } TEST_F(NetworkContextMockHostTest, CustomProxyConfigHeadersAddedBeforeCache) { @@ -4297,10 +4655,8 @@ TEST_F(NetworkContextMockHostTest, CustomProxyConfigHeadersAddedBeforeCache) { CreateContextWithParams(std::move(context_params)); auto config = mojom::CustomProxyConfig::New(); - std::string base_url = proxy_test_server.base_url().spec(); - // Remove slash from URL. - base_url.pop_back(); - config->rules.ParseFromString("http=" + base_url); + net::ProxyServer proxy_server = ConvertToProxyServer(proxy_test_server); + config->rules.ParseFromString("http=" + proxy_server.ToURI()); config->pre_cache_headers.SetHeader("foo", "foo_value"); config->post_cache_headers.SetHeader("bar", "bar_value"); proxy_config_client->OnCustomProxyConfigUpdated(config->Clone()); @@ -4315,8 +4671,7 @@ TEST_F(NetworkContextMockHostTest, CustomProxyConfigHeadersAddedBeforeCache) { mojo::BlockingCopyToString(client->response_body_release(), &response)); EXPECT_EQ(response, base::JoinString({"bar_value", "foo_value"}, "\n")); - EXPECT_EQ(client->response_head().proxy_server, - net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP)); + EXPECT_EQ(client->response_head().proxy_server, proxy_server); EXPECT_FALSE(client->response_head().was_fetched_via_cache); // post_cache_headers should not break caching. @@ -4341,8 +4696,7 @@ TEST_F(NetworkContextMockHostTest, CustomProxyConfigHeadersAddedBeforeCache) { mojo::BlockingCopyToString(client->response_body_release(), &response)); EXPECT_EQ(response, base::JoinString({"new_bar", "new_foo"}, "\n")); - EXPECT_EQ(client->response_head().proxy_server, - net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP)); + EXPECT_EQ(client->response_head().proxy_server, proxy_server); EXPECT_FALSE(client->response_head().was_fetched_via_cache); } @@ -4362,10 +4716,8 @@ TEST_F(NetworkContextMockHostTest, CustomProxyRequestHeadersAddedBeforeCache) { CreateContextWithParams(std::move(context_params)); auto config = mojom::CustomProxyConfig::New(); - std::string base_url = proxy_test_server.base_url().spec(); - // Remove slash from URL. - base_url.pop_back(); - config->rules.ParseFromString("http=" + base_url); + net::ProxyServer proxy_server = ConvertToProxyServer(proxy_test_server); + config->rules.ParseFromString("http=" + proxy_server.ToURI()); proxy_config_client->OnCustomProxyConfigUpdated(std::move(config)); scoped_task_environment_.RunUntilIdle(); @@ -4380,8 +4732,7 @@ TEST_F(NetworkContextMockHostTest, CustomProxyRequestHeadersAddedBeforeCache) { mojo::BlockingCopyToString(client->response_body_release(), &response)); EXPECT_EQ(response, base::JoinString({"bar_value", "foo_value"}, "\n")); - EXPECT_EQ(client->response_head().proxy_server, - net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP)); + EXPECT_EQ(client->response_head().proxy_server, proxy_server); EXPECT_FALSE(client->response_head().was_fetched_via_cache); // custom_proxy_post_cache_headers should not break caching. @@ -4402,8 +4753,7 @@ TEST_F(NetworkContextMockHostTest, CustomProxyRequestHeadersAddedBeforeCache) { mojo::BlockingCopyToString(client->response_body_release(), &response)); EXPECT_EQ(response, base::JoinString({"new_bar", "new_foo"}, "\n")); - EXPECT_EQ(client->response_head().proxy_server, - net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP)); + EXPECT_EQ(client->response_head().proxy_server, proxy_server); EXPECT_FALSE(client->response_head().was_fetched_via_cache); } @@ -4453,10 +4803,8 @@ TEST_F(NetworkContextMockHostTest, mojom::NetworkContextParamsPtr context_params = CreateContextParams(); // Set up a proxy to be used by the proxy config service. net::ProxyConfig proxy_config; - std::string base_url = proxy_test_server.base_url().spec(); - // Remove slash from URL. - base_url.pop_back(); - proxy_config.proxy_rules().ParseFromString("http=" + base_url); + proxy_config.proxy_rules().ParseFromString( + "http=" + ConvertToProxyServer(proxy_test_server).ToURI()); context_params->initial_proxy_config = net::ProxyConfigWithAnnotation( proxy_config, TRAFFIC_ANNOTATION_FOR_TESTS); @@ -4485,7 +4833,46 @@ TEST_F(NetworkContextMockHostTest, EXPECT_EQ(response, base::JoinString({"None", "None", "None", "None"}, "\n")); EXPECT_EQ(client->response_head().proxy_server, - net::ProxyServer::FromURI(base_url, net::ProxyServer::SCHEME_HTTP)); + ConvertToProxyServer(proxy_test_server)); +} + +TEST_F(NetworkContextMockHostTest, CustomProxyUsesAlternateProxyList) { + net::EmbeddedTestServer invalid_server; + ASSERT_TRUE(invalid_server.Start()); + + net::EmbeddedTestServer proxy_test_server; + net::test_server::RegisterDefaultHandlers(&proxy_test_server); + ASSERT_TRUE(proxy_test_server.Start()); + + mojom::CustomProxyConfigClientPtr proxy_config_client; + mojom::NetworkContextParamsPtr context_params = CreateContextParams(); + context_params->custom_proxy_config_client_request = + mojo::MakeRequest(&proxy_config_client); + std::unique_ptr<NetworkContext> network_context = + CreateContextWithParams(std::move(context_params)); + + auto config = mojom::CustomProxyConfig::New(); + config->rules.ParseFromString("http=" + + ConvertToProxyServer(invalid_server).ToURI()); + + config->alternate_proxy_list.AddProxyServer( + ConvertToProxyServer(proxy_test_server)); + proxy_config_client->OnCustomProxyConfigUpdated(std::move(config)); + scoped_task_environment_.RunUntilIdle(); + + ResourceRequest request; + request.url = GURL("http://does.not.resolve/echo"); + request.custom_proxy_use_alternate_proxy_list = true; + std::unique_ptr<TestURLLoaderClient> client = + FetchRequest(request, network_context.get()); + std::string response; + EXPECT_TRUE( + mojo::BlockingCopyToString(client->response_body_release(), &response)); + + // |invalid_server| has no handlers set up so would return an empty response. + EXPECT_EQ(response, "Echo"); + EXPECT_EQ(client->response_head().proxy_server, + ConvertToProxyServer(proxy_test_server)); } } // namespace diff --git a/chromium/services/network/network_qualities_pref_delegate.cc b/chromium/services/network/network_qualities_pref_delegate.cc new file mode 100644 index 00000000000..7f87fb9df11 --- /dev/null +++ b/chromium/services/network/network_qualities_pref_delegate.cc @@ -0,0 +1,115 @@ +// Copyright 2018 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 "services/network/network_qualities_pref_delegate.h" + +#include <memory> +#include <string> + +#include "base/bind.h" +#include "base/memory/ptr_util.h" +#include "base/metrics/histogram_macros.h" +#include "base/values.h" +#include "components/prefs/pref_registry.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "net/nqe/network_quality_estimator.h" + +namespace { + +// Prefs for persisting network qualities. +const char kNetworkQualities[] = "net.network_qualities"; + +// PrefDelegateImpl writes the provided dictionary value to the network quality +// estimator prefs on the disk. +class PrefDelegateImpl + : public net::NetworkQualitiesPrefsManager::PrefDelegate { + public: + // |pref_service| is used to read and write prefs from/to the disk. + explicit PrefDelegateImpl(PrefService* pref_service) + : pref_service_(pref_service), path_(kNetworkQualities) { + DCHECK(pref_service_); + } + ~PrefDelegateImpl() override {} + + void SetDictionaryValue(const base::DictionaryValue& value) override { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + pref_service_->Set(path_, value); + UMA_HISTOGRAM_EXACT_LINEAR("NQE.Prefs.WriteCount", 1, 2); + } + + std::unique_ptr<base::DictionaryValue> GetDictionaryValue() override { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + UMA_HISTOGRAM_EXACT_LINEAR("NQE.Prefs.ReadCount", 1, 2); + return pref_service_->GetDictionary(path_)->CreateDeepCopy(); + } + + private: + PrefService* pref_service_; + + // |path_| is the location of the network quality estimator prefs. + const std::string path_; + + SEQUENCE_CHECKER(sequence_checker_); + + DISALLOW_COPY_AND_ASSIGN(PrefDelegateImpl); +}; + +// Returns true if |pref_service| has been initialized. +bool IsPrefServiceInitialized(PrefService* pref_service) { + return pref_service->GetInitializationStatus() != + PrefService::INITIALIZATION_STATUS_WAITING; +} + +} // namespace + +namespace network { + +NetworkQualitiesPrefDelegate::NetworkQualitiesPrefDelegate( + PrefService* pref_service, + net::NetworkQualityEstimator* network_quality_estimator) + : prefs_manager_(std::make_unique<PrefDelegateImpl>(pref_service)), + network_quality_estimator_(network_quality_estimator), + weak_ptr_factory_(this) { + DCHECK(pref_service); + DCHECK(network_quality_estimator_); + + if (IsPrefServiceInitialized(pref_service)) { + OnPrefServiceInitialized(true); + } else { + // Register for a callback that will be invoked when |pref_service| is + // initialized. + pref_service->AddPrefInitObserver( + base::BindOnce(&NetworkQualitiesPrefDelegate::OnPrefServiceInitialized, + weak_ptr_factory_.GetWeakPtr())); + } +} + +NetworkQualitiesPrefDelegate::~NetworkQualitiesPrefDelegate() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +void NetworkQualitiesPrefDelegate::OnPrefServiceInitialized(bool success) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + prefs_manager_.InitializeOnNetworkThread(network_quality_estimator_); +} + +void NetworkQualitiesPrefDelegate::ClearPrefs() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + prefs_manager_.ClearPrefs(); +} + +// static +void NetworkQualitiesPrefDelegate::RegisterPrefs(PrefRegistrySimple* registry) { + registry->RegisterDictionaryPref(kNetworkQualities); +} + +std::map<net::nqe::internal::NetworkID, + net::nqe::internal::CachedNetworkQuality> +NetworkQualitiesPrefDelegate::ForceReadPrefsForTesting() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return prefs_manager_.ForceReadPrefsForTesting(); +} + +} // namespace network diff --git a/chromium/services/network/network_qualities_pref_delegate.h b/chromium/services/network/network_qualities_pref_delegate.h new file mode 100644 index 00000000000..0f6273e3d31 --- /dev/null +++ b/chromium/services/network/network_qualities_pref_delegate.h @@ -0,0 +1,67 @@ +// Copyright 2018 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. + +#ifndef SERVICES_NETWORK_NETWORK_QUALITIES_PREF_DELEGATE_H_ +#define SERVICES_NETWORK_NETWORK_QUALITIES_PREF_DELEGATE_H_ + +#include <map> + +#include "base/component_export.h" +#include "base/macros.h" +#include "base/memory/weak_ptr.h" +#include "base/sequence_checker.h" +#include "net/nqe/cached_network_quality.h" +#include "net/nqe/network_id.h" +#include "net/nqe/network_qualities_prefs_manager.h" + +namespace net { +class NetworkQualityEstimator; +} + +class PrefRegistrySimple; +class PrefService; + +namespace network { + +// UI service to manage storage of network quality prefs. +class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkQualitiesPrefDelegate { + public: + NetworkQualitiesPrefDelegate( + PrefService* pref_service, + net::NetworkQualityEstimator* network_quality_estimator); + ~NetworkQualitiesPrefDelegate(); + + // Registers the profile-specific network quality estimator prefs. + static void RegisterPrefs(PrefRegistrySimple* registry); + + // Clear the network quality estimator prefs. + void ClearPrefs(); + + // Reads the prefs from the disk, parses them into a map of NetworkIDs and + // CachedNetworkQualities, and returns the map. + std::map<net::nqe::internal::NetworkID, + net::nqe::internal::CachedNetworkQuality> + ForceReadPrefsForTesting() const; + + private: + // Called when pref service is initialized. + void OnPrefServiceInitialized(bool success); + + // Prefs manager that is owned by this service. Created on the UI thread, but + // used and deleted on the IO thread. + net::NetworkQualitiesPrefsManager prefs_manager_; + + // Guaranteed to be non-null during the lifetime of |this|. + net::NetworkQualityEstimator* network_quality_estimator_; + + SEQUENCE_CHECKER(sequence_checker_); + + base::WeakPtrFactory<NetworkQualitiesPrefDelegate> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(NetworkQualitiesPrefDelegate); +}; + +} // namespace network + +#endif // SERVICES_NETWORK_NETWORK_QUALITIES_PREF_DELEGATE_H_ diff --git a/chromium/services/network/network_qualities_pref_delegate_unittest.cc b/chromium/services/network/network_qualities_pref_delegate_unittest.cc new file mode 100644 index 00000000000..066555bc7aa --- /dev/null +++ b/chromium/services/network/network_qualities_pref_delegate_unittest.cc @@ -0,0 +1,112 @@ +// Copyright 2018 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 "services/network/network_qualities_pref_delegate.h" + +#include <map> +#include <string> + +#include "base/bind.h" +#include "base/macros.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/scoped_task_environment.h" +#include "components/prefs/testing_pref_service.h" +#include "net/base/network_change_notifier.h" +#include "net/nqe/cached_network_quality.h" +#include "net/nqe/effective_connection_type.h" +#include "net/nqe/network_id.h" +#include "net/nqe/network_quality_estimator_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { + +namespace { + +class NetworkQualitiesPrefDelegateTest : public testing::Test { + public: + NetworkQualitiesPrefDelegateTest() + : scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::IO) {} + + ~NetworkQualitiesPrefDelegateTest() override = default; + + private: + base::test::ScopedTaskEnvironment scoped_task_environment_; + + DISALLOW_COPY_AND_ASSIGN(NetworkQualitiesPrefDelegateTest); +}; + +// Verify that prefs are writen and read correctly. +TEST_F(NetworkQualitiesPrefDelegateTest, WritingReadingToPrefsEnabled) { + TestingPrefServiceSimple pref_service_simple; + net::TestNetworkQualityEstimator estimator; + NetworkQualitiesPrefDelegate::RegisterPrefs(pref_service_simple.registry()); + + base::HistogramTester initial_histogram_tester; + NetworkQualitiesPrefDelegate pref_delegate(&pref_service_simple, &estimator); + // NetworkQualityEstimator must be notified of the read prefs at startup. + EXPECT_FALSE( + initial_histogram_tester.GetAllSamples("NQE.Prefs.ReadSize").empty()); + + { + base::HistogramTester histogram_tester; + estimator.set_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_OFFLINE); + estimator.set_recent_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_OFFLINE); + estimator.RunOneRequest(); + + // Prefs are written only if persistent caching was enabled. + EXPECT_FALSE( + histogram_tester.GetAllSamples("NQE.Prefs.WriteCount").empty()); + histogram_tester.ExpectTotalCount("NQE.Prefs.ReadCount", 0); + + // NetworkQualityEstimator should not be notified of change in prefs. + histogram_tester.ExpectTotalCount("NQE.Prefs.ReadSize", 0); + } + + { + base::HistogramTester histogram_tester; + estimator.set_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G); + estimator.set_recent_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G); + estimator.RunOneRequest(); + + // Prefs are written even if the network id was unavailable. + EXPECT_FALSE( + histogram_tester.GetAllSamples("NQE.Prefs.WriteCount").empty()); + histogram_tester.ExpectTotalCount("NQE.Prefs.ReadCount", 0); + + // NetworkQualityEstimator should not be notified of change in prefs. + histogram_tester.ExpectTotalCount("NQE.Prefs.ReadSize", 0); + } + + // Verify the contents of the prefs by reading them again. + std::map<net::nqe::internal::NetworkID, + net::nqe::internal::CachedNetworkQuality> + read_prefs = pref_delegate.ForceReadPrefsForTesting(); + // Number of entries must be between 1 and 2. It's possible that 2 entries + // are added if the connection type is unknown to network quality estimator + // at the time of startup, and shortly after it receives a notification + // about the change in the connection type. + EXPECT_LE(1u, read_prefs.size()); + EXPECT_GE(2u, read_prefs.size()); + + // Verify that the cached network quality was written correctly. + EXPECT_EQ(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, + read_prefs.begin()->second.effective_connection_type()); + if (net::NetworkChangeNotifier::GetConnectionType() == + net::NetworkChangeNotifier::CONNECTION_ETHERNET) { + // Verify that the network ID was written correctly. + net::nqe::internal::NetworkID ethernet_network_id( + net::NetworkChangeNotifier::CONNECTION_ETHERNET, std::string(), + INT32_MIN); + EXPECT_EQ(ethernet_network_id, read_prefs.begin()->first); + } +} + +} // namespace + +} // namespace network diff --git a/chromium/services/network/network_sandbox_hook_linux.cc b/chromium/services/network/network_sandbox_hook_linux.cc index f20450d437e..da2704dfeab 100644 --- a/chromium/services/network/network_sandbox_hook_linux.cc +++ b/chromium/services/network/network_sandbox_hook_linux.cc @@ -6,7 +6,7 @@ #include "sandbox/linux/syscall_broker/broker_command.h" #include "base/rand_util.h" -#include "base/sys_info.h" +#include "base/system/sys_info.h" using sandbox::syscall_broker::BrokerFilePermission; using sandbox::syscall_broker::MakeBrokerCommandSet; diff --git a/chromium/services/network/network_service.cc b/chromium/services/network/network_service.cc index b353f8b21a6..a6dd62ca558 100644 --- a/chromium/services/network/network_service.cc +++ b/chromium/services/network/network_service.cc @@ -17,13 +17,12 @@ #include "base/task/post_task.h" #include "base/timer/timer.h" #include "base/values.h" -#include "components/certificate_transparency/sth_distributor.h" -#include "components/certificate_transparency/sth_observer.h" #include "components/os_crypt/os_crypt.h" #include "mojo/public/cpp/bindings/strong_binding.h" #include "mojo/public/cpp/bindings/type_converter.h" #include "net/base/logging_network_change_observer.h" #include "net/base/network_change_notifier.h" +#include "net/cert/cert_database.h" #include "net/cert/ct_log_response_parser.h" #include "net/cert/signed_tree_head.h" #include "net/dns/dns_config_overrides.h" @@ -39,6 +38,7 @@ #include "net/url_request/url_request_context_builder.h" #include "services/network/crl_set_distributor.h" #include "services/network/cross_origin_read_blocking.h" +#include "services/network/dns_config_change_manager.h" #include "services/network/net_log_capture_mode_type_converter.h" #include "services/network/net_log_exporter.h" #include "services/network/network_context.h" @@ -48,6 +48,10 @@ #include "services/network/url_loader.h" #include "services/network/url_request_context_builder_mojo.h" +#if BUILDFLAG(IS_CT_SUPPORTED) +#include "components/certificate_transparency/sth_distributor.h" +#endif // BUILDFLAG(IS_CT_SUPPORTED) + #if defined(OS_ANDROID) && defined(ARCH_CPU_ARMEL) #include "crypto/openssl_util.h" #include "third_party/boringssl/src/include/openssl/cpu.h" @@ -85,11 +89,9 @@ CreateNetworkChangeNotifierIfNeeded() { // On Android, NetworkChangeNotifier objects are always set up in process // before NetworkService is run. return nullptr; -#elif defined(OS_CHROMEOS) || defined(OS_IOS) || defined(OS_FUCHSIA) - // ChromeOS has its own implementation of NetworkChangeNotifier that lives - // outside of //net. iOS doesn't embed //content. Fuchsia doesn't have an - // implementation yet. - // TODO(xunjieli): Figure out what to do for these 3 platforms. +#elif defined(OS_IOS) || defined(OS_FUCHSIA) + // iOS doesn't embed //content. Fuchsia doesn't have an implementation yet. + // TODO(xunjieli): Figure out what to do for these 2 platforms. NOTIMPLEMENTED(); return nullptr; #endif @@ -138,10 +140,16 @@ bool LoadInfoIsMoreInteresting(const mojom::LoadInfo& a, NetworkService::NetworkService( std::unique_ptr<service_manager::BinderRegistry> registry, mojom::NetworkServiceRequest request, - net::NetLog* net_log) + net::NetLog* net_log, + service_manager::mojom::ServiceRequest service_request) : registry_(std::move(registry)), binding_(this) { DCHECK(!g_network_service); g_network_service = this; + + // In testing environments, |service_request| may not be provided. + if (service_request.is_pending()) + service_binding_.Bind(std::move(service_request)); + // |registry_| is nullptr when an in-process NetworkService is // created directly. The latter is done in concert with using // CreateNetworkContextWithBuilder to ease the transition to using the @@ -194,11 +202,15 @@ NetworkService::NetworkService( network_quality_estimator_manager_ = std::make_unique<NetworkQualityEstimatorManager>(net_log_); + dns_config_change_manager_ = std::make_unique<DnsConfigChangeManager>(); + host_resolver_ = CreateHostResolver(net_log_); network_usage_accumulator_ = std::make_unique<NetworkUsageAccumulator>(); +#if BUILDFLAG(IS_CT_SUPPORTED) sth_distributor_ = std::make_unique<certificate_transparency::STHDistributor>(); +#endif // BUILDFLAG(IS_CT_SUPPORTED) crl_set_distributor_ = std::make_unique<CRLSetDistributor>(); } @@ -225,8 +237,10 @@ void NetworkService::set_os_crypt_is_configured() { std::unique_ptr<NetworkService> NetworkService::Create( mojom::NetworkServiceRequest request, - net::NetLog* net_log) { - return std::make_unique<NetworkService>(nullptr, std::move(request), net_log); + net::NetLog* net_log, + service_manager::mojom::ServiceRequest service_request) { + return std::make_unique<NetworkService>(nullptr, std::move(request), net_log, + std::move(service_request)); } std::unique_ptr<mojom::NetworkContext> @@ -249,8 +263,14 @@ void NetworkService::SetHostResolver( } std::unique_ptr<NetworkService> NetworkService::CreateForTesting() { - return base::WrapUnique( - new NetworkService(std::make_unique<service_manager::BinderRegistry>())); + return CreateForTesting(nullptr); +} + +std::unique_ptr<NetworkService> NetworkService::CreateForTesting( + service_manager::mojom::ServiceRequest service_request) { + return std::make_unique<NetworkService>( + std::make_unique<service_manager::BinderRegistry>(), + nullptr /* request */, nullptr /* net_log */, std::move(service_request)); } void NetworkService::RegisterNetworkContext(NetworkContext* network_context) { @@ -406,20 +426,27 @@ void NetworkService::ConfigureHttpAuthPrefs( #endif } -void NetworkService::SetRawHeadersAccess(uint32_t process_id, bool allow) { +void NetworkService::SetRawHeadersAccess( + uint32_t process_id, + const std::vector<url::Origin>& origins) { DCHECK(process_id); - if (allow) - processes_with_raw_headers_access_.insert(process_id); - else - processes_with_raw_headers_access_.erase(process_id); + if (!origins.size()) { + raw_headers_access_origins_by_pid_.erase(process_id); + } else { + raw_headers_access_origins_by_pid_[process_id] = + base::flat_set<url::Origin>(origins.begin(), origins.end()); + } } -bool NetworkService::HasRawHeadersAccess(uint32_t process_id) const { +bool NetworkService::HasRawHeadersAccess(uint32_t process_id, + const GURL& resource_url) const { // Allow raw headers for browser-initiated requests. if (!process_id) return true; - return processes_with_raw_headers_access_.find(process_id) != - processes_with_raw_headers_access_.end(); + auto it = raw_headers_access_origins_by_pid_.find(process_id); + if (it == raw_headers_access_origins_by_pid_.end()) + return false; + return it->second.find(url::Origin::Create(resource_url)) != it->second.end(); } net::NetLog* NetworkService::net_log() const { @@ -436,19 +463,30 @@ void NetworkService::GetNetworkQualityEstimatorManager( network_quality_estimator_manager_->AddRequest(std::move(request)); } +void NetworkService::GetDnsConfigChangeManager( + mojom::DnsConfigChangeManagerRequest request) { + dns_config_change_manager_->AddBinding(std::move(request)); +} + void NetworkService::GetTotalNetworkUsages( mojom::NetworkService::GetTotalNetworkUsagesCallback callback) { std::move(callback).Run(network_usage_accumulator_->GetTotalNetworkUsages()); } +#if BUILDFLAG(IS_CT_SUPPORTED) void NetworkService::UpdateSignedTreeHead(const net::ct::SignedTreeHead& sth) { sth_distributor_->NewSTHObserved(sth); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) void NetworkService::UpdateCRLSet(base::span<const uint8_t> crl_set) { crl_set_distributor_->OnNewCRLSet(crl_set); } +void NetworkService::OnCertDBChanged() { + net::CertDatabase::GetInstance()->NotifyObserversCertDBChanged(); +} + #if defined(OS_LINUX) && !defined(OS_CHROMEOS) void NetworkService::SetCryptConfig(mojom::CryptConfigPtr crypt_config) { #if !defined(IS_CHROMECAST) @@ -481,6 +519,11 @@ void NetworkService::RemoveCorbExceptionForPlugin(uint32_t process_id) { CrossOriginReadBlocking::RemoveExceptionForPlugin(process_id); } +void NetworkService::OnMemoryPressure( + base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) { + base::MemoryPressureListener::NotifyMemoryPressure(memory_pressure_level); +} + #if defined(OS_ANDROID) void NetworkService::OnApplicationStateChange( base::android::ApplicationState state) { @@ -502,9 +545,11 @@ void NetworkService::OnBeforeURLRequest() { MaybeStartUpdateLoadInfoTimer(); } +#if BUILDFLAG(IS_CT_SUPPORTED) certificate_transparency::STHReporter* NetworkService::sth_reporter() { return sth_distributor_.get(); } +#endif // BUILDFLAG(IS_CT_SUPPORTED) void NetworkService::OnBindInterface( const service_manager::BindSourceInfo& source_info, diff --git a/chromium/services/network/network_service.h b/chromium/services/network/network_service.h index ed544fe474b..cc7e9450d93 100644 --- a/chromium/services/network/network_service.h +++ b/chromium/services/network/network_service.h @@ -10,6 +10,7 @@ #include <string> #include "base/component_export.h" +#include "base/containers/flat_set.h" #include "base/containers/span.h" #include "base/containers/unique_ptr_adapters.h" #include "base/macros.h" @@ -24,12 +25,16 @@ #include "services/network/keepalive_statistics_recorder.h" #include "services/network/network_change_manager.h" #include "services/network/network_quality_estimator_manager.h" +#include "services/network/public/cpp/network_service_buildflags.h" +#include "services/network/public/mojom/host_resolver.mojom.h" #include "services/network/public/mojom/net_log.mojom.h" #include "services/network/public/mojom/network_change_manager.mojom.h" #include "services/network/public/mojom/network_quality_estimator_manager.mojom.h" #include "services/network/public/mojom/network_service.mojom.h" #include "services/service_manager/public/cpp/binder_registry.h" #include "services/service_manager/public/cpp/service.h" +#include "services/service_manager/public/cpp/service_binding.h" +#include "services/service_manager/public/mojom/service.mojom.h" namespace net { class FileNetLogObserver; @@ -40,14 +45,17 @@ class NetworkQualityEstimator; class URLRequestContext; } // namespace net +#if BUILDFLAG(IS_CT_SUPPORTED) namespace certificate_transparency { class STHDistributor; class STHReporter; } // namespace certificate_transparency +#endif // BUILDFLAG(IS_CT_SUPPORTED) namespace network { class CRLSetDistributor; +class DnsConfigChangeManager; class NetworkContext; class NetworkUsageAccumulator; class URLRequestContextBuilderMojo; @@ -61,9 +69,11 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService // // TODO(https://crbug.com/767450): Once the NetworkService can always create // its own NetLog in production, remove the |net_log| argument. - NetworkService(std::unique_ptr<service_manager::BinderRegistry> registry, - mojom::NetworkServiceRequest request = nullptr, - net::NetLog* net_log = nullptr); + NetworkService( + std::unique_ptr<service_manager::BinderRegistry> registry, + mojom::NetworkServiceRequest request = nullptr, + net::NetLog* net_log = nullptr, + service_manager::mojom::ServiceRequest service_request = nullptr); ~NetworkService() override; @@ -106,10 +116,20 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService // its own NetLog, instead of sharing one. static std::unique_ptr<NetworkService> Create( mojom::NetworkServiceRequest request, - net::NetLog* net_log = nullptr); + net::NetLog* net_log = nullptr, + service_manager::mojom::ServiceRequest service_request = nullptr); + // Creates a testing instance of NetworkService not bound to an actual + // Service pipe. This instance must be driven by direct calls onto the + // NetworkService object. static std::unique_ptr<NetworkService> CreateForTesting(); + // Creates a testing instance of NetworkService similar to above, but the + // instance is bound to |request|. Test code may use an appropriate Connector + // to bind interface requests within this service instance. + static std::unique_ptr<NetworkService> CreateForTesting( + service_manager::mojom::ServiceRequest service_request); + // These are called by NetworkContexts as they are being created and // destroyed. // TODO(mmenke): Remove once all NetworkContexts are owned by the @@ -139,15 +159,21 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService mojom::HttpAuthStaticParamsPtr http_auth_static_params) override; void ConfigureHttpAuthPrefs( mojom::HttpAuthDynamicParamsPtr http_auth_dynamic_params) override; - void SetRawHeadersAccess(uint32_t process_id, bool allow) override; + void SetRawHeadersAccess(uint32_t process_id, + const std::vector<url::Origin>& origins) override; void GetNetworkChangeManager( mojom::NetworkChangeManagerRequest request) override; void GetNetworkQualityEstimatorManager( mojom::NetworkQualityEstimatorManagerRequest request) override; + void GetDnsConfigChangeManager( + mojom::DnsConfigChangeManagerRequest request) override; void GetTotalNetworkUsages( mojom::NetworkService::GetTotalNetworkUsagesCallback callback) override; +#if BUILDFLAG(IS_CT_SUPPORTED) void UpdateSignedTreeHead(const net::ct::SignedTreeHead& sth) override; +#endif // !BUILDFLAG(IS_CT_SUPPORTED) void UpdateCRLSet(base::span<const uint8_t> crl_set) override; + void OnCertDBChanged() override; #if defined(OS_LINUX) && !defined(OS_CHROMEOS) void SetCryptConfig(mojom::CryptConfigPtr crypt_config) override; #endif @@ -156,6 +182,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService #endif void AddCorbExceptionForPlugin(uint32_t process_id) override; void RemoveCorbExceptionForPlugin(uint32_t process_id) override; + void OnMemoryPressure(base::MemoryPressureListener::MemoryPressureLevel + memory_pressure_level) override; #if defined(OS_ANDROID) void OnApplicationStateChange(base::android::ApplicationState state) override; #endif @@ -168,7 +196,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService void OnBeforeURLRequest(); bool quic_disabled() const { return quic_disabled_; } - bool HasRawHeadersAccess(uint32_t process_id) const; + bool HasRawHeadersAccess(uint32_t process_id, const GURL& resource_url) const; mojom::NetworkServiceClient* client() { return client_.get(); } net::NetworkQualityEstimator* network_quality_estimator() { @@ -183,7 +211,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService return network_usage_accumulator_.get(); } +#if BUILDFLAG(IS_CT_SUPPORTED) certificate_transparency::STHReporter* sth_reporter(); +#endif // BUILDFLAG(IS_CT_SUPPORTED) + CRLSetDistributor* crl_set_distributor() { return crl_set_distributor_.get(); } @@ -217,6 +248,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService // Starts timer call UpdateLoadInfo() again, if needed. void AckUpdateLoadInfo(); + service_manager::ServiceBinding service_binding_{this}; + net::NetLog* net_log_ = nullptr; std::unique_ptr<net::FileNetLogObserver> file_net_log_observer_; @@ -240,6 +273,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService std::unique_ptr<NetworkQualityEstimatorManager> network_quality_estimator_manager_; + std::unique_ptr<DnsConfigChangeManager> dns_config_change_manager_; + std::unique_ptr<net::HostResolver> host_resolver_; std::unique_ptr<NetworkUsageAccumulator> network_usage_accumulator_; @@ -265,13 +300,18 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkService // this with |owned_network_contexts_|. std::set<NetworkContext*> network_contexts_; - std::set<uint32_t> processes_with_raw_headers_access_; + // A per-process_id map of origins that are white-listed to allow + // them to request raw headers for resources they request. + std::map<uint32_t, base::flat_set<url::Origin>> + raw_headers_access_origins_by_pid_; bool quic_disabled_ = false; bool os_crypt_config_set_ = false; +#if BUILDFLAG(IS_CT_SUPPORTED) std::unique_ptr<certificate_transparency::STHDistributor> sth_distributor_; +#endif // BUILDFLAG(IS_CT_SUPPORTED) std::unique_ptr<CRLSetDistributor> crl_set_distributor_; // A timer that periodically calls UpdateLoadInfo while there are pending diff --git a/chromium/services/network/network_service_network_delegate.cc b/chromium/services/network/network_service_network_delegate.cc index b9f1a1b1cfc..d2fad631dfd 100644 --- a/chromium/services/network/network_service_network_delegate.cc +++ b/chromium/services/network/network_service_network_delegate.cc @@ -8,6 +8,7 @@ #include "services/network/network_context.h" #include "services/network/network_service.h" #include "services/network/network_service_proxy_delegate.h" +#include "services/network/pending_callback_chain.h" #include "services/network/public/cpp/features.h" #include "services/network/url_loader.h" @@ -33,6 +34,9 @@ int NetworkServiceNetworkDelegate::OnBeforeStartTransaction( network_context_->proxy_delegate()->OnBeforeStartTransaction(request, headers); } + URLLoader* url_loader = URLLoader::ForRequest(*request); + if (url_loader) + return url_loader->OnBeforeStartTransaction(std::move(callback), headers); return net::OK; } @@ -53,12 +57,21 @@ int NetworkServiceNetworkDelegate::OnHeadersReceived( const net::HttpResponseHeaders* original_response_headers, scoped_refptr<net::HttpResponseHeaders>* override_response_headers, GURL* allowed_unsafe_redirect_url) { - // Clear-Site-Data header will be handled by |ResourceDispatcherHost|. - if (!base::FeatureList::IsEnabled(network::features::kNetworkService)) - return net::OK; + auto chain = base::MakeRefCounted<PendingCallbackChain>(std::move(callback)); + URLLoader* url_loader = URLLoader::ForRequest(*request); + if (url_loader) { + chain->AddResult(url_loader->OnHeadersReceived( + chain->CreateCallback(), original_response_headers, + override_response_headers, allowed_unsafe_redirect_url)); + } - return HandleClearSiteDataHeader(request, std::move(callback), - original_response_headers); + // Clear-Site-Data header will be handled by |ResourceDispatcherHost| if + // network service is disabled. + if (base::FeatureList::IsEnabled(network::features::kNetworkService)) { + chain->AddResult(HandleClearSiteDataHeader(request, chain->CreateCallback(), + original_response_headers)); + } + return chain->GetResult(); } bool NetworkServiceNetworkDelegate::OnCanGetCookies( diff --git a/chromium/services/network/network_service_proxy_delegate.cc b/chromium/services/network/network_service_proxy_delegate.cc index 105a070a6b2..ffc1aedf889 100644 --- a/chromium/services/network/network_service_proxy_delegate.cc +++ b/chromium/services/network/network_service_proxy_delegate.cc @@ -7,11 +7,19 @@ #include "net/http/http_request_headers.h" #include "net/http/http_util.h" #include "net/proxy_resolution/proxy_info.h" +#include "net/proxy_resolution/proxy_resolution_service.h" #include "services/network/url_loader.h" namespace network { namespace { +// The maximum size of the cache that contains the GURLs that should use +// alternate proxy list. +constexpr size_t kMaxCacheSize = 15; + +// The maximum number of previous configs to keep. +constexpr size_t kMaxPreviousConfigs = 2; + void GetAlternativeProxy(const GURL& url, const net::ProxyRetryInfoMap& proxy_retry_info, net::ProxyInfo* result) { @@ -44,24 +52,53 @@ bool ApplyProxyConfigToProxyInfo(const net::ProxyConfig::ProxyRules& rules, rules.Apply(url, proxy_info); proxy_info->DeprioritizeBadProxies(proxy_retry_info); - return !proxy_info->proxy_server().is_direct(); + return !proxy_info->is_empty() && !proxy_info->proxy_server().is_direct(); } // Checks if |target_proxy| is in |proxy_list|. bool CheckProxyList(const net::ProxyList& proxy_list, const net::ProxyServer& target_proxy) { for (const auto& proxy : proxy_list.GetAll()) { - if (proxy.host_port_pair().Equals(target_proxy.host_port_pair())) + if (!proxy.is_direct() && + proxy.host_port_pair().Equals(target_proxy.host_port_pair())) { return true; + } } return false; } +// Whether the custom proxy can proxy |url|. +bool IsURLValidForProxy(const GURL& url) { + return url.SchemeIs(url::kHttpScheme) && !net::IsLocalhost(url); +} + +// Merges headers from |in| to |out|. If the header already exists in |out| they +// are combined. +void MergeRequestHeaders(net::HttpRequestHeaders* out, + const net::HttpRequestHeaders& in) { + for (net::HttpRequestHeaders::Iterator it(in); it.GetNext();) { + std::string old_value; + if (out->GetHeader(it.name(), &old_value)) { + out->SetHeader(it.name(), old_value + ", " + it.value()); + } else { + out->SetHeader(it.name(), it.value()); + } + } +} + } // namespace NetworkServiceProxyDelegate::NetworkServiceProxyDelegate( + mojom::CustomProxyConfigPtr initial_config, mojom::CustomProxyConfigClientRequest config_client_request) - : binding_(this, std::move(config_client_request)) {} + : proxy_config_(std::move(initial_config)), + binding_(this, std::move(config_client_request)), + should_use_alternate_proxy_list_cache_(kMaxCacheSize) { + // Make sure there is always a valid proxy config so we don't need to null + // check it. + if (!proxy_config_) + proxy_config_ = mojom::CustomProxyConfig::New(); +} void NetworkServiceProxyDelegate::OnBeforeStartTransaction( net::URLRequest* request, @@ -69,11 +106,14 @@ void NetworkServiceProxyDelegate::OnBeforeStartTransaction( if (!MayProxyURL(request->url())) return; - headers->MergeFrom(proxy_config_->pre_cache_headers); + MergeRequestHeaders(headers, proxy_config_->pre_cache_headers); auto* url_loader = URLLoader::ForRequest(*request); if (url_loader) { - headers->MergeFrom(url_loader->custom_proxy_pre_cache_headers()); + if (url_loader->custom_proxy_use_alternate_proxy_list()) { + should_use_alternate_proxy_list_cache_.Put(request->url().spec(), true); + } + MergeRequestHeaders(headers, url_loader->custom_proxy_pre_cache_headers()); } } @@ -83,14 +123,13 @@ void NetworkServiceProxyDelegate::OnBeforeSendHeaders( net::HttpRequestHeaders* headers) { auto* url_loader = URLLoader::ForRequest(*request); if (IsInProxyConfig(proxy_info.proxy_server())) { - headers->MergeFrom(proxy_config_->post_cache_headers); + MergeRequestHeaders(headers, proxy_config_->post_cache_headers); if (url_loader) { - headers->MergeFrom(url_loader->custom_proxy_post_cache_headers()); + MergeRequestHeaders(headers, + url_loader->custom_proxy_post_cache_headers()); } - // TODO(crbug.com/721403): This check may be incorrect if a new proxy config - // is set between OnBeforeStartTransaction and here. - } else if (MayProxyURL(request->url())) { + } else if (MayHaveProxiedURL(request->url())) { for (const auto& kv : proxy_config_->pre_cache_headers.GetHeaderVector()) { headers->RemoveHeader(kv.key); } @@ -115,8 +154,8 @@ void NetworkServiceProxyDelegate::OnResolveProxy( return; net::ProxyInfo proxy_info; - if (ApplyProxyConfigToProxyInfo(proxy_config_->rules, proxy_retry_info, url, - &proxy_info)) { + if (ApplyProxyConfigToProxyInfo(GetProxyRulesForURL(url), proxy_retry_info, + url, &proxy_info)) { DCHECK(!proxy_info.is_empty() && !proxy_info.is_direct()); result->OverrideProxyList(proxy_info.proxy_list()); GetAlternativeProxy(url, proxy_retry_info, result); @@ -130,20 +169,75 @@ void NetworkServiceProxyDelegate::OnCustomProxyConfigUpdated( mojom::CustomProxyConfigPtr proxy_config) { DCHECK(proxy_config->rules.empty() || !proxy_config->rules.proxies_for_http.IsEmpty()); + if (proxy_config_) { + previous_proxy_configs_.push_front(std::move(proxy_config_)); + if (previous_proxy_configs_.size() > kMaxPreviousConfigs) + previous_proxy_configs_.pop_back(); + } proxy_config_ = std::move(proxy_config); } +void NetworkServiceProxyDelegate::MarkProxiesAsBad( + base::TimeDelta bypass_duration, + const net::ProxyList& bad_proxies_list, + MarkProxiesAsBadCallback callback) { + std::vector<net::ProxyServer> bad_proxies = bad_proxies_list.GetAll(); + + // Synthesize a suitable |ProxyInfo| to add the proxies to the + // |ProxyRetryInfoMap| of the proxy service. + // + // TODO(eroman): Support this more directly on ProxyResolutionService. + net::ProxyList proxy_list; + for (const auto& bad_proxy : bad_proxies) + proxy_list.AddProxyServer(bad_proxy); + proxy_list.AddProxyServer(net::ProxyServer::Direct()); + + net::ProxyInfo proxy_info; + proxy_info.UseProxyList(proxy_list); + + proxy_resolution_service_->MarkProxiesAsBadUntil( + proxy_info, bypass_duration, bad_proxies, net::NetLogWithSource()); + + std::move(callback).Run(); +} + +void NetworkServiceProxyDelegate::ClearBadProxiesCache() { + proxy_resolution_service_->ClearBadProxiesCache(); +} + bool NetworkServiceProxyDelegate::IsInProxyConfig( const net::ProxyServer& proxy_server) const { if (!proxy_server.is_valid() || proxy_server.is_direct()) return false; - return CheckProxyList(proxy_config_->rules.proxies_for_http, proxy_server); + if (CheckProxyList(proxy_config_->rules.proxies_for_http, proxy_server)) + return true; + + for (const auto& config : previous_proxy_configs_) { + if (CheckProxyList(config->rules.proxies_for_http, proxy_server)) + return true; + } + + return false; } bool NetworkServiceProxyDelegate::MayProxyURL(const GURL& url) const { - return url.SchemeIs(url::kHttpScheme) && !proxy_config_->rules.empty() && - !net::IsLocalhost(url); + return IsURLValidForProxy(url) && !proxy_config_->rules.empty(); +} + +bool NetworkServiceProxyDelegate::MayHaveProxiedURL(const GURL& url) const { + if (!IsURLValidForProxy(url)) + return false; + + if (!proxy_config_->rules.empty()) + return true; + + for (const auto& config : previous_proxy_configs_) { + if (!config->rules.empty()) + return true; + } + + return false; } bool NetworkServiceProxyDelegate::EligibleForProxy( @@ -154,4 +248,15 @@ bool NetworkServiceProxyDelegate::EligibleForProxy( MayProxyURL(url) && net::HttpUtil::IsMethodIdempotent(method); } +net::ProxyConfig::ProxyRules NetworkServiceProxyDelegate::GetProxyRulesForURL( + const GURL& url) const { + net::ProxyConfig::ProxyRules rules = proxy_config_->rules; + const auto iter = should_use_alternate_proxy_list_cache_.Peek(url.spec()); + if (iter == should_use_alternate_proxy_list_cache_.end()) + return rules; + + rules.proxies_for_http = proxy_config_->alternate_proxy_list; + return rules; +} + } // namespace network diff --git a/chromium/services/network/network_service_proxy_delegate.h b/chromium/services/network/network_service_proxy_delegate.h index 1d8fc1c7f65..6d36748a5e0 100644 --- a/chromium/services/network/network_service_proxy_delegate.h +++ b/chromium/services/network/network_service_proxy_delegate.h @@ -5,7 +5,10 @@ #ifndef SERVICES_NETWORK_NETWORK_SERVICE_PROXY_DELEGATE_H_ #define SERVICES_NETWORK_NETWORK_SERVICE_PROXY_DELEGATE_H_ +#include <deque> + #include "base/component_export.h" +#include "base/containers/mru_cache.h" #include "base/macros.h" #include "mojo/public/cpp/bindings/binding.h" #include "net/base/proxy_delegate.h" @@ -13,6 +16,7 @@ namespace net { class HttpRequestHeaders; +class ProxyResolutionService; class URLRequest; } // namespace net @@ -26,9 +30,15 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkServiceProxyDelegate public mojom::CustomProxyConfigClient { public: explicit NetworkServiceProxyDelegate( + mojom::CustomProxyConfigPtr initial_config, mojom::CustomProxyConfigClientRequest config_client_request); ~NetworkServiceProxyDelegate() override; + void SetProxyResolutionService( + net::ProxyResolutionService* proxy_resolution_service) { + proxy_resolution_service_ = proxy_resolution_service; + } + // These methods are forwarded from the NetworkDelegate. void OnBeforeStartTransaction(net::URLRequest* request, net::HttpRequestHeaders* headers); @@ -50,18 +60,38 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkServiceProxyDelegate // Whether the current config may proxy |url|. bool MayProxyURL(const GURL& url) const; + // Whether the current config may have proxied |url| with the current config + // or a previous config. + bool MayHaveProxiedURL(const GURL& url) const; + // Whether the |url| with current |proxy_info| is eligible to be proxied. bool EligibleForProxy(const net::ProxyInfo& proxy_info, const GURL& url, const std::string& method) const; + // Get the proxy rules that apply to |url|. + net::ProxyConfig::ProxyRules GetProxyRulesForURL(const GURL& url) const; + // mojom::CustomProxyConfigClient implementation: void OnCustomProxyConfigUpdated( mojom::CustomProxyConfigPtr proxy_config) override; + void MarkProxiesAsBad(base::TimeDelta bypass_duration, + const net::ProxyList& bad_proxies, + MarkProxiesAsBadCallback callback) override; + void ClearBadProxiesCache() override; mojom::CustomProxyConfigPtr proxy_config_; mojo::Binding<mojom::CustomProxyConfigClient> binding_; + base::MRUCache<std::string, bool> should_use_alternate_proxy_list_cache_; + + // We keep track of a limited number of previous configs so we can determine + // if a request used a custom proxy if the config happened to change during + // the request. + std::deque<mojom::CustomProxyConfigPtr> previous_proxy_configs_; + + net::ProxyResolutionService* proxy_resolution_service_ = nullptr; + DISALLOW_COPY_AND_ASSIGN(NetworkServiceProxyDelegate); }; diff --git a/chromium/services/network/network_service_proxy_delegate_unittest.cc b/chromium/services/network/network_service_proxy_delegate_unittest.cc index ddce4b2152a..f0397b6a4fb 100644 --- a/chromium/services/network/network_service_proxy_delegate_unittest.cc +++ b/chromium/services/network/network_service_proxy_delegate_unittest.cc @@ -29,11 +29,9 @@ class NetworkServiceProxyDelegateTest : public testing::Test { protected: std::unique_ptr<NetworkServiceProxyDelegate> CreateDelegate( mojom::CustomProxyConfigPtr config) { - mojom::CustomProxyConfigClientPtr client; auto delegate = std::make_unique<NetworkServiceProxyDelegate>( - mojo::MakeRequest(&client)); - client->OnCustomProxyConfigUpdated(std::move(config)); - scoped_task_environment_.RunUntilIdle(); + network::mojom::CustomProxyConfig::New(), mojo::MakeRequest(&client_)); + SetConfig(std::move(config)); return delegate; } @@ -41,11 +39,27 @@ class NetworkServiceProxyDelegateTest : public testing::Test { return context_->CreateRequest(url, net::DEFAULT_PRIORITY, nullptr); } + void SetConfig(mojom::CustomProxyConfigPtr config) { + client_->OnCustomProxyConfigUpdated(std::move(config)); + scoped_task_environment_.RunUntilIdle(); + } + private: + mojom::CustomProxyConfigClientPtr client_; std::unique_ptr<net::TestURLRequestContext> context_; base::test::ScopedTaskEnvironment scoped_task_environment_; }; +TEST_F(NetworkServiceProxyDelegateTest, NullConfigDoesNotCrash) { + mojom::CustomProxyConfigClientPtr client; + auto delegate = std::make_unique<NetworkServiceProxyDelegate>( + nullptr, mojo::MakeRequest(&client)); + + net::HttpRequestHeaders headers; + auto request = CreateRequest(GURL(kHttpUrl)); + delegate->OnBeforeStartTransaction(request.get(), &headers); +} + TEST_F(NetworkServiceProxyDelegateTest, AddsHeadersBeforeCache) { auto config = mojom::CustomProxyConfig::New(); config->rules.ParseFromString("http=proxy"); @@ -75,6 +89,19 @@ TEST_F(NetworkServiceProxyDelegateTest, EXPECT_TRUE(headers.IsEmpty()); } +TEST_F(NetworkServiceProxyDelegateTest, + DoesNotAddHeadersBeforeCacheWithEmptyConfig) { + auto config = mojom::CustomProxyConfig::New(); + config->pre_cache_headers.SetHeader("foo", "bar"); + auto delegate = CreateDelegate(std::move(config)); + + net::HttpRequestHeaders headers; + auto request = CreateRequest(GURL(kHttpUrl)); + delegate->OnBeforeStartTransaction(request.get(), &headers); + + EXPECT_TRUE(headers.IsEmpty()); +} + TEST_F(NetworkServiceProxyDelegateTest, DoesNotAddHeadersBeforeCacheForHttps) { auto config = mojom::CustomProxyConfig::New(); config->rules.ParseFromString("http=proxy"); @@ -190,6 +217,49 @@ TEST_F(NetworkServiceProxyDelegateTest, KeepsPreCacheHeadersWhenProxyInConfig) { EXPECT_EQ(value, "bar"); } +TEST_F(NetworkServiceProxyDelegateTest, KeepsHeadersWhenConfigUpdated) { + auto config = mojom::CustomProxyConfig::New(); + config->rules.ParseFromString("http=proxy"); + config->pre_cache_headers.SetHeader("foo", "bar"); + auto delegate = CreateDelegate(config->Clone()); + + // Update config with new proxy. + config->rules.ParseFromString("http=other"); + SetConfig(std::move(config)); + + net::HttpRequestHeaders headers; + headers.SetHeader("foo", "bar"); + auto request = CreateRequest(GURL(kHttpUrl)); + net::ProxyInfo info; + info.UsePacString("PROXY proxy"); + delegate->OnBeforeSendHeaders(request.get(), info, &headers); + + std::string value; + EXPECT_TRUE(headers.GetHeader("foo", &value)); + EXPECT_EQ(value, "bar"); +} + +TEST_F(NetworkServiceProxyDelegateTest, + RemovesPreCacheHeadersWhenConfigUpdatedToBeEmpty) { + auto config = mojom::CustomProxyConfig::New(); + config->rules.ParseFromString("http=proxy"); + config->pre_cache_headers.SetHeader("foo", "bar"); + auto delegate = CreateDelegate(config->Clone()); + + // Update config with empty proxy rules. + config->rules = net::ProxyConfig::ProxyRules(); + SetConfig(std::move(config)); + + net::HttpRequestHeaders headers; + headers.SetHeader("foo", "bar"); + auto request = CreateRequest(GURL(kHttpUrl)); + net::ProxyInfo info; + info.UseDirect(); + delegate->OnBeforeSendHeaders(request.get(), info, &headers); + + EXPECT_TRUE(headers.IsEmpty()); +} + TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxySuccessHttpProxy) { auto config = mojom::CustomProxyConfig::New(); config->rules.ParseFromString("http=foo"); @@ -204,7 +274,7 @@ TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxySuccessHttpProxy) { expected_proxy_list.AddProxyServer( net::ProxyServer::FromPacString("PROXY foo")); EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list)); - // HTTP proxies are nto used as alternative QUIC proxies. + // HTTP proxies are not used as alternative QUIC proxies. EXPECT_FALSE(result.alternative_proxy().is_valid()); } @@ -316,4 +386,38 @@ TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyDeprioritizesBadProxies) { EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list)); } +TEST_F(NetworkServiceProxyDelegateTest, OnResolveProxyAllProxiesBad) { + auto config = mojom::CustomProxyConfig::New(); + config->rules.ParseFromString("http=foo"); + auto delegate = CreateDelegate(std::move(config)); + + net::ProxyInfo result; + result.UseDirect(); + net::ProxyRetryInfoMap retry_map; + net::ProxyRetryInfo& info = retry_map["foo:80"]; + info.try_while_bad = false; + info.bad_until = base::TimeTicks::Now() + base::TimeDelta::FromDays(2); + delegate->OnResolveProxy(GURL(kHttpUrl), "GET", retry_map, &result); + + EXPECT_TRUE(result.is_direct()); +} + +TEST_F(NetworkServiceProxyDelegateTest, InitialConfigUsedForProxy) { + auto config = mojom::CustomProxyConfig::New(); + config->rules.ParseFromString("http=foo"); + mojom::CustomProxyConfigClientPtr client; + auto delegate = std::make_unique<NetworkServiceProxyDelegate>( + std::move(config), mojo::MakeRequest(&client)); + + net::ProxyInfo result; + result.UseDirect(); + delegate->OnResolveProxy(GURL(kHttpUrl), "GET", net::ProxyRetryInfoMap(), + &result); + + net::ProxyList expected_proxy_list; + expected_proxy_list.AddProxyServer( + net::ProxyServer::FromPacString("PROXY foo")); + EXPECT_TRUE(result.proxy_list().Equals(expected_proxy_list)); +} + } // namespace network diff --git a/chromium/services/network/network_service_unittest.cc b/chromium/services/network/network_service_unittest.cc index 7e6c4861c20..4ef51b53337 100644 --- a/chromium/services/network/network_service_unittest.cc +++ b/chromium/services/network/network_service_unittest.cc @@ -36,14 +36,13 @@ #include "services/network/network_context.h" #include "services/network/network_service.h" #include "services/network/public/cpp/features.h" +#include "services/network/public/cpp/network_switches.h" #include "services/network/public/mojom/net_log.mojom.h" #include "services/network/public/mojom/network_change_manager.mojom.h" #include "services/network/public/mojom/network_service.mojom.h" #include "services/network/test/test_network_service_client.h" #include "services/network/test/test_url_loader_client.h" -#include "services/service_manager/public/cpp/service_context.h" -#include "services/service_manager/public/cpp/service_test.h" -#include "services/service_manager/public/mojom/service_factory.mojom.h" +#include "services/service_manager/public/cpp/test/test_connector_factory.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -634,54 +633,22 @@ TEST_F(NetworkServiceTest, AuthAndroidNegotiateAccountType) { } #endif // defined(OS_ANDROID) -class ServiceTestClient : public service_manager::test::ServiceTestClient, - public service_manager::mojom::ServiceFactory { - public: - explicit ServiceTestClient(service_manager::test::ServiceTest* test) - : service_manager::test::ServiceTestClient(test) { - registry_.AddInterface<service_manager::mojom::ServiceFactory>(base::Bind( - &ServiceTestClient::BindServiceFactoryRequest, base::Unretained(this))); - } - ~ServiceTestClient() override {} - - protected: - void OnBindInterface(const service_manager::BindSourceInfo& source_info, - const std::string& interface_name, - mojo::ScopedMessagePipeHandle interface_pipe) override { - registry_.BindInterface(interface_name, std::move(interface_pipe)); - } - - void CreateService( - service_manager::mojom::ServiceRequest request, - const std::string& name, - service_manager::mojom::PIDReceiverPtr pid_receiver) override { - if (name == kNetworkServiceName) { - service_context_.reset(new service_manager::ServiceContext( - NetworkService::CreateForTesting(), std::move(request))); - } - } - - void BindServiceFactoryRequest( - service_manager::mojom::ServiceFactoryRequest request) { - service_factory_bindings_.AddBinding(this, std::move(request)); - } - - std::unique_ptr<service_manager::ServiceContext> service_context_; - - private: - service_manager::BinderRegistry registry_; - mojo::BindingSet<service_manager::mojom::ServiceFactory> - service_factory_bindings_; -}; - -class NetworkServiceTestWithService - : public service_manager::test::ServiceTest { +class NetworkServiceTestWithService : public testing::Test { public: NetworkServiceTestWithService() - : ServiceTest("network_unittests", - base::test::ScopedTaskEnvironment::MainThreadType::IO) {} + : task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::IO) {} ~NetworkServiceTestWithService() override {} + void SetUp() override { + test_server_.AddDefaultHandlers(base::FilePath(kServicesTestData)); + ASSERT_TRUE(test_server_.Start()); + service_ = NetworkService::CreateForTesting( + test_connector_factory_.RegisterInstance(kNetworkServiceName)); + test_connector_factory_.GetDefaultConnector()->BindInterface( + kNetworkServiceName, &network_service_); + } + void CreateNetworkContext() { mojom::NetworkContextParamsPtr context_params = mojom::NetworkContextParams::New(); @@ -716,6 +683,8 @@ class NetworkServiceTestWithService net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS)); } + void Shutdown() { service_.reset(); } + net::EmbeddedTestServer* test_server() { return &test_server_; } TestURLLoaderClient* client() { return client_.get(); } mojom::URLLoader* loader() { return loader_.get(); } @@ -723,16 +692,9 @@ class NetworkServiceTestWithService mojom::NetworkContext* context() { return network_context_.get(); } protected: - std::unique_ptr<service_manager::Service> CreateService() override { - return std::make_unique<ServiceTestClient>(this); - } - - void SetUp() override { - test_server_.AddDefaultHandlers(base::FilePath(kServicesTestData)); - ASSERT_TRUE(test_server_.Start()); - service_manager::test::ServiceTest::SetUp(); - connector()->BindInterface(kNetworkServiceName, &network_service_); - } + base::test::ScopedTaskEnvironment task_environment_; + service_manager::TestConnectorFactory test_connector_factory_; + std::unique_ptr<NetworkService> service_; net::EmbeddedTestServer test_server_; std::unique_ptr<TestURLLoaderClient> client_; @@ -775,7 +737,7 @@ TEST_F(NetworkServiceTestWithService, StartsNetLog) { Shutdown(); // |log_file| is closed on another thread, so have to wait for that to happen. - RunUntilIdle(); + task_environment_.RunUntilIdle(); JSONFileValueDeserializer deserializer(log_path); std::unique_ptr<base::Value> log_dict = @@ -796,7 +758,7 @@ TEST_F(NetworkServiceTestWithService, RawRequestHeadersAbsent) { client()->RunUntilRedirectReceived(); EXPECT_TRUE(client()->has_received_redirect()); EXPECT_TRUE(!client()->response_head().raw_request_response_info); - loader()->FollowRedirect(base::nullopt, base::nullopt); + loader()->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); client()->RunUntilComplete(); EXPECT_TRUE(!client()->response_head().raw_request_response_info); } @@ -826,7 +788,7 @@ TEST_F(NetworkServiceTestWithService, RawRequestHeadersPresent) { "HTTP/1.1 301 Moved Permanently\r", base::CompareCase::SENSITIVE)); } - loader()->FollowRedirect(base::nullopt, base::nullopt); + loader()->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); client()->RunUntilComplete(); { scoped_refptr<HttpRawRequestResponseInfo> request_response_info = @@ -856,7 +818,10 @@ TEST_F(NetworkServiceTestWithService, RawRequestAccessControl) { StartLoadingURL(request, process_id); client()->RunUntilComplete(); EXPECT_FALSE(client()->response_head().raw_request_response_info); - service()->SetRawHeadersAccess(process_id, true); + service()->SetRawHeadersAccess( + process_id, + {url::Origin::CreateFromNormalizedTuple("http", "example.com", 80), + url::Origin::Create(request.url)}); StartLoadingURL(request, process_id); client()->RunUntilComplete(); { @@ -867,10 +832,73 @@ TEST_F(NetworkServiceTestWithService, RawRequestAccessControl) { EXPECT_EQ("OK", request_response_info->http_status_text); } - service()->SetRawHeadersAccess(process_id, false); + service()->SetRawHeadersAccess(process_id, {}); StartLoadingURL(request, process_id); client()->RunUntilComplete(); EXPECT_FALSE(client()->response_head().raw_request_response_info.get()); + + service()->SetRawHeadersAccess( + process_id, + {url::Origin::CreateFromNormalizedTuple("http", "example.com", 80)}); + StartLoadingURL(request, process_id); + client()->RunUntilComplete(); + EXPECT_FALSE(client()->response_head().raw_request_response_info.get()); +} + +class NetworkServiceTestWithResolverMap : public NetworkServiceTestWithService { + void SetUp() override { + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + network::switches::kHostResolverRules, "MAP *.test 127.0.0.1"); + NetworkServiceTestWithService::SetUp(); + } +}; + +TEST_F(NetworkServiceTestWithResolverMap, RawRequestAccessControlWithRedirect) { + CreateNetworkContext(); + + const uint32_t process_id = 42; + // initial_url in a.test redirects to b_url (in b.test) that then redirects to + // url_a in a.test. + GURL url_a = test_server()->GetURL("a.test", "/echo"); + GURL url_b = + test_server()->GetURL("b.test", "/server-redirect?" + url_a.spec()); + GURL initial_url = + test_server()->GetURL("a.test", "/server-redirect?" + url_b.spec()); + ResourceRequest request; + request.url = initial_url; + request.method = "GET"; + request.report_raw_headers = true; + request.request_initiator = url::Origin(); + + service()->SetRawHeadersAccess(process_id, {url::Origin::Create(url_a)}); + + StartLoadingURL(request, process_id); + client()->RunUntilRedirectReceived(); // from a.test to b.test + EXPECT_TRUE(client()->response_head().raw_request_response_info); + + loader()->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); + client()->ClearHasReceivedRedirect(); + client()->RunUntilRedirectReceived(); // from b.test to a.test + EXPECT_FALSE(client()->response_head().raw_request_response_info); + + loader()->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); + client()->RunUntilComplete(); // Done loading a.test + EXPECT_TRUE(client()->response_head().raw_request_response_info.get()); + + service()->SetRawHeadersAccess(process_id, {url::Origin::Create(url_b)}); + + StartLoadingURL(request, process_id); + client()->RunUntilRedirectReceived(); // from a.test to b.test + EXPECT_FALSE(client()->response_head().raw_request_response_info); + + loader()->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); + client()->ClearHasReceivedRedirect(); + client()->RunUntilRedirectReceived(); // from b.test to a.test + EXPECT_TRUE(client()->response_head().raw_request_response_info); + + loader()->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); + client()->RunUntilComplete(); // Done loading a.test + EXPECT_FALSE(client()->response_head().raw_request_response_info.get()); } TEST_F(NetworkServiceTestWithService, SetNetworkConditions) { @@ -1172,6 +1200,14 @@ TEST_F(NetworkServiceTestWithService, EXPECT_TRUE(network_context.encountered_error()); } +TEST_F(NetworkServiceTestWithService, GetDnsConfigChangeManager) { + mojom::DnsConfigChangeManagerPtr ptr; + ASSERT_FALSE(ptr.is_bound()); + + network_service_->GetDnsConfigChangeManager(mojo::MakeRequest(&ptr)); + EXPECT_TRUE(ptr.is_bound()); +} + class TestNetworkChangeManagerClient : public mojom::NetworkChangeManagerClient { public: @@ -1209,6 +1245,8 @@ class TestNetworkChangeManagerClient run_loop_.Run(); } + void Flush() { binding_.FlushForTesting(); } + private: base::RunLoop run_loop_; mojom::ConnectionType connection_type_; @@ -1254,49 +1292,29 @@ TEST_F(NetworkChangeTest, MAYBE_NetworkChangeManagerRequest) { manager_client.WaitForNotification(mojom::ConnectionType::CONNECTION_3G); } -class NetworkServiceNetworkChangeTest - : public service_manager::test::ServiceTest { +class NetworkServiceNetworkChangeTest : public testing::Test { public: NetworkServiceNetworkChangeTest() - : ServiceTest("network_unittests", - base::test::ScopedTaskEnvironment::MainThreadType::IO) {} + : task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::IO), + service_(NetworkService::CreateForTesting( + test_connector_factory_.RegisterInstance(kNetworkServiceName))) { + test_connector_factory_.GetDefaultConnector()->BindInterface( + kNetworkServiceName, &network_service_); + } + ~NetworkServiceNetworkChangeTest() override {} mojom::NetworkService* service() { return network_service_.get(); } - private: - // A ServiceTestClient that broadcasts a network change notification in the - // network service's process. - class ServiceTestClientWithNetworkChange : public ServiceTestClient { - public: - explicit ServiceTestClientWithNetworkChange( - service_manager::test::ServiceTest* test) - : ServiceTestClient(test) {} - ~ServiceTestClientWithNetworkChange() override {} - - protected: - void CreateService( - service_manager::mojom::ServiceRequest request, - const std::string& name, - service_manager::mojom::PIDReceiverPtr pid_receiver) override { - if (name == kNetworkServiceName) { - service_context_.reset(new service_manager::ServiceContext( - NetworkService::CreateForTesting(), std::move(request))); - // Send a broadcast after NetworkService is actually created. - // Otherwise, this NotifyObservers is a no-op. - net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests( - net::NetworkChangeNotifier::CONNECTION_3G); - } - } - }; - std::unique_ptr<service_manager::Service> CreateService() override { - return std::make_unique<ServiceTestClientWithNetworkChange>(this); + void SimulateNetworkChange() { + // This posts a task to simulate a network change notification } - void SetUp() override { - service_manager::test::ServiceTest::SetUp(); - connector()->BindInterface(kNetworkServiceName, &network_service_); - } + private: + base::test::ScopedTaskEnvironment task_environment_; + service_manager::TestConnectorFactory test_connector_factory_; + std::unique_ptr<NetworkService> service_; mojom::NetworkServicePtr network_service_; #if defined(OS_ANDROID) @@ -1311,6 +1329,15 @@ class NetworkServiceNetworkChangeTest TEST_F(NetworkServiceNetworkChangeTest, MAYBE_NetworkChangeManagerRequest) { TestNetworkChangeManagerClient manager_client(service()); + + // Wait for the NetworkChangeManagerClient registration to be processed within + // the NetworkService impl before simulating a change. Flushing guarantees + // end-to-end connection of the client interface. + manager_client.Flush(); + + net::NetworkChangeNotifier::NotifyObserversOfNetworkChangeForTests( + net::NetworkChangeNotifier::CONNECTION_3G); + manager_client.WaitForNotification(mojom::ConnectionType::CONNECTION_3G); } diff --git a/chromium/services/network/nss_temp_certs_cache_chromeos.cc b/chromium/services/network/nss_temp_certs_cache_chromeos.cc new file mode 100644 index 00000000000..cd20188f86c --- /dev/null +++ b/chromium/services/network/nss_temp_certs_cache_chromeos.cc @@ -0,0 +1,28 @@ +// Copyright 2017 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 "services/network/nss_temp_certs_cache_chromeos.h" + +#include "net/cert/x509_util_nss.h" + +namespace network { + +NSSTempCertsCacheChromeOS::NSSTempCertsCacheChromeOS( + const net::CertificateList& certificates) { + for (const auto& certificate : certificates) { + net::ScopedCERTCertificate x509_cert = + net::x509_util::CreateCERTCertificateFromX509Certificate( + certificate.get()); + if (!x509_cert) { + LOG(ERROR) << "Unable to create CERTCertificate"; + continue; + } + + temp_certs_.push_back(std::move(x509_cert)); + } +} + +NSSTempCertsCacheChromeOS::~NSSTempCertsCacheChromeOS() {} + +} // namespace network diff --git a/chromium/services/network/nss_temp_certs_cache_chromeos.h b/chromium/services/network/nss_temp_certs_cache_chromeos.h new file mode 100644 index 00000000000..bda81c61b6f --- /dev/null +++ b/chromium/services/network/nss_temp_certs_cache_chromeos.h @@ -0,0 +1,39 @@ +// Copyright 2017 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. + +#ifndef SERVICES_NETWORK_NSS_TEMP_CERTS_CACHE_CHROMEOS_H_ +#define SERVICES_NETWORK_NSS_TEMP_CERTS_CACHE_CHROMEOS_H_ + +#include "base/component_export.h" +#include "base/macros.h" +#include "net/cert/scoped_nss_types.h" +#include "net/cert/x509_certificate.h" + +namespace network { + +// Holds NSS temporary certificates in memory as ScopedCERTCertificates, making +// them available e.g. for client certificate discovery. +class COMPONENT_EXPORT(NETWORK_SERVICE) NSSTempCertsCacheChromeOS { + public: + explicit NSSTempCertsCacheChromeOS(const net::CertificateList& certificates); + ~NSSTempCertsCacheChromeOS(); + + private: + // The actual cache of NSS temporary certificates. + // Don't delete this field, even if it looks unused! + // This is a list which owns ScopedCERTCertificate objects. This is sufficient + // for NSS to be able to find them using CERT_FindCertByName, which is enough + // for them to be used as intermediate certificates during client certificate + // matching. Note that when the ScopedCERTCertificate objects go out of scope, + // they don't necessarily become unavailable in NSS due to caching behavior. + // However, this is not an issue, as these certificates are not imported into + // permanent databases, nor are the trust settings mutated to trust them. + net::ScopedCERTCertificateList temp_certs_; + + DISALLOW_COPY_AND_ASSIGN(NSSTempCertsCacheChromeOS); +}; + +} // namespace network + +#endif // SERVICES_NETWORK_NSS_TEMP_CERTS_CACHE_CHROMEOS_H_ diff --git a/chromium/services/network/nss_temp_certs_cache_chromeos_unittest.cc b/chromium/services/network/nss_temp_certs_cache_chromeos_unittest.cc new file mode 100644 index 00000000000..b1b995ddf31 --- /dev/null +++ b/chromium/services/network/nss_temp_certs_cache_chromeos_unittest.cc @@ -0,0 +1,131 @@ +// Copyright 2017 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 "services/network/nss_temp_certs_cache_chromeos.h" + +#include <cert.h> +#include <certdb.h> +#include <secitem.h> + +#include <string> +#include <vector> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/macros.h" +#include "base/run_loop.h" +#include "net/cert/internal/cert_errors.h" +#include "net/cert/internal/parse_certificate.h" +#include "net/cert/pem_tokenizer.h" +#include "net/cert/x509_certificate.h" +#include "net/der/input.h" +#include "net/test/test_data_directory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { + +namespace { + +class NSSTempCertsCacheChromeOSTest : public testing::Test { + public: + NSSTempCertsCacheChromeOSTest() {} + ~NSSTempCertsCacheChromeOSTest() override {} + + protected: + // Checks if the certificate stored in |pem_cert_file| can be found in the + // default NSS certificate database using CERT_FindCertByName. + // Stores the result in *|out_available|. + // Note: This funcion uses ASSERT_ macros, so the caller must verify for + // failures after it returns. + void CheckIsCertificateAvailable(const base::FilePath& pem_cert_file, + bool* out_available) { + std::string cert_contents_buffer; + net::der::Input subject; + ASSERT_NO_FATAL_FAILURE(GetCertificateSubjectDN( + pem_cert_file, &cert_contents_buffer, &subject)); + + SECItem subject_item; + subject_item.len = subject.Length(); + subject_item.data = const_cast<unsigned char*>(subject.UnsafeData()); + + net::ScopedCERTCertificate found_cert( + CERT_FindCertByName(CERT_GetDefaultCertDB(), &subject_item)); + *out_available = static_cast<bool>(found_cert); + } + + // Determines the subject DN of the certificate stored in + // |pem_cert_file|. Stores the result in *|out_subject|. + // The der::Input data structure contains unowned pointers into the + // certificate data buffer. The caller must pass a buffer in + // |cert_contents_buffer| and ensure to only use *|out_subject| while + // *|cert_contents_buffer| is in scope. + // Note: This funcion uses ASSERT_ macros, so the caller must verify for + // failures after it returns. + void GetCertificateSubjectDN(const base::FilePath& pem_cert_file, + std::string* cert_contents_buffer, + net::der::Input* out_subject) { + std::string file_data; + ASSERT_TRUE(base::ReadFileToString(pem_cert_file, &file_data)); + + std::vector<std::string> pem_headers; + pem_headers.push_back("CERTIFICATE"); + net::PEMTokenizer pem_tokenizer(file_data, pem_headers); + ASSERT_TRUE(pem_tokenizer.GetNext()); + *cert_contents_buffer = pem_tokenizer.data(); + + // Parsing the certificate. + net::der::Input tbs_certificate_tlv; + net::der::Input signature_algorithm_tlv; + net::der::BitString signature_value; + net::CertErrors errors; + ASSERT_TRUE(net::ParseCertificate( + net::der::Input(cert_contents_buffer), &tbs_certificate_tlv, + &signature_algorithm_tlv, &signature_value, &errors)); + + net::ParsedTbsCertificate tbs; + net::ParseCertificateOptions options; + options.allow_invalid_serial_numbers = true; + ASSERT_TRUE( + net::ParseTbsCertificate(tbs_certificate_tlv, options, &tbs, nullptr)); + *out_subject = tbs.subject_tlv; + } + + private: + DISALLOW_COPY_AND_ASSIGN(NSSTempCertsCacheChromeOSTest); +}; + +// Checks that a certificate made available through the +// NSSTempCertsCacheChromeOS can be found by NSS. We specifically check for +// lookup through the CERT_FindCertByName function, as this is what is used in +// client certificate matching (see MatchClientCertificateIssuers in +// net/third_party/nss/ssl/cmpcert.cc). Additionally, checks that the +// certificate is not available after the NSSTempCertsCacheChromeOS goes out of +// scope. +TEST_F(NSSTempCertsCacheChromeOSTest, CertMadeAvailable) { + base::FilePath cert_file_path = + net::GetTestCertsDirectory().AppendASCII("client_1_ca.pem"); + { + std::string x509_authority_cert; + ASSERT_TRUE(base::ReadFileToString(cert_file_path, &x509_authority_cert)); + net::CertificateList x509_authority_certs = + net::X509Certificate::CreateCertificateListFromBytes( + x509_authority_cert.data(), x509_authority_cert.length(), + net::X509Certificate::Format::FORMAT_AUTO); + + NSSTempCertsCacheChromeOS cache(x509_authority_certs); + + bool cert_available = false; + ASSERT_NO_FATAL_FAILURE( + CheckIsCertificateAvailable(cert_file_path, &cert_available)); + EXPECT_TRUE(cert_available); + } + + bool cert_available_no_cache = true; + ASSERT_NO_FATAL_FAILURE( + CheckIsCertificateAvailable(cert_file_path, &cert_available_no_cache)); + EXPECT_FALSE(cert_available_no_cache); +} + +} // namespace +} // namespace network diff --git a/chromium/services/network/p2p/socket_manager.cc b/chromium/services/network/p2p/socket_manager.cc index 27d85b6a4f5..5429b9c67cb 100644 --- a/chromium/services/network/p2p/socket_manager.cc +++ b/chromium/services/network/p2p/socket_manager.cc @@ -61,14 +61,22 @@ bool IsRtcpPacket(base::span<const uint8_t> data) { return type >= 64 && type < 96; } +// Names ending in ".local." are link-local and used with Multicast DNS as +// described in RFC6762 (https://tools.ietf.org/html/rfc6762#section-3). +constexpr char kLocalTld[] = ".local."; + +bool HasLocalTld(const std::string& host_name) { + return EndsWith(host_name, kLocalTld, base::CompareCase::INSENSITIVE_ASCII); +} + } // namespace class P2PSocketManager::DnsRequest { public: typedef base::Callback<void(const net::IPAddressList&)> DoneCallback; - explicit DnsRequest(net::HostResolver* host_resolver) - : resolver_(host_resolver) {} + DnsRequest(net::HostResolver* host_resolver, bool enable_mdns) + : resolver_(host_resolver), enable_mdns_(enable_mdns) {} void Resolve(const std::string& host_name, const DoneCallback& done_callback) { @@ -90,11 +98,18 @@ class P2PSocketManager::DnsRequest { host_name_ += '.'; net::HostPortPair host(host_name_, 0); - // TODO(crbug.com/879746): Pass in a - // net::HostResolver::ResolveHostParameters with source set to MDNS if we - // have a ".local." TLD (once MDNS is supported). + + net::HostResolver::ResolveHostParameters parameters; + if (enable_mdns_ && HasLocalTld(host_name_)) { +#if BUILDFLAG(ENABLE_MDNS) + // HostResolver/MDnsClient expects a key without a trailing dot. + host.set_host(host_name_.substr(0, host_name_.size() - 1)); + parameters.source = net::HostResolverSource::MULTICAST_DNS; +#endif // ENABLE_MDNS + } request_ = - resolver_->CreateRequest(host, net::NetLogWithSource(), base::nullopt); + resolver_->CreateRequest(host, net::NetLogWithSource(), parameters); + int result = request_->Start(base::BindOnce( &P2PSocketManager::DnsRequest::OnDone, base::Unretained(this))); if (result != net::ERR_IO_PENDING) @@ -124,6 +139,8 @@ class P2PSocketManager::DnsRequest { std::unique_ptr<net::HostResolver::ResolveHostRequest> request_; DoneCallback done_callback_; + + const bool enable_mdns_; }; P2PSocketManager::P2PSocketManager( @@ -258,9 +275,10 @@ void P2PSocketManager::StartNetworkNotifications( void P2PSocketManager::GetHostAddress( const std::string& host_name, + bool enable_mdns, mojom::P2PSocketManager::GetHostAddressCallback callback) { - std::unique_ptr<DnsRequest> request = - std::make_unique<DnsRequest>(url_request_context_->host_resolver()); + auto request = std::make_unique<DnsRequest>( + url_request_context_->host_resolver(), enable_mdns); DnsRequest* request_ptr = request.get(); dns_requests_.insert(std::move(request)); request_ptr->Resolve( diff --git a/chromium/services/network/p2p/socket_manager.h b/chromium/services/network/p2p/socket_manager.h index 9ed8bd16c93..191fe96f303 100644 --- a/chromium/services/network/p2p/socket_manager.h +++ b/chromium/services/network/p2p/socket_manager.h @@ -86,6 +86,7 @@ class P2PSocketManager mojom::P2PNetworkNotificationClientPtr client) override; void GetHostAddress( const std::string& host_name, + bool enable_mdns, mojom::P2PSocketManager::GetHostAddressCallback callback) override; void CreateSocket(P2PSocketType type, const net::IPEndPoint& local_address, diff --git a/chromium/services/network/p2p/socket_tcp.cc b/chromium/services/network/p2p/socket_tcp.cc index ba0919b9b37..5409a968e91 100644 --- a/chromium/services/network/p2p/socket_tcp.cc +++ b/chromium/services/network/p2p/socket_tcp.cc @@ -530,12 +530,14 @@ void P2PSocketStunTcp::DoSend( DCHECK_LE(pad_bytes, 4); memcpy(send_buffer.buffer->data() + data.size(), padding, pad_bytes); } - WriteOrQueue(send_buffer); + // WriteOrQueue may free the memory, so dump it first. delegate_->DumpPacket( base::make_span(reinterpret_cast<uint8_t*>(send_buffer.buffer->data()), data.size()), false); + + WriteOrQueue(send_buffer); } int P2PSocketStunTcp::GetExpectedPacketSize(const uint8_t* data, diff --git a/chromium/services/network/p2p/socket_udp.cc b/chromium/services/network/p2p/socket_udp.cc index e7609dd65d8..7efbc8a4f6a 100644 --- a/chromium/services/network/p2p/socket_udp.cc +++ b/chromium/services/network/p2p/socket_udp.cc @@ -426,15 +426,9 @@ void P2PSocketUdp::SetOption(P2PSocketOption option, int32_t value) { } } -// TODO(crbug.com/812137): We don't call SetDiffServCodePoint for the Windows -// UDP socket, because this is known to cause a hanging thread. int P2PSocketUdp::SetSocketDiffServCodePointInternal( net::DiffServCodePoint dscp) { -#if defined(OS_WIN) - return net::OK; -#else return socket_->SetDiffServCodePoint(dscp); -#endif } } // namespace network diff --git a/chromium/services/network/p2p/socket_udp_unittest.cc b/chromium/services/network/p2p/socket_udp_unittest.cc index 21a946cf2af..6cb714ef17a 100644 --- a/chromium/services/network/p2p/socket_udp_unittest.cc +++ b/chromium/services/network/p2p/socket_udp_unittest.cc @@ -148,6 +148,8 @@ class FakeDatagramServerSocket : public net::DatagramServerSocket { void AllowBroadcast() override { NOTIMPLEMENTED(); } + void AllowAddressSharingForMulticast() override { NOTIMPLEMENTED(); } + int JoinGroup(const net::IPAddress& group_address) const override { NOTIMPLEMENTED(); return net::ERR_NOT_IMPLEMENTED; diff --git a/chromium/services/network/pending_callback_chain.cc b/chromium/services/network/pending_callback_chain.cc new file mode 100644 index 00000000000..71bb122508d --- /dev/null +++ b/chromium/services/network/pending_callback_chain.cc @@ -0,0 +1,49 @@ +// Copyright 2018 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 "services/network/pending_callback_chain.h" + +namespace network { + +PendingCallbackChain::PendingCallbackChain(net::CompletionOnceCallback complete) + : complete_(std::move(complete)) {} + +PendingCallbackChain::~PendingCallbackChain() {} + +net::CompletionOnceCallback PendingCallbackChain::CreateCallback() { + return base::BindOnce(&PendingCallbackChain::CallbackComplete, this); +} + +void PendingCallbackChain::AddResult(int result) { + if (result == net::ERR_IO_PENDING) + num_waiting_++; + else + SetResult(result); +} + +int PendingCallbackChain::GetResult() const { + if (num_waiting_ > 0) + return net::ERR_IO_PENDING; + return final_result_; +} + +void PendingCallbackChain::CallbackComplete(int result) { + DCHECK_GT(num_waiting_, 0); + SetResult(result); + num_waiting_--; + if (num_waiting_ == 0) + std::move(complete_).Run(final_result_); +} + +void PendingCallbackChain::SetResult(int result) { + DCHECK_NE(result, net::ERR_IO_PENDING); + if (final_result_ == net::OK) { + final_result_ = result; + } else if (result != net::OK && result != final_result_) { + // If we have two non-OK results, default to ERR_FAILED. + final_result_ = net::ERR_FAILED; + } +} + +} // namespace network diff --git a/chromium/services/network/pending_callback_chain.h b/chromium/services/network/pending_callback_chain.h new file mode 100644 index 00000000000..d04ad20fd06 --- /dev/null +++ b/chromium/services/network/pending_callback_chain.h @@ -0,0 +1,53 @@ +// Copyright 2018 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. + +#ifndef SERVICES_NETWORK_PENDING_CALLBACK_CHAIN_H_ +#define SERVICES_NETWORK_PENDING_CALLBACK_CHAIN_H_ + +#include "base/component_export.h" +#include "base/memory/ref_counted.h" +#include "net/base/completion_once_callback.h" +#include "net/base/net_errors.h" + +namespace network { + +// Helper class to keep track of multiple functions which may return +// net::ERR_IO_PENDING and call a net::CompletionOnceCallback, or return another +// net error synchronously, and not call the completion callback. If there are +// one or more pending results added, the original completion callback will not +// be called until all those results have completed. If there are multiple error +// results that are different, net::ERR_FAILED will be used. +class COMPONENT_EXPORT(NETWORK_SERVICE) PendingCallbackChain + : public base::RefCounted<PendingCallbackChain> { + public: + explicit PendingCallbackChain(net::CompletionOnceCallback complete); + + // Creates a callback that can be called to decrement the wait count if a + // net::ERR_IO_PENDING result is added with AddResult(). + net::CompletionOnceCallback CreateCallback(); + + // Adds a result to the chain. If the result is net::ERR_IO_PENDING, + // a corresponding callback will need to be called before the original + // completion callback is called. + void AddResult(int result); + + // Gets the current result of the chain. This will be net::ERR_IO_PENDING if + // there are any pending results. + int GetResult() const; + + private: + friend class base::RefCounted<PendingCallbackChain>; + ~PendingCallbackChain(); + + void CallbackComplete(int result); + void SetResult(int result); + + int num_waiting_ = 0; + int final_result_ = net::OK; + net::CompletionOnceCallback complete_; +}; + +} // namespace network + +#endif // SERVICES_NETWORK_PENDING_CALLBACK_CHAIN_H_ diff --git a/chromium/services/network/pending_callback_chain_unittest.cc b/chromium/services/network/pending_callback_chain_unittest.cc new file mode 100644 index 00000000000..d7a03a4147d --- /dev/null +++ b/chromium/services/network/pending_callback_chain_unittest.cc @@ -0,0 +1,172 @@ +// Copyright 2018 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 "services/network/pending_callback_chain.h" +#include "base/test/bind_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { +namespace { + +int SyncReturn(int result, net::CompletionOnceCallback callback) { + return result; +} + +class AsyncReturn { + public: + int Wait(net::CompletionOnceCallback callback) { + callback_ = std::move(callback); + return net::ERR_IO_PENDING; + } + + void Finish(int result) { std::move(callback_).Run(result); } + + private: + net::CompletionOnceCallback callback_; +}; + +TEST(PendingCallbackChainTest, SingleSyncResultOk) { + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([](int r) {})); + + chain->AddResult(SyncReturn(net::OK, chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::OK); +} + +TEST(PendingCallbackChainTest, SingleSyncResultError) { + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([](int r) {})); + + chain->AddResult( + SyncReturn(net::ERR_INVALID_ARGUMENT, chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_INVALID_ARGUMENT); +} + +TEST(PendingCallbackChainTest, SingleAsyncResultOk) { + int result = net::ERR_UNEXPECTED; + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([&](int r) { result = r; })); + + AsyncReturn async; + chain->AddResult(async.Wait(chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + async.Finish(net::OK); + EXPECT_EQ(result, net::OK); + EXPECT_EQ(chain->GetResult(), net::OK); +} + +TEST(PendingCallbackChainTest, SingleAsyncResultError) { + int result = net::ERR_UNEXPECTED; + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([&](int r) { result = r; })); + + AsyncReturn async; + chain->AddResult(async.Wait(chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + async.Finish(net::ERR_INVALID_ARGUMENT); + EXPECT_EQ(result, net::ERR_INVALID_ARGUMENT); + EXPECT_EQ(chain->GetResult(), net::ERR_INVALID_ARGUMENT); +} + +TEST(PendingCallbackChainTest, MultipleSyncResultOk) { + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([](int r) {})); + + chain->AddResult(SyncReturn(net::OK, chain->CreateCallback())); + chain->AddResult(SyncReturn(net::OK, chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::OK); +} + +TEST(PendingCallbackChainTest, MultipleSyncResultError) { + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([](int r) {})); + + chain->AddResult( + SyncReturn(net::ERR_INVALID_ARGUMENT, chain->CreateCallback())); + chain->AddResult(SyncReturn(net::OK, chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_INVALID_ARGUMENT); +} + +TEST(PendingCallbackChainTest, MultipleSyncSameError) { + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([](int r) {})); + + chain->AddResult( + SyncReturn(net::ERR_INVALID_ARGUMENT, chain->CreateCallback())); + chain->AddResult( + SyncReturn(net::ERR_INVALID_ARGUMENT, chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_INVALID_ARGUMENT); +} + +TEST(PendingCallbackChainTest, MultipleSyncResultDifferentError) { + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([](int r) {})); + + chain->AddResult( + SyncReturn(net::ERR_INVALID_ARGUMENT, chain->CreateCallback())); + chain->AddResult( + SyncReturn(net::ERR_CONNECTION_FAILED, chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_FAILED); +} + +TEST(PendingCallbackChainTest, SyncAndAsyncResultOk) { + int result = net::ERR_UNEXPECTED; + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([&](int r) { result = r; })); + + AsyncReturn async; + chain->AddResult(async.Wait(chain->CreateCallback())); + chain->AddResult(SyncReturn(net::OK, chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + async.Finish(net::OK); + EXPECT_EQ(result, net::OK); +} + +TEST(PendingCallbackChainTest, MultipleAsyncResultOk) { + int result = net::ERR_UNEXPECTED; + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([&](int r) { result = r; })); + + AsyncReturn async1; + chain->AddResult(async1.Wait(chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + AsyncReturn async2; + chain->AddResult(async2.Wait(chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + async1.Finish(net::OK); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + async2.Finish(net::OK); + EXPECT_EQ(result, net::OK); + EXPECT_EQ(chain->GetResult(), net::OK); +} + +TEST(PendingCallbackChainTest, MultipleAsyncResultError) { + int result = net::ERR_UNEXPECTED; + auto chain = base::MakeRefCounted<PendingCallbackChain>( + base::BindLambdaForTesting([&](int r) { result = r; })); + + AsyncReturn async1; + chain->AddResult(async1.Wait(chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + AsyncReturn async2; + chain->AddResult(async2.Wait(chain->CreateCallback())); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + async1.Finish(net::OK); + EXPECT_EQ(chain->GetResult(), net::ERR_IO_PENDING); + + async2.Finish(net::ERR_INVALID_ARGUMENT); + EXPECT_EQ(result, net::ERR_INVALID_ARGUMENT); + EXPECT_EQ(chain->GetResult(), net::ERR_INVALID_ARGUMENT); +} + +} // namespace +} // namespace network diff --git a/chromium/services/network/proxy_resolver_factory_mojo.cc b/chromium/services/network/proxy_resolver_factory_mojo.cc index 383ef89dbe8..b398f5d95c2 100644 --- a/chromium/services/network/proxy_resolver_factory_mojo.cc +++ b/chromium/services/network/proxy_resolver_factory_mojo.cc @@ -14,20 +14,22 @@ #include "base/sequence_checker.h" #include "base/stl_util.h" #include "base/strings/utf_string_conversions.h" +#include "base/task/post_task.h" +#include "base/task_runner.h" #include "base/values.h" #include "mojo/public/cpp/bindings/binding.h" #include "net/base/load_states.h" #include "net/base/net_errors.h" -#include "net/dns/mojo_host_resolver_impl.h" -#include "net/interfaces/host_resolver_service.mojom.h" #include "net/log/net_log.h" #include "net/log/net_log_capture_mode.h" #include "net/log/net_log_event_type.h" #include "net/log/net_log_with_source.h" #include "net/proxy_resolution/pac_file_data.h" +#include "net/proxy_resolution/pac_library.h" #include "net/proxy_resolution/proxy_info.h" #include "net/proxy_resolution/proxy_resolver.h" #include "net/proxy_resolution/proxy_resolver_error_observer.h" +#include "services/network/mojo_host_resolver_impl.h" #include "services/proxy_resolver/public/mojom/proxy_resolver.mojom.h" namespace network { @@ -44,9 +46,38 @@ std::unique_ptr<base::Value> NetLogErrorCallback( return std::move(dict); } +// Implementation for myIpAddress() and myIpAddressEx() that is expected to run +// on a worker thread. Will notify |client| on completion. +void DoMyIpAddressOnWorker( + bool is_ex, + proxy_resolver::mojom::HostResolverRequestClientPtrInfo client_info) { + // Resolve the list of IP addresses. + net::IPAddressList my_ip_addresses = + is_ex ? net::PacMyIpAddressEx() : net::PacMyIpAddress(); + + proxy_resolver::mojom::HostResolverRequestClientPtr client; + client.Bind(std::move(client_info)); + + // TODO(eroman): Note that this code always returns a success response (with + // loopback) rather than passing forward the error. This is to ensure that the + // response gets cached on the proxy resolver process side, since this layer + // here does not currently do any caching or de-duplication. This should be + // cleaned up once the interfaces are refactored. Lastly note that for + // myIpAddress() this doesn't change the final result. However for + // myIpAddressEx() it means we return 127.0.0.1 rather than empty string. + if (my_ip_addresses.empty()) + my_ip_addresses.push_back(net::IPAddress::IPv4Localhost()); + + // Convert to a net::AddressList. + net::AddressList list; + for (const auto& ip : my_ip_addresses) + list.push_back(net::IPEndPoint(ip, 80)); + client->ReportResult(net::OK, list); +} + // A mixin that forwards logging to (Bound)NetLog and ProxyResolverErrorObserver // and DNS requests to a MojoHostResolverImpl, which is implemented in terms of -// a HostResolver. +// a HostResolver, or myIpAddress[Ex]() which is implemented by //net. template <typename ClientInterface> class ClientMixin : public ClientInterface { public: @@ -82,19 +113,45 @@ class ClientMixin : public ClientInterface { } } + // TODO(eroman): Split the client interfaces so ResolveDns() does not also + // carry the myIpAddress(Ex) requests. void ResolveDns( std::unique_ptr<net::HostResolver::RequestInfo> request_info, - net::interfaces::HostResolverRequestClientPtr client) override { - host_resolver_.Resolve(std::move(request_info), std::move(client)); + proxy_resolver::mojom::HostResolverRequestClientPtr client) override { + if (request_info->is_my_ip_address()) { + bool is_ex = + request_info->address_family() == net::ADDRESS_FAMILY_UNSPECIFIED; + + GetMyIpAddressTaskRuner()->PostTask( + FROM_HERE, base::BindOnce(&DoMyIpAddressOnWorker, is_ex, + client.PassInterface())); + } else { + // Request was for dnsResolve() or dnsResolveEx(). + host_resolver_.Resolve(std::move(request_info), std::move(client)); + } } protected: + // TODO(eroman): This doesn't track being blocked in myIpAddress(Ex) handler. bool dns_request_in_progress() { return host_resolver_.request_in_progress(); } + // Returns a task runner used to run the code for myIpAddress[Ex]. + static scoped_refptr<base::TaskRunner> GetMyIpAddressTaskRuner() { + // TODO(eroman): While these tasks are expected to normally run quickly, + // it would be prudent to enforce a bound on outstanding tasks, and maybe + // de-duplication of requests. + // + // However the better place to focus on is de-duplication and caching on the + // proxy service side (which currently caches but doesn't de-duplicate). + return base::CreateSequencedTaskRunnerWithTraits( + {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, + base::TaskPriority::USER_VISIBLE}); + } + private: - net::MojoHostResolverImpl host_resolver_; + MojoHostResolverImpl host_resolver_; net::ProxyResolverErrorObserver* const error_observer_; net::NetLog* const net_log_; const net::NetLogWithSource net_log_with_source_; diff --git a/chromium/services/network/proxy_resolver_factory_mojo_unittest.cc b/chromium/services/network/proxy_resolver_factory_mojo_unittest.cc index 5f45c733672..0bc76797538 100644 --- a/chromium/services/network/proxy_resolver_factory_mojo_unittest.cc +++ b/chromium/services/network/proxy_resolver_factory_mojo_unittest.cc @@ -277,7 +277,7 @@ void MockMojoProxyResolver::GetProxyForUrl( case GetProxyForUrlAction::MAKE_DNS_REQUEST: { auto request = std::make_unique<net::HostResolver::RequestInfo>( net::HostPortPair(url.spec(), 12345)); - net::interfaces::HostResolverRequestClientPtr dns_client; + proxy_resolver::mojom::HostResolverRequestClientPtr dns_client; mojo::MakeRequest(&dns_client); client->ResolveDns(std::move(request), std::move(dns_client)); blocked_clients_.push_back( @@ -457,7 +457,7 @@ void MockMojoProxyResolverFactory::CreateResolver( case CreateProxyResolverAction::MAKE_DNS_REQUEST: { auto request = std::make_unique<net::HostResolver::RequestInfo>( net::HostPortPair(pac_script, 12345)); - net::interfaces::HostResolverRequestClientPtr dns_client; + proxy_resolver::mojom::HostResolverRequestClientPtr dns_client; mojo::MakeRequest(&dns_client); client->ResolveDns(std::move(request), std::move(dns_client)); blocked_clients_.push_back( diff --git a/chromium/services/network/proxy_resolving_socket_factory_mojo.cc b/chromium/services/network/proxy_resolving_socket_factory_mojo.cc index 53dd76394a5..0841e60a72c 100644 --- a/chromium/services/network/proxy_resolving_socket_factory_mojo.cc +++ b/chromium/services/network/proxy_resolving_socket_factory_mojo.cc @@ -6,6 +6,7 @@ #include <utility> +#include "jingle/glue/fake_ssl_client_socket.h" #include "net/url_request/url_request_context.h" #include "services/network/proxy_resolving_client_socket.h" #include "services/network/proxy_resolving_client_socket_factory.h" @@ -24,13 +25,21 @@ ProxyResolvingSocketFactoryMojo::~ProxyResolvingSocketFactoryMojo() {} void ProxyResolvingSocketFactoryMojo::CreateProxyResolvingSocket( const GURL& url, - bool use_tls, + mojom::ProxyResolvingSocketOptionsPtr options, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojom::ProxyResolvingSocketRequest request, mojom::SocketObserverPtr observer, CreateProxyResolvingSocketCallback callback) { + std::unique_ptr<net::StreamSocket> net_socket = + factory_impl_.CreateSocket(url, options && options->use_tls); + if (options && options->fake_tls_handshake) { + DCHECK(!options->use_tls); + net_socket = std::make_unique<jingle_glue::FakeSSLClientSocket>( + std::move(net_socket)); + } + auto socket = std::make_unique<ProxyResolvingSocketMojo>( - factory_impl_.CreateSocket(url, use_tls), + std::move(net_socket), static_cast<net::NetworkTrafficAnnotationTag>(traffic_annotation), std::move(observer), &tls_socket_factory_); ProxyResolvingSocketMojo* socket_raw = socket.get(); diff --git a/chromium/services/network/proxy_resolving_socket_factory_mojo.h b/chromium/services/network/proxy_resolving_socket_factory_mojo.h index 59d33bdb0e4..a2684978025 100644 --- a/chromium/services/network/proxy_resolving_socket_factory_mojo.h +++ b/chromium/services/network/proxy_resolving_socket_factory_mojo.h @@ -31,7 +31,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ProxyResolvingSocketFactoryMojo // mojom::ProxyResolvingSocketFactory implementation. void CreateProxyResolvingSocket( const GURL& url, - bool use_tls, + mojom::ProxyResolvingSocketOptionsPtr options, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation, mojom::ProxyResolvingSocketRequest request, mojom::SocketObserverPtr observer, diff --git a/chromium/services/network/proxy_resolving_socket_mojo.cc b/chromium/services/network/proxy_resolving_socket_mojo.cc index 86a574991e3..2f6909c6dc1 100644 --- a/chromium/services/network/proxy_resolving_socket_mojo.cc +++ b/chromium/services/network/proxy_resolving_socket_mojo.cc @@ -14,7 +14,7 @@ namespace network { ProxyResolvingSocketMojo::ProxyResolvingSocketMojo( - std::unique_ptr<ProxyResolvingClientSocket> socket, + std::unique_ptr<net::StreamSocket> socket, const net::NetworkTrafficAnnotationTag& traffic_annotation, mojom::SocketObserverPtr observer, TLSSocketFactory* tls_socket_factory) diff --git a/chromium/services/network/proxy_resolving_socket_mojo.h b/chromium/services/network/proxy_resolving_socket_mojo.h index 866c1de82ec..0b810546091 100644 --- a/chromium/services/network/proxy_resolving_socket_mojo.h +++ b/chromium/services/network/proxy_resolving_socket_mojo.h @@ -26,7 +26,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ProxyResolvingSocketMojo public TLSSocketFactory::Delegate { public: ProxyResolvingSocketMojo( - std::unique_ptr<ProxyResolvingClientSocket> socket, + std::unique_ptr<net::StreamSocket> socket, const net::NetworkTrafficAnnotationTag& traffic_annotation, mojom::SocketObserverPtr observer, TLSSocketFactory* tls_socket_factory); @@ -57,7 +57,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ProxyResolvingSocketMojo mojom::SocketObserverPtr observer_; TLSSocketFactory* tls_socket_factory_; - std::unique_ptr<ProxyResolvingClientSocket> socket_; + std::unique_ptr<net::StreamSocket> socket_; const net::NetworkTrafficAnnotationTag traffic_annotation_; mojom::ProxyResolvingSocketFactory::CreateProxyResolvingSocketCallback connect_callback_; diff --git a/chromium/services/network/proxy_resolving_socket_mojo_unittest.cc b/chromium/services/network/proxy_resolving_socket_mojo_unittest.cc index ac180df6210..62730e17085 100644 --- a/chromium/services/network/proxy_resolving_socket_mojo_unittest.cc +++ b/chromium/services/network/proxy_resolving_socket_mojo_unittest.cc @@ -12,6 +12,7 @@ #include "base/run_loop.h" #include "base/test/bind_test_util.h" #include "base/test/scoped_task_environment.h" +#include "jingle/glue/fake_ssl_client_socket.h" #include "mojo/public/cpp/bindings/strong_binding.h" #include "mojo/public/cpp/system/data_pipe_utils.h" #include "net/base/net_errors.h" @@ -54,6 +55,7 @@ class ProxyResolvingSocketTestBase { public: ProxyResolvingSocketTestBase(bool use_tls) : use_tls_(use_tls), + fake_tls_handshake_(false), scoped_task_environment_( base::test::ScopedTaskEnvironment::MainThreadType::IO) {} @@ -114,8 +116,12 @@ class ProxyResolvingSocketTestBase { mojo::ScopedDataPipeProducerHandle* send_pipe_handle_out) { base::RunLoop run_loop; int net_error = net::ERR_FAILED; + network::mojom::ProxyResolvingSocketOptionsPtr options = + network::mojom::ProxyResolvingSocketOptions::New(); + options->use_tls = use_tls_; + options->fake_tls_handshake = fake_tls_handshake_; factory_ptr_->CreateProxyResolvingSocket( - url, use_tls_, + url, std::move(options), net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS), std::move(request), std::move(socket_observer), base::BindLambdaForTesting( @@ -141,11 +147,13 @@ class ProxyResolvingSocketTestBase { } bool use_tls() const { return use_tls_; } + void set_fake_tls_handshake(bool val) { fake_tls_handshake_ = val; } mojom::ProxyResolvingSocketFactory* factory() { return factory_ptr_.get(); } private: const bool use_tls_; + bool fake_tls_handshake_; base::test::ScopedTaskEnvironment scoped_task_environment_; std::unique_ptr<net::MockClientSocketFactory> mock_client_socket_factory_; std::unique_ptr<TestURLRequestContextWithProxy> context_with_proxy_; @@ -334,6 +342,45 @@ class ProxyResolvingSocketMojoTest : public ProxyResolvingSocketTestBase, DISALLOW_COPY_AND_ASSIGN(ProxyResolvingSocketMojoTest); }; +TEST_F(ProxyResolvingSocketMojoTest, ConnectWithFakeTLSHandshake) { + const GURL kDestination("https://example.com:443"); + const char kTestMsg[] = "abcdefghij"; + const size_t kMsgSize = strlen(kTestMsg); + + Init("DIRECT"); + set_fake_tls_handshake(true); + + base::StringPiece client_hello = + jingle_glue::FakeSSLClientSocket::GetSslClientHello(); + base::StringPiece server_hello = + jingle_glue::FakeSSLClientSocket::GetSslServerHello(); + std::vector<net::MockRead> reads = { + net::MockRead(net::ASYNC, server_hello.data(), server_hello.length(), 1), + net::MockRead(net::ASYNC, 2, kTestMsg), + net::MockRead(net::ASYNC, net::OK, 3)}; + + std::vector<net::MockWrite> writes = {net::MockWrite( + net::ASYNC, client_hello.data(), client_hello.length(), 0)}; + + net::StaticSocketDataProvider data_provider(reads, writes); + data_provider.set_connect_data(net::MockConnect(net::ASYNC, net::OK)); + mock_client_socket_factory()->AddSocketDataProvider(&data_provider); + + mojom::ProxyResolvingSocketPtr socket; + mojo::ScopedDataPipeConsumerHandle client_socket_receive_handle; + mojo::ScopedDataPipeProducerHandle client_socket_send_handle; + net::IPEndPoint actual_remote_addr; + EXPECT_EQ(net::OK, + CreateSocketSync(mojo::MakeRequest(&socket), + nullptr /* socket_observer*/, &actual_remote_addr, + kDestination, &client_socket_receive_handle, + &client_socket_send_handle)); + + EXPECT_EQ(kTestMsg, Read(&client_socket_receive_handle, kMsgSize)); + EXPECT_TRUE(data_provider.AllReadDataConsumed()); + EXPECT_TRUE(data_provider.AllWriteDataConsumed()); +} + // Tests that when ProxyResolvingSocketPtr is destroyed but not the // ProxyResolvingSocketFactory, the connect callback is not dropped. // Regression test for https://crbug.com/862608. @@ -350,7 +397,7 @@ TEST_F(ProxyResolvingSocketMojoTest, SocketDestroyedBeforeConnectCompletes) { base::RunLoop run_loop; int net_error = net::OK; factory()->CreateProxyResolvingSocket( - kDestination, false, + kDestination, nullptr, net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS), mojo::MakeRequest(&socket), nullptr /* observer */, base::BindLambdaForTesting( diff --git a/chromium/services/network/public/cpp/BUILD.gn b/chromium/services/network/public/cpp/BUILD.gn index 5086f0b5798..4a047ac01ab 100644 --- a/chromium/services/network/public/cpp/BUILD.gn +++ b/chromium/services/network/public/cpp/BUILD.gn @@ -2,8 +2,15 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//build/buildflag_header.gni") import("//build/config/jumbo.gni") import("//mojo/public/tools/bindings/mojom.gni") +import("//services/network/public/cpp/features.gni") + +buildflag_header("buildflags") { + header = "network_service_buildflags.h" + flags = [ "IS_CT_SUPPORTED=$is_ct_supported" ] +} jumbo_component("cpp") { output_name = "network_cpp" @@ -166,19 +173,25 @@ source_set("tests") { "cors/preflight_result_unittest.cc", "cross_thread_shared_url_loader_factory_info_unittest.cc", "digitally_signed_mojom_traits_unittest.cc", + "host_resolver_mojom_traits_unittest.cc", + "ip_address_mojom_traits_unittest.cc", "mutable_network_traffic_annotation_tag_mojom_traits_unittest.cc", "mutable_partial_network_traffic_annotation_tag_mojom_traits_unittest.cc", "network_connection_tracker_unittest.cc", "network_mojom_traits_unittest.cc", "network_quality_tracker_unittest.cc", "proxy_config_mojom_traits_unittest.cc", - "signed_tree_head_mojom_traits_unittest.cc", "simple_url_loader_unittest.cc", ] + if (is_ct_supported) { + sources += [ "signed_tree_head_mojom_traits_unittest.cc" ] + } + if (!is_ios) { sources += [ "server/http_server_unittest.cc" ] } + deps = [ ":cpp", ":test_interfaces", @@ -191,4 +204,8 @@ source_set("tests") { "//services/network:test_support", "//testing/gtest", ] + + public_deps = [ + ":buildflags", + ] } diff --git a/chromium/services/network/public/cpp/address_family.typemap b/chromium/services/network/public/cpp/address_family.typemap new file mode 100644 index 00000000000..ab8f25fad78 --- /dev/null +++ b/chromium/services/network/public/cpp/address_family.typemap @@ -0,0 +1,15 @@ +# Copyright 2017 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. + +mojom = "//services/network/public/mojom/address_family.mojom" +public_headers = [ "//net/base/address_family.h" ] +traits_headers = + [ "/services/network/public/cpp/address_family_mojom_traits.h" ] +sources = [ + "//services/network/public/cpp/address_family_mojom_traits.cc", +] +type_mappings = [ "network.mojom.AddressFamily=net::AddressFamily" ] +public_deps = [ + "//net", +] diff --git a/chromium/services/network/public/cpp/address_family_mojom_traits.cc b/chromium/services/network/public/cpp/address_family_mojom_traits.cc new file mode 100644 index 00000000000..c825c53548f --- /dev/null +++ b/chromium/services/network/public/cpp/address_family_mojom_traits.cc @@ -0,0 +1,45 @@ +// Copyright 2017 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 "services/network/public/cpp/address_family_mojom_traits.h" + +namespace mojo { + +// static +bool EnumTraits<network::mojom::AddressFamily, net::AddressFamily>::FromMojom( + network::mojom::AddressFamily address_family, + net::AddressFamily* out) { + using network::mojom::AddressFamily; + switch (address_family) { + case AddressFamily::UNSPECIFIED: + *out = net::ADDRESS_FAMILY_UNSPECIFIED; + return true; + case AddressFamily::IPV4: + *out = net::ADDRESS_FAMILY_IPV4; + return true; + case AddressFamily::IPV6: + *out = net::ADDRESS_FAMILY_IPV6; + return true; + } + return false; +} + +// static +network::mojom::AddressFamily +EnumTraits<network::mojom::AddressFamily, net::AddressFamily>::ToMojom( + net::AddressFamily address_family) { + using network::mojom::AddressFamily; + switch (address_family) { + case net::ADDRESS_FAMILY_UNSPECIFIED: + return AddressFamily::UNSPECIFIED; + case net::ADDRESS_FAMILY_IPV4: + return AddressFamily::IPV4; + case net::ADDRESS_FAMILY_IPV6: + return AddressFamily::IPV6; + } + NOTREACHED(); + return AddressFamily::UNSPECIFIED; +} + +} // namespace mojo diff --git a/chromium/services/network/public/cpp/address_family_mojom_traits.h b/chromium/services/network/public/cpp/address_family_mojom_traits.h new file mode 100644 index 00000000000..645e7a4f996 --- /dev/null +++ b/chromium/services/network/public/cpp/address_family_mojom_traits.h @@ -0,0 +1,23 @@ +// Copyright 2017 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. + +#ifndef SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_FAMILY_MOJOM_TRAITS_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_FAMILY_MOJOM_TRAITS_H_ + +#include "mojo/public/cpp/bindings/enum_traits.h" +#include "services/network/public/mojom/address_family.mojom.h" + +namespace mojo { + +template <> +struct EnumTraits<network::mojom::AddressFamily, net::AddressFamily> { + static network::mojom::AddressFamily ToMojom( + net::AddressFamily address_family); + static bool FromMojom(network::mojom::AddressFamily address_family, + net::AddressFamily* out); +}; + +} // namespace mojo + +#endif // SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_FAMILY_MOJOM_TRAITS_H_ diff --git a/chromium/services/network/public/cpp/address_list.typemap b/chromium/services/network/public/cpp/address_list.typemap new file mode 100644 index 00000000000..deb07fb035c --- /dev/null +++ b/chromium/services/network/public/cpp/address_list.typemap @@ -0,0 +1,14 @@ +# Copyright 2018 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. + +mojom = "//services/network/public/mojom/address_list.mojom" +public_headers = [ "//net/base/address_list.h" ] +traits_headers = [ "//services/network/public/cpp/address_list_mojom_traits.h" ] +sources = [ + "//services/network/public/cpp/address_list_mojom_traits.cc", +] +type_mappings = [ "network.mojom.AddressList=net::AddressList" ] +public_deps = [ + "//net", +] diff --git a/chromium/services/network/public/cpp/address_list_mojom_traits.cc b/chromium/services/network/public/cpp/address_list_mojom_traits.cc new file mode 100644 index 00000000000..0dac1c7c001 --- /dev/null +++ b/chromium/services/network/public/cpp/address_list_mojom_traits.cc @@ -0,0 +1,27 @@ +// Copyright 2018 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 "services/network/public/cpp/address_list_mojom_traits.h" + +#include "net/base/address_list.h" +#include "services/network/public/cpp/ip_endpoint_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits<network::mojom::AddressListDataView, net::AddressList>::Read( + network::mojom::AddressListDataView data, + net::AddressList* out) { + if (!data.ReadAddresses(&out->endpoints())) + return false; + + std::string canonical_name; + if (!data.ReadCanonicalName(&canonical_name)) + return false; + out->set_canonical_name(canonical_name); + + return true; +} + +} // namespace mojo diff --git a/chromium/services/network/public/cpp/address_list_mojom_traits.h b/chromium/services/network/public/cpp/address_list_mojom_traits.h new file mode 100644 index 00000000000..f2b03fa7a6d --- /dev/null +++ b/chromium/services/network/public/cpp/address_list_mojom_traits.h @@ -0,0 +1,33 @@ +// Copyright 2018 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. + +#ifndef SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_LIST_MOJOM_TRAITS_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_LIST_MOJOM_TRAITS_H_ + +#include <string> +#include <vector> + +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "services/network/public/mojom/address_list.mojom.h" + +namespace mojo { + +template <> +struct StructTraits<network::mojom::AddressListDataView, net::AddressList> { + static const std::vector<net::IPEndPoint>& addresses( + const net::AddressList& obj) { + return obj.endpoints(); + } + + static const std::string& canonical_name(const net::AddressList& obj) { + return obj.canonical_name(); + } + + static bool Read(network::mojom::AddressListDataView data, + net::AddressList* out); +}; + +} // namespace mojo + +#endif // SERVICES_NETWORK_PUBLIC_CPP_ADDRESS_LIST_MOJOM_TRAITS_H_ diff --git a/chromium/services/network/public/cpp/cors/cors.cc b/chromium/services/network/public/cpp/cors/cors.cc index 9e99fa3fb4d..17ed91da4d1 100644 --- a/chromium/services/network/public/cpp/cors/cors.cc +++ b/chromium/services/network/public/cpp/cors/cors.cc @@ -9,7 +9,9 @@ #include <set> #include <vector> +#include "base/containers/flat_set.h" #include "base/no_destructor.h" +#include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "net/base/mime_util.h" #include "net/http/http_request_headers.h" @@ -18,6 +20,11 @@ #include "url/url_constants.h" #include "url/url_util.h" +// String conversion from blink::String to std::string for header name/value +// should be latin-1, not utf-8, as per HTTP. Note that as we use ByteString +// as the IDL types of header name/value, a character whose code point is +// greater than 255 has already been blocked. + namespace { const char kAsterisk[] = "*"; @@ -86,11 +93,22 @@ bool IsSimilarToIntABNF(const std::string& header_value) { return true; } -// |lower_case_media_type| should be lower case. -bool IsCORSSafelistedLowerCaseContentType( - const std::string& lower_case_media_type) { - DCHECK_EQ(lower_case_media_type, base::ToLowerASCII(lower_case_media_type)); - std::string mime_type = ExtractMIMETypeFromMediaType(lower_case_media_type); +// https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte +bool IsCorsUnsafeRequestHeaderByte(char c) { + const auto u = static_cast<uint8_t>(c); + return (u < 0x20 && u != 0x09) || u == 0x22 || u == 0x28 || u == 0x29 || + u == 0x3a || u == 0x3c || u == 0x3e || u == 0x3f || u == 0x40 || + u == 0x5b || u == 0x5c || u == 0x5d || u == 0x7b || u == 0x7d || + u == 0x7f; +} + +// |value| should be lower case. +bool IsCorsSafelistedLowerCaseContentType(const std::string& value) { + DCHECK_EQ(value, base::ToLowerASCII(value)); + if (std::any_of(value.begin(), value.end(), IsCorsUnsafeRequestHeaderByte)) + return false; + + std::string mime_type = ExtractMIMETypeFromMediaType(value); return mime_type == "application/x-www-form-urlencoded" || mime_type == "multipart/form-data" || mime_type == "text/plain"; } @@ -117,7 +135,7 @@ const char kAccessControlRequestMethod[] = "Access-Control-Request-Method"; } // namespace header_names // See https://fetch.spec.whatwg.org/#cors-check. -base::Optional<CORSErrorStatus> CheckAccess( +base::Optional<CorsErrorStatus> CheckAccess( const GURL& response_url, const int response_status_code, const base::Optional<std::string>& allow_origin_header, @@ -127,7 +145,7 @@ base::Optional<CORSErrorStatus> CheckAccess( // TODO(toyoshim): This response status code check should not be needed. We // have another status code check after a CheckAccess() call if it is needed. if (!response_status_code) - return CORSErrorStatus(mojom::CORSError::kInvalidResponse); + return CorsErrorStatus(mojom::CorsError::kInvalidResponse); if (allow_origin_header == kAsterisk) { // A wildcard Access-Control-Allow-Origin can not be used if credentials are @@ -143,9 +161,9 @@ base::Optional<CORSErrorStatus> CheckAccess( // browser process or network service, this check won't be needed any more // because it is always for network requests there. if (response_url.SchemeIsHTTPOrHTTPS()) - return CORSErrorStatus(mojom::CORSError::kWildcardOriginNotAllowed); + return CorsErrorStatus(mojom::CorsError::kWildcardOriginNotAllowed); } else if (!allow_origin_header) { - return CORSErrorStatus(mojom::CORSError::kMissingAllowOriginHeader); + return CorsErrorStatus(mojom::CorsError::kMissingAllowOriginHeader); } else if (*allow_origin_header != origin.Serialize()) { // We do not use url::Origin::IsSameOriginWith() here for two reasons below. // 1. Allow "null" to match here. The latest spec does not have a clear @@ -164,13 +182,13 @@ base::Optional<CORSErrorStatus> CheckAccess( // Does not allow to have multiple origins in the allow origin header. // See https://fetch.spec.whatwg.org/#http-access-control-allow-origin. if (allow_origin_header->find_first_of(" ,") != std::string::npos) { - return CORSErrorStatus(mojom::CORSError::kMultipleAllowOriginValues, + return CorsErrorStatus(mojom::CorsError::kMultipleAllowOriginValues, *allow_origin_header); } // Check valid "null" first since GURL assumes it as invalid. if (*allow_origin_header == "null") { - return CORSErrorStatus(mojom::CORSError::kAllowOriginMismatch, + return CorsErrorStatus(mojom::CorsError::kAllowOriginMismatch, *allow_origin_header); } @@ -178,11 +196,11 @@ base::Optional<CORSErrorStatus> CheckAccess( // validation, but should be ok for providing error details to developers. GURL header_origin(*allow_origin_header); if (!header_origin.is_valid()) { - return CORSErrorStatus(mojom::CORSError::kInvalidAllowOriginValue, + return CorsErrorStatus(mojom::CorsError::kInvalidAllowOriginValue, *allow_origin_header); } - return CORSErrorStatus(mojom::CORSError::kAllowOriginMismatch, + return CorsErrorStatus(mojom::CorsError::kAllowOriginMismatch, *allow_origin_header); } @@ -191,14 +209,14 @@ base::Optional<CORSErrorStatus> CheckAccess( // This check should be case sensitive. // See also https://fetch.spec.whatwg.org/#http-new-header-syntax. if (allow_credentials_header != kLowerCaseTrue) { - return CORSErrorStatus(mojom::CORSError::kInvalidAllowCredentials, + return CorsErrorStatus(mojom::CorsError::kInvalidAllowCredentials, allow_credentials_header.value_or(std::string())); } } return base::nullopt; } -base::Optional<CORSErrorStatus> CheckPreflightAccess( +base::Optional<CorsErrorStatus> CheckPreflightAccess( const GURL& response_url, const int response_status_code, const base::Optional<std::string>& allow_origin_header, @@ -213,37 +231,37 @@ base::Optional<CORSErrorStatus> CheckPreflightAccess( // TODO(toyoshim): Remove following two lines when the status code check is // removed from CheckAccess(). - if (error_status->cors_error == mojom::CORSError::kInvalidResponse) + if (error_status->cors_error == mojom::CorsError::kInvalidResponse) return error_status; - mojom::CORSError error = error_status->cors_error; + mojom::CorsError error = error_status->cors_error; switch (error_status->cors_error) { - case mojom::CORSError::kWildcardOriginNotAllowed: - error = mojom::CORSError::kPreflightWildcardOriginNotAllowed; + case mojom::CorsError::kWildcardOriginNotAllowed: + error = mojom::CorsError::kPreflightWildcardOriginNotAllowed; break; - case mojom::CORSError::kMissingAllowOriginHeader: - error = mojom::CORSError::kPreflightMissingAllowOriginHeader; + case mojom::CorsError::kMissingAllowOriginHeader: + error = mojom::CorsError::kPreflightMissingAllowOriginHeader; break; - case mojom::CORSError::kMultipleAllowOriginValues: - error = mojom::CORSError::kPreflightMultipleAllowOriginValues; + case mojom::CorsError::kMultipleAllowOriginValues: + error = mojom::CorsError::kPreflightMultipleAllowOriginValues; break; - case mojom::CORSError::kInvalidAllowOriginValue: - error = mojom::CORSError::kPreflightInvalidAllowOriginValue; + case mojom::CorsError::kInvalidAllowOriginValue: + error = mojom::CorsError::kPreflightInvalidAllowOriginValue; break; - case mojom::CORSError::kAllowOriginMismatch: - error = mojom::CORSError::kPreflightAllowOriginMismatch; + case mojom::CorsError::kAllowOriginMismatch: + error = mojom::CorsError::kPreflightAllowOriginMismatch; break; - case mojom::CORSError::kInvalidAllowCredentials: - error = mojom::CORSError::kPreflightInvalidAllowCredentials; + case mojom::CorsError::kInvalidAllowCredentials: + error = mojom::CorsError::kPreflightInvalidAllowCredentials; break; default: NOTREACHED(); break; } - return CORSErrorStatus(error, error_status->failed_parameter); + return CorsErrorStatus(error, error_status->failed_parameter); } -base::Optional<CORSErrorStatus> CheckRedirectLocation( +base::Optional<CorsErrorStatus> CheckRedirectLocation( const GURL& url, mojom::FetchRequestMode request_mode, const base::Optional<url::Origin>& origin, @@ -260,44 +278,44 @@ base::Optional<CORSErrorStatus> CheckRedirectLocation( // credentials, and either |request|’s tainted origin flag is set or // |request|’s origin is not same origin with |actualResponse|’s location // URL’s origin, then return a network error. - DCHECK(!IsCORSEnabledRequestMode(request_mode) || origin); - if (IsCORSEnabledRequestMode(request_mode) && url_has_credentials && + DCHECK(!IsCorsEnabledRequestMode(request_mode) || origin); + if (IsCorsEnabledRequestMode(request_mode) && url_has_credentials && (tainted || !origin->IsSameOriginWith(url::Origin::Create(url)))) { - return CORSErrorStatus(mojom::CORSError::kRedirectContainsCredentials); + return CorsErrorStatus(mojom::CorsError::kRedirectContainsCredentials); } // If CORS flag is set and |actualResponse|’s location URL includes // credentials, then return a network error. if (cors_flag && url_has_credentials) - return CORSErrorStatus(mojom::CORSError::kRedirectContainsCredentials); + return CorsErrorStatus(mojom::CorsError::kRedirectContainsCredentials); return base::nullopt; } -base::Optional<mojom::CORSError> CheckPreflight(const int status_code) { +base::Optional<mojom::CorsError> CheckPreflight(const int status_code) { // CORS preflight with 3XX is considered network error in // Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch // CORS Spec: http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0 // https://crbug.com/452394 if (IsOkStatus(status_code)) return base::nullopt; - return mojom::CORSError::kPreflightInvalidStatus; + return mojom::CorsError::kPreflightInvalidStatus; } // https://wicg.github.io/cors-rfc1918/#http-headerdef-access-control-allow-external -base::Optional<CORSErrorStatus> CheckExternalPreflight( +base::Optional<CorsErrorStatus> CheckExternalPreflight( const base::Optional<std::string>& allow_external) { if (!allow_external) - return CORSErrorStatus(mojom::CORSError::kPreflightMissingAllowExternal); + return CorsErrorStatus(mojom::CorsError::kPreflightMissingAllowExternal); if (*allow_external == kLowerCaseTrue) return base::nullopt; - return CORSErrorStatus(mojom::CORSError::kPreflightInvalidAllowExternal, + return CorsErrorStatus(mojom::CorsError::kPreflightInvalidAllowExternal, *allow_external); } -bool IsCORSEnabledRequestMode(mojom::FetchRequestMode mode) { - return mode == mojom::FetchRequestMode::kCORS || - mode == mojom::FetchRequestMode::kCORSWithForcedPreflight; +bool IsCorsEnabledRequestMode(mojom::FetchRequestMode mode) { + return mode == mojom::FetchRequestMode::kCors || + mode == mojom::FetchRequestMode::kCorsWithForcedPreflight; } mojom::FetchResponseType CalculateResponseTainting( @@ -309,8 +327,8 @@ mojom::FetchResponseType CalculateResponseTainting( return mojom::FetchResponseType::kBasic; if (cors_flag) { - DCHECK(IsCORSEnabledRequestMode(request_mode)); - return mojom::FetchResponseType::kCORS; + DCHECK(IsCorsEnabledRequestMode(request_mode)); + return mojom::FetchResponseType::kCors; } if (!origin) { @@ -319,14 +337,14 @@ mojom::FetchResponseType CalculateResponseTainting( return mojom::FetchResponseType::kBasic; } - if (request_mode == mojom::FetchRequestMode::kNoCORS && + if (request_mode == mojom::FetchRequestMode::kNoCors && !origin->IsSameOriginWith(url::Origin::Create(url))) { return mojom::FetchResponseType::kOpaque; } return mojom::FetchResponseType::kBasic; } -bool IsCORSSafelistedMethod(const std::string& method) { +bool IsCorsSafelistedMethod(const std::string& method) { // https://fetch.spec.whatwg.org/#cors-safelisted-method // "A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`." std::string method_upper = base::ToUpperASCII(method); @@ -334,11 +352,11 @@ bool IsCORSSafelistedMethod(const std::string& method) { method_upper == kHeadMethod || method_upper == kPostMethod; } -bool IsCORSSafelistedContentType(const std::string& media_type) { - return IsCORSSafelistedLowerCaseContentType(base::ToLowerASCII(media_type)); +bool IsCorsSafelistedContentType(const std::string& media_type) { + return IsCorsSafelistedLowerCaseContentType(base::ToLowerASCII(media_type)); } -bool IsCORSSafelistedHeader(const std::string& name, const std::string& value) { +bool IsCorsSafelistedHeader(const std::string& name, const std::string& value) { // If |value|’s length is greater than 128, then return false. if (value.size() > 128) return false; @@ -386,12 +404,8 @@ bool IsCORSSafelistedHeader(const std::string& name, const std::string& value) { return lower_value == "on"; if (lower_name == "accept") { - return (value.end() == std::find_if(value.begin(), value.end(), [](char c) { - return (c < 0x20 && c != 0x09) || c == 0x22 || c == 0x28 || - c == 0x29 || c == 0x3a || c == 0x3c || c == 0x3e || - c == 0x3f || c == 0x40 || c == 0x5b || c == 0x5c || - c == 0x5d || c == 0x7b || c == 0x7d || c >= 0x7f; - })); + return !std::any_of(value.begin(), value.end(), + IsCorsUnsafeRequestHeaderByte); } if (lower_name == "accept-language" || lower_name == "content-language") { @@ -402,12 +416,12 @@ bool IsCORSSafelistedHeader(const std::string& name, const std::string& value) { } if (lower_name == "content-type") - return IsCORSSafelistedLowerCaseContentType(lower_value); + return IsCorsSafelistedLowerCaseContentType(lower_value); return true; } -bool IsNoCORSSafelistedHeader(const std::string& name, +bool IsNoCorsSafelistedHeader(const std::string& name, const std::string& value) { const std::string lower_name = base::ToLowerASCII(name); @@ -416,10 +430,10 @@ bool IsNoCORSSafelistedHeader(const std::string& name, return false; } - return IsCORSSafelistedHeader(lower_name, value); + return IsCorsSafelistedHeader(lower_name, value); } -std::vector<std::string> CORSUnsafeRequestHeaderNames( +std::vector<std::string> CorsUnsafeRequestHeaderNames( const net::HttpRequestHeaders::HeaderVector& headers) { std::vector<std::string> potentially_unsafe_names; std::vector<std::string> header_names; @@ -428,7 +442,7 @@ std::vector<std::string> CORSUnsafeRequestHeaderNames( size_t safe_list_value_size = 0; for (const auto& header : headers) { - if (!IsCORSSafelistedHeader(header.key, header.value)) { + if (!IsCorsSafelistedHeader(header.key, header.value)) { header_names.push_back(base::ToLowerASCII(header.key)); } else { potentially_unsafe_names.push_back(base::ToLowerASCII(header.key)); @@ -442,7 +456,7 @@ std::vector<std::string> CORSUnsafeRequestHeaderNames( return header_names; } -std::vector<std::string> CORSUnsafeNotForbiddenRequestHeaderNames( +std::vector<std::string> CorsUnsafeNotForbiddenRequestHeaderNames( const net::HttpRequestHeaders::HeaderVector& headers, bool is_revalidating) { std::vector<std::string> header_names; @@ -463,7 +477,7 @@ std::vector<std::string> CORSUnsafeNotForbiddenRequestHeaderNames( continue; } } - if (!IsCORSSafelistedHeader(name, header.value)) { + if (!IsCorsSafelistedHeader(name, header.value)) { header_names.push_back(name); } else { potentially_unsafe_names.push_back(name); @@ -493,44 +507,45 @@ bool IsForbiddenHeader(const std::string& name) { // `User-Agent`, `Via` // or starts with `Proxy-` or `Sec-` (including when it is just `Proxy-` or // `Sec-`)." - static const base::NoDestructor<std::set<std::string>> forbidden_names( - std::set<std::string>{"accept-charset", - "accept-encoding", - "access-control-request-headers", - "access-control-request-method", - "connection", - "content-length", - "cookie", - "cookie2", - "date", - "dnt", - "expect", - "host", - "keep-alive", - "origin", - "referer", - "te", - "trailer", - "transfer-encoding", - "upgrade", - "user-agent", - "via"}); + static const base::NoDestructor<base::flat_set<base::StringPiece>> + kForbiddenNames( + base::flat_set<base::StringPiece>{"accept-charset", + "accept-encoding", + "access-control-request-headers", + "access-control-request-method", + "connection", + "content-length", + "cookie", + "cookie2", + "date", + "dnt", + "expect", + "host", + "keep-alive", + "origin", + "referer", + "te", + "trailer", + "transfer-encoding", + "upgrade", + "user-agent", + "via"}); const std::string lower_name = base::ToLowerASCII(name); if (StartsWith(lower_name, "proxy-", base::CompareCase::SENSITIVE) || StartsWith(lower_name, "sec-", base::CompareCase::SENSITIVE)) { return true; } - return forbidden_names->find(lower_name) != forbidden_names->end(); + return kForbiddenNames->contains(lower_name); } bool IsOkStatus(int status) { return status >= 200 && status < 300; } -bool IsCORSSameOriginResponseType(mojom::FetchResponseType type) { +bool IsCorsSameOriginResponseType(mojom::FetchResponseType type) { switch (type) { case mojom::FetchResponseType::kBasic: - case mojom::FetchResponseType::kCORS: + case mojom::FetchResponseType::kCors: case mojom::FetchResponseType::kDefault: return true; case mojom::FetchResponseType::kError: @@ -540,10 +555,10 @@ bool IsCORSSameOriginResponseType(mojom::FetchResponseType type) { } } -bool IsCORSCrossOriginResponseType(mojom::FetchResponseType type) { +bool IsCorsCrossOriginResponseType(mojom::FetchResponseType type) { switch (type) { case mojom::FetchResponseType::kBasic: - case mojom::FetchResponseType::kCORS: + case mojom::FetchResponseType::kCors: case mojom::FetchResponseType::kDefault: case mojom::FetchResponseType::kError: return false; diff --git a/chromium/services/network/public/cpp/cors/cors.h b/chromium/services/network/public/cpp/cors/cors.h index c5db9b15141..f80899b69d4 100644 --- a/chromium/services/network/public/cpp/cors/cors.h +++ b/chromium/services/network/public/cpp/cors/cors.h @@ -50,7 +50,7 @@ extern const char kAccessControlRequestMethod[]; // Performs a CORS access check on the response parameters. // This implements https://fetch.spec.whatwg.org/#concept-cors-check COMPONENT_EXPORT(NETWORK_CPP) -base::Optional<CORSErrorStatus> CheckAccess( +base::Optional<CorsErrorStatus> CheckAccess( const GURL& response_url, const int response_status_code, const base::Optional<std::string>& allow_origin_header, @@ -63,7 +63,7 @@ base::Optional<CORSErrorStatus> CheckAccess( // step 6, even for a preflight check, |credentials_mode| should be checked on // the actual request rather than preflight one. COMPONENT_EXPORT(NETWORK_CPP) -base::Optional<CORSErrorStatus> CheckPreflightAccess( +base::Optional<CorsErrorStatus> CheckPreflightAccess( const GURL& response_url, const int response_status_code, const base::Optional<std::string>& allow_origin_header, @@ -76,7 +76,7 @@ base::Optional<CORSErrorStatus> CheckPreflightAccess( // - the URL has a CORS supported scheme and // - the URL does not contain the userinfo production. COMPONENT_EXPORT(NETWORK_CPP) -base::Optional<CORSErrorStatus> CheckRedirectLocation( +base::Optional<CorsErrorStatus> CheckRedirectLocation( const GURL& url, mojom::FetchRequestMode request_mode, const base::Optional<url::Origin>& origin, @@ -87,17 +87,17 @@ base::Optional<CORSErrorStatus> CheckRedirectLocation( // Returns |kPreflightSuccess| if preflight response was successful. // TODO(toyoshim): Rename to CheckPreflightStatus. COMPONENT_EXPORT(NETWORK_CPP) -base::Optional<mojom::CORSError> CheckPreflight(const int status_code); +base::Optional<mojom::CorsError> CheckPreflight(const int status_code); // Checks errors for the currently experimental "Access-Control-Allow-External:" // header. Shares error conditions with standard preflight checking. // See https://crbug.com/590714. COMPONENT_EXPORT(NETWORK_CPP) -base::Optional<CORSErrorStatus> CheckExternalPreflight( +base::Optional<CorsErrorStatus> CheckExternalPreflight( const base::Optional<std::string>& allow_external); COMPONENT_EXPORT(NETWORK_CPP) -bool IsCORSEnabledRequestMode(mojom::FetchRequestMode mode); +bool IsCorsEnabledRequestMode(mojom::FetchRequestMode mode); // Returns the response tainting value // (https://fetch.spec.whatwg.org/#concept-request-response-tainting) for a @@ -112,13 +112,13 @@ mojom::FetchResponseType CalculateResponseTainting( // Checks safelisted request parameters. COMPONENT_EXPORT(NETWORK_CPP) -bool IsCORSSafelistedMethod(const std::string& method); +bool IsCorsSafelistedMethod(const std::string& method); COMPONENT_EXPORT(NETWORK_CPP) -bool IsCORSSafelistedContentType(const std::string& name); +bool IsCorsSafelistedContentType(const std::string& name); COMPONENT_EXPORT(NETWORK_CPP) -bool IsCORSSafelistedHeader(const std::string& name, const std::string& value); +bool IsCorsSafelistedHeader(const std::string& name, const std::string& value); COMPONENT_EXPORT(NETWORK_CPP) -bool IsNoCORSSafelistedHeader(const std::string& name, +bool IsNoCorsSafelistedHeader(const std::string& name, const std::string& value); // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names @@ -126,7 +126,7 @@ bool IsNoCORSSafelistedHeader(const std::string& name, // The returned list is NOT sorted. // The returned list consists of lower-cased names. COMPONENT_EXPORT(NETWORK_CPP) -std::vector<std::string> CORSUnsafeRequestHeaderNames( +std::vector<std::string> CorsUnsafeRequestHeaderNames( const net::HttpRequestHeaders::HeaderVector& headers); // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names @@ -137,13 +137,13 @@ std::vector<std::string> CORSUnsafeRequestHeaderNames( // The returned list is NOT sorted. // The returned list consists of lower-cased names. COMPONENT_EXPORT(NETWORK_CPP) -std::vector<std::string> CORSUnsafeNotForbiddenRequestHeaderNames( +std::vector<std::string> CorsUnsafeNotForbiddenRequestHeaderNames( const net::HttpRequestHeaders::HeaderVector& headers, bool is_revalidating); // Checks forbidden method in the fetch spec. // See https://fetch.spec.whatwg.org/#forbidden-method. -// TODO(toyoshim): Move Blink FetchUtils::IsForbiddenMethod to CORS:: and use +// TODO(toyoshim): Move Blink FetchUtils::IsForbiddenMethod to cors:: and use // this implementation internally. COMPONENT_EXPORT(NETWORK_CPP) bool IsForbiddenMethod(const std::string& name); @@ -159,12 +159,12 @@ COMPONENT_EXPORT(NETWORK_CPP) bool IsOkStatus(int status); // Returns true if |type| is a response type which makes a response // CORS-same-origin. See https://html.spec.whatwg.org/#cors-same-origin. COMPONENT_EXPORT(NETWORK_CPP) -bool IsCORSSameOriginResponseType(mojom::FetchResponseType type); +bool IsCorsSameOriginResponseType(mojom::FetchResponseType type); // Returns true if |type| is a response type which makes a response // CORS-cross-origin. See https://html.spec.whatwg.org/#cors-cross-origin. COMPONENT_EXPORT(NETWORK_CPP) -bool IsCORSCrossOriginResponseType(mojom::FetchResponseType type); +bool IsCorsCrossOriginResponseType(mojom::FetchResponseType type); // Returns true if the credentials flag should be set for the given arguments // as in https://fetch.spec.whatwg.org/#http-network-or-cache-fetch. diff --git a/chromium/services/network/public/cpp/cors/cors_error_status.cc b/chromium/services/network/public/cpp/cors/cors_error_status.cc index a8bd29f1c94..e1a21928e6b 100644 --- a/chromium/services/network/public/cpp/cors/cors_error_status.cc +++ b/chromium/services/network/public/cpp/cors/cors_error_status.cc @@ -11,20 +11,20 @@ namespace network { // Note: |cors_error| is initialized to kLast to keep the value inside the // valid enum value range. The value is meaningless and should be overriden // immediately by IPC desrtialization code. -CORSErrorStatus::CORSErrorStatus() - : CORSErrorStatus(mojom::CORSError::kMaxValue) {} +CorsErrorStatus::CorsErrorStatus() + : CorsErrorStatus(mojom::CorsError::kMaxValue) {} -CORSErrorStatus::CORSErrorStatus(const CORSErrorStatus& status) = default; +CorsErrorStatus::CorsErrorStatus(const CorsErrorStatus& status) = default; -CORSErrorStatus::CORSErrorStatus(mojom::CORSError error) : cors_error(error) {} +CorsErrorStatus::CorsErrorStatus(mojom::CorsError error) : cors_error(error) {} -CORSErrorStatus::CORSErrorStatus(mojom::CORSError error, +CorsErrorStatus::CorsErrorStatus(mojom::CorsError error, const std::string& failed_parameter) : cors_error(error), failed_parameter(failed_parameter) {} -CORSErrorStatus::~CORSErrorStatus() = default; +CorsErrorStatus::~CorsErrorStatus() = default; -bool CORSErrorStatus::operator==(const CORSErrorStatus& rhs) const { +bool CorsErrorStatus::operator==(const CorsErrorStatus& rhs) const { return cors_error == rhs.cors_error && failed_parameter == rhs.failed_parameter; } diff --git a/chromium/services/network/public/cpp/cors/cors_error_status.h b/chromium/services/network/public/cpp/cors/cors_error_status.h index 6df12c4f967..3e79c69187a 100644 --- a/chromium/services/network/public/cpp/cors/cors_error_status.h +++ b/chromium/services/network/public/cpp/cors/cors_error_status.h @@ -14,24 +14,24 @@ namespace network { -struct COMPONENT_EXPORT(NETWORK_CPP_BASE) CORSErrorStatus { +struct COMPONENT_EXPORT(NETWORK_CPP_BASE) CorsErrorStatus { // This constructor is used by generated IPC serialization code. // Should not use this explicitly. // TODO(toyoshim, yhirano): Exploring a way to make this private, and allows // only serialization code for mojo can access. - CORSErrorStatus(); + CorsErrorStatus(); - CORSErrorStatus(const CORSErrorStatus& status); + CorsErrorStatus(const CorsErrorStatus& status); - explicit CORSErrorStatus(mojom::CORSError error); - CORSErrorStatus(mojom::CORSError error, const std::string& failed_parameter); + explicit CorsErrorStatus(mojom::CorsError error); + CorsErrorStatus(mojom::CorsError error, const std::string& failed_parameter); - ~CORSErrorStatus(); + ~CorsErrorStatus(); - bool operator==(const CORSErrorStatus& rhs) const; - bool operator!=(const CORSErrorStatus& rhs) const { return !(*this == rhs); } + bool operator==(const CorsErrorStatus& rhs) const; + bool operator!=(const CorsErrorStatus& rhs) const { return !(*this == rhs); } - mojom::CORSError cors_error; + mojom::CorsError cors_error; // Contains request method name, or header name that didn't pass a CORS check. std::string failed_parameter; diff --git a/chromium/services/network/public/cpp/cors/cors_legacy.h b/chromium/services/network/public/cpp/cors/cors_legacy.h index dc9295a92c4..af6c3bbe553 100644 --- a/chromium/services/network/public/cpp/cors/cors_legacy.h +++ b/chromium/services/network/public/cpp/cors/cors_legacy.h @@ -23,7 +23,7 @@ namespace cors { namespace legacy { // Registers whitelisted secure origins and hostname patterns for CORS checks in -// CORSURLLoader. +// CorsURLLoader. COMPONENT_EXPORT(NETWORK_CPP) void RegisterSecureOrigins(const std::vector<std::string>& secure_origins); diff --git a/chromium/services/network/public/cpp/cors/cors_unittest.cc b/chromium/services/network/public/cpp/cors/cors_unittest.cc index a87ccc1cbb3..f285e407ba6 100644 --- a/chromium/services/network/public/cpp/cors/cors_unittest.cc +++ b/chromium/services/network/public/cpp/cors/cors_unittest.cc @@ -14,27 +14,27 @@ namespace network { namespace cors { namespace { -using CORSTest = testing::Test; +using CorsTest = testing::Test; -TEST_F(CORSTest, CheckAccessDetectsInvalidResponse) { - base::Optional<CORSErrorStatus> error_status = +TEST_F(CorsTest, CheckAccessDetectsInvalidResponse) { + base::Optional<CorsErrorStatus> error_status = CheckAccess(GURL(), 0 /* response_status_code */, base::nullopt /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kOmit, url::Origin()); ASSERT_TRUE(error_status); - EXPECT_EQ(mojom::CORSError::kInvalidResponse, error_status->cors_error); + EXPECT_EQ(mojom::CorsError::kInvalidResponse, error_status->cors_error); } // Tests if CheckAccess detects kWildcardOriginNotAllowed error correctly. -TEST_F(CORSTest, CheckAccessDetectsWildcardOriginNotAllowed) { +TEST_F(CorsTest, CheckAccessDetectsWildcardOriginNotAllowed) { const GURL response_url("http://example.com/data"); const url::Origin origin = url::Origin::Create(GURL("http://google.com")); const int response_status_code = 200; const std::string allow_all_header("*"); // Access-Control-Allow-Origin '*' works. - base::Optional<CORSErrorStatus> error1 = + base::Optional<CorsErrorStatus> error1 = CheckAccess(response_url, response_status_code, allow_all_header /* allow_origin_header */, base::nullopt /* allow_credentials_header */, @@ -43,95 +43,95 @@ TEST_F(CORSTest, CheckAccessDetectsWildcardOriginNotAllowed) { // Access-Control-Allow-Origin '*' should not be allowed if credentials mode // is kInclude. - base::Optional<CORSErrorStatus> error2 = + base::Optional<CorsErrorStatus> error2 = CheckAccess(response_url, response_status_code, allow_all_header /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kInclude, origin); ASSERT_TRUE(error2); - EXPECT_EQ(mojom::CORSError::kWildcardOriginNotAllowed, error2->cors_error); + EXPECT_EQ(mojom::CorsError::kWildcardOriginNotAllowed, error2->cors_error); } // Tests if CheckAccess detects kMissingAllowOriginHeader error correctly. -TEST_F(CORSTest, CheckAccessDetectsMissingAllowOriginHeader) { +TEST_F(CorsTest, CheckAccessDetectsMissingAllowOriginHeader) { const GURL response_url("http://example.com/data"); const url::Origin origin = url::Origin::Create(GURL("http://google.com")); const int response_status_code = 200; // Access-Control-Allow-Origin is missed. - base::Optional<CORSErrorStatus> error = + base::Optional<CorsErrorStatus> error = CheckAccess(response_url, response_status_code, base::nullopt /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kOmit, origin); ASSERT_TRUE(error); - EXPECT_EQ(mojom::CORSError::kMissingAllowOriginHeader, error->cors_error); + EXPECT_EQ(mojom::CorsError::kMissingAllowOriginHeader, error->cors_error); } // Tests if CheckAccess detects kMultipleAllowOriginValues error // correctly. -TEST_F(CORSTest, CheckAccessDetectsMultipleAllowOriginValues) { +TEST_F(CorsTest, CheckAccessDetectsMultipleAllowOriginValues) { const GURL response_url("http://example.com/data"); const url::Origin origin = url::Origin::Create(GURL("http://google.com")); const int response_status_code = 200; const std::string space_separated_multiple_origins( "http://example.com http://another.example.com"); - base::Optional<CORSErrorStatus> error1 = + base::Optional<CorsErrorStatus> error1 = CheckAccess(response_url, response_status_code, space_separated_multiple_origins /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kOmit, origin); ASSERT_TRUE(error1); - EXPECT_EQ(mojom::CORSError::kMultipleAllowOriginValues, error1->cors_error); + EXPECT_EQ(mojom::CorsError::kMultipleAllowOriginValues, error1->cors_error); const std::string comma_separated_multiple_origins( "http://example.com,http://another.example.com"); - base::Optional<CORSErrorStatus> error2 = + base::Optional<CorsErrorStatus> error2 = CheckAccess(response_url, response_status_code, comma_separated_multiple_origins /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kOmit, origin); ASSERT_TRUE(error2); - EXPECT_EQ(mojom::CORSError::kMultipleAllowOriginValues, error2->cors_error); + EXPECT_EQ(mojom::CorsError::kMultipleAllowOriginValues, error2->cors_error); } // Tests if CheckAccess detects kInvalidAllowOriginValue error correctly. -TEST_F(CORSTest, CheckAccessDetectsInvalidAllowOriginValue) { +TEST_F(CorsTest, CheckAccessDetectsInvalidAllowOriginValue) { const GURL response_url("http://example.com/data"); const url::Origin origin = url::Origin::Create(GURL("http://google.com")); const int response_status_code = 200; - base::Optional<CORSErrorStatus> error = + base::Optional<CorsErrorStatus> error = CheckAccess(response_url, response_status_code, std::string("invalid.origin") /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kOmit, origin); ASSERT_TRUE(error); - EXPECT_EQ(mojom::CORSError::kInvalidAllowOriginValue, error->cors_error); + EXPECT_EQ(mojom::CorsError::kInvalidAllowOriginValue, error->cors_error); EXPECT_EQ("invalid.origin", error->failed_parameter); } // Tests if CheckAccess detects kAllowOriginMismatch error correctly. -TEST_F(CORSTest, CheckAccessDetectsAllowOriginMismatch) { +TEST_F(CorsTest, CheckAccessDetectsAllowOriginMismatch) { const GURL response_url("http://example.com/data"); const url::Origin origin = url::Origin::Create(GURL("http://google.com")); const int response_status_code = 200; - base::Optional<CORSErrorStatus> error1 = + base::Optional<CorsErrorStatus> error1 = CheckAccess(response_url, response_status_code, origin.Serialize() /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kOmit, origin); ASSERT_FALSE(error1); - base::Optional<CORSErrorStatus> error2 = CheckAccess( + base::Optional<CorsErrorStatus> error2 = CheckAccess( response_url, response_status_code, std::string("http://not.google.com") /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kOmit, origin); ASSERT_TRUE(error2); - EXPECT_EQ(mojom::CORSError::kAllowOriginMismatch, error2->cors_error); + EXPECT_EQ(mojom::CorsError::kAllowOriginMismatch, error2->cors_error); EXPECT_EQ("http://not.google.com", error2->failed_parameter); // Allow "null" value to match serialized unique origins. @@ -139,7 +139,7 @@ TEST_F(CORSTest, CheckAccessDetectsAllowOriginMismatch) { const url::Origin null_origin; EXPECT_EQ(null_string, null_origin.Serialize()); - base::Optional<CORSErrorStatus> error3 = CheckAccess( + base::Optional<CorsErrorStatus> error3 = CheckAccess( response_url, response_status_code, null_string /* allow_origin_header */, base::nullopt /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kOmit, null_origin); @@ -147,43 +147,43 @@ TEST_F(CORSTest, CheckAccessDetectsAllowOriginMismatch) { } // Tests if CheckAccess detects kInvalidAllowCredentials error correctly. -TEST_F(CORSTest, CheckAccessDetectsInvalidAllowCredential) { +TEST_F(CorsTest, CheckAccessDetectsInvalidAllowCredential) { const GURL response_url("http://example.com/data"); const url::Origin origin = url::Origin::Create(GURL("http://google.com")); const int response_status_code = 200; - base::Optional<CORSErrorStatus> error1 = + base::Optional<CorsErrorStatus> error1 = CheckAccess(response_url, response_status_code, origin.Serialize() /* allow_origin_header */, std::string("true") /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kInclude, origin); ASSERT_FALSE(error1); - base::Optional<CORSErrorStatus> error2 = + base::Optional<CorsErrorStatus> error2 = CheckAccess(response_url, response_status_code, origin.Serialize() /* allow_origin_header */, std::string("fuga") /* allow_credentials_header */, network::mojom::FetchCredentialsMode::kInclude, origin); ASSERT_TRUE(error2); - EXPECT_EQ(mojom::CORSError::kInvalidAllowCredentials, error2->cors_error); + EXPECT_EQ(mojom::CorsError::kInvalidAllowCredentials, error2->cors_error); EXPECT_EQ("fuga", error2->failed_parameter); } -// Tests if CheckRedirectLocation detects kCORSDisabledScheme and +// Tests if CheckRedirectLocation detects kCorsDisabledScheme and // kRedirectContainsCredentials errors correctly. -TEST_F(CORSTest, CheckRedirectLocation) { +TEST_F(CorsTest, CheckRedirectLocation) { struct TestCase { GURL url; mojom::FetchRequestMode request_mode; bool cors_flag; bool tainted; - base::Optional<CORSErrorStatus> expectation; + base::Optional<CorsErrorStatus> expectation; }; - const auto kCORS = mojom::FetchRequestMode::kCORS; - const auto kCORSWithForcedPreflight = - mojom::FetchRequestMode::kCORSWithForcedPreflight; - const auto kNoCORS = mojom::FetchRequestMode::kNoCORS; + const auto kCors = mojom::FetchRequestMode::kCors; + const auto kCorsWithForcedPreflight = + mojom::FetchRequestMode::kCorsWithForcedPreflight; + const auto kNoCors = mojom::FetchRequestMode::kNoCors; const url::Origin origin = url::Origin::Create(GURL("http://example.com/")); const GURL same_origin_url("http://example.com/"); @@ -194,81 +194,81 @@ TEST_F(CORSTest, CheckRedirectLocation) { const GURL cross_origin_url_with_user("http://yukari@example2.com/"); const GURL cross_origin_url_with_pass("http://:tamura@example2.com/"); const auto ok = base::nullopt; - const CORSErrorStatus kCORSDisabledScheme( - mojom::CORSError::kCORSDisabledScheme); - const CORSErrorStatus kRedirectContainsCredentials( - mojom::CORSError::kRedirectContainsCredentials); + const CorsErrorStatus kCorsDisabledScheme( + mojom::CorsError::kCorsDisabledScheme); + const CorsErrorStatus kRedirectContainsCredentials( + mojom::CorsError::kRedirectContainsCredentials); TestCase cases[] = { // "cors", no credentials information - {same_origin_url, kCORS, false, false, ok}, - {cross_origin_url, kCORS, false, false, ok}, - {data_url, kCORS, false, false, ok}, - {same_origin_url, kCORS, true, false, ok}, - {cross_origin_url, kCORS, true, false, ok}, - {data_url, kCORS, true, false, ok}, - {same_origin_url, kCORS, false, true, ok}, - {cross_origin_url, kCORS, false, true, ok}, - {data_url, kCORS, false, true, ok}, - {same_origin_url, kCORS, true, true, ok}, - {cross_origin_url, kCORS, true, true, ok}, - {data_url, kCORS, true, true, ok}, + {same_origin_url, kCors, false, false, ok}, + {cross_origin_url, kCors, false, false, ok}, + {data_url, kCors, false, false, ok}, + {same_origin_url, kCors, true, false, ok}, + {cross_origin_url, kCors, true, false, ok}, + {data_url, kCors, true, false, ok}, + {same_origin_url, kCors, false, true, ok}, + {cross_origin_url, kCors, false, true, ok}, + {data_url, kCors, false, true, ok}, + {same_origin_url, kCors, true, true, ok}, + {cross_origin_url, kCors, true, true, ok}, + {data_url, kCors, true, true, ok}, // "cors" with forced preflight, no credentials information - {same_origin_url, kCORSWithForcedPreflight, false, false, ok}, - {cross_origin_url, kCORSWithForcedPreflight, false, false, ok}, - {data_url, kCORSWithForcedPreflight, false, false, ok}, - {same_origin_url, kCORSWithForcedPreflight, true, false, ok}, - {cross_origin_url, kCORSWithForcedPreflight, true, false, ok}, - {data_url, kCORSWithForcedPreflight, true, false, ok}, - {same_origin_url, kCORSWithForcedPreflight, false, true, ok}, - {cross_origin_url, kCORSWithForcedPreflight, false, true, ok}, - {data_url, kCORSWithForcedPreflight, false, true, ok}, - {same_origin_url, kCORSWithForcedPreflight, true, true, ok}, - {cross_origin_url, kCORSWithForcedPreflight, true, true, ok}, - {data_url, kCORSWithForcedPreflight, true, true, ok}, + {same_origin_url, kCorsWithForcedPreflight, false, false, ok}, + {cross_origin_url, kCorsWithForcedPreflight, false, false, ok}, + {data_url, kCorsWithForcedPreflight, false, false, ok}, + {same_origin_url, kCorsWithForcedPreflight, true, false, ok}, + {cross_origin_url, kCorsWithForcedPreflight, true, false, ok}, + {data_url, kCorsWithForcedPreflight, true, false, ok}, + {same_origin_url, kCorsWithForcedPreflight, false, true, ok}, + {cross_origin_url, kCorsWithForcedPreflight, false, true, ok}, + {data_url, kCorsWithForcedPreflight, false, true, ok}, + {same_origin_url, kCorsWithForcedPreflight, true, true, ok}, + {cross_origin_url, kCorsWithForcedPreflight, true, true, ok}, + {data_url, kCorsWithForcedPreflight, true, true, ok}, // "no-cors", no credentials information - {same_origin_url, kNoCORS, false, false, ok}, - {cross_origin_url, kNoCORS, false, false, ok}, - {data_url, kNoCORS, false, false, ok}, - {same_origin_url, kNoCORS, false, true, ok}, - {cross_origin_url, kNoCORS, false, true, ok}, - {data_url, kNoCORS, false, true, ok}, + {same_origin_url, kNoCors, false, false, ok}, + {cross_origin_url, kNoCors, false, false, ok}, + {data_url, kNoCors, false, false, ok}, + {same_origin_url, kNoCors, false, true, ok}, + {cross_origin_url, kNoCors, false, true, ok}, + {data_url, kNoCors, false, true, ok}, // with credentials information (same origin) - {same_origin_url_with_user, kCORS, false, false, ok}, - {same_origin_url_with_user, kCORS, true, false, + {same_origin_url_with_user, kCors, false, false, ok}, + {same_origin_url_with_user, kCors, true, false, kRedirectContainsCredentials}, - {same_origin_url_with_user, kCORS, true, true, + {same_origin_url_with_user, kCors, true, true, kRedirectContainsCredentials}, - {same_origin_url_with_user, kNoCORS, false, false, ok}, - {same_origin_url_with_user, kNoCORS, false, true, ok}, - {same_origin_url_with_pass, kCORS, false, false, ok}, - {same_origin_url_with_pass, kCORS, true, false, + {same_origin_url_with_user, kNoCors, false, false, ok}, + {same_origin_url_with_user, kNoCors, false, true, ok}, + {same_origin_url_with_pass, kCors, false, false, ok}, + {same_origin_url_with_pass, kCors, true, false, kRedirectContainsCredentials}, - {same_origin_url_with_pass, kCORS, true, true, + {same_origin_url_with_pass, kCors, true, true, kRedirectContainsCredentials}, - {same_origin_url_with_pass, kNoCORS, false, false, ok}, - {same_origin_url_with_pass, kNoCORS, false, true, ok}, + {same_origin_url_with_pass, kNoCors, false, false, ok}, + {same_origin_url_with_pass, kNoCors, false, true, ok}, // with credentials information (cross origin) - {cross_origin_url_with_user, kCORS, false, false, + {cross_origin_url_with_user, kCors, false, false, kRedirectContainsCredentials}, - {cross_origin_url_with_user, kCORS, true, false, + {cross_origin_url_with_user, kCors, true, false, kRedirectContainsCredentials}, - {cross_origin_url_with_user, kCORS, true, true, + {cross_origin_url_with_user, kCors, true, true, kRedirectContainsCredentials}, - {cross_origin_url_with_user, kNoCORS, false, true, ok}, - {cross_origin_url_with_user, kNoCORS, false, false, ok}, - {cross_origin_url_with_pass, kCORS, false, false, + {cross_origin_url_with_user, kNoCors, false, true, ok}, + {cross_origin_url_with_user, kNoCors, false, false, ok}, + {cross_origin_url_with_pass, kCors, false, false, kRedirectContainsCredentials}, - {cross_origin_url_with_pass, kCORS, true, false, + {cross_origin_url_with_pass, kCors, true, false, kRedirectContainsCredentials}, - {cross_origin_url_with_pass, kCORS, true, true, + {cross_origin_url_with_pass, kCors, true, true, kRedirectContainsCredentials}, - {cross_origin_url_with_pass, kNoCORS, false, true, ok}, - {cross_origin_url_with_pass, kNoCORS, false, false, ok}, + {cross_origin_url_with_pass, kNoCors, false, true, ok}, + {cross_origin_url_with_pass, kNoCors, false, false, ok}, }; for (const auto& test : cases) { @@ -284,32 +284,32 @@ TEST_F(CORSTest, CheckRedirectLocation) { } } -TEST_F(CORSTest, CheckPreflightDetectsErrors) { +TEST_F(CorsTest, CheckPreflightDetectsErrors) { EXPECT_FALSE(CheckPreflight(200)); EXPECT_FALSE(CheckPreflight(299)); - base::Optional<mojom::CORSError> error1 = CheckPreflight(300); + base::Optional<mojom::CorsError> error1 = CheckPreflight(300); ASSERT_TRUE(error1); - EXPECT_EQ(mojom::CORSError::kPreflightInvalidStatus, *error1); + EXPECT_EQ(mojom::CorsError::kPreflightInvalidStatus, *error1); EXPECT_FALSE(CheckExternalPreflight(std::string("true"))); - base::Optional<CORSErrorStatus> error2 = + base::Optional<CorsErrorStatus> error2 = CheckExternalPreflight(base::nullopt); ASSERT_TRUE(error2); - EXPECT_EQ(mojom::CORSError::kPreflightMissingAllowExternal, + EXPECT_EQ(mojom::CorsError::kPreflightMissingAllowExternal, error2->cors_error); EXPECT_EQ("", error2->failed_parameter); - base::Optional<CORSErrorStatus> error3 = + base::Optional<CorsErrorStatus> error3 = CheckExternalPreflight(std::string("TRUE")); ASSERT_TRUE(error3); - EXPECT_EQ(mojom::CORSError::kPreflightInvalidAllowExternal, + EXPECT_EQ(mojom::CorsError::kPreflightInvalidAllowExternal, error3->cors_error); EXPECT_EQ("TRUE", error3->failed_parameter); } -TEST_F(CORSTest, CalculateResponseTainting) { +TEST_F(CorsTest, CalculateResponseTainting) { using mojom::FetchResponseType; using mojom::FetchRequestMode; @@ -324,13 +324,13 @@ TEST_F(CORSTest, CalculateResponseTainting) { same_origin_url, FetchRequestMode::kSameOrigin, origin, false)); EXPECT_EQ(FetchResponseType::kBasic, CalculateResponseTainting( - same_origin_url, FetchRequestMode::kNoCORS, origin, false)); + same_origin_url, FetchRequestMode::kNoCors, origin, false)); EXPECT_EQ(FetchResponseType::kBasic, - CalculateResponseTainting(same_origin_url, FetchRequestMode::kCORS, + CalculateResponseTainting(same_origin_url, FetchRequestMode::kCors, origin, false)); EXPECT_EQ(FetchResponseType::kBasic, CalculateResponseTainting( - same_origin_url, FetchRequestMode::kCORSWithForcedPreflight, + same_origin_url, FetchRequestMode::kCorsWithForcedPreflight, origin, false)); EXPECT_EQ(FetchResponseType::kBasic, CalculateResponseTainting( @@ -339,91 +339,97 @@ TEST_F(CORSTest, CalculateResponseTainting) { // CORS flag is false, cross-origin request EXPECT_EQ(FetchResponseType::kOpaque, CalculateResponseTainting( - cross_origin_url, FetchRequestMode::kNoCORS, origin, false)); + cross_origin_url, FetchRequestMode::kNoCors, origin, false)); EXPECT_EQ(FetchResponseType::kBasic, CalculateResponseTainting( cross_origin_url, FetchRequestMode::kNavigate, origin, false)); // CORS flag is true, same-origin request - EXPECT_EQ(FetchResponseType::kCORS, - CalculateResponseTainting(same_origin_url, FetchRequestMode::kCORS, + EXPECT_EQ(FetchResponseType::kCors, + CalculateResponseTainting(same_origin_url, FetchRequestMode::kCors, origin, true)); - EXPECT_EQ(FetchResponseType::kCORS, + EXPECT_EQ(FetchResponseType::kCors, CalculateResponseTainting( - same_origin_url, FetchRequestMode::kCORSWithForcedPreflight, + same_origin_url, FetchRequestMode::kCorsWithForcedPreflight, origin, true)); // CORS flag is true, cross-origin request - EXPECT_EQ(FetchResponseType::kCORS, - CalculateResponseTainting(cross_origin_url, FetchRequestMode::kCORS, + EXPECT_EQ(FetchResponseType::kCors, + CalculateResponseTainting(cross_origin_url, FetchRequestMode::kCors, origin, true)); - EXPECT_EQ(FetchResponseType::kCORS, + EXPECT_EQ(FetchResponseType::kCors, CalculateResponseTainting( - cross_origin_url, FetchRequestMode::kCORSWithForcedPreflight, + cross_origin_url, FetchRequestMode::kCorsWithForcedPreflight, origin, true)); // Origin is not provided. EXPECT_EQ(FetchResponseType::kBasic, CalculateResponseTainting( - same_origin_url, FetchRequestMode::kNoCORS, no_origin, false)); + same_origin_url, FetchRequestMode::kNoCors, no_origin, false)); EXPECT_EQ( FetchResponseType::kBasic, CalculateResponseTainting(same_origin_url, FetchRequestMode::kNavigate, no_origin, false)); EXPECT_EQ(FetchResponseType::kBasic, CalculateResponseTainting( - cross_origin_url, FetchRequestMode::kNoCORS, no_origin, false)); + cross_origin_url, FetchRequestMode::kNoCors, no_origin, false)); EXPECT_EQ( FetchResponseType::kBasic, CalculateResponseTainting(cross_origin_url, FetchRequestMode::kNavigate, no_origin, false)); } -TEST_F(CORSTest, SafelistedMethod) { +TEST_F(CorsTest, SafelistedMethod) { // Method check should be case-insensitive. - EXPECT_TRUE(IsCORSSafelistedMethod("get")); - EXPECT_TRUE(IsCORSSafelistedMethod("Get")); - EXPECT_TRUE(IsCORSSafelistedMethod("GET")); - EXPECT_TRUE(IsCORSSafelistedMethod("HEAD")); - EXPECT_TRUE(IsCORSSafelistedMethod("POST")); - EXPECT_FALSE(IsCORSSafelistedMethod("OPTIONS")); + EXPECT_TRUE(IsCorsSafelistedMethod("get")); + EXPECT_TRUE(IsCorsSafelistedMethod("Get")); + EXPECT_TRUE(IsCorsSafelistedMethod("GET")); + EXPECT_TRUE(IsCorsSafelistedMethod("HEAD")); + EXPECT_TRUE(IsCorsSafelistedMethod("POST")); + EXPECT_FALSE(IsCorsSafelistedMethod("OPTIONS")); } -TEST_F(CORSTest, SafelistedHeader) { +TEST_F(CorsTest, SafelistedHeader) { // See SafelistedAccept/AcceptLanguage/ContentLanguage/ContentType also. - EXPECT_TRUE(IsCORSSafelistedHeader("accept", "foo")); - EXPECT_FALSE(IsCORSSafelistedHeader("foo", "bar")); - EXPECT_FALSE(IsCORSSafelistedHeader("user-agent", "foo")); + EXPECT_TRUE(IsCorsSafelistedHeader("accept", "foo")); + EXPECT_FALSE(IsCorsSafelistedHeader("foo", "bar")); + EXPECT_FALSE(IsCorsSafelistedHeader("user-agent", "foo")); } -TEST_F(CORSTest, SafelistedAccept) { - EXPECT_TRUE(IsCORSSafelistedHeader("accept", "text/html")); - EXPECT_TRUE(IsCORSSafelistedHeader("AccepT", "text/html")); +TEST_F(CorsTest, SafelistedAccept) { + EXPECT_TRUE(IsCorsSafelistedHeader("accept", "text/html")); + EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", "text/html")); constexpr char kAllowed[] = "\t !#$%&'*+,-./0123456789;=" "ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~"; - for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) { + for (int i = 0; i < 128; ++i) { SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")"); char c = static_cast<char>(i); // 1 for the trailing null character. auto* end = kAllowed + base::size(kAllowed) - 1; EXPECT_EQ(std::find(kAllowed, end, c) != end, - IsCORSSafelistedHeader("accept", std::string(1, c))); + IsCorsSafelistedHeader("accept", std::string(1, c))); EXPECT_EQ(std::find(kAllowed, end, c) != end, - IsCORSSafelistedHeader("AccepT", std::string(1, c))); + IsCorsSafelistedHeader("AccepT", std::string(1, c))); + } + for (int i = 128; i <= 255; ++i) { + SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")"); + char c = static_cast<char>(static_cast<unsigned char>(i)); + EXPECT_TRUE(IsCorsSafelistedHeader("accept", std::string(1, c))); + EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", std::string(1, c))); } - EXPECT_TRUE(IsCORSSafelistedHeader("accept", std::string(128, 'a'))); - EXPECT_FALSE(IsCORSSafelistedHeader("accept", std::string(129, 'a'))); - EXPECT_TRUE(IsCORSSafelistedHeader("AccepT", std::string(128, 'a'))); - EXPECT_FALSE(IsCORSSafelistedHeader("AccepT", std::string(129, 'a'))); + EXPECT_TRUE(IsCorsSafelistedHeader("accept", std::string(128, 'a'))); + EXPECT_FALSE(IsCorsSafelistedHeader("accept", std::string(129, 'a'))); + EXPECT_TRUE(IsCorsSafelistedHeader("AccepT", std::string(128, 'a'))); + EXPECT_FALSE(IsCorsSafelistedHeader("AccepT", std::string(129, 'a'))); } -TEST_F(CORSTest, SafelistedAcceptLanguage) { - EXPECT_TRUE(IsCORSSafelistedHeader("accept-language", "en,ja")); - EXPECT_TRUE(IsCORSSafelistedHeader("aCcEPT-lAngUAge", "en,ja")); +TEST_F(CorsTest, SafelistedAcceptLanguage) { + EXPECT_TRUE(IsCorsSafelistedHeader("accept-language", "en,ja")); + EXPECT_TRUE(IsCorsSafelistedHeader("aCcEPT-lAngUAge", "en,ja")); constexpr char kAllowed[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz *,-.;="; @@ -433,19 +439,19 @@ TEST_F(CORSTest, SafelistedAcceptLanguage) { // 1 for the trailing null character. auto* end = kAllowed + base::size(kAllowed) - 1; EXPECT_EQ(std::find(kAllowed, end, c) != end, - IsCORSSafelistedHeader("aCcEPT-lAngUAge", std::string(1, c))); + IsCorsSafelistedHeader("aCcEPT-lAngUAge", std::string(1, c))); } - EXPECT_TRUE(IsCORSSafelistedHeader("accept-language", std::string(128, 'a'))); + EXPECT_TRUE(IsCorsSafelistedHeader("accept-language", std::string(128, 'a'))); EXPECT_FALSE( - IsCORSSafelistedHeader("accept-language", std::string(129, 'a'))); - EXPECT_TRUE(IsCORSSafelistedHeader("aCcEPT-lAngUAge", std::string(128, 'a'))); + IsCorsSafelistedHeader("accept-language", std::string(129, 'a'))); + EXPECT_TRUE(IsCorsSafelistedHeader("aCcEPT-lAngUAge", std::string(128, 'a'))); EXPECT_FALSE( - IsCORSSafelistedHeader("aCcEPT-lAngUAge", std::string(129, 'a'))); + IsCorsSafelistedHeader("aCcEPT-lAngUAge", std::string(129, 'a'))); } -TEST_F(CORSTest, SafelistedContentLanguage) { - EXPECT_TRUE(IsCORSSafelistedHeader("content-language", "en,ja")); - EXPECT_TRUE(IsCORSSafelistedHeader("cONTent-LANguaGe", "en,ja")); +TEST_F(CorsTest, SafelistedContentLanguage) { + EXPECT_TRUE(IsCorsSafelistedHeader("content-language", "en,ja")); + EXPECT_TRUE(IsCorsSafelistedHeader("cONTent-LANguaGe", "en,ja")); constexpr char kAllowed[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz *,-.;="; @@ -455,110 +461,132 @@ TEST_F(CORSTest, SafelistedContentLanguage) { // 1 for the trailing null character. auto* end = kAllowed + base::size(kAllowed) - 1; EXPECT_EQ(std::find(kAllowed, end, c) != end, - IsCORSSafelistedHeader("content-language", std::string(1, c))); + IsCorsSafelistedHeader("content-language", std::string(1, c))); EXPECT_EQ(std::find(kAllowed, end, c) != end, - IsCORSSafelistedHeader("cONTent-LANguaGe", std::string(1, c))); + IsCorsSafelistedHeader("cONTent-LANguaGe", std::string(1, c))); } EXPECT_TRUE( - IsCORSSafelistedHeader("content-language", std::string(128, 'a'))); + IsCorsSafelistedHeader("content-language", std::string(128, 'a'))); EXPECT_FALSE( - IsCORSSafelistedHeader("content-language", std::string(129, 'a'))); + IsCorsSafelistedHeader("content-language", std::string(129, 'a'))); EXPECT_TRUE( - IsCORSSafelistedHeader("cONTent-LANguaGe", std::string(128, 'a'))); + IsCorsSafelistedHeader("cONTent-LANguaGe", std::string(128, 'a'))); EXPECT_FALSE( - IsCORSSafelistedHeader("cONTent-LANguaGe", std::string(129, 'a'))); + IsCorsSafelistedHeader("cONTent-LANguaGe", std::string(129, 'a'))); } -TEST_F(CORSTest, SafelistedContentType) { - EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "text/plain")); - EXPECT_TRUE(IsCORSSafelistedHeader("CoNtEnt-TyPE", "text/plain")); +TEST_F(CorsTest, SafelistedContentType) { + constexpr char kAllowed[] = + "\t !#$%&'*+,-./0123456789;=" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~"; + for (int i = 0; i < 128; ++i) { + SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")"); + const char c = static_cast<char>(i); + // 1 for the trailing null character. + const auto* const end = kAllowed + base::size(kAllowed) - 1; + const bool is_allowed = std::find(kAllowed, end, c) != end; + const std::string value = std::string("text/plain; charset=") + c; + + EXPECT_EQ(is_allowed, IsCorsSafelistedHeader("content-type", value)); + EXPECT_EQ(is_allowed, IsCorsSafelistedHeader("cONtent-tYPe", value)); + } + for (int i = 128; i <= 255; ++i) { + SCOPED_TRACE(testing::Message() << "c = static_cast<char>(" << i << ")"); + char c = static_cast<char>(static_cast<unsigned char>(i)); + const std::string value = std::string("text/plain; charset=") + c; + EXPECT_TRUE(IsCorsSafelistedHeader("content-type", value)); + EXPECT_TRUE(IsCorsSafelistedHeader("ConTEnt-Type", value)); + } + + EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "text/plain")); + EXPECT_TRUE(IsCorsSafelistedHeader("CoNtEnt-TyPE", "text/plain")); EXPECT_TRUE( - IsCORSSafelistedHeader("content-type", "text/plain; charset=utf-8")); + IsCorsSafelistedHeader("content-type", "text/plain; charset=utf-8")); EXPECT_TRUE( - IsCORSSafelistedHeader("content-type", " text/plain ; charset=UTF-8")); + IsCorsSafelistedHeader("content-type", " text/plain ; charset=UTF-8")); EXPECT_TRUE( - IsCORSSafelistedHeader("content-type", "text/plain; param=BOGUS")); - EXPECT_TRUE(IsCORSSafelistedHeader("content-type", + IsCorsSafelistedHeader("content-type", "text/plain; param=BOGUS")); + EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "application/x-www-form-urlencoded")); - EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "multipart/form-data")); + EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "multipart/form-data")); - EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "Text/plain")); - EXPECT_TRUE(IsCORSSafelistedHeader("content-type", "tEXT/PLAIN")); - EXPECT_FALSE(IsCORSSafelistedHeader("content-type", "text/html")); - EXPECT_FALSE(IsCORSSafelistedHeader("CoNtEnt-TyPE", "text/html")); + EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "Text/plain")); + EXPECT_TRUE(IsCorsSafelistedHeader("content-type", "tEXT/PLAIN")); + EXPECT_FALSE(IsCorsSafelistedHeader("content-type", "text/html")); + EXPECT_FALSE(IsCorsSafelistedHeader("CoNtEnt-TyPE", "text/html")); - EXPECT_FALSE(IsCORSSafelistedHeader("content-type", "image/png")); - EXPECT_FALSE(IsCORSSafelistedHeader("CoNtEnt-TyPE", "image/png")); - EXPECT_TRUE(IsCORSSafelistedHeader( + EXPECT_FALSE(IsCorsSafelistedHeader("content-type", "image/png")); + EXPECT_FALSE(IsCorsSafelistedHeader("CoNtEnt-TyPE", "image/png")); + EXPECT_TRUE(IsCorsSafelistedHeader( "content-type", "text/plain; charset=" + std::string(108, 'a'))); - EXPECT_TRUE(IsCORSSafelistedHeader( + EXPECT_TRUE(IsCorsSafelistedHeader( "cONTent-tYPE", "text/plain; charset=" + std::string(108, 'a'))); - EXPECT_FALSE(IsCORSSafelistedHeader( + EXPECT_FALSE(IsCorsSafelistedHeader( "content-type", "text/plain; charset=" + std::string(109, 'a'))); - EXPECT_FALSE(IsCORSSafelistedHeader( + EXPECT_FALSE(IsCorsSafelistedHeader( "cONTent-tYPE", "text/plain; charset=" + std::string(109, 'a'))); } -TEST_F(CORSTest, CheckCORSClientHintsSafelist) { - EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "")); - EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "abc")); - EXPECT_TRUE(IsCORSSafelistedHeader("device-memory", "1.25")); - EXPECT_TRUE(IsCORSSafelistedHeader("DEVICE-memory", "1.25")); - EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "1.25-2.5")); - EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "-1.25")); - EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "1e2")); - EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "inf")); - EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "-2.3")); - EXPECT_FALSE(IsCORSSafelistedHeader("device-memory", "NaN")); - EXPECT_FALSE(IsCORSSafelistedHeader("DEVICE-memory", "1.25.3")); - EXPECT_FALSE(IsCORSSafelistedHeader("DEVICE-memory", "1.")); - EXPECT_FALSE(IsCORSSafelistedHeader("DEVICE-memory", ".1")); - EXPECT_FALSE(IsCORSSafelistedHeader("DEVICE-memory", ".")); - EXPECT_TRUE(IsCORSSafelistedHeader("DEVICE-memory", "1")); - - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "abc")); - EXPECT_TRUE(IsCORSSafelistedHeader("dpr", "1.25")); - EXPECT_TRUE(IsCORSSafelistedHeader("Dpr", "1.25")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "1.25-2.5")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "-1.25")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "1e2")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "inf")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "-2.3")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "NaN")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "1.25.3")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", "1.")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", ".1")); - EXPECT_FALSE(IsCORSSafelistedHeader("dpr", ".")); - EXPECT_TRUE(IsCORSSafelistedHeader("dpr", "1")); - - EXPECT_FALSE(IsCORSSafelistedHeader("width", "")); - EXPECT_FALSE(IsCORSSafelistedHeader("width", "abc")); - EXPECT_TRUE(IsCORSSafelistedHeader("width", "125")); - EXPECT_TRUE(IsCORSSafelistedHeader("width", "1")); - EXPECT_TRUE(IsCORSSafelistedHeader("WIDTH", "125")); - EXPECT_FALSE(IsCORSSafelistedHeader("width", "125.2")); - EXPECT_FALSE(IsCORSSafelistedHeader("width", "-125")); - EXPECT_TRUE(IsCORSSafelistedHeader("width", "2147483648")); - - EXPECT_FALSE(IsCORSSafelistedHeader("viewport-width", "")); - EXPECT_FALSE(IsCORSSafelistedHeader("viewport-width", "abc")); - EXPECT_TRUE(IsCORSSafelistedHeader("viewport-width", "125")); - EXPECT_TRUE(IsCORSSafelistedHeader("viewport-width", "1")); - EXPECT_TRUE(IsCORSSafelistedHeader("viewport-Width", "125")); - EXPECT_FALSE(IsCORSSafelistedHeader("viewport-width", "125.2")); - EXPECT_TRUE(IsCORSSafelistedHeader("viewport-width", "2147483648")); +TEST_F(CorsTest, CheckCorsClientHintsSafelist) { + EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "")); + EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "abc")); + EXPECT_TRUE(IsCorsSafelistedHeader("device-memory", "1.25")); + EXPECT_TRUE(IsCorsSafelistedHeader("DEVICE-memory", "1.25")); + EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "1.25-2.5")); + EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "-1.25")); + EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "1e2")); + EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "inf")); + EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "-2.3")); + EXPECT_FALSE(IsCorsSafelistedHeader("device-memory", "NaN")); + EXPECT_FALSE(IsCorsSafelistedHeader("DEVICE-memory", "1.25.3")); + EXPECT_FALSE(IsCorsSafelistedHeader("DEVICE-memory", "1.")); + EXPECT_FALSE(IsCorsSafelistedHeader("DEVICE-memory", ".1")); + EXPECT_FALSE(IsCorsSafelistedHeader("DEVICE-memory", ".")); + EXPECT_TRUE(IsCorsSafelistedHeader("DEVICE-memory", "1")); + + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "abc")); + EXPECT_TRUE(IsCorsSafelistedHeader("dpr", "1.25")); + EXPECT_TRUE(IsCorsSafelistedHeader("Dpr", "1.25")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "1.25-2.5")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "-1.25")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "1e2")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "inf")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "-2.3")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "NaN")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "1.25.3")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", "1.")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", ".1")); + EXPECT_FALSE(IsCorsSafelistedHeader("dpr", ".")); + EXPECT_TRUE(IsCorsSafelistedHeader("dpr", "1")); + + EXPECT_FALSE(IsCorsSafelistedHeader("width", "")); + EXPECT_FALSE(IsCorsSafelistedHeader("width", "abc")); + EXPECT_TRUE(IsCorsSafelistedHeader("width", "125")); + EXPECT_TRUE(IsCorsSafelistedHeader("width", "1")); + EXPECT_TRUE(IsCorsSafelistedHeader("WIDTH", "125")); + EXPECT_FALSE(IsCorsSafelistedHeader("width", "125.2")); + EXPECT_FALSE(IsCorsSafelistedHeader("width", "-125")); + EXPECT_TRUE(IsCorsSafelistedHeader("width", "2147483648")); + + EXPECT_FALSE(IsCorsSafelistedHeader("viewport-width", "")); + EXPECT_FALSE(IsCorsSafelistedHeader("viewport-width", "abc")); + EXPECT_TRUE(IsCorsSafelistedHeader("viewport-width", "125")); + EXPECT_TRUE(IsCorsSafelistedHeader("viewport-width", "1")); + EXPECT_TRUE(IsCorsSafelistedHeader("viewport-Width", "125")); + EXPECT_FALSE(IsCorsSafelistedHeader("viewport-width", "125.2")); + EXPECT_TRUE(IsCorsSafelistedHeader("viewport-width", "2147483648")); } -TEST_F(CORSTest, CORSUnsafeRequestHeaderNames) { +TEST_F(CorsTest, CorsUnsafeRequestHeaderNames) { // Needed because initializer list is not allowed for a macro argument. using List = std::vector<std::string>; // Empty => Empty - EXPECT_EQ(CORSUnsafeRequestHeaderNames({}), List({})); + EXPECT_EQ(CorsUnsafeRequestHeaderNames({}), List({})); // Some headers are safelisted. - EXPECT_EQ(CORSUnsafeRequestHeaderNames({{"content-type", "text/plain"}, + EXPECT_EQ(CorsUnsafeRequestHeaderNames({{"content-type", "text/plain"}, {"dpr", "12345"}, {"aCCept", "en,ja"}, {"accept-charset", "utf-8"}, @@ -568,7 +596,7 @@ TEST_F(CORSTest, CORSUnsafeRequestHeaderNames) { // All headers are not safelisted. EXPECT_EQ( - CORSUnsafeRequestHeaderNames({{"content-type", "text/html"}, + CorsUnsafeRequestHeaderNames({{"content-type", "text/html"}, {"dpr", "123-45"}, {"aCCept", "en,ja"}, {"accept-charset", "utf-8"}, @@ -578,7 +606,7 @@ TEST_F(CORSTest, CORSUnsafeRequestHeaderNames) { // |safelistValueSize| is 1024. EXPECT_EQ( - CORSUnsafeRequestHeaderNames( + CorsUnsafeRequestHeaderNames( {{"content-type", "text/plain; charset=" + std::string(108, '1')}, {"accept", std::string(128, '1')}, {"accept-language", std::string(128, '1')}, @@ -593,7 +621,7 @@ TEST_F(CORSTest, CORSUnsafeRequestHeaderNames) { // |safelistValueSize| is 1025. EXPECT_EQ( - CORSUnsafeRequestHeaderNames( + CorsUnsafeRequestHeaderNames( {{"content-type", "text/plain; charset=" + std::string(108, '1')}, {"accept", std::string(128, '1')}, {"accept-language", std::string(128, '1')}, @@ -610,7 +638,7 @@ TEST_F(CORSTest, CORSUnsafeRequestHeaderNames) { // |safelistValueSize| is 897 because "content-type" is not safelisted. EXPECT_EQ( - CORSUnsafeRequestHeaderNames( + CorsUnsafeRequestHeaderNames( {{"content-type", "text/plain; charset=" + std::string(128, '1')}, {"accept", std::string(128, '1')}, {"accept-language", std::string(128, '1')}, @@ -624,18 +652,18 @@ TEST_F(CORSTest, CORSUnsafeRequestHeaderNames) { List({"content-type", "hoge"})); } -TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNames) { +TEST_F(CorsTest, CorsUnsafeNotForbiddenRequestHeaderNames) { // Needed because initializer list is not allowed for a macro argument. using List = std::vector<std::string>; // Empty => Empty EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames({}, false /* is_revalidating */), + CorsUnsafeNotForbiddenRequestHeaderNames({}, false /* is_revalidating */), List({})); // "user-agent" is NOT forbidden per spec, but forbidden in Chromium. EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames({{"content-type", "text/plain"}, + CorsUnsafeNotForbiddenRequestHeaderNames({{"content-type", "text/plain"}, {"dpr", "12345"}, {"aCCept", "en,ja"}, {"accept-charset", "utf-8"}, @@ -645,7 +673,7 @@ TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNames) { List({"hoge"})); EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames({{"content-type", "text/html"}, + CorsUnsafeNotForbiddenRequestHeaderNames({{"content-type", "text/html"}, {"dpr", "123-45"}, {"aCCept", "en,ja"}, {"accept-charset", "utf-8"}, @@ -655,7 +683,7 @@ TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNames) { // |safelistValueSize| is 1024. EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames( + CorsUnsafeNotForbiddenRequestHeaderNames( {{"content-type", "text/plain; charset=" + std::string(108, '1')}, {"accept", std::string(128, '1')}, {"accept-language", std::string(128, '1')}, @@ -672,7 +700,7 @@ TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNames) { // |safelistValueSize| is 1025. EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames( + CorsUnsafeNotForbiddenRequestHeaderNames( {{"content-type", "text/plain; charset=" + std::string(108, '1')}, {"accept", std::string(128, '1')}, {"accept-language", std::string(128, '1')}, @@ -691,7 +719,7 @@ TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNames) { // |safelistValueSize| is 897 because "content-type" is not safelisted. EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames( + CorsUnsafeNotForbiddenRequestHeaderNames( {{"content-type", "text/plain; charset=" + std::string(128, '1')}, {"accept", std::string(128, '1')}, {"accept-language", std::string(128, '1')}, @@ -707,18 +735,18 @@ TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNames) { List({"content-type", "hoge"})); } -TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNamesWithRevalidating) { +TEST_F(CorsTest, CorsUnsafeNotForbiddenRequestHeaderNamesWithRevalidating) { // Needed because initializer list is not allowed for a macro argument. using List = std::vector<std::string>; // Empty => Empty EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames({}, true /* is_revalidating */), + CorsUnsafeNotForbiddenRequestHeaderNames({}, true /* is_revalidating */), List({})); // These three headers will be ignored. EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames({{"If-MODifIED-since", "x"}, + CorsUnsafeNotForbiddenRequestHeaderNames({{"If-MODifIED-since", "x"}, {"iF-nONE-MATCh", "y"}, {"CACHE-ContrOl", "z"}}, true /* is_revalidating */), @@ -726,7 +754,7 @@ TEST_F(CORSTest, CORSUnsafeNotForbiddenRequestHeaderNamesWithRevalidating) { // Without is_revalidating set, these three headers will not be safelisted. EXPECT_EQ( - CORSUnsafeNotForbiddenRequestHeaderNames({{"If-MODifIED-since", "x"}, + CorsUnsafeNotForbiddenRequestHeaderNames({{"If-MODifIED-since", "x"}, {"iF-nONE-MATCh", "y"}, {"CACHE-ContrOl", "z"}}, false /* is_revalidating */), diff --git a/chromium/services/network/public/cpp/cors/origin_access_entry.cc b/chromium/services/network/public/cpp/cors/origin_access_entry.cc index 26361e43d12..b9eb5facf56 100644 --- a/chromium/services/network/public/cpp/cors/origin_access_entry.cc +++ b/chromium/services/network/public/cpp/cors/origin_access_entry.cc @@ -6,6 +6,7 @@ #include "base/strings/string_util.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" +#include "services/network/public/mojom/cors_origin_pattern.mojom.h" #include "url/origin.h" #include "url/url_util.h" @@ -33,11 +34,11 @@ bool IsSubdomainOfHost(const std::string& subdomain, const std::string& host) { OriginAccessEntry::OriginAccessEntry( const std::string& protocol, const std::string& host, - MatchMode match_mode, - const network::mojom::CORSOriginAccessMatchPriority priority) + const mojom::CorsOriginAccessMatchMode mode, + const mojom::CorsOriginAccessMatchPriority priority) : protocol_(protocol), host_(host), - match_mode_(match_mode), + mode_(mode), priority_(priority), host_is_ip_address_(url::HostIsIPAddress(host)), host_is_public_suffix_(false) { @@ -56,7 +57,9 @@ OriginAccessEntry::OriginAccessEntry( if (host_.length() <= public_suffix_length + 1) { host_is_public_suffix_ = true; - } else if (match_mode_ == kAllowRegisterableDomains && public_suffix_length) { + } else if (mode_ == + mojom::CorsOriginAccessMatchMode::kAllowRegisterableDomains && + public_suffix_length) { // The "2" in the next line is 1 for the '.', plus a 1-char minimum label // length. const size_t dot = @@ -82,7 +85,8 @@ OriginAccessEntry::MatchResult OriginAccessEntry::MatchesDomain( const url::Origin& origin) const { // Special case: Include subdomains and empty host means "all hosts, including // ip addresses". - if (match_mode_ != kDisallowSubdomains && host_.empty()) + if (mode_ != mojom::CorsOriginAccessMatchMode::kDisallowSubdomains && + host_.empty()) return kMatchesOrigin; // Exact match. @@ -94,16 +98,16 @@ OriginAccessEntry::MatchResult OriginAccessEntry::MatchesDomain( return kDoesNotMatchOrigin; // Match subdomains. - switch (match_mode_) { - case kDisallowSubdomains: + switch (mode_) { + case mojom::CorsOriginAccessMatchMode::kDisallowSubdomains: return kDoesNotMatchOrigin; - case kAllowSubdomains: + case mojom::CorsOriginAccessMatchMode::kAllowSubdomains: if (!IsSubdomainOfHost(origin.host(), host_)) return kDoesNotMatchOrigin; break; - case kAllowRegisterableDomains: + case mojom::CorsOriginAccessMatchMode::kAllowRegisterableDomains: // Fall back to a simple subdomain check if no registerable domain could // be found: if (registerable_domain_.empty()) { @@ -122,6 +126,11 @@ OriginAccessEntry::MatchResult OriginAccessEntry::MatchesDomain( return kMatchesOrigin; } +mojo::InlinedStructPtr<mojom::CorsOriginPattern> +OriginAccessEntry::CreateCorsOriginPattern() const { + return mojom::CorsOriginPattern::New(protocol_, host_, mode_, priority_); +} + } // namespace cors } // namespace network diff --git a/chromium/services/network/public/cpp/cors/origin_access_entry.h b/chromium/services/network/public/cpp/cors/origin_access_entry.h index 5052f1aec4e..f49838d644f 100644 --- a/chromium/services/network/public/cpp/cors/origin_access_entry.h +++ b/chromium/services/network/public/cpp/cors/origin_access_entry.h @@ -10,6 +10,7 @@ #include "base/component_export.h" #include "base/macros.h" #include "services/network/public/mojom/cors.mojom-shared.h" +#include "services/network/public/mojom/cors_origin_pattern.mojom-shared.h" namespace url { class Origin; @@ -17,6 +18,10 @@ class Origin; namespace network { +namespace mojom { +class CorsOriginPattern; +} // namespace mojom + namespace cors { // A class to hold a protocol and host pair and to provide methods to determine @@ -24,37 +29,23 @@ namespace cors { // to control if the matching methods accept a partial match. class COMPONENT_EXPORT(NETWORK_CPP) OriginAccessEntry final { public: - // A enum to represent a mode if matching functions can accept a partial match - // for sub-domains, or for registerable domains. - enum MatchMode { - // 'www.example.com' matches an OriginAccessEntry for 'example.com' - kAllowSubdomains, - - // 'www.example.com' matches an OriginAccessEntry for 'not-www.example.com' - kAllowRegisterableDomains, - - // 'www.example.com' does not match an OriginAccessEntry for 'example.com' - kDisallowSubdomains, - }; - enum MatchResult { kMatchesOrigin, kMatchesOriginButIsPublicSuffix, kDoesNotMatchOrigin }; - // If host is empty string and MatchMode is not DisallowSubdomains, the entry - // will match all domains in the specified protocol. + // If host is empty string and CorsOriginAccessMatchMode is not + // DisallowSubdomains, the entry will match all domains in the specified + // protocol. // IPv6 addresses must include brackets (e.g. // '[2001:db8:85a3::8a2e:370:7334]', not '2001:db8:85a3::8a2e:370:7334'). - // The priority argument is used to break ties when multiple entries - // match. - OriginAccessEntry( - const std::string& protocol, - const std::string& host, - MatchMode match_mode, - const network::mojom::CORSOriginAccessMatchPriority priority = - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + // The priority argument is used to break ties when multiple entries match. + OriginAccessEntry(const std::string& protocol, + const std::string& host, + const mojom::CorsOriginAccessMatchMode mode, + const mojom::CorsOriginAccessMatchPriority priority = + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); OriginAccessEntry(OriginAccessEntry&& from); // 'matchesOrigin' requires a protocol match (e.g. 'http' != 'https'). @@ -63,18 +54,21 @@ class COMPONENT_EXPORT(NETWORK_CPP) OriginAccessEntry final { MatchResult MatchesDomain(const url::Origin& domain) const; bool host_is_ip_address() const { return host_is_ip_address_; } - network::mojom::CORSOriginAccessMatchPriority priority() const { - return priority_; - } + mojom::CorsOriginAccessMatchPriority priority() const { return priority_; } const std::string& registerable_domain() const { return registerable_domain_; } + // Creates mojom::CorsOriginPattern instance that represents |this| + // OriginAccessEntry instance. + mojo::InlinedStructPtr<mojom::CorsOriginPattern> CreateCorsOriginPattern() + const; + private: const std::string protocol_; const std::string host_; - const MatchMode match_mode_; - network::mojom::CORSOriginAccessMatchPriority priority_; + const mojom::CorsOriginAccessMatchMode mode_; + const mojom::CorsOriginAccessMatchPriority priority_; const bool host_is_ip_address_; std::string registerable_domain_; diff --git a/chromium/services/network/public/cpp/cors/origin_access_entry_unittest.cc b/chromium/services/network/public/cpp/cors/origin_access_entry_unittest.cc index a68e978baf5..1f086e0a4fb 100644 --- a/chromium/services/network/public/cpp/cors/origin_access_entry_unittest.cc +++ b/chromium/services/network/public/cpp/cors/origin_access_entry_unittest.cc @@ -3,8 +3,8 @@ // found in the LICENSE file. #include "services/network/public/cpp/cors/origin_access_entry.h" -#include "services/network/public/mojom/cors.mojom.h" +#include "services/network/public/mojom/cors_origin_pattern.mojom.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" @@ -18,14 +18,14 @@ namespace { TEST(OriginAccessEntryTest, PublicSuffixListTest) { url::Origin origin = url::Origin::Create(GURL("http://www.google.com")); OriginAccessEntry entry1( - "http", "google.com", OriginAccessEntry::kAllowSubdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + "http", "google.com", mojom::CorsOriginAccessMatchMode::kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); OriginAccessEntry entry2( - "http", "hamster.com", OriginAccessEntry::kAllowSubdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + "http", "hamster.com", mojom::CorsOriginAccessMatchMode::kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); OriginAccessEntry entry3( - "http", "com", OriginAccessEntry::kAllowSubdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + "http", "com", mojom::CorsOriginAccessMatchMode::kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_EQ(OriginAccessEntry::kMatchesOrigin, entry1.MatchesOrigin(origin)); EXPECT_EQ(OriginAccessEntry::kDoesNotMatchOrigin, entry2.MatchesOrigin(origin)); @@ -92,8 +92,9 @@ TEST(OriginAccessEntryTest, AllowSubdomainsTest) { << "Host: " << test.host << ", Origin: " << test.origin); url::Origin origin_to_test = url::Origin::Create(GURL(test.origin)); OriginAccessEntry entry1( - test.protocol, test.host, OriginAccessEntry::kAllowSubdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + test.protocol, test.host, + mojom::CorsOriginAccessMatchMode::kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_EQ(test.expected_origin, entry1.MatchesOrigin(origin_to_test)); EXPECT_EQ(test.expected_domain, entry1.MatchesDomain(origin_to_test)); } @@ -141,8 +142,9 @@ TEST(OriginAccessEntryTest, AllowRegisterableDomainsTest) { for (const auto& test : inputs) { url::Origin origin_to_test = url::Origin::Create(GURL(test.origin)); OriginAccessEntry entry1( - test.protocol, test.host, OriginAccessEntry::kAllowRegisterableDomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + test.protocol, test.host, + mojom::CorsOriginAccessMatchMode::kAllowRegisterableDomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); SCOPED_TRACE(testing::Message() << "Host: " << test.host << ", Origin: " << test.origin @@ -194,8 +196,9 @@ TEST(OriginAccessEntryTest, AllowRegisterableDomainsTestWithDottedSuffix) { for (const auto& test : inputs) { url::Origin origin_to_test = url::Origin::Create(GURL(test.origin)); OriginAccessEntry entry1( - test.protocol, test.host, OriginAccessEntry::kAllowRegisterableDomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + test.protocol, test.host, + mojom::CorsOriginAccessMatchMode::kAllowRegisterableDomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); SCOPED_TRACE(testing::Message() << "Host: " << test.host << ", Origin: " << test.origin @@ -244,8 +247,9 @@ TEST(OriginAccessEntryTest, DisallowSubdomainsTest) { << "Host: " << test.host << ", Origin: " << test.origin); url::Origin origin_to_test = url::Origin::Create(GURL(test.origin)); OriginAccessEntry entry1( - test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + test.protocol, test.host, + mojom::CorsOriginAccessMatchMode::kDisallowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_EQ(test.expected, entry1.MatchesOrigin(origin_to_test)); } } @@ -270,8 +274,9 @@ TEST(OriginAccessEntryTest, IPAddressTest) { for (const auto& test : inputs) { SCOPED_TRACE(testing::Message() << "Host: " << test.host); OriginAccessEntry entry( - test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + test.protocol, test.host, + mojom::CorsOriginAccessMatchMode::kDisallowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_EQ(test.is_ip_address, entry.host_is_ip_address()) << test.host; } } @@ -298,17 +303,33 @@ TEST(OriginAccessEntryTest, IPAddressMatchingTest) { << "Host: " << test.host << ", Origin: " << test.origin); url::Origin origin_to_test = url::Origin::Create(GURL(test.origin)); OriginAccessEntry entry1( - test.protocol, test.host, OriginAccessEntry::kAllowSubdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + test.protocol, test.host, + mojom::CorsOriginAccessMatchMode::kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_EQ(test.expected, entry1.MatchesOrigin(origin_to_test)); OriginAccessEntry entry2( - test.protocol, test.host, OriginAccessEntry::kDisallowSubdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + test.protocol, test.host, + mojom::CorsOriginAccessMatchMode::kDisallowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_EQ(test.expected, entry2.MatchesOrigin(origin_to_test)); } } +TEST(OriginAccessEntryTest, CreateCorsOriginPattern) { + const std::string kProtocol = "https"; + const std::string kDomain = "google.com"; + const auto kMode = mojom::CorsOriginAccessMatchMode::kAllowSubdomains; + const auto kPriority = mojom::CorsOriginAccessMatchPriority::kDefaultPriority; + + OriginAccessEntry entry(kProtocol, kDomain, kMode, kPriority); + mojom::CorsOriginPatternPtr pattern = entry.CreateCorsOriginPattern(); + DCHECK_EQ(kProtocol, pattern->protocol); + DCHECK_EQ(kDomain, pattern->domain); + DCHECK_EQ(kMode, pattern->mode); + DCHECK_EQ(kPriority, pattern->priority); +} + } // namespace } // namespace cors diff --git a/chromium/services/network/public/cpp/cors/origin_access_list.cc b/chromium/services/network/public/cpp/cors/origin_access_list.cc index 150d9a0aafb..6a302632a37 100644 --- a/chromium/services/network/public/cpp/cors/origin_access_list.cc +++ b/chromium/services/network/public/cpp/cors/origin_access_list.cc @@ -4,6 +4,8 @@ #include "services/network/public/cpp/cors/origin_access_list.h" +#include "services/network/public/mojom/cors_origin_pattern.mojom.h" + namespace network { namespace cors { @@ -13,19 +15,25 @@ OriginAccessList::~OriginAccessList() = default; void OriginAccessList::SetAllowListForOrigin( const url::Origin& source_origin, - const std::vector<mojom::CorsOriginPatternPtr>& patterns) { - SetForOrigin(source_origin, patterns, &allow_list_, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + const std::vector<CorsOriginPatternPtr>& patterns) { + SetForOrigin(source_origin, patterns, &allow_list_); } void OriginAccessList::AddAllowListEntryForOrigin( const url::Origin& source_origin, const std::string& protocol, const std::string& domain, - bool allow_subdomains, - const network::mojom::CORSOriginAccessMatchPriority priority) { - AddForOrigin(source_origin, protocol, domain, allow_subdomains, &allow_list_, - priority); + const mojom::CorsOriginAccessMatchMode mode, + const mojom::CorsOriginAccessMatchPriority priority) { + AddForOrigin(source_origin, + mojom::CorsOriginPattern::New(protocol, domain, mode, priority), + &allow_list_); +} + +void OriginAccessList::ClearAllowListForOrigin( + const url::Origin& source_origin) { + SetForOrigin(source_origin, std::vector<mojom::CorsOriginPatternPtr>(), + &allow_list_); } void OriginAccessList::ClearAllowList() { @@ -34,19 +42,25 @@ void OriginAccessList::ClearAllowList() { void OriginAccessList::SetBlockListForOrigin( const url::Origin& source_origin, - const std::vector<mojom::CorsOriginPatternPtr>& patterns) { - SetForOrigin(source_origin, patterns, &block_list_, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + const std::vector<CorsOriginPatternPtr>& patterns) { + SetForOrigin(source_origin, patterns, &block_list_); } void OriginAccessList::AddBlockListEntryForOrigin( const url::Origin& source_origin, const std::string& protocol, const std::string& domain, - bool allow_subdomains, - const network::mojom::CORSOriginAccessMatchPriority priority) { - AddForOrigin(source_origin, protocol, domain, allow_subdomains, &block_list_, - priority); + const mojom::CorsOriginAccessMatchMode mode, + const mojom::CorsOriginAccessMatchPriority priority) { + AddForOrigin(source_origin, + mojom::CorsOriginPattern::New(protocol, domain, mode, priority), + &block_list_); +} + +void OriginAccessList::ClearBlockListForOrigin( + const url::Origin& source_origin) { + SetForOrigin(source_origin, std::vector<mojom::CorsOriginPatternPtr>(), + &block_list_); } void OriginAccessList::ClearBlockList() { @@ -59,27 +73,54 @@ bool OriginAccessList::IsAllowed(const url::Origin& source_origin, return false; std::string source = source_origin.Serialize(); url::Origin destination_origin = url::Origin::Create(destination); - network::mojom::CORSOriginAccessMatchPriority allow_list_priority = + network::mojom::CorsOriginAccessMatchPriority allow_list_priority = GetHighestPriorityOfRuleForOrigin(source, destination_origin, allow_list_); if (allow_list_priority == - network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin) + network::mojom::CorsOriginAccessMatchPriority::kNoMatchingOrigin) return false; - network::mojom::CORSOriginAccessMatchPriority block_list_priority = + network::mojom::CorsOriginAccessMatchPriority block_list_priority = GetHighestPriorityOfRuleForOrigin(source, destination_origin, block_list_); if (block_list_priority == - network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin) + network::mojom::CorsOriginAccessMatchPriority::kNoMatchingOrigin) return true; return allow_list_priority > block_list_priority; } +std::vector<mojo::StructPtr<mojom::CorsOriginAccessPatterns>> +OriginAccessList::CreateCorsOriginAccessPatternsList() const { + std::set<std::string> origins; + for (const auto& allow_map : allow_list_) + origins.insert(allow_map.first); + for (const auto& block_map : block_list_) + origins.insert(block_map.first); + + std::vector<mojom::CorsOriginAccessPatternsPtr> access_patterns; + for (const auto& origin : origins) { + std::vector<mojom::CorsOriginPatternPtr> allow_patterns; + const auto& allow_entries = allow_list_.find(origin); + if (allow_entries != allow_list_.end()) { + for (const auto& pattern : allow_entries->second) + allow_patterns.push_back(pattern.CreateCorsOriginPattern()); + } + std::vector<mojom::CorsOriginPatternPtr> block_patterns; + const auto& block_entries = block_list_.find(origin); + if (block_entries != block_list_.end()) { + for (const auto& pattern : block_entries->second) + block_patterns.push_back(pattern.CreateCorsOriginPattern()); + } + access_patterns.push_back(mojom::CorsOriginAccessPatterns::New( + origin, std::move(allow_patterns), std::move(block_patterns))); + } + return access_patterns; +} + // static void OriginAccessList::SetForOrigin( const url::Origin& source_origin, - const std::vector<mojom::CorsOriginPatternPtr>& patterns, - PatternMap* map, - const network::mojom::CORSOriginAccessMatchPriority priority) { + const std::vector<CorsOriginPatternPtr>& patterns, + PatternMap* map) { DCHECK(map); DCHECK(!source_origin.opaque()); @@ -91,42 +132,32 @@ void OriginAccessList::SetForOrigin( Patterns& native_patterns = (*map)[source]; for (const auto& pattern : patterns) { native_patterns.push_back(OriginAccessEntry( - pattern->protocol, pattern->domain, - pattern->allow_subdomains ? OriginAccessEntry::kAllowSubdomains - : OriginAccessEntry::kDisallowSubdomains, - priority)); + pattern->protocol, pattern->domain, pattern->mode, pattern->priority)); } } // static -void OriginAccessList::AddForOrigin( - const url::Origin& source_origin, - const std::string& protocol, - const std::string& domain, - bool allow_subdomains, - PatternMap* map, - const network::mojom::CORSOriginAccessMatchPriority priority) { +void OriginAccessList::AddForOrigin(const url::Origin& source_origin, + const CorsOriginPatternPtr& pattern, + PatternMap* map) { DCHECK(map); DCHECK(!source_origin.opaque()); std::string source = source_origin.Serialize(); - (*map)[source].push_back(OriginAccessEntry( - protocol, domain, - allow_subdomains ? OriginAccessEntry::kAllowSubdomains - : OriginAccessEntry::kDisallowSubdomains, - priority)); + (*map)[source].push_back(OriginAccessEntry(pattern->protocol, pattern->domain, + pattern->mode, pattern->priority)); } // static // TODO(nrpeter): Sort OriginAccessEntry entries on edit then we can return the // first match which will be the top priority. -network::mojom::CORSOriginAccessMatchPriority +network::mojom::CorsOriginAccessMatchPriority OriginAccessList::GetHighestPriorityOfRuleForOrigin( const std::string& source, const url::Origin& destination_origin, const PatternMap& map) { - network::mojom::CORSOriginAccessMatchPriority highest_priority = - network::mojom::CORSOriginAccessMatchPriority::kNoMatchingOrigin; + network::mojom::CorsOriginAccessMatchPriority highest_priority = + network::mojom::CorsOriginAccessMatchPriority::kNoMatchingOrigin; auto patterns_for_origin_it = map.find(source); if (patterns_for_origin_it == map.end()) return highest_priority; diff --git a/chromium/services/network/public/cpp/cors/origin_access_list.h b/chromium/services/network/public/cpp/cors/origin_access_list.h index 0a8cffdc97d..9f8f31884b1 100644 --- a/chromium/services/network/public/cpp/cors/origin_access_list.h +++ b/chromium/services/network/public/cpp/cors/origin_access_list.h @@ -12,54 +12,65 @@ #include "base/component_export.h" #include "base/macros.h" #include "services/network/public/cpp/cors/origin_access_entry.h" -#include "services/network/public/mojom/cors_origin_pattern.mojom.h" +#include "services/network/public/mojom/cors_origin_pattern.mojom-shared.h" #include "url/origin.h" namespace network { +namespace mojom { +class CorsOriginPattern; +class CorsOriginAccessPatterns; +} // namespace mojom + namespace cors { // A class to manage origin access allow / block lists. If these lists conflict, // blacklisting is respected. These lists are managed per source-origin basis. class COMPONENT_EXPORT(NETWORK_CPP) OriginAccessList { public: + using CorsOriginPatternPtr = mojo::InlinedStructPtr<mojom::CorsOriginPattern>; + OriginAccessList(); ~OriginAccessList(); // Clears the old allow list for |source_origin|, and set |patterns| to the - // allow list. - void SetAllowListForOrigin( - const url::Origin& source_origin, - const std::vector<mojom::CorsOriginPatternPtr>& patterns); + // allow list. When two or more patterns in a list match, the entry with the + // higher |priority| takes precedence. + void SetAllowListForOrigin(const url::Origin& source_origin, + const std::vector<CorsOriginPatternPtr>& patterns); - // Adds a matching pattern for |protocol|, |domain|, and |allow_subdomains| - // to the allow list. When two or more entries in a list match the entry - // with the higher |priority| takes precedence. + // Adds an access pattern by |protocol|, |domain|, |mode|, and |priority|, + // to the allow list for |source_origin|. void AddAllowListEntryForOrigin( const url::Origin& source_origin, const std::string& protocol, const std::string& domain, - bool allow_subdomains, - const network::mojom::CORSOriginAccessMatchPriority priority); + const mojom::CorsOriginAccessMatchMode mode, + const mojom::CorsOriginAccessMatchPriority priority); + + // Clears the old allow list for |source_origin|. + void ClearAllowListForOrigin(const url::Origin& source_origin); // Clears the old allow list. void ClearAllowList(); // Clears the old block list for |source_origin| and set |patterns| to the - // block list. - void SetBlockListForOrigin( - const url::Origin& source_origin, - const std::vector<mojom::CorsOriginPatternPtr>& patterns); + // block list. When two or more patterns in a list match, the entry with the + // higher |priority| takes precedence. + void SetBlockListForOrigin(const url::Origin& source_origin, + const std::vector<CorsOriginPatternPtr>& patterns); - // Adds a matching pattern for |protocol|, |domain|, and |allow_subdomains| - // to the block list. When two or more entries in a list match the entry - // with the higher |priority| takes precedence. + // Adds an access pattern by |protocol|, |domain|, |mode|, and |priority|, + // to the block list for |source_origin|. void AddBlockListEntryForOrigin( const url::Origin& source_origin, const std::string& protocol, const std::string& domain, - bool allow_subdomains, - const network::mojom::CORSOriginAccessMatchPriority priority); + const mojom::CorsOriginAccessMatchMode mode, + const mojom::CorsOriginAccessMatchPriority priority); + + // Clears the old block list for |source_origin|. + void ClearBlockListForOrigin(const url::Origin& source_origin); // Clears the old block list. void ClearBlockList(); @@ -69,27 +80,28 @@ class COMPONENT_EXPORT(NETWORK_CPP) OriginAccessList { bool IsAllowed(const url::Origin& source_origin, const GURL& destination) const; + // Creates mojom::CorsPriginAccessPatterns instance vector that represents + // |this| OriginAccessList instance. + std::vector<mojo::StructPtr<mojom::CorsOriginAccessPatterns>> + CreateCorsOriginAccessPatternsList() const; + private: using Patterns = std::vector<OriginAccessEntry>; using PatternMap = std::map<std::string, Patterns>; - static void SetForOrigin( - const url::Origin& source_origin, - const std::vector<mojom::CorsOriginPatternPtr>& patterns, - PatternMap* map, - const network::mojom::CORSOriginAccessMatchPriority priority); - static void AddForOrigin( - const url::Origin& source_origin, - const std::string& protocol, - const std::string& domain, - bool allow_subdomains, - PatternMap* map, - const network::mojom::CORSOriginAccessMatchPriority priority); - static network::mojom::CORSOriginAccessMatchPriority - GetHighestPriorityOfRuleForOrigin(const std::string& source, - const url::Origin& destination_origin, - const PatternMap& map); - + static void SetForOrigin(const url::Origin& source_origin, + const std::vector<CorsOriginPatternPtr>& patterns, + PatternMap* map); + static void AddForOrigin(const url::Origin& source_origin, + const CorsOriginPatternPtr& pattern, + PatternMap* map); + static mojom::CorsOriginAccessMatchPriority GetHighestPriorityOfRuleForOrigin( + const std::string& source, + const url::Origin& destination_origin, + const PatternMap& map); + + // TODO(toyoshim): Redesign to have an unified map to be consistent with + // mojom::CorsOriginAccessPatterns. See https://crbug.com/908756. PatternMap allow_list_; PatternMap block_list_; diff --git a/chromium/services/network/public/cpp/cors/origin_access_list_unittest.cc b/chromium/services/network/public/cpp/cors/origin_access_list_unittest.cc index 7bab76846e9..f64e1536cc3 100644 --- a/chromium/services/network/public/cpp/cors/origin_access_list_unittest.cc +++ b/chromium/services/network/public/cpp/cors/origin_access_list_unittest.cc @@ -4,6 +4,7 @@ #include "services/network/public/cpp/cors/origin_access_list.h" #include "services/network/public/mojom/cors.mojom.h" +#include "services/network/public/mojom/cors_origin_pattern.mojom.h" #include <memory> @@ -17,6 +18,12 @@ namespace cors { namespace { +const auto kAllowSubdomains = + mojom::CorsOriginAccessMatchMode::kAllowSubdomains; + +const auto kDisallowSubdomains = + mojom::CorsOriginAccessMatchMode::kDisallowSubdomains; + // OriginAccessListTest is a out of blink version of blink::SecurityPolicyTest, // but it contains only tests for the allow/block lists management. class OriginAccessListTest : public testing::Test { @@ -49,37 +56,35 @@ class OriginAccessListTest : public testing::Test { } void SetAllowListEntry(const std::string& protocol, const std::string& host, - bool allow_subdomains) { + const mojom::CorsOriginAccessMatchMode mode) { std::vector<mojom::CorsOriginPatternPtr> patterns; patterns.push_back(mojom::CorsOriginPattern::New( - protocol, host, allow_subdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority)); + protocol, host, mode, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority)); list_.SetAllowListForOrigin(source_origin_, patterns); } - void AddAllowListEntry( - const std::string& protocol, - const std::string& host, - bool allow_subdomains, - const network::mojom::CORSOriginAccessMatchPriority priority) { - list_.AddAllowListEntryForOrigin(source_origin_, protocol, host, - allow_subdomains, priority); + void AddAllowListEntry(const std::string& protocol, + const std::string& host, + const mojom::CorsOriginAccessMatchMode mode, + const mojom::CorsOriginAccessMatchPriority priority) { + list_.AddAllowListEntryForOrigin(source_origin_, protocol, host, mode, + priority); } void SetBlockListEntry(const std::string& protocol, const std::string& host, - bool allow_subdomains) { + const mojom::CorsOriginAccessMatchMode mode) { std::vector<mojom::CorsOriginPatternPtr> patterns; patterns.push_back(mojom::CorsOriginPattern::New( - protocol, host, allow_subdomains, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority)); + protocol, host, mode, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority)); list_.SetBlockListForOrigin(source_origin_, patterns); } - void AddBlockListEntry( - const std::string& protocol, - const std::string& host, - bool allow_subdomains, - const network::mojom::CORSOriginAccessMatchPriority priority) { - list_.AddBlockListEntryForOrigin(source_origin_, protocol, host, - allow_subdomains, priority); + void AddBlockListEntry(const std::string& protocol, + const std::string& host, + const mojom::CorsOriginAccessMatchMode mode, + const mojom::CorsOriginAccessMatchPriority priority) { + list_.AddBlockListEntryForOrigin(source_origin_, protocol, host, mode, + priority); } void ResetLists() { std::vector<mojom::CorsOriginPatternPtr> patterns; @@ -108,7 +113,7 @@ TEST_F(OriginAccessListTest, IsAccessAllowed) { // Adding access for https://example.com should work, but should not grant // access to subdomains or other schemes. - SetAllowListEntry("https", "example.com", false); + SetAllowListEntry("https", "example.com", kDisallowSubdomains); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_FALSE(IsAllowed(https_sub_example_origin())); EXPECT_FALSE(IsAllowed(http_example_origin())); @@ -121,9 +126,8 @@ TEST_F(OriginAccessListTest, IsAccessAllowed) { // Adding an entry that matches subdomains should grant access to any // subdomains. - AddAllowListEntry( - "https", "example.com", true, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + AddAllowListEntry("https", "example.com", kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_TRUE(IsAllowed(https_sub_example_origin())); EXPECT_FALSE(IsAllowed(http_example_origin())); @@ -131,7 +135,7 @@ TEST_F(OriginAccessListTest, IsAccessAllowed) { TEST_F(OriginAccessListTest, IsAccessAllowedWildCard) { // An empty domain that matches subdomains results in matching every domain. - SetAllowListEntry("https", "", true); + SetAllowListEntry("https", "", kAllowSubdomains); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_TRUE(IsAllowed(https_google_origin())); EXPECT_FALSE(IsAllowed(http_example_origin())); @@ -139,63 +143,109 @@ TEST_F(OriginAccessListTest, IsAccessAllowedWildCard) { TEST_F(OriginAccessListTest, IsAccessAllowedWithBlockListEntry) { // The block list takes priority over the allow list. - SetAllowListEntry("https", "example.com", true); - SetBlockListEntry("https", "example.com", false); + SetAllowListEntry("https", "example.com", kAllowSubdomains); + SetBlockListEntry("https", "example.com", kDisallowSubdomains); EXPECT_FALSE(IsAllowed(https_example_origin())); EXPECT_TRUE(IsAllowed(https_sub_example_origin())); } TEST_F(OriginAccessListTest, IsAccessAllowedWildcardWithBlockListEntry) { - SetAllowListEntry("https", "", true); - AddBlockListEntry( - "https", "google.com", false, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + SetAllowListEntry("https", "", kAllowSubdomains); + AddBlockListEntry("https", "google.com", kDisallowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_FALSE(IsAllowed(https_google_origin())); } TEST_F(OriginAccessListTest, IsPriorityRespected) { - SetAllowListEntry("https", "example.com", true); + SetAllowListEntry("https", "example.com", kAllowSubdomains); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_TRUE(IsAllowed(https_sub_example_origin())); // Higher priority blocklist overrides lower priority allowlist. - AddBlockListEntry( - "https", "example.com", true, - network::mojom::CORSOriginAccessMatchPriority::kLowPriority); + AddBlockListEntry("https", "example.com", kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kLowPriority); EXPECT_FALSE(IsAllowed(https_example_origin())); EXPECT_FALSE(IsAllowed(https_sub_example_origin())); // Higher priority allowlist overrides lower priority blocklist. - AddAllowListEntry( - "https", "example.com", false, - network::mojom::CORSOriginAccessMatchPriority::kMediumPriority); + AddAllowListEntry("https", "example.com", kDisallowSubdomains, + mojom::CorsOriginAccessMatchPriority::kMediumPriority); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_FALSE(IsAllowed(https_sub_example_origin())); } TEST_F(OriginAccessListTest, IsPriorityRespectedReverse) { - AddAllowListEntry( - "https", "example.com", false, - network::mojom::CORSOriginAccessMatchPriority::kMediumPriority); + AddAllowListEntry("https", "example.com", kDisallowSubdomains, + mojom::CorsOriginAccessMatchPriority::kMediumPriority); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_FALSE(IsAllowed(https_sub_example_origin())); - AddBlockListEntry( - "https", "example.com", true, - network::mojom::CORSOriginAccessMatchPriority::kLowPriority); + AddBlockListEntry("https", "example.com", kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kLowPriority); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_FALSE(IsAllowed(https_sub_example_origin())); - AddAllowListEntry( - "https", "example.com", true, - network::mojom::CORSOriginAccessMatchPriority::kDefaultPriority); + AddAllowListEntry("https", "example.com", kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); EXPECT_TRUE(IsAllowed(https_example_origin())); EXPECT_FALSE(IsAllowed(https_sub_example_origin())); } +TEST_F(OriginAccessListTest, CreateCorsOriginAccessPatternsList) { + const url::Origin kOrigin1 = + url::Origin::Create(GURL("https://foo.google.com")); + const url::Origin kOrigin2 = + url::Origin::Create(GURL("https://bar.google.com")); + const std::string kProtocol = "https"; + const std::string kDomain1 = "foo.example.com"; + const std::string kDomain2 = "bar.example.com"; + + OriginAccessList list; + list.AddAllowListEntryForOrigin( + kOrigin1, kProtocol, kDomain1, kAllowSubdomains, + mojom::CorsOriginAccessMatchPriority::kMediumPriority); + list.AddBlockListEntryForOrigin( + kOrigin2, kProtocol, kDomain2, kDisallowSubdomains, + mojom::CorsOriginAccessMatchPriority::kDefaultPriority); + + std::vector<mojom::CorsOriginAccessPatternsPtr> patterns = + list.CreateCorsOriginAccessPatternsList(); + bool found_origin1 = false; + bool found_origin2 = false; + for (const auto& pattern : patterns) { + if (pattern->source_origin == kOrigin1.Serialize()) { + EXPECT_FALSE(found_origin1); + found_origin1 = true; + + EXPECT_EQ(0u, pattern->block_patterns.size()); + ASSERT_EQ(1u, pattern->allow_patterns.size()); + EXPECT_EQ(kProtocol, pattern->allow_patterns[0]->protocol); + EXPECT_EQ(kDomain1, pattern->allow_patterns[0]->domain); + EXPECT_EQ(kAllowSubdomains, pattern->allow_patterns[0]->mode); + EXPECT_EQ(mojom::CorsOriginAccessMatchPriority::kMediumPriority, + pattern->allow_patterns[0]->priority); + } else if (pattern->source_origin == kOrigin2.Serialize()) { + EXPECT_FALSE(found_origin2); + found_origin2 = true; + + EXPECT_EQ(0u, pattern->allow_patterns.size()); + ASSERT_EQ(1u, pattern->block_patterns.size()); + EXPECT_EQ(kProtocol, pattern->block_patterns[0]->protocol); + EXPECT_EQ(kDomain2, pattern->block_patterns[0]->domain); + EXPECT_EQ(kDisallowSubdomains, pattern->block_patterns[0]->mode); + EXPECT_EQ(mojom::CorsOriginAccessMatchPriority::kDefaultPriority, + pattern->block_patterns[0]->priority); + } else { + FAIL(); + } + } + EXPECT_TRUE(found_origin1); + EXPECT_TRUE(found_origin2); +} + } // namespace } // namespace cors diff --git a/chromium/services/network/public/cpp/cors/preflight_result.cc b/chromium/services/network/public/cpp/cors/preflight_result.cc index a26a8b7e884..c07e0fab2fa 100644 --- a/chromium/services/network/public/cpp/cors/preflight_result.cc +++ b/chromium/services/network/public/cpp/cors/preflight_result.cc @@ -93,10 +93,10 @@ std::unique_ptr<PreflightResult> PreflightResult::Create( const base::Optional<std::string>& allow_methods_header, const base::Optional<std::string>& allow_headers_header, const base::Optional<std::string>& max_age_header, - base::Optional<mojom::CORSError>* detected_error) { + base::Optional<mojom::CorsError>* detected_error) { std::unique_ptr<PreflightResult> result = base::WrapUnique(new PreflightResult(credentials_mode)); - base::Optional<mojom::CORSError> error = + base::Optional<mojom::CorsError> error = result->Parse(allow_methods_header, allow_headers_header, max_age_header); if (error) { if (detected_error) @@ -112,25 +112,25 @@ PreflightResult::PreflightResult( PreflightResult::~PreflightResult() = default; -base::Optional<CORSErrorStatus> PreflightResult::EnsureAllowedCrossOriginMethod( +base::Optional<CorsErrorStatus> PreflightResult::EnsureAllowedCrossOriginMethod( const std::string& method) const { // Request method is normalized to upper case, and comparison is performed in // case-sensitive way, that means access control header should provide an // upper case method list. const std::string normalized_method = base::ToUpperASCII(method); if (methods_.find(normalized_method) != methods_.end() || - IsCORSSafelistedMethod(normalized_method)) { + IsCorsSafelistedMethod(normalized_method)) { return base::nullopt; } if (!credentials_ && methods_.find("*") != methods_.end()) return base::nullopt; - return CORSErrorStatus(mojom::CORSError::kMethodDisallowedByPreflightResponse, + return CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, method); } -base::Optional<CORSErrorStatus> +base::Optional<CorsErrorStatus> PreflightResult::EnsureAllowedCrossOriginHeaders( const net::HttpRequestHeaders& headers, bool is_revalidating) const { @@ -140,14 +140,14 @@ PreflightResult::EnsureAllowedCrossOriginHeaders( // Forbidden headers are forbidden to be used by JavaScript, and checked // beforehand. But user-agents may add these headers internally, and it's // fine. - for (const auto& name : CORSUnsafeNotForbiddenRequestHeaderNames( + for (const auto& name : CorsUnsafeNotForbiddenRequestHeaderNames( headers.GetHeaderVector(), is_revalidating)) { // Header list check is performed in case-insensitive way. Here, we have a // parsed header list set in lower case, and search each header in lower // case. if (headers_.find(name) == headers_.end()) { - return CORSErrorStatus( - mojom::CORSError::kHeaderDisallowedByPreflightResponse, name); + return CorsErrorStatus( + mojom::CorsError::kHeaderDisallowedByPreflightResponse, name); } } return base::nullopt; @@ -175,7 +175,7 @@ bool PreflightResult::EnsureAllowedRequest( return true; } -base::Optional<mojom::CORSError> PreflightResult::Parse( +base::Optional<mojom::CorsError> PreflightResult::Parse( const base::Optional<std::string>& allow_methods_header, const base::Optional<std::string>& allow_headers_header, const base::Optional<std::string>& max_age_header) { @@ -184,11 +184,11 @@ base::Optional<mojom::CORSError> PreflightResult::Parse( // Keeps parsed method case for case-sensitive search. if (!ParseAccessControlAllowList(allow_methods_header, &methods_, false)) - return mojom::CORSError::kInvalidAllowMethodsPreflightResponse; + return mojom::CorsError::kInvalidAllowMethodsPreflightResponse; // Holds parsed headers in lower case for case-insensitive search. if (!ParseAccessControlAllowList(allow_headers_header, &headers_, true)) - return mojom::CORSError::kInvalidAllowHeadersPreflightResponse; + return mojom::CorsError::kInvalidAllowHeadersPreflightResponse; base::TimeDelta expiry_delta; if (max_age_header) { diff --git a/chromium/services/network/public/cpp/cors/preflight_result.h b/chromium/services/network/public/cpp/cors/preflight_result.h index 0df888a86b7..53c86630410 100644 --- a/chromium/services/network/public/cpp/cors/preflight_result.h +++ b/chromium/services/network/public/cpp/cors/preflight_result.h @@ -42,11 +42,11 @@ class COMPONENT_EXPORT(NETWORK_CPP) PreflightResult final { const base::Optional<std::string>& allow_methods_header, const base::Optional<std::string>& allow_headers_header, const base::Optional<std::string>& max_age_header, - base::Optional<mojom::CORSError>* detected_error); + base::Optional<mojom::CorsError>* detected_error); ~PreflightResult(); // Checks if the given |method| is allowed by the CORS-preflight response. - base::Optional<CORSErrorStatus> EnsureAllowedCrossOriginMethod( + base::Optional<CorsErrorStatus> EnsureAllowedCrossOriginMethod( const std::string& method) const; // Checks if the given all |headers| are allowed by the CORS-preflight @@ -55,7 +55,7 @@ class COMPONENT_EXPORT(NETWORK_CPP) PreflightResult final { // (https://fetch.spec.whatwg.org/#forbidden-header-name) because they may be // added by the user agent. They must be checked separately and rejected for // JavaScript-initiated requests. - base::Optional<CORSErrorStatus> EnsureAllowedCrossOriginHeaders( + base::Optional<CorsErrorStatus> EnsureAllowedCrossOriginHeaders( const net::HttpRequestHeaders& headers, bool is_revalidating) const; @@ -74,7 +74,7 @@ class COMPONENT_EXPORT(NETWORK_CPP) PreflightResult final { protected: explicit PreflightResult(const mojom::FetchCredentialsMode credentials_mode); - base::Optional<mojom::CORSError> Parse( + base::Optional<mojom::CorsError> Parse( const base::Optional<std::string>& allow_methods_header, const base::Optional<std::string>& allow_headers_header, const base::Optional<std::string>& max_age_header); diff --git a/chromium/services/network/public/cpp/cors/preflight_result_unittest.cc b/chromium/services/network/public/cpp/cors/preflight_result_unittest.cc index d1a709cb2d7..61bf603701f 100644 --- a/chromium/services/network/public/cpp/cors/preflight_result_unittest.cc +++ b/chromium/services/network/public/cpp/cors/preflight_result_unittest.cc @@ -26,7 +26,7 @@ struct TestCase { const std::string request_headers; const mojom::FetchCredentialsMode request_credentials_mode; - const base::Optional<CORSErrorStatus> expected_result; + const base::Optional<CorsErrorStatus> expected_result; }; const TestCase method_cases[] = { @@ -67,23 +67,23 @@ const TestCase method_cases[] = { // Not found in the preflight response and the safe lit. {"", "", mojom::FetchCredentialsMode::kOmit, "OPTIONS", "", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kMethodDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, "OPTIONS")}, {"", "", mojom::FetchCredentialsMode::kOmit, "PUT", "", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kMethodDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, "PUT")}, {"", "", mojom::FetchCredentialsMode::kOmit, "DELETE", "", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kMethodDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, "DELETE")}, {"GET", "", mojom::FetchCredentialsMode::kOmit, "PUT", "", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kMethodDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, "PUT")}, {"GET, POST, DELETE", "", mojom::FetchCredentialsMode::kOmit, "PUT", "", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kMethodDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, "PUT")}, // Request method is normalized to upper-case, but allowed methods is not. @@ -91,11 +91,11 @@ const TestCase method_cases[] = { // upper case. {"put", "", mojom::FetchCredentialsMode::kOmit, "PUT", "", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kMethodDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, "PUT")}, {"put", "", mojom::FetchCredentialsMode::kOmit, "put", "", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kMethodDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, "put")}, {"PUT", "", mojom::FetchCredentialsMode::kOmit, "put", "", mojom::FetchCredentialsMode::kOmit, base::nullopt}, @@ -124,7 +124,7 @@ const TestCase header_cases[] = { mojom::FetchCredentialsMode::kOmit, base::nullopt}, {"GET", "*", mojom::FetchCredentialsMode::kInclude, "GET", "xyzzy:t", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kHeaderDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kHeaderDisallowedByPreflightResponse, "xyzzy")}, // Forbidden headers can pass. @@ -134,15 +134,15 @@ const TestCase header_cases[] = { // Not found in the preflight response and the safe list. {"GET", "", mojom::FetchCredentialsMode::kOmit, "GET", "X-MY-HEADER:t", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kHeaderDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kHeaderDisallowedByPreflightResponse, "x-my-header")}, {"GET", "X-SOME-OTHER-HEADER", mojom::FetchCredentialsMode::kOmit, "GET", "X-MY-HEADER:t", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kHeaderDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kHeaderDisallowedByPreflightResponse, "x-my-header")}, {"GET", "X-MY-HEADER", mojom::FetchCredentialsMode::kOmit, "GET", "X-MY-HEADER:t\r\nY-MY-HEADER:t", mojom::FetchCredentialsMode::kOmit, - CORSErrorStatus(mojom::CORSError::kHeaderDisallowedByPreflightResponse, + CorsErrorStatus(mojom::CorsError::kHeaderDisallowedByPreflightResponse, "y-my-header")}, }; diff --git a/chromium/services/network/public/cpp/cors_error_status.typemap b/chromium/services/network/public/cpp/cors_error_status.typemap index 89e83ff3b6b..f7b4575c8d6 100644 --- a/chromium/services/network/public/cpp/cors_error_status.typemap +++ b/chromium/services/network/public/cpp/cors_error_status.typemap @@ -11,4 +11,4 @@ deps = [ public_deps = [ "//services/network/public/cpp:cpp_base", ] -type_mappings = [ "network.mojom.CORSErrorStatus=network::CORSErrorStatus" ] +type_mappings = [ "network.mojom.CorsErrorStatus=network::CorsErrorStatus" ] diff --git a/chromium/services/network/public/cpp/features.cc b/chromium/services/network/public/cpp/features.cc index bd224a18f6d..747ef9e662a 100644 --- a/chromium/services/network/public/cpp/features.cc +++ b/chromium/services/network/public/cpp/features.cc @@ -19,7 +19,7 @@ const base::Feature kNetworkService{"NetworkService", base::FEATURE_DISABLED_BY_DEFAULT}; // Out of Blink CORS -const base::Feature kOutOfBlinkCORS{"OutOfBlinkCORS", +const base::Feature kOutOfBlinkCors{"OutOfBlinkCors", base::FEATURE_DISABLED_BY_DEFAULT}; const base::Feature kReporting{"Reporting", base::FEATURE_ENABLED_BY_DEFAULT}; @@ -42,5 +42,13 @@ const base::Feature kThrottleDelayable{"ThrottleDelayable", const base::Feature kDelayRequestsOnMultiplexedConnections{ "DelayRequestsOnMultiplexedConnections", base::FEATURE_ENABLED_BY_DEFAULT}; +// When kUnthrottleRequestsAfterLongQueuingDelay is enabled, an upper bound +// is placed on how long the resource scheduler can queue any given request. +// Once a request is queued for more than the specified duration, the request +// is dispatched to the network. +const base::Feature kUnthrottleRequestsAfterLongQueuingDelay{ + "UnthrottleRequestsAfterLongQueuingDelay", + base::FEATURE_DISABLED_BY_DEFAULT}; + } // namespace features } // namespace network diff --git a/chromium/services/network/public/cpp/features.gni b/chromium/services/network/public/cpp/features.gni new file mode 100644 index 00000000000..18fef9132d7 --- /dev/null +++ b/chromium/services/network/public/cpp/features.gni @@ -0,0 +1,11 @@ +# Copyright 2018 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. + +import("//build/config/features.gni") + +declare_args() { + # Certificate transparency is not supported on iOS. + # TODO(mmenke): It's actually not supported on Android, either. + is_ct_supported = !is_ios +} diff --git a/chromium/services/network/public/cpp/features.h b/chromium/services/network/public/cpp/features.h index be4f5a8278a..c76bc52cd66 100644 --- a/chromium/services/network/public/cpp/features.h +++ b/chromium/services/network/public/cpp/features.h @@ -18,13 +18,15 @@ extern const base::Feature kNetworkErrorLogging; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kNetworkService; COMPONENT_EXPORT(NETWORK_CPP) -extern const base::Feature kOutOfBlinkCORS; +extern const base::Feature kOutOfBlinkCors; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kReporting; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kThrottleDelayable; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kDelayRequestsOnMultiplexedConnections; +COMPONENT_EXPORT(NETWORK_CPP) +extern const base::Feature kUnthrottleRequestsAfterLongQueuingDelay; } // namespace features } // namespace network diff --git a/chromium/services/network/public/cpp/host_resolver.typemap b/chromium/services/network/public/cpp/host_resolver.typemap index b71768ade43..18b14f878ad 100644 --- a/chromium/services/network/public/cpp/host_resolver.typemap +++ b/chromium/services/network/public/cpp/host_resolver.typemap @@ -3,7 +3,11 @@ # found in the LICENSE file. mojom = "//services/network/public/mojom/host_resolver.mojom" -public_headers = [ "//net/dns/host_resolver.h" ] +public_headers = [ + "//net/dns/dns_config_overrides.h", + "//net/dns/host_resolver_source.h", + "//net/dns/public/dns_query_type.h", +] traits_headers = [ "//services/network/public/cpp/host_resolver_mojom_traits.h" ] sources = [ @@ -13,6 +17,7 @@ public_deps = [ "//net", ] type_mappings = [ - "network.mojom.ResolveHostParameters.DnsQueryType=net::HostResolver::DnsQueryType", + "network.mojom.DnsConfigOverrides=net::DnsConfigOverrides", + "network.mojom.ResolveHostParameters.DnsQueryType=net::DnsQueryType", "network.mojom.ResolveHostParameters.Source=net::HostResolverSource", ] diff --git a/chromium/services/network/public/cpp/host_resolver_mojom_traits.cc b/chromium/services/network/public/cpp/host_resolver_mojom_traits.cc index 0364c3e0d83..d545770d9df 100644 --- a/chromium/services/network/public/cpp/host_resolver_mojom_traits.cc +++ b/chromium/services/network/public/cpp/host_resolver_mojom_traits.cc @@ -4,39 +4,243 @@ #include "services/network/public/cpp/host_resolver_mojom_traits.h" +#include "mojo/public/cpp/base/time_mojom_traits.h" +#include "services/network/public/cpp/ip_address_mojom_traits.h" +#include "services/network/public/cpp/ip_endpoint_mojom_traits.h" + namespace mojo { +using network::mojom::DnsConfigOverrides; +using network::mojom::DnsConfigOverridesDataView; +using network::mojom::DnsHost; +using network::mojom::DnsHostDataView; +using network::mojom::DnsHostPtr; +using network::mojom::DnsOverHttpsServer; +using network::mojom::DnsOverHttpsServerDataView; +using network::mojom::DnsOverHttpsServerPtr; using network::mojom::ResolveHostParameters; +namespace { + +DnsConfigOverrides::Tristate ToTristate(base::Optional<bool> optional) { + if (!optional) + return DnsConfigOverrides::Tristate::NO_OVERRIDE; + if (optional.value()) + return DnsConfigOverrides::Tristate::TRISTATE_TRUE; + return DnsConfigOverrides::Tristate::TRISTATE_FALSE; +} + +base::Optional<bool> FromTristate(DnsConfigOverrides::Tristate tristate) { + switch (tristate) { + case DnsConfigOverrides::Tristate::NO_OVERRIDE: + return base::nullopt; + case DnsConfigOverrides::Tristate::TRISTATE_TRUE: + return true; + case DnsConfigOverrides::Tristate::TRISTATE_FALSE: + return false; + } +} + +bool ReadHostData(mojo::ArrayDataView<DnsHostDataView> data, + base::Optional<net::DnsHosts>* out) { + if (data.is_null()) { + out->reset(); + return true; + } + + out->emplace(); + for (size_t i = 0; i < data.size(); ++i) { + DnsHostDataView host_data; + data.GetDataView(i, &host_data); + + std::string hostname; + if (!host_data.ReadHostname(&hostname)) + return false; + + net::IPAddress address; + if (!host_data.ReadAddress(&address) || !address.IsValid()) + return false; + + net::AddressFamily address_family; + if (address.IsIPv4()) { + address_family = net::ADDRESS_FAMILY_IPV4; + } else if (address.IsIPv6()) { + address_family = net::ADDRESS_FAMILY_IPV6; + } else { + return false; + } + + net::DnsHostsKey key = std::make_pair(std::move(hostname), address_family); + if (out->value().find(key) != out->value().end()) { + // Each DnsHostsKey expected to be unique. + return false; + } + out->value()[std::move(key)] = std::move(address); + } + + return true; +} + +bool ReadDnsOverHttpsServerData( + mojo::ArrayDataView<DnsOverHttpsServerDataView> data, + base::Optional<std::vector<net::DnsConfig::DnsOverHttpsServerConfig>>* + out) { + if (data.is_null()) { + out->reset(); + return true; + } + + out->emplace(); + for (size_t i = 0; i < data.size(); ++i) { + DnsOverHttpsServerDataView server_data; + data.GetDataView(i, &server_data); + + std::string server_template; + if (!server_data.ReadServerTemplate(&server_template)) + return false; + + out->value().emplace_back(std::move(server_template), + server_data.use_post()); + } + + return true; +} + +} // namespace + +// static +base::Optional<std::vector<DnsHostPtr>> +StructTraits<DnsConfigOverridesDataView, net::DnsConfigOverrides>::hosts( + const net::DnsConfigOverrides& overrides) { + if (!overrides.dns_over_https_servers) + return base::nullopt; + + std::vector<DnsHostPtr> out_hosts; + for (const net::DnsHosts::value_type& host : overrides.hosts.value()) { + out_hosts.push_back(DnsHost::New(host.first.first, host.second)); + } + + return base::make_optional(std::move(out_hosts)); +} + +// static +DnsConfigOverrides::Tristate +StructTraits<DnsConfigOverridesDataView, net::DnsConfigOverrides>:: + append_to_multi_label_name(const net::DnsConfigOverrides& overrides) { + return ToTristate(overrides.append_to_multi_label_name); +} + +// static +DnsConfigOverrides::Tristate +StructTraits<DnsConfigOverridesDataView, net::DnsConfigOverrides>:: + randomize_ports(const net::DnsConfigOverrides& overrides) { + return ToTristate(overrides.randomize_ports); +} + +// static +DnsConfigOverrides::Tristate +StructTraits<DnsConfigOverridesDataView, net::DnsConfigOverrides>::rotate( + const net::DnsConfigOverrides& overrides) { + return ToTristate(overrides.rotate); +} + +// static +DnsConfigOverrides::Tristate +StructTraits<DnsConfigOverridesDataView, net::DnsConfigOverrides>:: + use_local_ipv6(const net::DnsConfigOverrides& overrides) { + return ToTristate(overrides.use_local_ipv6); +} + +// static +base::Optional<std::vector<DnsOverHttpsServerPtr>> +StructTraits<DnsConfigOverridesDataView, net::DnsConfigOverrides>:: + dns_over_https_servers(const net::DnsConfigOverrides& overrides) { + if (!overrides.dns_over_https_servers) + return base::nullopt; + + std::vector<DnsOverHttpsServerPtr> out_servers; + for (net::DnsConfig::DnsOverHttpsServerConfig server : + overrides.dns_over_https_servers.value()) { + out_servers.push_back( + DnsOverHttpsServer::New(server.server_template, server.use_post)); + } + + return base::make_optional(std::move(out_servers)); +} + +// static +bool StructTraits<DnsConfigOverridesDataView, net::DnsConfigOverrides>::Read( + DnsConfigOverridesDataView data, + net::DnsConfigOverrides* out) { + if (!data.ReadNameservers(&out->nameservers)) + return false; + if (!data.ReadSearch(&out->search)) + return false; + + mojo::ArrayDataView<DnsHostDataView> hosts_data; + data.GetHostsDataView(&hosts_data); + if (!ReadHostData(hosts_data, &out->hosts)) + return false; + + out->append_to_multi_label_name = + FromTristate(data.append_to_multi_label_name()); + out->randomize_ports = FromTristate(data.randomize_ports()); + + if (data.ndots() < -1) + return false; + if (data.ndots() >= 0) + out->ndots = data.ndots(); + // if == -1, leave nullopt. + + if (!data.ReadTimeout(&out->timeout)) + return false; + + if (data.attempts() < -1) + return false; + if (data.attempts() >= 0) + out->attempts = data.attempts(); + // if == -1, leave nullopt. + + out->rotate = FromTristate(data.rotate()); + out->use_local_ipv6 = FromTristate(data.use_local_ipv6()); + + mojo::ArrayDataView<DnsOverHttpsServerDataView> dns_over_https_servers_data; + data.GetDnsOverHttpsServersDataView(&dns_over_https_servers_data); + if (!ReadDnsOverHttpsServerData(dns_over_https_servers_data, + &out->dns_over_https_servers)) { + return false; + } + + return true; +} + // static -ResolveHostParameters::DnsQueryType EnumTraits< - ResolveHostParameters::DnsQueryType, - net::HostResolver::DnsQueryType>::ToMojom(net::HostResolver::DnsQueryType - input) { +ResolveHostParameters::DnsQueryType +EnumTraits<ResolveHostParameters::DnsQueryType, net::DnsQueryType>::ToMojom( + net::DnsQueryType input) { switch (input) { - case net::HostResolver::DnsQueryType::UNSPECIFIED: + case net::DnsQueryType::UNSPECIFIED: return ResolveHostParameters::DnsQueryType::UNSPECIFIED; - case net::HostResolver::DnsQueryType::A: + case net::DnsQueryType::A: return ResolveHostParameters::DnsQueryType::A; - case net::HostResolver::DnsQueryType::AAAA: + case net::DnsQueryType::AAAA: return ResolveHostParameters::DnsQueryType::AAAA; } } // static -bool EnumTraits<ResolveHostParameters::DnsQueryType, - net::HostResolver::DnsQueryType>:: +bool EnumTraits<ResolveHostParameters::DnsQueryType, net::DnsQueryType>:: FromMojom(ResolveHostParameters::DnsQueryType input, - net::HostResolver::DnsQueryType* output) { + net::DnsQueryType* output) { switch (input) { case ResolveHostParameters::DnsQueryType::UNSPECIFIED: - *output = net::HostResolver::DnsQueryType::UNSPECIFIED; + *output = net::DnsQueryType::UNSPECIFIED; return true; case ResolveHostParameters::DnsQueryType::A: - *output = net::HostResolver::DnsQueryType::A; + *output = net::DnsQueryType::A; return true; case ResolveHostParameters::DnsQueryType::AAAA: - *output = net::HostResolver::DnsQueryType::AAAA; + *output = net::DnsQueryType::AAAA; return true; } } diff --git a/chromium/services/network/public/cpp/host_resolver_mojom_traits.h b/chromium/services/network/public/cpp/host_resolver_mojom_traits.h index fab0ed1eae0..3d18769a53f 100644 --- a/chromium/services/network/public/cpp/host_resolver_mojom_traits.h +++ b/chromium/services/network/public/cpp/host_resolver_mojom_traits.h @@ -5,20 +5,80 @@ #ifndef SERVICES_NETWORK_PUBLIC_CPP_HOST_RESOLVER_MOJOM_TRAITS_H_ #define SERVICES_NETWORK_PUBLIC_CPP_HOST_RESOLVER_MOJOM_TRAITS_H_ +#include <string> +#include <utility> +#include <vector> + +#include "base/optional.h" +#include "base/time/time.h" +#include "mojo/public/cpp/bindings/array_traits.h" #include "mojo/public/cpp/bindings/enum_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "net/base/address_family.h" +#include "net/base/ip_address.h" +#include "net/base/ip_endpoint.h" +#include "net/dns/dns_config_overrides.h" +#include "net/dns/dns_hosts.h" #include "net/dns/host_resolver.h" +#include "net/dns/public/dns_query_type.h" #include "services/network/public/mojom/host_resolver.mojom.h" namespace mojo { template <> +struct StructTraits<network::mojom::DnsConfigOverridesDataView, + net::DnsConfigOverrides> { + static const base::Optional<std::vector<net::IPEndPoint>>& nameservers( + const net::DnsConfigOverrides& overrides) { + return overrides.nameservers; + } + + static const base::Optional<std::vector<std::string>>& search( + const net::DnsConfigOverrides& overrides) { + return overrides.search; + } + + static base::Optional<std::vector<network::mojom::DnsHostPtr>> hosts( + const net::DnsConfigOverrides& overrides); + + static network::mojom::DnsConfigOverrides::Tristate + append_to_multi_label_name(const net::DnsConfigOverrides& overrides); + static network::mojom::DnsConfigOverrides::Tristate randomize_ports( + const net::DnsConfigOverrides& overrides); + + static int ndots(const net::DnsConfigOverrides& overrides) { + return overrides.ndots.value_or(-1); + } + + static const base::Optional<base::TimeDelta>& timeout( + const net::DnsConfigOverrides& overrides) { + return overrides.timeout; + } + + static int attempts(const net::DnsConfigOverrides& overrides) { + return overrides.attempts.value_or(-1); + } + + static network::mojom::DnsConfigOverrides::Tristate rotate( + const net::DnsConfigOverrides& overrides); + static network::mojom::DnsConfigOverrides::Tristate use_local_ipv6( + const net::DnsConfigOverrides& overrides); + + static base::Optional<std::vector<network::mojom::DnsOverHttpsServerPtr>> + dns_over_https_servers(const net::DnsConfigOverrides& overrides); + + static bool Read(network::mojom::DnsConfigOverridesDataView data, + net::DnsConfigOverrides* out); +}; + +template <> struct EnumTraits<network::mojom::ResolveHostParameters::DnsQueryType, - net::HostResolver::DnsQueryType> { + net::DnsQueryType> { static network::mojom::ResolveHostParameters::DnsQueryType ToMojom( - net::HostResolver::DnsQueryType input); + net::DnsQueryType input); static bool FromMojom( network::mojom::ResolveHostParameters::DnsQueryType input, - net::HostResolver::DnsQueryType* output); + net::DnsQueryType* output); }; template <> diff --git a/chromium/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc new file mode 100644 index 00000000000..8969473cd03 --- /dev/null +++ b/chromium/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc @@ -0,0 +1,89 @@ +// Copyright 2018 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 "services/network/public/cpp/host_resolver_mojom_traits.h" + +#include "mojo/public/cpp/base/time_mojom_traits.h" +#include "mojo/public/cpp/test_support/test_utils.h" +#include "services/network/public/cpp/ip_address_mojom_traits.h" +#include "services/network/public/cpp/ip_endpoint_mojom_traits.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { +namespace { + +TEST(HostResolverMojomTraitsTest, DnsConfigOverridesRoundtrip_Empty) { + net::DnsConfigOverrides original; + + net::DnsConfigOverrides deserialized; + EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::DnsConfigOverrides>( + &original, &deserialized)); + + EXPECT_EQ(original, deserialized); +} + +TEST(HostResolverMojomTraitsTest, DnsConfigOverridesRoundtrip_FullySpecified) { + net::DnsConfigOverrides original; + original.nameservers.emplace( + {net::IPEndPoint(net::IPAddress(1, 2, 3, 4), 80)}); + original.search.emplace({std::string("str")}); + original.hosts = net::DnsHosts( + {std::make_pair(net::DnsHostsKey("host1", net::ADDRESS_FAMILY_IPV4), + net::IPAddress(2, 3, 4, 5)), + std::make_pair(net::DnsHostsKey("host2", net::ADDRESS_FAMILY_IPV4), + net::IPAddress(2, 3, 4, 5))}); + original.append_to_multi_label_name = true; + original.randomize_ports = false; + original.ndots = 2; + original.timeout = base::TimeDelta::FromHours(4); + original.attempts = 1; + original.rotate = true; + original.use_local_ipv6 = false; + original.dns_over_https_servers.emplace( + {net::DnsConfig::DnsOverHttpsServerConfig("example.com", false)}); + + net::DnsConfigOverrides deserialized; + EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::DnsConfigOverrides>( + &original, &deserialized)); + + EXPECT_EQ(original, deserialized); +} + +TEST(HostResolverMojomTraitsTest, DnsConfigOverrides_BadInt) { + mojom::DnsConfigOverridesPtr overrides = mojom::DnsConfigOverrides::New(); + overrides->ndots = -10; + + std::vector<uint8_t> serialized = + mojom::DnsConfigOverrides::Serialize(&overrides); + + net::DnsConfigOverrides deserialized; + EXPECT_FALSE( + mojom::DnsConfigOverrides::Deserialize(serialized, &deserialized)); +} + +TEST(HostResolverMojomTraitsTest, DnsConfigOverrides_NonUniqueHostKeys) { + mojom::DnsConfigOverridesPtr overrides = mojom::DnsConfigOverrides::New(); + overrides->hosts.emplace(); + + // Create two different entries that share the key ("host", IPV4). + mojom::DnsHostPtr host_entry1 = mojom::DnsHost::New(); + host_entry1->hostname = "host"; + host_entry1->address = net::IPAddress(1, 1, 1, 1); + overrides->hosts.value().push_back(std::move(host_entry1)); + + mojom::DnsHostPtr host_entry2 = mojom::DnsHost::New(); + host_entry2->hostname = "host"; + host_entry2->address = net::IPAddress(2, 2, 2, 2); + overrides->hosts.value().push_back(std::move(host_entry2)); + + std::vector<uint8_t> serialized = + mojom::DnsConfigOverrides::Serialize(&overrides); + + net::DnsConfigOverrides deserialized; + EXPECT_FALSE( + mojom::DnsConfigOverrides::Deserialize(serialized, &deserialized)); +} + +} // namespace +} // namespace network diff --git a/chromium/services/network/public/cpp/ip_address.typemap b/chromium/services/network/public/cpp/ip_address.typemap new file mode 100644 index 00000000000..97c933424b1 --- /dev/null +++ b/chromium/services/network/public/cpp/ip_address.typemap @@ -0,0 +1,14 @@ +# Copyright 2017 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. + +mojom = "//services/network/public/mojom/ip_address.mojom" +public_headers = [ "//net/base/ip_address.h" ] +traits_headers = [ "//services/network/public/cpp/ip_address_mojom_traits.h" ] +sources = [ + "//services/network/public/cpp/ip_address_mojom_traits.cc", +] +type_mappings = [ "network.mojom.IPAddress=net::IPAddress" ] +public_deps = [ + "//net", +] diff --git a/chromium/services/network/public/cpp/ip_address_mojom_traits.cc b/chromium/services/network/public/cpp/ip_address_mojom_traits.cc new file mode 100644 index 00000000000..99b3b32c17e --- /dev/null +++ b/chromium/services/network/public/cpp/ip_address_mojom_traits.cc @@ -0,0 +1,26 @@ +// Copyright 2017 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 "services/network/public/cpp/ip_address_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits<network::mojom::IPAddressDataView, net::IPAddress>::Read( + network::mojom::IPAddressDataView data, + net::IPAddress* out) { + std::vector<uint8_t> bytes; + if (!data.ReadAddressBytes(&bytes)) + return false; + + if (bytes.size() && bytes.size() != net::IPAddress::kIPv4AddressSize && + bytes.size() != net::IPAddress::kIPv6AddressSize) { + return false; + } + + *out = net::IPAddress(bytes.data(), bytes.size()); + return true; +} + +} // namespace mojo diff --git a/chromium/services/network/public/cpp/ip_address_mojom_traits.h b/chromium/services/network/public/cpp/ip_address_mojom_traits.h new file mode 100644 index 00000000000..3d9abc15b67 --- /dev/null +++ b/chromium/services/network/public/cpp/ip_address_mojom_traits.h @@ -0,0 +1,26 @@ +// Copyright 2017 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. + +#ifndef SERVICES_NETWORK_PUBLIC_CPP_IP_ADDRESS_MOJOM_TRAITS_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_IP_ADDRESS_MOJOM_TRAITS_H_ + +#include "base/containers/span.h" +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "net/base/ip_address.h" +#include "services/network/public/mojom/ip_address.mojom.h" + +namespace mojo { +template <> +struct StructTraits<network::mojom::IPAddressDataView, net::IPAddress> { + static base::span<const uint8_t> address_bytes( + const net::IPAddress& ip_address) { + return ip_address.bytes(); + } + + static bool Read(network::mojom::IPAddressDataView obj, net::IPAddress* out); +}; + +} // namespace mojo + +#endif // SERVICES_NETWORK_PUBLIC_CPP_IP_ADDRESS_MOJOM_TRAITS_H_ diff --git a/chromium/services/network/public/cpp/ip_address_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/ip_address_mojom_traits_unittest.cc new file mode 100644 index 00000000000..c3485d4b4cf --- /dev/null +++ b/chromium/services/network/public/cpp/ip_address_mojom_traits_unittest.cc @@ -0,0 +1,46 @@ +// Copyright 2018 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 "services/network/public/cpp/ip_address_mojom_traits.h" + +#include "mojo/public/cpp/test_support/test_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { +namespace { + +TEST(IPAddressStructTraitsTest, Ipv4) { + IPAddress original(1, 2, 3, 4); + + IPAddress deserialized; + EXPECT_TRUE(mojo::test::SerializeAndDeserialize<network::mojom::IPAddress>( + &original, &deserialized)); + + EXPECT_EQ(original, deserialized); +} + +TEST(IPAddressStructTraitsTest, Ipv6) { + IPAddress original(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); + + IPAddress deserialized; + EXPECT_TRUE(mojo::test::SerializeAndDeserialize<network::mojom::IPAddress>( + &original, &deserialized)); + + EXPECT_EQ(original, deserialized); +} + +// Serialization/deserialization not expected to work for invalid addresses, +// e.g. an address with 5 octets. +TEST(IPAddressStructTraitsTest, InvalidAddress) { + uint8_t bad_address_bytes[] = {1, 2, 3, 4, 5}; + IPAddress original(bad_address_bytes); + ASSERT_FALSE(original.IsValid()); + + IPAddress deserialized; + EXPECT_FALSE(mojo::test::SerializeAndDeserialize<network::mojom::IPAddress>( + &original, &deserialized)); +} + +} // namespace +} // namespace net diff --git a/chromium/services/network/public/cpp/ip_endpoint.typemap b/chromium/services/network/public/cpp/ip_endpoint.typemap new file mode 100644 index 00000000000..3612774fe22 --- /dev/null +++ b/chromium/services/network/public/cpp/ip_endpoint.typemap @@ -0,0 +1,14 @@ +# Copyright 2017 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. + +mojom = "//services/network/public/mojom/ip_endpoint.mojom" +public_headers = [ "//net/base/ip_endpoint.h" ] +traits_headers = [ "//services/network/public/cpp/ip_endpoint_mojom_traits.h" ] +sources = [ + "//services/network/public/cpp/ip_endpoint_mojom_traits.cc", +] +type_mappings = [ "network.mojom.IPEndPoint=net::IPEndPoint" ] +public_deps = [ + "//net", +] diff --git a/chromium/services/network/public/cpp/ip_endpoint_mojom_traits.cc b/chromium/services/network/public/cpp/ip_endpoint_mojom_traits.cc new file mode 100644 index 00000000000..f95e0635159 --- /dev/null +++ b/chromium/services/network/public/cpp/ip_endpoint_mojom_traits.cc @@ -0,0 +1,23 @@ +// Copyright 2017 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 "services/network/public/cpp/ip_endpoint_mojom_traits.h" + +#include "services/network/public/cpp/ip_address_mojom_traits.h" + +namespace mojo { + +// static +bool StructTraits<network::mojom::IPEndPointDataView, net::IPEndPoint>::Read( + network::mojom::IPEndPointDataView data, + net::IPEndPoint* out) { + net::IPAddress address; + if (!data.ReadAddress(&address)) + return false; + + *out = net::IPEndPoint(address, data.port()); + return true; +} + +} // namespace mojo diff --git a/chromium/services/network/public/cpp/ip_endpoint_mojom_traits.h b/chromium/services/network/public/cpp/ip_endpoint_mojom_traits.h new file mode 100644 index 00000000000..83c47cc3f7d --- /dev/null +++ b/chromium/services/network/public/cpp/ip_endpoint_mojom_traits.h @@ -0,0 +1,26 @@ +// Copyright 2017 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. + +#ifndef SERVICES_NETWORK_PUBLIC_CPP_IP_ENDPOINT_MOJOM_TRAITS_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_IP_ENDPOINT_MOJOM_TRAITS_H_ + +#include "mojo/public/cpp/bindings/struct_traits.h" +#include "net/base/ip_endpoint.h" +#include "services/network/public/mojom/ip_endpoint.mojom.h" + +namespace mojo { +template <> +struct StructTraits<network::mojom::IPEndPointDataView, net::IPEndPoint> { + static const net::IPAddress& address(const net::IPEndPoint& obj) { + return obj.address(); + } + static uint16_t port(const net::IPEndPoint& obj) { return obj.port(); } + + static bool Read(network::mojom::IPEndPointDataView obj, + net::IPEndPoint* out); +}; + +} // namespace mojo + +#endif // SERVICES_NETWORK_PUBLIC_CPP_IP_ENDPOINT_MOJOM_TRAITS_H_ diff --git a/chromium/services/network/public/cpp/net_ipc_param_traits.cc b/chromium/services/network/public/cpp/net_ipc_param_traits.cc index 445809f000a..bdfd131c7b8 100644 --- a/chromium/services/network/public/cpp/net_ipc_param_traits.cc +++ b/chromium/services/network/public/cpp/net_ipc_param_traits.cc @@ -324,8 +324,8 @@ void ParamTraits<net::SSLInfo>::Write(base::Pickle* m, const param_type& p) { WriteParam(m, p.cert); WriteParam(m, p.unverified_cert); WriteParam(m, p.cert_status); - WriteParam(m, p.security_bits); WriteParam(m, p.key_exchange_group); + WriteParam(m, p.peer_signature_algorithm); WriteParam(m, p.connection_status); WriteParam(m, p.is_issued_by_known_root); WriteParam(m, p.pkp_bypassed); @@ -351,8 +351,8 @@ bool ParamTraits<net::SSLInfo>::Read(const base::Pickle* m, return ReadParam(m, iter, &r->cert) && ReadParam(m, iter, &r->unverified_cert) && ReadParam(m, iter, &r->cert_status) && - ReadParam(m, iter, &r->security_bits) && ReadParam(m, iter, &r->key_exchange_group) && + ReadParam(m, iter, &r->peer_signature_algorithm) && ReadParam(m, iter, &r->connection_status) && ReadParam(m, iter, &r->is_issued_by_known_root) && ReadParam(m, iter, &r->pkp_bypassed) && @@ -566,11 +566,6 @@ void ParamTraits<url::Origin>::Log(const url::Origin& p, std::string* l) { #include "ipc/struct_constructor_macros.h" #include "net_ipc_param_traits.h" -// Generate destructors. -#undef SERVICES_NETWORK_PUBLIC_CPP_NET_IPC_PARAM_TRAITS_H_ -#include "ipc/struct_destructor_macros.h" -#include "net_ipc_param_traits.h" - // Generate param traits write methods. #undef SERVICES_NETWORK_PUBLIC_CPP_NET_IPC_PARAM_TRAITS_H_ #include "ipc/param_traits_write_macros.h" diff --git a/chromium/services/network/public/cpp/network_connection_tracker.h b/chromium/services/network/public/cpp/network_connection_tracker.h index a370627fc63..be9fd29dc85 100644 --- a/chromium/services/network/public/cpp/network_connection_tracker.h +++ b/chromium/services/network/public/cpp/network_connection_tracker.h @@ -26,6 +26,10 @@ namespace network { class NetworkConnectionTracker; using NetworkConnectionTrackerGetter = base::RepeatingCallback<NetworkConnectionTracker*()>; +// Defines the type of a callback that can be used to asynchronously get a +// NetworkConnectionTracker instance. +using NetworkConnectionTrackerAsyncGetter = base::RepeatingCallback<void( + base::OnceCallback<void(NetworkConnectionTracker*)>)>; // This class subscribes to network change events from // network::mojom::NetworkChangeManager and propogates these notifications to @@ -58,11 +62,11 @@ class COMPONENT_EXPORT(NETWORK_CPP) NetworkConnectionTracker ~NetworkConnectionTracker() override; // If connection type can be retrieved synchronously, returns true and |type| - // will contain the current connection type; Otherwise, returns false and - // does not modify |type|, in which case, |callback| will be called on the - // calling thread when connection type is ready. This method is thread safe. - // Please also refer to net::NetworkChangeNotifier::GetConnectionType() for - // documentation. + // will contain the current connection type, and |callback| will not be + // called; Otherwise, returns false and does not modify |type|, in which + // case, |callback| will be called on the calling thread when connection type + // is ready. This method is thread safe. Please also refer to + // net::NetworkChangeNotifier::GetConnectionType() for documentation. virtual bool GetConnectionType(network::mojom::ConnectionType* type, ConnectionTypeCallback callback); diff --git a/chromium/services/network/public/cpp/network_ipc_param_traits.cc b/chromium/services/network/public/cpp/network_ipc_param_traits.cc index fbbdb8296d8..b11b82240a3 100644 --- a/chromium/services/network/public/cpp/network_ipc_param_traits.cc +++ b/chromium/services/network/public/cpp/network_ipc_param_traits.cc @@ -265,11 +265,6 @@ void ParamTraits<scoped_refptr<network::ResourceRequestBody>>::Log( #include "ipc/struct_constructor_macros.h" #include "network_ipc_param_traits.h" -// Generate destructors. -#undef SERVICES_NETWORK_PUBLIC_CPP_NETWORK_IPC_PARAM_TRAITS_H_ -#include "ipc/struct_destructor_macros.h" -#include "network_ipc_param_traits.h" - // Generate param traits write methods. #undef SERVICES_NETWORK_PUBLIC_CPP_NETWORK_IPC_PARAM_TRAITS_H_ #include "ipc/param_traits_write_macros.h" diff --git a/chromium/services/network/public/cpp/network_ipc_param_traits.h b/chromium/services/network/public/cpp/network_ipc_param_traits.h index b1f0d480e6c..053601c39be 100644 --- a/chromium/services/network/public/cpp/network_ipc_param_traits.h +++ b/chromium/services/network/public/cpp/network_ipc_param_traits.h @@ -89,8 +89,8 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) #endif // INTERNAL_SERVICES_NETWORK_PUBLIC_CPP_NETWORK_IPC_PARAM_TRAITS_H_ -IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::CORSError, - network::mojom::CORSError::kMaxValue) +IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::CorsError, + network::mojom::CorsError::kMaxValue) IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::FetchCredentialsMode, network::mojom::FetchCredentialsMode::kMaxValue) @@ -104,10 +104,10 @@ IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::FetchRequestMode, IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::RequestContextFrameType, network::mojom::RequestContextFrameType::kMaxValue) -IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::CORSPreflightPolicy, - network::mojom::CORSPreflightPolicy::kMaxValue) +IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::CorsPreflightPolicy, + network::mojom::CorsPreflightPolicy::kMaxValue) -IPC_STRUCT_TRAITS_BEGIN(network::CORSErrorStatus) +IPC_STRUCT_TRAITS_BEGIN(network::CorsErrorStatus) IPC_STRUCT_TRAITS_MEMBER(cors_error) IPC_STRUCT_TRAITS_MEMBER(failed_parameter) IPC_STRUCT_TRAITS_END() @@ -146,7 +146,8 @@ IPC_STRUCT_TRAITS_BEGIN(network::ResourceRequest) IPC_STRUCT_TRAITS_MEMBER(referrer_policy) IPC_STRUCT_TRAITS_MEMBER(is_prerendering) IPC_STRUCT_TRAITS_MEMBER(headers) - IPC_STRUCT_TRAITS_MEMBER(requested_with) + IPC_STRUCT_TRAITS_MEMBER(requested_with_header) + IPC_STRUCT_TRAITS_MEMBER(client_data_header) IPC_STRUCT_TRAITS_MEMBER(load_flags) IPC_STRUCT_TRAITS_MEMBER(allow_credentials) IPC_STRUCT_TRAITS_MEMBER(plugin_child_id) @@ -182,6 +183,7 @@ IPC_STRUCT_TRAITS_BEGIN(network::ResourceRequest) IPC_STRUCT_TRAITS_MEMBER(throttling_profile_id) IPC_STRUCT_TRAITS_MEMBER(custom_proxy_pre_cache_headers) IPC_STRUCT_TRAITS_MEMBER(custom_proxy_post_cache_headers) + IPC_STRUCT_TRAITS_MEMBER(custom_proxy_use_alternate_proxy_list) IPC_STRUCT_TRAITS_MEMBER(fetch_window_id) IPC_STRUCT_TRAITS_END() @@ -225,6 +227,7 @@ IPC_STRUCT_TRAITS_BEGIN(network::ResourceResponseInfo) IPC_STRUCT_TRAITS_MEMBER(async_revalidation_requested) IPC_STRUCT_TRAITS_MEMBER(did_mime_sniff) IPC_STRUCT_TRAITS_MEMBER(is_signed_exchange_inner_response) + IPC_STRUCT_TRAITS_MEMBER(is_legacy_tls_version) IPC_STRUCT_TRAITS_END() IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::FetchResponseType, diff --git a/chromium/services/network/public/cpp/network_param.typemap b/chromium/services/network/public/cpp/network_param.typemap index e8a71b2f126..32dc46c7826 100644 --- a/chromium/services/network/public/cpp/network_param.typemap +++ b/chromium/services/network/public/cpp/network_param.typemap @@ -4,6 +4,7 @@ mojom = "//services/network/public/mojom/network_param.mojom" public_headers = [ + "//base/memory/memory_pressure_listener.h", "//net/base/auth.h", "//net/base/host_port_pair.h", "//net/cert/cert_verify_result.h", @@ -40,6 +41,7 @@ type_mappings = [ "network.mojom.HostPortPair=net::HostPortPair", "network.mojom.HttpResponseHeaders=scoped_refptr<net::HttpResponseHeaders>[nullable_is_same_type]", "network.mojom.HttpVersion=net::HttpVersion", + "network.mojom.MemoryPressureLevel=base::MemoryPressureListener::MemoryPressureLevel", "network.mojom.SSLCertRequestInfo=scoped_refptr<net::SSLCertRequestInfo>[nullable_is_same_type]", "network.mojom.SSLInfo=net::SSLInfo", "network.mojom.X509Certificate=scoped_refptr<net::X509Certificate>[nullable_is_same_type]", diff --git a/chromium/services/network/public/cpp/network_param_mojom_traits.cc b/chromium/services/network/public/cpp/network_param_mojom_traits.cc index 7b51f550e09..47ea7922d90 100644 --- a/chromium/services/network/public/cpp/network_param_mojom_traits.cc +++ b/chromium/services/network/public/cpp/network_param_mojom_traits.cc @@ -62,4 +62,41 @@ bool EnumTraits<network::mojom::ApplicationState, } #endif +network::mojom::MemoryPressureLevel +EnumTraits<network::mojom::MemoryPressureLevel, + base::MemoryPressureListener::MemoryPressureLevel>:: + ToMojom(base::MemoryPressureListener::MemoryPressureLevel input) { + switch (input) { + case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE: + return network::mojom::MemoryPressureLevel::NONE; + case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE: + return network::mojom::MemoryPressureLevel::MODERATE; + case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL: + return network::mojom::MemoryPressureLevel::CRITICAL; + } + NOTREACHED(); + return static_cast<network::mojom::MemoryPressureLevel>(input); +} + +bool EnumTraits<network::mojom::MemoryPressureLevel, + base::MemoryPressureListener::MemoryPressureLevel>:: + FromMojom(network::mojom::MemoryPressureLevel input, + base::MemoryPressureListener::MemoryPressureLevel* output) { + switch (input) { + case network::mojom::MemoryPressureLevel::NONE: + *output = base::MemoryPressureListener::MemoryPressureLevel:: + MEMORY_PRESSURE_LEVEL_NONE; + return true; + case network::mojom::MemoryPressureLevel::MODERATE: + *output = base::MemoryPressureListener::MemoryPressureLevel:: + MEMORY_PRESSURE_LEVEL_MODERATE; + return true; + case network::mojom::MemoryPressureLevel::CRITICAL: + *output = base::MemoryPressureListener::MemoryPressureLevel:: + MEMORY_PRESSURE_LEVEL_CRITICAL; + return true; + } + return false; +} + } // namespace mojo diff --git a/chromium/services/network/public/cpp/network_param_mojom_traits.h b/chromium/services/network/public/cpp/network_param_mojom_traits.h index 2840fe9743a..dbf9dab4ad8 100644 --- a/chromium/services/network/public/cpp/network_param_mojom_traits.h +++ b/chromium/services/network/public/cpp/network_param_mojom_traits.h @@ -5,6 +5,7 @@ #ifndef SERVICES_NETWORK_PUBLIC_CPP_NETWORK_PARAM_MOJOM_TRAITS_H_ #define SERVICES_NETWORK_PUBLIC_CPP_NETWORK_PARAM_MOJOM_TRAITS_H_ +#include "base/memory/memory_pressure_listener.h" #include "build/build_config.h" #include "mojo/public/cpp/bindings/struct_traits.h" #include "net/http/http_version.h" @@ -41,6 +42,16 @@ struct EnumTraits<network::mojom::ApplicationState, }; #endif +template <> +struct EnumTraits<network::mojom::MemoryPressureLevel, + base::MemoryPressureListener::MemoryPressureLevel> { + static network::mojom::MemoryPressureLevel ToMojom( + base::MemoryPressureListener::MemoryPressureLevel input); + static bool FromMojom( + network::mojom::MemoryPressureLevel input, + base::MemoryPressureListener::MemoryPressureLevel* output); +}; + } // namespace mojo #endif // SERVICES_NETWORK_PUBLIC_CPP_NETWORK_PARAM_MOJOM_TRAITS_H_ diff --git a/chromium/services/network/public/cpp/network_switches.cc b/chromium/services/network/public/cpp/network_switches.cc index a06bd76f3c3..8b2d4ed3b39 100644 --- a/chromium/services/network/public/cpp/network_switches.cc +++ b/chromium/services/network/public/cpp/network_switches.cc @@ -15,6 +15,12 @@ const char kForceEffectiveConnectionType[] = "force-effective-connection-type"; // These mappings only apply to the host resolver. const char kHostResolverRules[] = "host-resolver-rules"; +// Causes net::URLFetchers to ignore requests for SSL client certificates, +// causing them to attempt an unauthenticated SSL/TLS session. This is intended +// for use when testing various service URLs (eg: kPromoServerURL, kSbURLPrefix, +// kSyncServiceURL, etc). +const char kIgnoreUrlFetcherCertRequests[] = "ignore-urlfetcher-cert-requests"; + // A set of public key hashes for which to ignore certificate-related errors. // // If the certificate chain presented by the server does not validate, and one diff --git a/chromium/services/network/public/cpp/network_switches.h b/chromium/services/network/public/cpp/network_switches.h index ec23a353fa2..83e78d0f3f7 100644 --- a/chromium/services/network/public/cpp/network_switches.h +++ b/chromium/services/network/public/cpp/network_switches.h @@ -16,6 +16,7 @@ COMPONENT_EXPORT(NETWORK_CPP) extern const char kHostResolverRules[]; COMPONENT_EXPORT(NETWORK_CPP) extern const char kIgnoreCertificateErrorsSPKIList[]; +COMPONENT_EXPORT(NETWORK_CPP) extern const char kIgnoreUrlFetcherCertRequests[]; COMPONENT_EXPORT(NETWORK_CPP) extern const char kLogNetLog[]; COMPONENT_EXPORT(NETWORK_CPP) extern const char kSSLKeyLogFile[]; COMPONENT_EXPORT(NETWORK_CPP) extern const char kNoReferrers[]; diff --git a/chromium/services/network/public/cpp/p2p_param_traits.cc b/chromium/services/network/public/cpp/p2p_param_traits.cc index 2028a28526d..37c6c5ca120 100644 --- a/chromium/services/network/public/cpp/p2p_param_traits.cc +++ b/chromium/services/network/public/cpp/p2p_param_traits.cc @@ -67,11 +67,6 @@ void ParamTraits<net::IPAddress>::Log(const param_type& p, std::string* l) { #include "ipc/struct_constructor_macros.h" #include "p2p_param_traits.h" -// Generate destructors. -#undef SERVICES_NETWORK_PUBLIC_CPP_P2P_PARAM_TRAITS_H_ -#include "ipc/struct_destructor_macros.h" -#include "p2p_param_traits.h" - // Generate param traits write methods. #undef SERVICES_NETWORK_PUBLIC_CPP_P2P_PARAM_TRAITS_H_ #include "ipc/param_traits_write_macros.h" diff --git a/chromium/services/network/public/cpp/proxy_config_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/proxy_config_mojom_traits_unittest.cc index b638e5a42e7..f64ff8e4ca8 100644 --- a/chromium/services/network/public/cpp/proxy_config_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/proxy_config_mojom_traits_unittest.cc @@ -85,8 +85,15 @@ TEST(ProxyConfigTraitsTest, BypassRules) { // These should cover every one of the rule types documented in // proxy_bypass_rules.h. const char* kTestCases[] = { - ".foo.com", "*foo1.com:80, foo2.com", "*", - "<local>", "http://1.2.3.4:99", "1.2.3.4/16", + ".foo.com", + "*foo1.com:80, foo2.com", + "*", + "<local>", + "http://1.2.3.4:99", + "1.2.3.4/16", + "fe80::/10", + "<-loopback>", + "[e1f3:dEaD::3]", }; for (const char* test_case : kTestCases) { diff --git a/chromium/services/network/public/cpp/resource_request.h b/chromium/services/network/public/cpp/resource_request.h index 8481b5d6faa..fbadf62a939 100644 --- a/chromium/services/network/public/cpp/resource_request.h +++ b/chromium/services/network/public/cpp/resource_request.h @@ -75,7 +75,14 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { // Network Service), so the value is stored here (rather than in |headers|) // and later populated in the headers after CORS check. // TODO(toyoshim): Remove it once PPAPI is deprecated. - std::string requested_with; + std::string requested_with_header; + + // 'X-Client-Data' header value. See comments for |requested_with_header| + // above, too. + // TODO(toyoshim): Consider to rename this to have a chrome specific prefix + // such as 'Chrome-' instead of 'X-', and to add 'Chrome-' prefixed header + // names into the forbidden header name list. + std::string client_data_header; // net::URLRequest load flags (0 by default). int load_flags = 0; @@ -116,8 +123,8 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { bool is_external_request = false; // A policy to decide if CORS-preflight fetch should be performed. - mojom::CORSPreflightPolicy cors_preflight_policy = - mojom::CORSPreflightPolicy::kConsiderPreflight; + mojom::CorsPreflightPolicy cors_preflight_policy = + mojom::CorsPreflightPolicy::kConsiderPreflight; // Indicates which frame (or worker context) the request is being loaded into. // -1 corresponds to kInvalidServiceWorkerProviderId. @@ -138,9 +145,9 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { // https://fetch.spec.whatwg.org/#concept-request-mode // Used mainly by CORS handling (out-of-blink CORS), CORB, Service Worker. // CORS handling needs a proper origin (including a unique opaque origin). - // Hence a request with kSameOrigin, kCORS, or kCORSWithForcedPreflight should + // Hence a request with kSameOrigin, kCors, or kCorsWithForcedPreflight should // have a non-null request_initiator. - mojom::FetchRequestMode fetch_request_mode = mojom::FetchRequestMode::kNoCORS; + mojom::FetchRequestMode fetch_request_mode = mojom::FetchRequestMode::kNoCors; // https://fetch.spec.whatwg.org/#concept-request-credentials-mode // Used mainly by CORS handling (out-of-blink CORS), Service Worker. @@ -151,7 +158,7 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { // https://fetch.spec.whatwg.org/#concept-request-redirect-mode // Used mainly by CORS handling (out-of-blink CORS), Service Worker. - // This member must be kFollow as long as |fetch_request_mode| is kNoCORS. + // This member must be kFollow as long as |fetch_request_mode| is kNoCors. mojom::FetchRedirectMode fetch_redirect_mode = mojom::FetchRedirectMode::kFollow; @@ -238,6 +245,9 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { net::HttpRequestHeaders custom_proxy_pre_cache_headers; net::HttpRequestHeaders custom_proxy_post_cache_headers; + // Whether to use the alternate proxies set in the custom proxy config. + bool custom_proxy_use_alternate_proxy_list = false; + // See https://fetch.spec.whatwg.org/#concept-request-window // // This is an opaque id of the original requestor of the resource, which might diff --git a/chromium/services/network/public/cpp/resource_response.cc b/chromium/services/network/public/cpp/resource_response.cc index d3c8d8ba7fa..f7b9ea57781 100644 --- a/chromium/services/network/public/cpp/resource_response.cc +++ b/chromium/services/network/public/cpp/resource_response.cc @@ -39,6 +39,7 @@ scoped_refptr<ResourceResponse> ResourceResponse::DeepCopy() const { new_response->head.alpn_negotiated_protocol = head.alpn_negotiated_protocol; new_response->head.socket_address = head.socket_address; new_response->head.was_fetched_via_cache = head.was_fetched_via_cache; + new_response->head.proxy_server = head.proxy_server; new_response->head.was_fetched_via_service_worker = head.was_fetched_via_service_worker; new_response->head.was_fallback_required_by_service_worker = @@ -64,6 +65,7 @@ scoped_refptr<ResourceResponse> ResourceResponse::DeepCopy() const { new_response->head.is_signed_exchange_inner_response = head.is_signed_exchange_inner_response; new_response->head.intercepted_by_plugin = head.intercepted_by_plugin; + new_response->head.is_legacy_tls_version = head.is_legacy_tls_version; return new_response; } diff --git a/chromium/services/network/public/cpp/resource_response_info.cc b/chromium/services/network/public/cpp/resource_response_info.cc index 04cc3b23ee7..9cfd458e0fc 100644 --- a/chromium/services/network/public/cpp/resource_response_info.cc +++ b/chromium/services/network/public/cpp/resource_response_info.cc @@ -21,7 +21,6 @@ ResourceResponseInfo::ResourceResponseInfo() was_alpn_negotiated(false), was_alternate_protocol_available(false), connection_info(net::HttpResponseInfo::CONNECTION_INFO_UNKNOWN), - was_fetched_via_proxy(false), was_fetched_via_service_worker(false), was_fallback_required_by_service_worker(false), response_type(mojom::FetchResponseType::kDefault), diff --git a/chromium/services/network/public/cpp/resource_response_info.h b/chromium/services/network/public/cpp/resource_response_info.h index 5a47c03bac7..effedca0ebb 100644 --- a/chromium/services/network/public/cpp/resource_response_info.h +++ b/chromium/services/network/public/cpp/resource_response_info.h @@ -112,9 +112,6 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceResponseInfo { // True if the response came from cache. bool was_fetched_via_cache = false; - // True if the response was delivered through a proxy. - bool was_fetched_via_proxy; - // The proxy server used for this request, if any. net::ProxyServer proxy_server; @@ -195,6 +192,10 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceResponseInfo { // True if the response was intercepted by a plugin. bool intercepted_by_plugin = false; + // True if the response was sent over TLS 1.0 or 1.1, which are deprecated and + // will be removed in the future. + bool is_legacy_tls_version = false; + // NOTE: When adding or changing fields here, also update // ResourceResponse::DeepCopy in resource_response.cc. }; diff --git a/chromium/services/network/public/cpp/simple_url_loader.cc b/chromium/services/network/public/cpp/simple_url_loader.cc index 10a891080c3..04bc90ebd0b 100644 --- a/chromium/services/network/public/cpp/simple_url_loader.cc +++ b/chromium/services/network/public/cpp/simple_url_loader.cc @@ -7,6 +7,7 @@ #include <stdint.h> #include <algorithm> +#include <utility> #include "base/bind.h" #include "base/debug/alias.h" @@ -23,6 +24,8 @@ #include "base/task/post_task.h" #include "base/task/task_traits.h" #include "base/threading/sequenced_task_runner_handle.h" +#include "base/time/time.h" +#include "base/timer/timer.h" #include "mojo/public/cpp/bindings/binding.h" #include "mojo/public/cpp/bindings/binding_set.h" #include "mojo/public/cpp/system/data_pipe.h" @@ -46,6 +49,9 @@ const size_t SimpleURLLoader::kMaxUploadStringSizeToCopy = 256 * 1024; namespace { +// Used by tests to override the tick clock for the timeout timer. +const base::TickClock* timeout_tick_clock_ = nullptr; + // This file contains SimpleURLLoaderImpl, several BodyHandler implementations, // BodyReader, and StringUploadDataPipeGetter. // @@ -220,6 +226,8 @@ class SimpleURLLoaderImpl : public SimpleURLLoader, uint64_t offset = 0, uint64_t length = std::numeric_limits<uint64_t>::max()) override; void SetRetryOptions(int max_retries, int retry_mode) override; + void SetTimeoutDuration(base::TimeDelta timeout_duration) override; + int NetError() const override; const ResourceResponseHead* ResponseInfo() const override; const GURL& GetFinalURL() const override; @@ -355,6 +363,12 @@ class SimpleURLLoaderImpl : public SimpleURLLoader, GURL final_url_; + // The timer that triggers a timeout when a request takes too long. + base::OneShotTimer timeout_timer_; + // How long |timeout_timer_| should wait before timing out a request. A value + // of zero means do not set a timeout. + base::TimeDelta timeout_duration_ = base::TimeDelta(); + SEQUENCE_CHECKER(sequence_checker_); base::WeakPtrFactory<SimpleURLLoaderImpl> weak_ptr_factory_; @@ -1139,6 +1153,7 @@ SimpleURLLoaderImpl::SimpleURLLoaderImpl( client_binding_(this), request_state_(std::make_unique<RequestState>()), final_url_(resource_request_->url), + timeout_timer_(timeout_tick_clock_), weak_ptr_factory_(this) { // Allow creation and use on different threads. DETACH_FROM_SEQUENCE(sequence_checker_); @@ -1226,11 +1241,18 @@ void SimpleURLLoaderImpl::DownloadAsStream( void SimpleURLLoaderImpl::SetOnRedirectCallback( const OnRedirectCallback& on_redirect_callback) { + // Check if a request has not yet been started. + DCHECK(!body_handler_); + on_redirect_callback_.push_back(on_redirect_callback); + DCHECK(on_redirect_callback); } void SimpleURLLoaderImpl::SetOnResponseStartedCallback( OnResponseStartedCallback on_response_started_callback) { + // Check if a request has not yet been started. + DCHECK(!body_handler_); + on_response_started_callback_ = std::move(on_response_started_callback); DCHECK(on_response_started_callback_); } @@ -1340,6 +1362,12 @@ void SimpleURLLoaderImpl::SetRetryOptions(int max_retries, int retry_mode) { #endif // DCHECK_IS_ON() } +void SimpleURLLoaderImpl::SetTimeoutDuration(base::TimeDelta timeout_duration) { + DCHECK(!request_state_->body_started); + DCHECK(timeout_duration >= base::TimeDelta()); + timeout_duration_ = timeout_duration; +} + int SimpleURLLoaderImpl::NetError() const { // Should only be called once the request is compelete. DCHECK(request_state_->finished); @@ -1426,6 +1454,7 @@ void SimpleURLLoaderImpl::FinishWithResult(int net_error) { client_binding_.Close(); url_loader_.reset(); + timeout_timer_.Stop(); request_state_->finished = true; request_state_->net_error = net_error; @@ -1491,6 +1520,14 @@ void SimpleURLLoaderImpl::StartRequest( 0 /* options */, *resource_request_, std::move(client_ptr), net::MutableNetworkTrafficAnnotationTag(annotation_tag_)); + // Note that this ends up restarting the timer on each retry. + if (!timeout_duration_.is_zero()) { + timeout_timer_.Start( + FROM_HERE, timeout_duration_, + base::BindOnce(&SimpleURLLoaderImpl::FinishWithResult, + weak_ptr_factory_.GetWeakPtr(), net::ERR_TIMED_OUT)); + } + // If no more retries left, can clean up a little. if (remaining_retries_ == 0) { resource_request_.reset(); @@ -1580,10 +1617,12 @@ void SimpleURLLoaderImpl::OnReceiveRedirect( } final_url_ = redirect_info.new_url; - if (to_be_removed_headers.empty()) - url_loader_->FollowRedirect(base::nullopt, base::nullopt); - else - url_loader_->FollowRedirect(to_be_removed_headers, base::nullopt); + if (to_be_removed_headers.empty()) { + url_loader_->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); + } else { + url_loader_->FollowRedirect(to_be_removed_headers, base::nullopt, + base::nullopt); + } } void SimpleURLLoaderImpl::OnReceiveCachedMetadata( @@ -1709,6 +1748,11 @@ std::unique_ptr<SimpleURLLoader> SimpleURLLoader::Create( annotation_tag); } +void SimpleURLLoader::SetTimeoutTickClockForTest( + const base::TickClock* timeout_tick_clock) { + timeout_tick_clock_ = timeout_tick_clock; +} + SimpleURLLoader::~SimpleURLLoader() {} SimpleURLLoader::SimpleURLLoader() {} diff --git a/chromium/services/network/public/cpp/simple_url_loader.h b/chromium/services/network/public/cpp/simple_url_loader.h index 9677c7315ed..dd9c8e094a5 100644 --- a/chromium/services/network/public/cpp/simple_url_loader.h +++ b/chromium/services/network/public/cpp/simple_url_loader.h @@ -23,6 +23,8 @@ class scoped_refptr; namespace base { class FilePath; +class TickClock; +class TimeDelta; } namespace net { @@ -84,8 +86,11 @@ class COMPONENT_EXPORT(NETWORK_CPP) SimpleURLLoader { static const size_t kMaxUploadStringSizeToCopy; // Callback used when downloading the response body as a std::string. - // |response_body| is the body of the response, or nullptr on failure. It is - // safe to delete the SimpleURLLoader during the callback. + // |response_body| is the body of the response, or nullptr on failure. Note + // that |response_body| may be nullptr even if there's a valid response code + // like HTTP_OK, which could happen if there's an interruption before the + // full response body is received. It is safe to delete the SimpleURLLoader + // during the callback. using BodyAsStringCallback = base::OnceCallback<void(std::unique_ptr<std::string> response_body)>; @@ -135,6 +140,12 @@ class COMPONENT_EXPORT(NETWORK_CPP) SimpleURLLoader { std::unique_ptr<ResourceRequest> resource_request, const net::NetworkTrafficAnnotationTag& annotation_tag); + // The TickClock to use to configure a timer that tracks if |timeout_duration| + // has been reached or not. This can be removed once https://crbug.com/905412 + // is completed. When null, the timer falls back to base::TimeTicks::Now(). + static void SetTimeoutTickClockForTest( + const base::TickClock* timeout_tick_clock); + virtual ~SimpleURLLoader(); // Starts the request using |url_loader_factory|. The SimpleURLLoader will @@ -308,6 +319,11 @@ class COMPONENT_EXPORT(NETWORK_CPP) SimpleURLLoader { // was added to the ResourceRequest passed to Create() by the consumer. virtual void SetRetryOptions(int max_retries, int retry_mode) = 0; + // The amount of time to wait before giving up on a given network request and + // considering it an error. If not set, then the request is allowed to take + // as much time as it wants. + virtual void SetTimeoutDuration(base::TimeDelta timeout_duration) = 0; + // Returns the net::Error representing the final status of the request. May // only be called once the loader has informed the caller of completion. virtual int NetError() const = 0; diff --git a/chromium/services/network/public/cpp/simple_url_loader_unittest.cc b/chromium/services/network/public/cpp/simple_url_loader_unittest.cc index 949e3b62e05..f2b5368c14b 100644 --- a/chromium/services/network/public/cpp/simple_url_loader_unittest.cc +++ b/chromium/services/network/public/cpp/simple_url_loader_unittest.cc @@ -1707,6 +1707,9 @@ enum class TestLoaderEvent { kResponseCompleteWithExtraData, kClientPipeClosed, kBodyBufferClosed, + // Advances time by 1 second. Only callable when the test environment is + // configured to be MainThreadType::MOCK_TIME. + kAdvanceOneSecond, }; // URLLoader that the test fixture can control. This allows finer grained @@ -1909,6 +1912,11 @@ class MockURLLoader : public network::mojom::URLLoader { body_stream_.reset(); break; } + case TestLoaderEvent::kAdvanceOneSecond: { + scoped_task_environment_->FastForwardBy( + base::TimeDelta::FromSeconds(1)); + break; + } } // Wait for Mojo to pass along the message, to ensure expected ordering. scoped_task_environment_->RunUntilIdle(); @@ -1917,10 +1925,11 @@ class MockURLLoader : public network::mojom::URLLoader { ~MockURLLoader() override {} // network::mojom::URLLoader implementation: - void FollowRedirect(const base::Optional<std::vector<std::string>>& - to_be_removed_request_headers, - const base::Optional<net::HttpRequestHeaders>& - modified_request_headers) override {} + void FollowRedirect( + const base::Optional<std::vector<std::string>>& + to_be_removed_request_headers, + const base::Optional<net::HttpRequestHeaders>& modified_request_headers, + const base::Optional<GURL>& new_url) override {} void ProceedWithResponse() override {} void SetPriority(net::RequestPriority priority, int32_t intra_priority_value) override { @@ -2982,6 +2991,147 @@ TEST_F(SimpleURLLoaderStreamTest, OnRetryDestruction) { base::RunLoop().RunUntilIdle(); } +// Don't inherit from SimpleURLLoaderTestBase so that we can initialize our +// |scoped_task_environment_| different namely with MainThreadType::MOCK_TIME. +class SimpleURLLoaderMockTimeTest : public testing::Test { + public: + SimpleURLLoaderMockTimeTest() + : scoped_task_environment_( + base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME), + disallow_blocking_(std::make_unique<base::ScopedDisallowBlocking>()) { + SimpleURLLoader::SetTimeoutTickClockForTest( + scoped_task_environment_.GetMockTickClock()); + } + ~SimpleURLLoaderMockTimeTest() override {} + + std::unique_ptr<SimpleLoaderTestHelper> CreateHelper() { + std::unique_ptr<network::ResourceRequest> resource_request = + std::make_unique<network::ResourceRequest>(); + resource_request->url = GURL("foo://bar/"); + resource_request->method = "GET"; + resource_request->enable_upload_progress = true; + return std::make_unique<SimpleLoaderTestHelper>( + std::move(resource_request), + SimpleLoaderTestHelper::DownloadType::TO_STRING); + } + + protected: + base::test::ScopedTaskEnvironment scoped_task_environment_; + std::unique_ptr<base::ScopedDisallowBlocking> disallow_blocking_; +}; + +// The amount of time that's simulated passing is equal to the timeout value +// specified, so the request should fail. +TEST_F(SimpleURLLoaderMockTimeTest, TimeoutTriggered) { + MockURLLoaderFactory loader_factory(&scoped_task_environment_); + loader_factory.AddEvents( + {TestLoaderEvent::kAdvanceOneSecond, TestLoaderEvent::kReceivedResponse, + TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kResponseComplete, + TestLoaderEvent::kBodyBufferClosed}); + std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateHelper(); + test_helper->simple_url_loader()->SetTimeoutDuration( + base::TimeDelta::FromSeconds(1)); + + loader_factory.RunTest(test_helper.get()); + + EXPECT_EQ(net::ERR_TIMED_OUT, test_helper->simple_url_loader()->NetError()); +} + +// Less time is simulated passing than the timeout value, so this request should +// succeed normally. +TEST_F(SimpleURLLoaderMockTimeTest, TimeoutNotTriggered) { + MockURLLoaderFactory loader_factory(&scoped_task_environment_); + loader_factory.AddEvents( + {TestLoaderEvent::kAdvanceOneSecond, TestLoaderEvent::kReceivedResponse, + TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kResponseComplete, + TestLoaderEvent::kBodyBufferClosed}); + std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateHelper(); + test_helper->simple_url_loader()->SetTimeoutDuration( + base::TimeDelta::FromSeconds(2)); + + loader_factory.RunTest(test_helper.get()); + + EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError()); + EXPECT_EQ(200, test_helper->GetResponseCode()); +} + +// Simulate time passing, without setting the timeout. This should result in no +// timer being started, and request should succeed. +TEST_F(SimpleURLLoaderMockTimeTest, TimeNotSetAndTimeAdvanced) { + MockURLLoaderFactory loader_factory(&scoped_task_environment_); + loader_factory.AddEvents( + {TestLoaderEvent::kAdvanceOneSecond, TestLoaderEvent::kReceivedResponse, + TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kResponseComplete, + TestLoaderEvent::kBodyBufferClosed}); + std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateHelper(); + + loader_factory.RunTest(test_helper.get()); + + EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError()); + EXPECT_EQ(200, test_helper->GetResponseCode()); +} + +// Simulate time passing before and after a redirect. The redirect should not +// reset the timeout timer, and the request should timeout. +TEST_F(SimpleURLLoaderMockTimeTest, TimeoutAfterRedirectTriggered) { + MockURLLoaderFactory loader_factory(&scoped_task_environment_); + loader_factory.AddEvents( + {TestLoaderEvent::kAdvanceOneSecond, TestLoaderEvent::kReceivedRedirect, + TestLoaderEvent::kAdvanceOneSecond, TestLoaderEvent::kReceivedResponse, + TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kBodyBufferClosed, + TestLoaderEvent::kResponseComplete}); + std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateHelper(); + test_helper->simple_url_loader()->SetTimeoutDuration( + base::TimeDelta::FromSeconds(2)); + + loader_factory.RunTest(test_helper.get()); + + EXPECT_EQ(net::ERR_TIMED_OUT, test_helper->simple_url_loader()->NetError()); +} + +// Simulate time passing after a failure. The retry restarts the timeout timer, +// so the second attempt gets a full two seconds and it is not exhausted. +TEST_F(SimpleURLLoaderMockTimeTest, TimeoutAfterRetryNotTriggered) { + MockURLLoaderFactory loader_factory(&scoped_task_environment_); + loader_factory.AddEvents({TestLoaderEvent::kAdvanceOneSecond, + TestLoaderEvent::kReceived501Response}); + loader_factory.AddEvents( + {TestLoaderEvent::kAdvanceOneSecond, TestLoaderEvent::kReceivedResponse, + TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kBodyBufferClosed, + TestLoaderEvent::kResponseComplete}); + std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateHelper(); + test_helper->simple_url_loader()->SetTimeoutDuration( + base::TimeDelta::FromSeconds(2)); + test_helper->simple_url_loader()->SetRetryOptions( + 1, SimpleURLLoader::RETRY_ON_5XX); + + loader_factory.RunTest(test_helper.get()); + + EXPECT_EQ(net::OK, test_helper->simple_url_loader()->NetError()); + EXPECT_EQ(200, test_helper->GetResponseCode()); +} + +// Trigger a failure and retry, and then simulate enough time passing to trigger +// the timeout. The retry should have correctly started its timeout timer. +TEST_F(SimpleURLLoaderMockTimeTest, TimeoutAfterRetryTriggered) { + MockURLLoaderFactory loader_factory(&scoped_task_environment_); + loader_factory.AddEvents({TestLoaderEvent::kAdvanceOneSecond, + TestLoaderEvent::kReceived501Response}); + loader_factory.AddEvents( + {TestLoaderEvent::kAdvanceOneSecond, TestLoaderEvent::kAdvanceOneSecond, + TestLoaderEvent::kBodyBufferReceived, TestLoaderEvent::kBodyBufferClosed, + TestLoaderEvent::kResponseComplete}); + std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateHelper(); + test_helper->simple_url_loader()->SetTimeoutDuration( + base::TimeDelta::FromMilliseconds(1900)); + test_helper->simple_url_loader()->SetRetryOptions( + 1, SimpleURLLoader::RETRY_ON_5XX); + + loader_factory.RunTest(test_helper.get()); + + EXPECT_EQ(net::ERR_TIMED_OUT, test_helper->simple_url_loader()->NetError()); +} + TEST_P(SimpleURLLoaderTest, OnUploadProgressCallback) { // The size of the payload cannot be bigger than // net::test_server::<anonymous>::kRequestSizeLimit which is diff --git a/chromium/services/network/public/cpp/typemaps.gni b/chromium/services/network/public/cpp/typemaps.gni index 3bdbeed5221..44468d5c459 100644 --- a/chromium/services/network/public/cpp/typemaps.gni +++ b/chromium/services/network/public/cpp/typemaps.gni @@ -2,12 +2,18 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//services/network/public/cpp/features.gni") + typemaps = [ + "//services/network/public/cpp/address_family.typemap", + "//services/network/public/cpp/address_list.typemap", "//services/network/public/cpp/cookie_manager.typemap", "//services/network/public/cpp/cors_error_status.typemap", "//services/network/public/cpp/digitally_signed.typemap", "//services/network/public/cpp/http_request_headers.typemap", "//services/network/public/cpp/host_resolver.typemap", + "//services/network/public/cpp/ip_address.typemap", + "//services/network/public/cpp/ip_endpoint.typemap", "//services/network/public/cpp/mutable_network_traffic_annotation_tag.typemap", "//services/network/public/cpp/mutable_partial_network_traffic_annotation_tag.typemap", "//services/network/public/cpp/network_param.typemap", @@ -16,9 +22,12 @@ typemaps = [ "//services/network/public/cpp/p2p.typemap", "//services/network/public/cpp/proxy_config.typemap", "//services/network/public/cpp/proxy_config_with_annotation.typemap", - "//services/network/public/cpp/signed_tree_head.typemap", "//services/network/public/cpp/url_loader_completion_status.typemap", "//services/network/public/cpp/url_request.typemap", "//services/network/public/cpp/url_request_redirect_info.typemap", "//services/network/public/cpp/url_response_head.typemap", ] + +if (is_ct_supported) { + typemaps += [ "//services/network/public/cpp/signed_tree_head.typemap" ] +} diff --git a/chromium/services/network/public/cpp/url_loader_completion_status.cc b/chromium/services/network/public/cpp/url_loader_completion_status.cc index 4a6d60dbe62..7cfe6e1af4b 100644 --- a/chromium/services/network/public/cpp/url_loader_completion_status.cc +++ b/chromium/services/network/public/cpp/url_loader_completion_status.cc @@ -16,7 +16,7 @@ URLLoaderCompletionStatus::URLLoaderCompletionStatus(int error_code) : error_code(error_code), completion_time(base::TimeTicks::Now()) {} URLLoaderCompletionStatus::URLLoaderCompletionStatus( - const CORSErrorStatus& error) + const CorsErrorStatus& error) : URLLoaderCompletionStatus(net::ERR_FAILED) { cors_error_status = error; } diff --git a/chromium/services/network/public/cpp/url_loader_completion_status.h b/chromium/services/network/public/cpp/url_loader_completion_status.h index 6e243cb7f09..6ca5b2377be 100644 --- a/chromium/services/network/public/cpp/url_loader_completion_status.h +++ b/chromium/services/network/public/cpp/url_loader_completion_status.h @@ -31,7 +31,7 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) URLLoaderCompletionStatus { // Sets ERR_FAILED to |error_code|, |error| to |cors_error_status|, and // base::TimeTicks::Now() to |completion_time|. - explicit URLLoaderCompletionStatus(const CORSErrorStatus& error); + explicit URLLoaderCompletionStatus(const CorsErrorStatus& error); ~URLLoaderCompletionStatus(); @@ -62,7 +62,7 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) URLLoaderCompletionStatus { int64_t decoded_body_length = 0; // Optional CORS error details. - base::Optional<CORSErrorStatus> cors_error_status; + base::Optional<CorsErrorStatus> cors_error_status; // Optional SSL certificate info. base::Optional<net::SSLInfo> ssl_info; diff --git a/chromium/services/network/public/mojom/BUILD.gn b/chromium/services/network/public/mojom/BUILD.gn index 268ccc892eb..c394d43f761 100644 --- a/chromium/services/network/public/mojom/BUILD.gn +++ b/chromium/services/network/public/mojom/BUILD.gn @@ -3,6 +3,23 @@ # found in the LICENSE file. import("//mojo/public/tools/bindings/mojom.gni") +import("//services/network/public/cpp/features.gni") + +# These interfaces are put in their own target to avoid a circular dependency, +# which comes from the fact that proxy_resolver service uses these interfaces, +# and the network service uses the proxy_resolver service. +mojom("mojom_ip_address") { + sources = [ + "address_family.mojom", + "address_list.mojom", + "ip_address.mojom", + "ip_endpoint.mojom", + ] + + public_deps = [ + "//url/mojom:url_mojom_gurl", + ] +} # These interfaces are put in their own target to avoid a circular dependency, # which comes from the fact that the typemap for url_loader.mojom @@ -41,9 +58,9 @@ mojom("udp_socket_interface") { ] public_deps = [ + ":mojom_ip_address", ":mutable_network_traffic_annotation_interface", "//mojo/public/mojom/base:read_only_buffer", - "//net/interfaces:interfaces", ] } @@ -77,11 +94,11 @@ mojom("mojom") { "cookie_manager.mojom", "cors.mojom", "cors_origin_pattern.mojom", - "ct_log_info.mojom", "digitally_signed.mojom", "fetch_api.mojom", "host_resolver.mojom", "http_request_headers.mojom", + "mdns_responder.mojom", "net_log.mojom", "network_change_manager.mojom", "network_context.mojom", @@ -95,9 +112,9 @@ mojom("mojom") { "proxy_config_with_annotation.mojom", "proxy_lookup_client.mojom", "proxy_resolving_socket.mojom", + "referrer_policy.mojom", "request_context_frame_type.mojom", "restricted_cookie_manager.mojom", - "signed_tree_head.mojom", "ssl_config.mojom", "tcp_socket.mojom", "tls_socket.mojom", @@ -107,22 +124,32 @@ mojom("mojom") { public_deps = [ ":data_pipe_interfaces", + ":mojom_ip_address", ":mutable_network_traffic_annotation_interface", ":udp_socket_interface", ":websocket_mojom", "//components/content_settings/core/common:mojo_bindings", "//mojo/public/mojom/base", "//mojo/public/mojom/base:read_only_buffer", - "//net/interfaces", "//services/proxy_resolver/public/mojom", "//url/mojom:url_mojom_gurl", "//url/mojom:url_mojom_origin", ] + enabled_features = [] + # TODO(crbug/598073): When moving the service implementation to # //services/network, add the correct values for export_class_attribute / # export_define / export_header here. Context in https://crrev.com/2225673002. + if (is_ct_supported) { + enabled_features += [ "is_ct_supported" ] + sources += [ + "ct_log_info.mojom", + "signed_tree_head.mojom", + ] + } + if (!is_ios) { export_class_attribute_blink = "BLINK_PLATFORM_EXPORT" export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1" @@ -132,6 +159,6 @@ mojom("mojom") { # This is only needed on desktop linux, but the defines make this difficult # because IS_CHROMECAST is not available in build/build_config.h. if (is_linux && !is_chromeos) { - enabled_features = [ "needs_crypt_config" ] + enabled_features += [ "needs_crypt_config" ] } } diff --git a/chromium/services/network/public/mojom/address_family.mojom b/chromium/services/network/public/mojom/address_family.mojom new file mode 100644 index 00000000000..d2175bd2f5c --- /dev/null +++ b/chromium/services/network/public/mojom/address_family.mojom @@ -0,0 +1,12 @@ +// Copyright 2017 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. + +module network.mojom; + +// Mirror of net::AddressFamily. +enum AddressFamily { + UNSPECIFIED, + IPV4, + IPV6, +}; diff --git a/chromium/services/network/public/mojom/address_list.mojom b/chromium/services/network/public/mojom/address_list.mojom new file mode 100644 index 00000000000..9af17c47bd2 --- /dev/null +++ b/chromium/services/network/public/mojom/address_list.mojom @@ -0,0 +1,13 @@ +// Copyright 2018 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. + +module network.mojom; + +import "services/network/public/mojom/ip_endpoint.mojom"; + +// Mirror of net::AddressList. +struct AddressList { + array<IPEndPoint> addresses; + string canonical_name; +}; diff --git a/chromium/services/network/public/mojom/cors.mojom b/chromium/services/network/public/mojom/cors.mojom index 5199351f84e..1bb981924d6 100644 --- a/chromium/services/network/public/mojom/cors.mojom +++ b/chromium/services/network/public/mojom/cors.mojom @@ -4,16 +4,14 @@ module network.mojom; -// TODO(crbug.com/875759): Rename to Cors* rather than CORS*. - // A policy to decide if CORS-preflight fetch should be performed. -enum CORSPreflightPolicy { +enum CorsPreflightPolicy { kConsiderPreflight, kPreventPreflight, }; // Error conditions of the CORS check. -enum CORSError { +enum CorsError { // Access control kDisallowedByMode, kInvalidResponse, @@ -40,7 +38,7 @@ enum CORSError { kInvalidAllowCredentials, // The scheme is not for CORS. - kCORSDisabledScheme, + kCorsDisabledScheme, // Preflight: // Failed to check HTTP response ok status in a CORS-preflight response. @@ -95,13 +93,3 @@ enum CORSError { // Cross origin redirect location contains credentials such as 'user:pass'. kRedirectContainsCredentials, }; - -// Determine which Cors exception takes precedence when multiple matches occur. -enum CORSOriginAccessMatchPriority { - kNoMatchingOrigin, - kDefaultPriority, - kLowPriority, - kMediumPriority, - kHighPriority, - kMaxPriority -}; diff --git a/chromium/services/network/public/mojom/cors_origin_pattern.mojom b/chromium/services/network/public/mojom/cors_origin_pattern.mojom index a00e97cffc8..787a5892baf 100644 --- a/chromium/services/network/public/mojom/cors_origin_pattern.mojom +++ b/chromium/services/network/public/mojom/cors_origin_pattern.mojom @@ -4,9 +4,30 @@ module network.mojom; -import "services/network/public/mojom/cors.mojom"; +// An enum to represent a mode if matching functions can accept a partial match +// for sub-domains, or for registerable domains. +enum CorsOriginAccessMatchMode { + // 'www.example.com' matches an OriginAccessEntry for 'example.com' + kAllowSubdomains, -// Parameters for representing a access origin whitelist or blacklist for CORS. + // 'www.example.com' matches an OriginAccessEntry for 'not-www.example.com' + kAllowRegisterableDomains, + + // 'www.example.com' does not match an OriginAccessEntry for 'example.com' + kDisallowSubdomains, +}; + +// Determine which Cors exception takes precedence when multiple matches occur. +enum CorsOriginAccessMatchPriority { + kNoMatchingOrigin, + kDefaultPriority, + kLowPriority, + kMediumPriority, + kHighPriority, + kMaxPriority +}; + +// Parameters for representing a access origin allowlist or blocklist for CORS. struct CorsOriginPattern { // The protocol part of the destination URL. string protocol; @@ -14,12 +35,21 @@ struct CorsOriginPattern { // The domain part of the destination URL. string domain; - // Whether subdomains match this protocol and host pattern. - bool allow_subdomains; + // Specifies a mode for domain match. + CorsOriginAccessMatchMode mode; // Order of preference in which the pattern is applied. Higher priority // patterns take precedence over lower ones. In the case were both a // allow list and block list rule of the same priority match a request, // the block list rule takes priority. - CORSOriginAccessMatchPriority priority; + CorsOriginAccessMatchPriority priority; +}; + +// Parameters for representing pairs of source origin and allow/block-lists +// for CORS. +struct CorsOriginAccessPatterns { + string source_origin; + + array<CorsOriginPattern> allow_patterns; + array<CorsOriginPattern> block_patterns; }; diff --git a/chromium/services/network/public/mojom/fetch_api.mojom b/chromium/services/network/public/mojom/fetch_api.mojom index 2cb441f250b..5daf7d05e1c 100644 --- a/chromium/services/network/public/mojom/fetch_api.mojom +++ b/chromium/services/network/public/mojom/fetch_api.mojom @@ -11,9 +11,9 @@ module network.mojom; // See the "FetchRequestMode" enum in enums.xml. enum FetchRequestMode { kSameOrigin, - kNoCORS, - kCORS, - kCORSWithForcedPreflight, + kNoCors, + kCors, + kCorsWithForcedPreflight, kNavigate, // Add a new type here, then update enums.xml. }; @@ -41,7 +41,7 @@ enum FetchCredentialsMode { // See the "FetchResponseType" enum in enums.xml. enum FetchResponseType { kBasic, - kCORS, + kCors, kDefault, kError, kOpaque, diff --git a/chromium/services/network/public/mojom/host_resolver.mojom b/chromium/services/network/public/mojom/host_resolver.mojom index 28dda6efe8b..bd67190b902 100644 --- a/chromium/services/network/public/mojom/host_resolver.mojom +++ b/chromium/services/network/public/mojom/host_resolver.mojom @@ -4,10 +4,83 @@ module network.mojom; -import "net/interfaces/address_list.mojom"; +import "mojo/public/mojom/base/time.mojom"; +import "services/network/public/mojom/address_list.mojom"; +import "services/network/public/mojom/ip_address.mojom"; +import "services/network/public/mojom/ip_endpoint.mojom"; import "services/network/public/mojom/network_param.mojom"; import "services/network/public/mojom/url_loader.mojom"; +// A single entry from a HOSTS file. +struct DnsHost { + string hostname; + IPAddress address; +}; + +// An HTTPS server to send DNS queries to, per the DNS Queries over HTTPS spec. +// spec: https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-12 +struct DnsOverHttpsServer { + // DNS over HTTPS server URI template. Must be HTTPS. + string server_template; + + // Whether to use POST to do DNS lookups. Otherwise, GET is used. See spec + // for more details. + bool use_post = false; +}; + +// Overridable DNS configuration values for host resolution. All fields default +// to a non-overriding state where the relevant value will be used from system +// DNS configuration. +struct DnsConfigOverrides { + // Representation of an optional boolean. Needed because Mojo does not support + // optional primitive types. + enum Tristate { + NO_OVERRIDE, + TRISTATE_TRUE, + TRISTATE_FALSE, + }; + + // List of nameserver addresses. + array<IPEndPoint>? nameservers; + + // Suffix search list, used on first lookup when number of dots in given name + // is less than |ndots|. + array<string>? search; + + // Entries from a HOSTS file. This array is intended for serialization to/from + // a lookup map, so unlike the source data from actual HOSTS files, each entry + // should represent a unique host query key (|hostname| and AddressFamily). + array<DnsHost>? hosts; + + // Whether suffix search should be performed for multi-label names. + Tristate append_to_multi_label_name = Tristate.NO_OVERRIDE; + + // Whether source port randomization is required. This uses additional + // resources on some platforms. + Tristate randomize_ports = Tristate.NO_OVERRIDE; + + // Minimum number of dots before global resolution precedes |search|. + int8 ndots = -1; // -1 for no override. + + // Time between retransmissions, see res_state.retrans. + mojo_base.mojom.TimeDelta? timeout; + + // Maximum number of attempts, see res_state.retry. + int32 attempts = -1; // -1 for no override. + + // Whether to roundrobin entries in |nameservers| for subsequent requests. + Tristate rotate = Tristate.NO_OVERRIDE; + + // Whether system configuration uses local IPv6 connectivity, e.g., + // DirectAccess. This is exposed for HostResolver to skip IPv6 probes, + // as it may cause them to return incorrect results. + Tristate use_local_ipv6 = Tristate.NO_OVERRIDE; + + // List of servers to query over HTTPS, queried in order + // (https://tools.ietf.org/id/draft-ietf-doh-dns-over-https-12.txt). + array<DnsOverHttpsServer>? dns_over_https_servers; +}; + // Control handle used to control outstanding NetworkContext::ResolveHost // requests. Handle is optional for all requests, and may be closed at any time // without affecting the request. @@ -29,7 +102,7 @@ interface ResolveHostHandle { interface ResolveHostClient { // Called on completion of a resolve host request. Results are a network error // code, and on success (network error code OK), an AddressList. - OnComplete(int32 result, net.interfaces.AddressList? resolved_addresses); + OnComplete(int32 result, AddressList? resolved_addresses); }; // Parameter-grouping struct for additional optional parameters for @@ -126,3 +199,17 @@ interface HostResolver { ResolveHostParameters? optional_parameters, ResolveHostClient response_client); }; + +// A client interface that subscribes to DNS config change events from +// DnsConfigChangeManager. +interface DnsConfigChangeManagerClient { + // Notifies that a potential change has been detected in the DNS settings of + // the system that may affect results of host resolution. + OnSystemDnsConfigChanged(); +}; + +// An interface that broadcasts DNS config change events. +interface DnsConfigChangeManager { + // Requests to receive notification when there is a DNS config change. + RequestNotifications(DnsConfigChangeManagerClient client_ptr); +}; diff --git a/chromium/services/network/public/mojom/ip_address.mojom b/chromium/services/network/public/mojom/ip_address.mojom new file mode 100644 index 00000000000..45463c48648 --- /dev/null +++ b/chromium/services/network/public/mojom/ip_address.mojom @@ -0,0 +1,12 @@ +// Copyright 2017 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. + +module network.mojom; + +// Mirror of net::IPAddress. +struct IPAddress { + // IP address as a numeric value from most to least significant byte. + // Will be of length 4 for IPv4 addresses and 16 for IPv6. + array<uint8> address_bytes; +}; diff --git a/chromium/services/network/public/mojom/ip_endpoint.mojom b/chromium/services/network/public/mojom/ip_endpoint.mojom new file mode 100644 index 00000000000..f4982bdb3e4 --- /dev/null +++ b/chromium/services/network/public/mojom/ip_endpoint.mojom @@ -0,0 +1,13 @@ +// Copyright 2017 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. + +module network.mojom; + +import "services/network/public/mojom/ip_address.mojom"; + +// Mirror of net::IPEndPoint. +struct IPEndPoint { + IPAddress address; + uint16 port; +}; diff --git a/chromium/services/network/public/mojom/mdns_responder.mojom b/chromium/services/network/public/mojom/mdns_responder.mojom new file mode 100644 index 00000000000..0871a0f4447 --- /dev/null +++ b/chromium/services/network/public/mojom/mdns_responder.mojom @@ -0,0 +1,50 @@ +// Copyright 2018 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. + +module network.mojom; + +import "services/network/public/mojom/ip_address.mojom"; + +// An mDNS responder is created for each Mojo binding and it manages the +// name-address maps created through the interface. The name-address maps are +// isolated among different responders, so that the creation and the removal +// of a name for an address is specific to the given responder. When the Mojo +// pipe is closed, all name-address maps created through the interface are +// removed by the responder and the responder is destroyed afterwards. For a +// given responder, all created names are reference counted, and each +// CreateNameForAddress call and RemoveNameForAddress call respectively +// increases and decreases the reference count for existing names. +// +// This interface is intended to be safe to use from renderer processes. +interface MdnsResponder { + // Creates and returns a new name for the address if there is no name mapped + // to it by this responder, and initializes the reference count of this name + // to one. Otherwise the existing name mapped to the given address is returned + // and its reference count is incremented by one. Since the name-address maps + // are specific to the given responder, there could be separated names for the + // same address. After a new name is mapped to an address, an mDNS response to + // announce the ownership of this name is scheduled to send to the mDNS + // multicast group over all interfaces, with a TTL for the name set to 120 + // seconds (see RFC 6762, Section 10). Returns true in + // |announcement_scheduled| if the schedule is done on all interfaces after + // rate limiting, and false otherwise. The responder will also start to + // respond to queries for the created name until its reference count is + // decremented to zero. + CreateNameForAddress(IPAddress address) + => (string name, bool announcement_scheduled); + // Decrements the reference count of the mapped name of the given address, if + // there is a map created previously via CreateNameForAddress; removes the + // association between the address and its mapped name and returns true in + // |removed| if the decremented reference count reaches zero. Otherwise no + // operation is done and false is returned in |removed|. If the association + // between the address and its mapped name is removed, an mDNS response to + // renounce the ownership of this name is scheduled to send to the mDNS + // multicast group over all interfaces, by setting a zero TTL. Returns true in + // |goodbye_scheduled| if the schedule is done on all interfaces after rate + // limiting, and false otherwise. The responder will also stop responding to + // queries for the removed name. Note that this does not impact separated + // names mapped to the same address by other responders. + RemoveNameForAddress(IPAddress address) + => (bool removed, bool goodbye_scheduled); +}; diff --git a/chromium/services/network/public/mojom/network_change_manager.mojom b/chromium/services/network/public/mojom/network_change_manager.mojom index ce157d501bf..ed0c245bc25 100644 --- a/chromium/services/network/public/mojom/network_change_manager.mojom +++ b/chromium/services/network/public/mojom/network_change_manager.mojom @@ -18,6 +18,44 @@ enum ConnectionType { CONNECTION_LAST = CONNECTION_BLUETOOTH }; +// This needs to match the definition of net::ConnectionSubtype. +enum ConnectionSubtype { + SUBTYPE_UNKNOWN = 0, + SUBTYPE_NONE, + SUBTYPE_OTHER, + SUBTYPE_GSM, + SUBTYPE_IDEN, + SUBTYPE_CDMA, + SUBTYPE_1XRTT, + SUBTYPE_GPRS, + SUBTYPE_EDGE, + SUBTYPE_UMTS, + SUBTYPE_EVDO_REV_0, + SUBTYPE_EVDO_REV_A, + SUBTYPE_HSPA, + SUBTYPE_EVDO_REV_B, + SUBTYPE_HSDPA, + SUBTYPE_HSUPA, + SUBTYPE_EHRPD, + SUBTYPE_HSPAP, + SUBTYPE_LTE, + SUBTYPE_LTE_ADVANCED, + SUBTYPE_BLUETOOTH_1_2, + SUBTYPE_BLUETOOTH_2_1, + SUBTYPE_BLUETOOTH_3_0, + SUBTYPE_BLUETOOTH_4_0, + SUBTYPE_ETHERNET, + SUBTYPE_FAST_ETHERNET, + SUBTYPE_GIGABIT_ETHERNET, + SUBTYPE_10_GIGABIT_ETHERNET, + SUBTYPE_WIFI_B, + SUBTYPE_WIFI_G, + SUBTYPE_WIFI_N, + SUBTYPE_WIFI_AC, + SUBTYPE_WIFI_AD, + SUBTYPE_LAST = SUBTYPE_WIFI_AD +}; + // A client interface that subscribes to network change events from // NetworkChangeManager. interface NetworkChangeManagerClient { @@ -43,8 +81,22 @@ interface NetworkChangeManagerClient { OnNetworkChanged(ConnectionType type); }; -// An interface that broadcasts network change events. +// An interface for facilitating notifications of network change events. interface NetworkChangeManager { // Requests to receive notification when there is a network change. RequestNotifications(NetworkChangeManagerClient client_ptr); + + // Notifies the network service that the network configuration has changed. + // This is needed for ChromeOS because only one process can listen for network + // changes from Shill, and currently that has to be the browser process. This + // allows the browser process to forward the network changes to the network + // service. + [EnableIf=is_chromeos] + OnNetworkChanged( + bool dns_changed, + bool ip_address_changed, + bool connection_type_changed, + ConnectionType new_connection_type, + bool connection_subtype_changed, + ConnectionSubtype new_connection_subtype); }; diff --git a/chromium/services/network/public/mojom/network_context.mojom b/chromium/services/network/public/mojom/network_context.mojom index f5e6b59d9a3..1071227db66 100644 --- a/chromium/services/network/public/mojom/network_context.mojom +++ b/chromium/services/network/public/mojom/network_context.mojom @@ -8,13 +8,13 @@ import "mojo/public/mojom/base/file_path.mojom"; import "mojo/public/mojom/base/time.mojom"; import "mojo/public/mojom/base/unguessable_token.mojom"; import "mojo/public/mojom/base/values.mojom"; -import "net/interfaces/address_list.mojom"; -import "net/interfaces/ip_endpoint.mojom"; +import "services/network/public/mojom/address_list.mojom"; import "services/network/public/mojom/cookie_manager.mojom"; import "services/network/public/mojom/cors_origin_pattern.mojom"; -import "services/network/public/mojom/ct_log_info.mojom"; import "services/network/public/mojom/host_resolver.mojom"; import "services/network/public/mojom/http_request_headers.mojom"; +import "services/network/public/mojom/ip_endpoint.mojom"; +import "services/network/public/mojom/mdns_responder.mojom"; import "services/network/public/mojom/mutable_network_traffic_annotation_tag.mojom"; import "services/network/public/mojom/net_log.mojom"; import "services/network/public/mojom/network_param.mojom"; @@ -35,6 +35,9 @@ import "services/proxy_resolver/public/mojom/proxy_resolver.mojom"; import "url/mojom/origin.mojom"; import "url/mojom/url.mojom"; +[EnableIf=is_ct_supported] +import "services/network/public/mojom/ct_log_info.mojom"; + // Config for setting a custom proxy config that will be used if a request // matches the proxy rules and would otherwise be direct. This config allows // headers to be set on requests to the proxies from the config before and/or @@ -44,6 +47,10 @@ struct CustomProxyConfig { // http requests. ProxyRules rules; + // List of proxies that will be used if + // ResourceRequest::custom_proxy_use_alternate_proxy_list is set. + ProxyList alternate_proxy_list; + // The custom proxy can set these headers in this config which will be added // to all requests using the proxy. This allows setting headers that may be // privacy/security sensitive which we don't want to send to the renderer. @@ -67,6 +74,40 @@ struct CustomProxyConfig { // Client to update the custom proxy config. interface CustomProxyConfigClient { OnCustomProxyConfigUpdated(CustomProxyConfig proxy_config); + + // Marks the custom proxies in |bad_proxies| as temporarily bad, so they are + // not layered onto the proxy resolution results for subsequent requests. + MarkProxiesAsBad(mojo_base.mojom.TimeDelta bypass_duration, + ProxyList bad_proxies) => (); + + // Clears the list of bad proxy servers that has been cached in the proxy + // resolution service. + ClearBadProxiesCache(); +}; + +[EnableIf=is_chromeos] +struct AdditionalCertificates { + // List of all additional certificates. + array<X509Certificate> all_certificates; + + // List of additional trust anchors. + array<X509Certificate> trust_anchors; +}; + +// Interface to allow modifying the full request and response headers. This +// interface exposes sensitive headers such as set-cookie, so should only be +// sent to trusted processes. +interface TrustedURLLoaderHeaderClient { + // Allows modifying request headers before the request is sent. + OnBeforeSendHeaders(int32 request_id, HttpRequestHeaders headers) => + (int32 result, HttpRequestHeaders? headers); + + // Allows modifying response headers, including sensitive headers such as + // set-cookie. This should only be used from a trusted process. + OnHeadersReceived(int32 request_id, string headers) => + (int32 result, + string? headers, + url.mojom.Url allowed_unsafe_redirect_url); }; // Parameters for constructing a network context. @@ -155,6 +196,7 @@ struct NetworkContextParams { // // See //net/docs/certificate-transparency.md before setting this flag to // true. + [EnableIf=is_ct_supported] bool enforce_chrome_ct_policy = false; // Enables HTTP/0.9 on ports other than 80 for HTTP and 443 for HTTPS. @@ -179,6 +221,8 @@ struct NetworkContextParams { // If |custom_proxy_config_client_request| is set, this context will listen // for updates to the custom proxy config, and use it if applicable for // requests which would otherwise be made direct. + // |initial_custom_proxy_config| is the initial config settings. + CustomProxyConfig? initial_custom_proxy_config; CustomProxyConfigClient&? custom_proxy_config_client_request; // If |proxy_config_client_request| is non-null, this is called during @@ -211,16 +255,41 @@ struct NetworkContextParams { // Enables Expect CT reporting, which sends reports for opted-in sites that // don't serve sufficient Certificate Transparency information. + [EnableIf=is_ct_supported] bool enable_expect_ct_reporting = false; // The Certificate Transparency logs that are known to the client. SCTs from // these logs will be extracted and verified; other SCTs will be treated as // unrecognized. + [EnableIf=is_ct_supported] array<CTLogInfo> ct_logs; + // Specifies the path to the directory where NSS will store its database. + [EnableIf=is_chromeos] + mojo_base.mojom.FilePath? nss_path; + + // This is used in combination with nss_path, to ensure that the NSS database + // isn't opened multiple times for NetworkContexts in the same profie. + [EnableIf=is_chromeos] + string username_hash; + + // Initial additional certificates that will be used for certificate + // validation. + [EnableIf=is_chromeos] + AdditionalCertificates? initial_additional_certificates; + // Parameters for constructing the cookie manager. CookieManagerParams? cookie_manager_params; + // Whether to enable Domain Reliability. + bool enable_domain_reliability = false; + + // The uploader reporter name to use for Domain Reliability uploads. + string domain_reliability_upload_reporter; + + // Whether to discard Domain Reliability uploads. + bool discard_domain_reliablity_uploads = false; + // Sets whether the NetworkContext should be used for globally scoped tasks // that need to make network requests. Currently this includes DNS over HTTPS // requests and certain cert validation requests (OCSP, AIA, etc) on some @@ -238,6 +307,10 @@ struct NetworkContextParams { // verification, we should consider using each URLRequestContext to do its own // validation. bool primary_network_context = false; + + // Specifies the initial set of allowed and blocked origins for the + // URLLoaderFactory consumers to access beyond the same-origin-policy. + array<CorsOriginAccessPatterns> cors_origin_access_list; }; struct NetworkConditions { @@ -310,21 +383,22 @@ struct URLLoaderFactoryParams { int32 corb_detachable_resource_type = -1; // TODO(lukasza): https://crbug.com/846339: Remove the field below and instead // make plugins use a separate URLoaderFactory. Note requests of this type are - // only excluded if ResourceRequest::fetch_request_mode is kNoCORS. The field + // only excluded if ResourceRequest::fetch_request_mode is kNoCors. The field // below in practice is always set to RESOURCE_TYPE_PLUGIN_RESOURCE by the // content layer, but in the long-term we want to avoid using resource types // (even as an opaque int) in //services/network. See also the TODO comment // for network::ResourceRequest::resource_type. int32 corb_excluded_resource_type = -1; - // TODO(lukasza): https://crbug.com/846346: Replace the field below with a - // granular list of origins that content scripts can XHR into (based on - // extension manifest V3 / assumming that content scripts have a - // URLLoaderFactory separate from the rest of the renderer). - string corb_excluded_initiator_scheme; // True if web related security (e.g., CORS) should be disabled. This is // mainly used by people testing their sites, via a command line switch. bool disable_web_security = false; + + // If this is set, requests with the kURLLoadOptionUseHeaderClient option will + // callback to the |header_client|, allowing the Cookie/Referrer request + // headers and Cookie response headers to be modified. This has a performance + // impact because of the extra process hops, so use should be minimized. + TrustedURLLoaderHeaderClient? header_client; }; // Callback interface for NetworkContext when routing identifiers aren't @@ -335,6 +409,9 @@ interface NetworkContextClient { // Replies with the origins that are allowed. OnCanSendReportingReports(array<url.mojom.Origin> origins) => (array<url.mojom.Origin> origins); + + // Checks if a Domain Reliability report can be uploaded for the given origin. + OnCanSendDomainReliabilityUpload(url.mojom.Url origin) => (bool allowed); }; // Represents a distinct context for making network requests, with its own @@ -347,6 +424,11 @@ interface NetworkContext { CreateURLLoaderFactory(URLLoaderFactory& url_loader_factory, URLLoaderFactoryParams params); + // Destroys all URLLoaderFactory bindings, which should then be regenerated. + // This should be called if there is a change to the proxies which should be + // used on URLLoaders. + ResetURLLoaderFactories(); + // Gets the CookieManager associated with this network context. // // The CookieManager must only be passed to trusted processes. Whenever @@ -393,6 +475,12 @@ interface NetworkContext { mojo_base.mojom.Time end_time) => (bool is_upper_bound, int64 size_or_error); + // Caches |data| associated with |url| and |expected_response_time| in the + // HttpCache related to this NetworkContext. + WriteCacheMetadata(url.mojom.Url url, + RequestPriority priority, + mojo_base.mojom.Time expected_response_time, + array<uint8> data); // Clears channel IDs. A specific range of time can be specified with // |start_time| and |end_time|. This supports unbounded deletes in either @@ -438,6 +526,32 @@ interface NetworkContext { // filter. ClearNetworkErrorLogging(ClearDataFilter? filter) => (); + // Mirror of domain_reliability::DomainReliabilityClearMode. + enum DomainReliabilityClearMode {CLEAR_CONTEXTS, CLEAR_BEACONS}; + // Clears Domain Reliability entries, specified by |mode|. + ClearDomainReliability(ClearDataFilter? filter, + DomainReliabilityClearMode mode) => (); + + // Returns a JSON value containing data for displaying on a debugging page. + GetDomainReliabilityJSON() => (mojo_base.mojom.Value data); + + // Queues a report via the Reporting API. |type| describes the type of report + // (as well as what data will contained in |body|). |group| specifies the + // endpoint group that the report will be delivered to. |url| indicates the + // URL of the resource that the report describes; |user_agent| may be + // provided, or will be automatically generated if omitted; |body| holds the + // contents of the report. + // + // Note that this queued report will never be delivered if no reporting + // endpoint is registered for this |url|. + // + // Spec: https://w3c.github.io/reporting/#concept-reports + QueueReport(string type, + string group, + url.mojom.Url url, + string? user_agent, + mojo_base.mojom.DictionaryValue body); + // Closes all open connections within this context. CloseAllConnections() => (); @@ -458,10 +572,15 @@ interface NetworkContext { // If false, the referrer of requests is never populated. SetEnableReferrers(bool enable_referrers); + // Updates the additional trust anchors for certificate verification. + [EnableIf=is_chromeos] + UpdateAdditionalCertificates(AdditionalCertificates? additional_certificates); + // Updates the CT policy to be used for requests. Only applies if the // NetworkContextParams set enforce_chrome_ct_policy to true. // TODO(rsleevi): Remove this once Chrome-specific policies are moved out // of the network service. + [EnableIf=is_ct_supported] SetCTPolicy(array<string> required_hosts, array<string> excluded_hosts, array<string> excluded_spkis, @@ -469,14 +588,17 @@ interface NetworkContext { // Adds explicitly-specified data as if it was processed from an Expect-CT // header. + [EnableIf=is_ct_supported] AddExpectCT(string host, mojo_base.mojom.Time expiry, bool enforce, url.mojom.Url report_uri) => (bool success); // Send a test CT report with dummy data for test purposes. + [EnableIf=is_ct_supported] SetExpectCTTestReport(url.mojom.Url report_uri) => (bool success); // Retrieves the expect CT state from the associated network context // transport security state. + [EnableIf=is_ct_supported] GetExpectCTState(string domain) => (mojo_base.mojom.DictionaryValue state); // Creates a UDP socket. Caller can supply a |receiver| interface pointer @@ -498,11 +620,11 @@ interface NetworkContext { // // Any sockets that are created but are yet to be destroyed will be destroyed // when NetworkContext goes away. - CreateTCPServerSocket(net.interfaces.IPEndPoint local_addr, + CreateTCPServerSocket(IPEndPoint local_addr, uint32 backlog, MutableNetworkTrafficAnnotationTag traffic_annotation, TCPServerSocket& socket) - => (int32 result, net.interfaces.IPEndPoint? local_addr_out); + => (int32 result, IPEndPoint? local_addr_out); // Creates a TCP socket connected to |remote_addr|. |observer| if non-null // will be used to listen for any network connection error on the newly @@ -520,15 +642,15 @@ interface NetworkContext { // Any sockets that are created but are yet to be destroyed will be destroyed // when NetworkContext goes away. CreateTCPConnectedSocket( - net.interfaces.IPEndPoint? local_addr, - net.interfaces.AddressList remote_addr_list, + IPEndPoint? local_addr, + AddressList remote_addr_list, TCPConnectedSocketOptions? tcp_connected_socket_options, MutableNetworkTrafficAnnotationTag traffic_annotation, TCPConnectedSocket& socket, SocketObserver? observer) => (int32 result, - net.interfaces.IPEndPoint? local_addr, - net.interfaces.IPEndPoint? peer_addr, + IPEndPoint? local_addr, + IPEndPoint? peer_addr, handle<data_pipe_consumer>? receive_stream, handle<data_pipe_producer>? send_stream); @@ -543,11 +665,10 @@ interface NetworkContext { // It's recommended consumers use CreateTCPServerSocket() or // CreateTCPConnectedSocket(). This method is just provided so legacy // consumers can mimic Berkeley sockets semantics. - CreateTCPBoundSocket(net.interfaces.IPEndPoint local_addr, + CreateTCPBoundSocket(IPEndPoint local_addr, MutableNetworkTrafficAnnotationTag traffic_annotation, TCPBoundSocket& socket) - => (int32 result, - net.interfaces.IPEndPoint? local_addr); + => (int32 result, IPEndPoint? local_addr); // Creates a ProxyResolvingSocketFactory that shares some configuration params // with this NetworkContext, but uses separate socket pools. @@ -556,13 +677,6 @@ interface NetworkContext { // when NetworkContext goes away. CreateProxyResolvingSocketFactory(ProxyResolvingSocketFactory& factory); - // Creates a WebSocket connection. - CreateWebSocket(WebSocket& request, - int32 process_id, - int32 render_frame_id, - url.mojom.Origin origin, - AuthenticationHandler? auth_handler); - // Looks up what proxy to use for a particular URL. LookUpProxyForURL(url.mojom.Url url, ProxyLookupClient proxy_lookup_client); @@ -573,6 +687,13 @@ interface NetworkContext { // Clears the list of bad proxy servers that has been cached. ClearBadProxiesCache() => (); + // Creates a WebSocket connection. + CreateWebSocket(WebSocket& request, + int32 process_id, + int32 render_frame_id, + url.mojom.Origin origin, + AuthenticationHandler? auth_handler); + // Create a NetLogExporter, which helps export NetLog to an existing file. // Note that the log is generally global, including all NetworkContexts // managed by the same NetworkService. The particular NetworkContext this is @@ -597,10 +718,8 @@ interface NetworkContext { P2PTrustedSocketManager& trusted_socket_manager, P2PSocketManager& socket_manager); - // Destroys all URLLoaderFactory bindings, which should then be regenerated. - // This should be called if there is a change to the proxies which should be - // used on URLLoaders. - ResetURLLoaderFactories(); + // Creates an MdnsResponder instance. + CreateMdnsResponder(MdnsResponder& responder_request); // Resolves the given hostname (or IP address literal). See documentation at // HostResolver::ResolveHost. @@ -618,6 +737,10 @@ interface NetworkContext { // Creates a HostResolver interface that can be passed to code/processes // without direct access to NetworkContext to make ResolveHost requests. // + // If set, |config_overrides| will override configuration read from the system + // DNS configuration when resolution is performed using the built-in resolver + // (which can be forced using ResolveHostParameters::source = Source.DNS). + // // If this NetworkContext is destroyed, all outstanding requests from child // HostResolvers will be cancelled. Such requests will receive ERR_FAILED via // |response_client|. @@ -625,14 +748,8 @@ interface NetworkContext { // TODO(crbug.com/821021): If necessary as usage and functionality is added to // the contained ResolveHost method, consider adding the ability for this to // be a restricted resolver with some functionality disabled (eg maybe MDNS). - CreateHostResolver(HostResolver& host_resolver); - - // Caches |data| associated with |url| and |expected_response_time| in the - // HttpCache related to this NetworkContext. - WriteCacheMetadata(url.mojom.Url url, - RequestPriority priority, - mojo_base.mojom.Time expected_response_time, - array<uint8> data); + CreateHostResolver(DnsConfigOverrides? config_overrides, + HostResolver& host_resolver); // Checks the given certificate against the CertVerifier and CTVerifier. This // implementation is currently specific for use by Signed Exchange. @@ -643,10 +760,19 @@ interface NetworkContext { CertVerifyResult cv_result, CTVerifyResult ct_result); + // Adds explicitly-specified data as if it was processed from an + // HSTS header. Used by tests and implementation of chrome://net-internals. + AddHSTS(string host, mojo_base.mojom.Time expiry, + bool include_subdomains) => (); + // Returns true if it is known that |host| has requested to always be // accessed via HTTPS. IsHSTSActiveForHost(string host) => (bool result); + // Retrieve values from the HSTS state from the associated contexts + // transport security state. + GetHSTSState(string domain) => (mojo_base.mojom.DictionaryValue state); + // Sets allowed and blocked origins respectively for the URLLoaderFactory // consumers to access beyond the same-origin policy. The list is managed per // each |source_origin|, and each call will flash old set lists for the @@ -657,21 +783,37 @@ interface NetworkContext { url.mojom.Origin source_origin, array<CorsOriginPattern> allow_patterns, array<CorsOriginPattern> block_patterns) => (); - // Adds explicitly-specified data as if it was processed from an - // HSTS header. Used by tests and implementation of chrome://net-internals. - AddHSTS(string host, mojo_base.mojom.Time expiry, - bool include_subdomains) => (); - - // Retrieve values from the HSTS state from the associated contexts - // transport security state. - GetHSTSState(string domain) => (mojo_base.mojom.DictionaryValue state); - // Deletes any dynamic data stored for |host| from the transport // security state. Returns true iff an entry was deleted. // See net::TransportSecurityState::DeleteDynamicDataForHost for more detail. DeleteDynamicDataForHost(string host) => (bool result); + // Looks up credentials in the HttpAuthCache using the origin and path from + // |url|. Only supports basic auth scheme. + LookupBasicAuthCredentials(url.mojom.Url url) + => (AuthCredentials? credentials); + + [Sync] + // Enables the checking of static PKP records. + EnableStaticKeyPinningForTesting() => (); + [Sync] // Will force the transaction to fail with the given error code. SetFailingHttpTransactionForTesting(int32 rv) => (); + + [Sync] + // Verifies the given certificate using the context's CertVerifier. + VerifyCertificateForTesting(X509Certificate certificate, + string hostname, + string ocsp_response) => (int32 error_code); + + [Sync] + // Adds a Domain Reliability Context. + AddDomainReliabilityContextForTesting( + url.mojom.Url origin, url.mojom.Url upload_url) => (); + + [Sync] + // Forces all pending Domain Reliability uploads to run now, even if their + // minimum delay has not yet passed. + ForceDomainReliabilityUploadsForTesting() => (); }; diff --git a/chromium/services/network/public/mojom/network_param.mojom b/chromium/services/network/public/mojom/network_param.mojom index e8b54d75eea..57dec30fd35 100644 --- a/chromium/services/network/public/mojom/network_param.mojom +++ b/chromium/services/network/public/mojom/network_param.mojom @@ -14,6 +14,13 @@ enum ApplicationState { HAS_DESTROYED_ACTIVITIES, }; +// Mirror of base::MemoryPressureListener::MemoryPressureLevel. +enum MemoryPressureLevel { + NONE, + MODERATE, + CRITICAL, +}; + [Native] struct AuthChallengeInfo; diff --git a/chromium/services/network/public/mojom/network_service.mojom b/chromium/services/network/public/mojom/network_service.mojom index 48be6a73dd4..49fbe92ffa8 100644 --- a/chromium/services/network/public/mojom/network_service.mojom +++ b/chromium/services/network/public/mojom/network_service.mojom @@ -11,17 +11,20 @@ import "mojo/public/mojom/base/string16.mojom"; import "mojo/public/mojom/base/unguessable_token.mojom"; import "mojo/public/mojom/base/values.mojom"; import "services/network/public/mojom/cookie_manager.mojom"; +import "services/network/public/mojom/host_resolver.mojom"; import "services/network/public/mojom/net_log.mojom"; import "services/network/public/mojom/network_change_manager.mojom"; import "services/network/public/mojom/network_context.mojom"; import "services/network/public/mojom/network_param.mojom"; import "services/network/public/mojom/network_quality_estimator_manager.mojom"; -import "services/network/public/mojom/signed_tree_head.mojom"; import "services/network/public/mojom/url_loader.mojom"; import "services/network/public/mojom/url_loader_factory.mojom"; import "url/mojom/origin.mojom"; import "url/mojom/url.mojom"; +[EnableIf=is_ct_supported] +import "services/network/public/mojom/signed_tree_head.mojom"; + // The content/browser implementation of this SSLPrivateKey interface wraps the // scoped_refptr<net::SSLPrivateKey> that is received from // SSLClientAuthDelegate::ContinueWithCertificate(), and this mojo interface is @@ -72,6 +75,8 @@ interface NetworkServiceClient { AuthChallengeResponder auth_challenge_responder); // Called when an SSL certificate requested message is received for client // authentication. + // The |provider_name| parameter corresponses to the return value of + // net::SSLPrivateKey::GetProviderName(). // The |algorithm_preferences| parameter corresponds to the return value // of net::SSLPrivateKey::GetAlgorithmPreferences(). // The |cancel_certificate_selection| parameter is used to distinguish @@ -92,6 +97,7 @@ interface NetworkServiceClient { uint32 request_id, network.mojom.SSLCertRequestInfo cert_info) => ( network.mojom.X509Certificate x509_certificate, + string provider_name, array<uint16> algorithm_preferences, SSLPrivateKey ssl_private_key, bool cancel_certificate_selection); @@ -105,6 +111,12 @@ interface NetworkServiceClient { url.mojom.Url url, SSLInfo ssl_info, bool fatal) => (int32 net_error); + + // Notification that a trust anchor was used for the given user. + // |username_hash| was the parameter passed in NetworkContextParams. + [EnableIf=is_chromeos] + OnTrustAnchorUsed(string username_hash); + // Called when file uploading was requested. // If the process that requested the uploads has permission to read all of // the files referenced by |file_paths|, the callback arguments will be @@ -144,17 +156,13 @@ interface NetworkServiceClient { url.mojom.Url url, string header_value, int32 load_flags) => (); -}; - -// An HTTPS server to send DNS queries to, per the DNS Queries over HTTPS spec. -// spec: https://tools.ietf.org/html/draft-ietf-doh-dns-over-https-12 - struct DnsOverHttpsServer { - // DNS over HTTPS server URI template. Must be HTTPS. - string server_template; - // Whether to use POST to do DNS lookups. Otherwise, GET is used. See spec - // for more details. - bool use_post = false; + // Called on every request completion to update the network traffic annotation + // ID, and the total bytes received and sent. + // |network_traffic_annotation_id_hash| represents the hash of unique tag that + // identifies the annotation of the request. + OnDataUseUpdate(int32 network_traffic_annotation_id_hash, int64 recv_bytes, + int64 sent_bytes); }; // Values for configuring HTTP authentication that can only be set once. @@ -283,10 +291,15 @@ interface NetworkService { ConfigureHttpAuthPrefs(HttpAuthDynamicParams http_auth_dynamic_params); // Specifies whether requests for raw headers coming through URLLoaderFactory - // associated with the specified process will be granted. Granting such a - // permission increases risks in case the child process becomes compromised, - // so this should be done only in specific cases (e.g. DevTools attached). - SetRawHeadersAccess(uint32 process_id, bool allow); + // associated with the specified process will be granted. Only raw headers + // for requests with URLs matching a listed origin will be reported (this + // permission has no effect on the network request itself). + // The list overwrites the list set for given process with a previous call + // to this method. + // Granting a permission increases risks in case the child process becomes + // compromised, so this should be done only in specific cases + // (e.g. DevTools attached). + SetRawHeadersAccess(uint32 process_id, array<url.mojom.Origin> origins); // Gets the NetworkChangeManager. GetNetworkChangeManager( @@ -296,12 +309,16 @@ interface NetworkService { GetNetworkQualityEstimatorManager( NetworkQualityEstimatorManager& network_quality_estimator_manager); + // Gets the DnsConfigChangeManager. + GetDnsConfigChangeManager(DnsConfigChangeManager& dns_config_change_manager); + // Gets the accumulated network usage since the start/restart of the service. GetTotalNetworkUsages() => (array<NetworkUsage> total_network_usages); // Updates Signed Tree Heads (STH) used in the handling of Certificate // Transparency. Broadcast to each NetworkContext using the NetworkService. // NetworkContextes ignore STHs from unrecognized logs. + [EnableIf=is_ct_supported] UpdateSignedTreeHead(SignedTreeHead signed_tree_head); // Updates the CRLSet used in the verification of certificates. CRLSets that @@ -311,6 +328,9 @@ interface NetworkService { // this call, will use the same CRLSet. UpdateCRLSet(mojo_base.mojom.ReadOnlyBuffer crl_set); + // Notification that the certificate database has been modified. + OnCertDBChanged(); + // Sets up OSCrypt for the network service process. Must be called before // encrypted cookies can be read or set. [EnableIf=needs_crypt_config] @@ -332,6 +352,9 @@ interface NetworkService { // Reverts AddCorbExceptionForPlugin. RemoveCorbExceptionForPlugin(uint32 process_id); + // Called when the system is low on memory. + OnMemoryPressure(MemoryPressureLevel memory_pressure_level); + // Called on state changes of the Android application. [EnableIf=is_android] OnApplicationStateChange(ApplicationState state); diff --git a/chromium/services/network/public/mojom/network_service_test.mojom b/chromium/services/network/public/mojom/network_service_test.mojom index 90149711cdf..9925ae05e93 100644 --- a/chromium/services/network/public/mojom/network_service_test.mojom +++ b/chromium/services/network/public/mojom/network_service_test.mojom @@ -64,6 +64,18 @@ interface NetworkServiceTest { [Sync] SetShouldRequireCT(ShouldRequireCT required) => (); + // Set the global transport security state preloaded static data source to + // the unittest_default source, with the reporting URIs rewritten to use + // |reporting_port|. If |reporting_port| is 0, the source will be reset to + // the default. + [Sync] + SetTransportSecurityStateSource(uint16 reporting_port) => (); + // Causes the next host resolve to the given hostname to crash the process. CrashOnResolveHost(string host); + + // Gets the latest memory pressure level reported by the + // MemoryPressureListener. + [Sync] + GetLatestMemoryPressureLevel() => (MemoryPressureLevel memory_pressure_level); }; diff --git a/chromium/services/network/public/mojom/p2p.mojom b/chromium/services/network/public/mojom/p2p.mojom index ed16c381985..d19c2846851 100644 --- a/chromium/services/network/public/mojom/p2p.mojom +++ b/chromium/services/network/public/mojom/p2p.mojom @@ -5,8 +5,8 @@ module network.mojom; import "mojo/public/mojom/base/time.mojom"; -import "net/interfaces/ip_address.mojom"; -import "net/interfaces/ip_endpoint.mojom"; +import "services/network/public/mojom/ip_address.mojom"; +import "services/network/public/mojom/ip_endpoint.mojom"; import "services/network/public/mojom/mutable_network_traffic_annotation_tag.mojom"; [Native] @@ -32,19 +32,19 @@ enum P2PSocketOption; interface P2PNetworkNotificationClient { NetworkListChanged(array<NetworkInterface> networks, - net.interfaces.IPAddress default_ipv4_local_address, - net.interfaces.IPAddress default_ipv6_local_address); + IPAddress default_ipv4_local_address, + IPAddress default_ipv6_local_address); }; interface P2PSocketManager { // Starts listening to network list changed events. StartNetworkNotifications(P2PNetworkNotificationClient client); - GetHostAddress(string host_name) - => (array<net.interfaces.IPAddress> addresses); + GetHostAddress(string host_name, bool enable_mdns) + => (array<IPAddress> addresses); CreateSocket(P2PSocketType type, - net.interfaces.IPEndPoint local_address, + IPEndPoint local_address, P2PPortRange port_range, P2PHostAndIPEndPoint remote_address, P2PSocketClient client, @@ -60,13 +60,12 @@ interface P2PSocket { }; interface P2PSocketClient { - SocketCreated(net.interfaces.IPEndPoint local_address, - net.interfaces.IPEndPoint remote_address); + SocketCreated(IPEndPoint local_address, IPEndPoint remote_address); SendComplete(P2PSendPacketMetrics send_metrics); - IncomingTcpConnection(net.interfaces.IPEndPoint socket_address, + IncomingTcpConnection(IPEndPoint socket_address, P2PSocket socket, P2PSocketClient& client); - DataReceived(net.interfaces.IPEndPoint socket_address, + DataReceived(IPEndPoint socket_address, array<int8> data, mojo_base.mojom.TimeTicks timestamp); }; diff --git a/chromium/services/network/public/mojom/proxy_resolving_socket.mojom b/chromium/services/network/public/mojom/proxy_resolving_socket.mojom index 11c93585c6b..616e57f322e 100644 --- a/chromium/services/network/public/mojom/proxy_resolving_socket.mojom +++ b/chromium/services/network/public/mojom/proxy_resolving_socket.mojom @@ -4,7 +4,7 @@ module network.mojom; -import "net/interfaces/ip_endpoint.mojom"; +import "services/network/public/mojom/ip_endpoint.mojom"; import "services/network/public/mojom/mutable_network_traffic_annotation_tag.mojom"; import "services/network/public/mojom/network_param.mojom"; import "services/network/public/mojom/ssl_config.mojom"; @@ -36,14 +36,24 @@ interface ProxyResolvingSocket { handle<data_pipe_producer>? send_stream); }; +struct ProxyResolvingSocketOptions { + // Establish a TLS connection on top of the TCP connection. + bool use_tls = false; + + // Tries to do a fake TLS handshake on the connection. + // This is sometimes used with XMPP to pass through proxies. + // See jingle_glue::FakeSSLClientSocket for more details. + // Should not be used with |use_tls| set to true. + bool fake_tls_handshake = false; +}; + // Factory interface for creating ProxyResolvingSocket. Each factory instance // has separate socket pools from the NetworkContext which created the // factory instance. interface ProxyResolvingSocketFactory { // Creates a socket connected to |url|. This connection might be done through - // proxies if any is set in system's proxy settings. If |use_tls|, a TLS - // connection will be established on top of a TCP connection. On success, - // |result| is net::OK. Caller is to use |send_stream| to send data and + // proxies if any is set in system's proxy settings. On success, |result| is + // net::OK. Caller is to use |send_stream| to send data and // |receive_stream| to receive data over the connection. On failure, |result| // is a network error code. |local_addr| contains the local address of the // socket. |peer_addr| contains the peer address. If socket is connected to a @@ -54,13 +64,14 @@ interface ProxyResolvingSocketFactory { // // Any sockets that are created but are yet to be destroyed will be destroyed // when the implementation of this factory goes away. - CreateProxyResolvingSocket(url.mojom.Url url, bool use_tls, + CreateProxyResolvingSocket(url.mojom.Url url, + ProxyResolvingSocketOptions? options, MutableNetworkTrafficAnnotationTag traffic_annotation, ProxyResolvingSocket& socket, SocketObserver? observer) => (int32 result, - net.interfaces.IPEndPoint? local_addr, - net.interfaces.IPEndPoint? peer_addr, + network.mojom.IPEndPoint? local_addr, + network.mojom.IPEndPoint? peer_addr, handle<data_pipe_consumer>? receive_stream, handle<data_pipe_producer>? send_stream); }; diff --git a/chromium/services/network/public/mojom/referrer_policy.mojom b/chromium/services/network/public/mojom/referrer_policy.mojom new file mode 100644 index 00000000000..b70c88673d9 --- /dev/null +++ b/chromium/services/network/public/mojom/referrer_policy.mojom @@ -0,0 +1,24 @@ +// Copyright 2018 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. + +module network.mojom; + +// Don't make backwards-incompatible changes to this definition! +// It's used in PageState serialization, so backwards incompatible changes +// would cause stored PageState objects to be un-parseable. Please contact the +// page state serialization owners before making such a change. +enum ReferrerPolicy { + kAlways, + kDefault, + kNoReferrerWhenDowngrade, + kNever, + kOrigin, + kOriginWhenCrossOrigin, + // This policy corresponds to strict-origin-when-cross-origin. + // TODO(estark): rename to match the spec. + kNoReferrerWhenDowngradeOriginWhenCrossOrigin, + kSameOrigin, + kStrictOrigin, + kLast = kStrictOrigin, +};
\ No newline at end of file diff --git a/chromium/services/network/public/mojom/ssl_config.mojom b/chromium/services/network/public/mojom/ssl_config.mojom index a8a4122fa13..bca851a6a1a 100644 --- a/chromium/services/network/public/mojom/ssl_config.mojom +++ b/chromium/services/network/public/mojom/ssl_config.mojom @@ -29,7 +29,7 @@ struct SSLConfig { // SSL 2.0 and 3.0 are not supported. Note these lines must be kept in sync // with net/ssl/ssl_config.cc. SSLVersion version_min = kTLS1; - SSLVersion version_max = kTLS12; + SSLVersion version_max = kTLS13; TLS13Variant tls13_variant = kFinal; diff --git a/chromium/services/network/public/mojom/tcp_socket.mojom b/chromium/services/network/public/mojom/tcp_socket.mojom index cb9d63b1a07..5637a880258 100644 --- a/chromium/services/network/public/mojom/tcp_socket.mojom +++ b/chromium/services/network/public/mojom/tcp_socket.mojom @@ -4,8 +4,8 @@ module network.mojom; -import "net/interfaces/address_list.mojom"; -import "net/interfaces/ip_endpoint.mojom"; +import "services/network/public/mojom/address_list.mojom"; +import "services/network/public/mojom/ip_endpoint.mojom"; import "services/network/public/mojom/ssl_config.mojom"; import "services/network/public/mojom/tls_socket.mojom"; import "services/network/public/mojom/network_param.mojom"; @@ -47,13 +47,13 @@ interface TCPBoundSocket { // already bound socket. The TCPBoundSocket will be destroyed on completion, // whether the call succeeds or not. Connect( - net.interfaces.AddressList remote_addr_list, + AddressList remote_addr_list, TCPConnectedSocketOptions? tcp_connected_socket_options, TCPConnectedSocket& socket, SocketObserver? observer) => (int32 net_error, - net.interfaces.IPEndPoint? local_addr, - net.interfaces.IPEndPoint? peer_addr, + IPEndPoint? local_addr, + IPEndPoint? peer_addr, handle<data_pipe_consumer>? receive_stream, handle<data_pipe_producer>? send_stream); }; @@ -144,7 +144,7 @@ interface TCPServerSocket { // UpgradeToTLS only supports the client part of the TLS handshake. Accept(SocketObserver? observer) => (int32 net_error, - net.interfaces.IPEndPoint? remote_addr, + IPEndPoint? remote_addr, TCPConnectedSocket? connected_socket, handle<data_pipe_consumer>? send_stream, handle<data_pipe_producer>? receive_stream); diff --git a/chromium/services/network/public/mojom/tls_socket.mojom b/chromium/services/network/public/mojom/tls_socket.mojom index 35e96296a07..55db8a8e024 100644 --- a/chromium/services/network/public/mojom/tls_socket.mojom +++ b/chromium/services/network/public/mojom/tls_socket.mojom @@ -4,7 +4,7 @@ module network.mojom; -import "net/interfaces/ip_endpoint.mojom"; +import "services/network/public/mojom/ip_endpoint.mojom"; import "services/network/public/mojom/ssl_config.mojom"; // Represents a connected TLS client socket. Writes and Reads are through the @@ -19,7 +19,7 @@ interface TLSClientSocket { // TLSClientSocket. struct TLSClientSocketOptions { SSLVersion version_min = kTLS1; - SSLVersion version_max = kTLS12; + SSLVersion version_max = kTLS13; // If true, the SSLInfo will be returned in the UpgradeToTLS callback on // success. diff --git a/chromium/services/network/public/mojom/udp_socket.mojom b/chromium/services/network/public/mojom/udp_socket.mojom index baf71bff771..c97b333840b 100644 --- a/chromium/services/network/public/mojom/udp_socket.mojom +++ b/chromium/services/network/public/mojom/udp_socket.mojom @@ -4,11 +4,10 @@ module network.mojom; -import "services/network/public/mojom/mutable_network_traffic_annotation_tag.mojom"; import "mojo/public/mojom/base/read_only_buffer.mojom"; -import "net/interfaces/address_family.mojom"; -import "net/interfaces/ip_address.mojom"; -import "net/interfaces/ip_endpoint.mojom"; +import "services/network/public/mojom/ip_address.mojom"; +import "services/network/public/mojom/ip_endpoint.mojom"; +import "services/network/public/mojom/mutable_network_traffic_annotation_tag.mojom"; // Represents options that consumers can set when requesting a UDPSocket // interface pointer. @@ -16,12 +15,23 @@ struct UDPSocketOptions { // If true, this enables SO_REUSEADDR on the underlying socket. bool allow_address_reuse = false; - // It true, allows sending and receiving packets to and from broadcast + // If true, allows sending and receiving packets to and from broadcast // addresses. It's recommended this be used instead of SetBroadcast(), as // Bind() may fail on some platforms when reusing a UDP port and broadcast // is not enabled when the socket is created. bool allow_broadcast = false; + // If true, allows the socket to share the local address to which the socket + // will be bound with other processes and attempts to allow all such sockets + // to receive the same multicast messages. + // + // For best cross-platform results in allowing the messages to be shared, all + // sockets sharing the same address should join the same multicast group (via + // UDPSocket::JoinGroup()) and set the same |multicast_interface|. Also, the + // socket should bind to the specific multicast group address rather than a + // wildcard address (e.g. 0.0.0.0) on platforms where doing so is allowed. + bool allow_address_sharing_for_multicast = false; + // Sets interface to use for multicast. Default value is 0, in which case the // default interface is used. uint32 multicast_interface = 0; @@ -79,20 +89,19 @@ interface UDPSocket { // configures the socket with the options before binding the socket. // Returns net::OK and the real local address used on success and a negative // net error code on failure. - Bind(net.interfaces.IPEndPoint local_addr, UDPSocketOptions? socket_options) - => (int32 result, net.interfaces.IPEndPoint? local_addr_out); + Bind(IPEndPoint local_addr, UDPSocketOptions? socket_options) + => (int32 result, IPEndPoint? local_addr_out); // Connects the socket to |remote_addr|. This automatically binds the socket // to an available local port, so this cannot be used with Bind(). // If |socket_options| is not null, configures the socket with the options // before connecting the socket. // The address family of the local socket will be of the same - // net.interfaces.AddressFamily as |remote_addr|. Returns net::OK and the - // local address of socket on success. Subsequent packets received will be - // from |remote_addr|. Returns a negative net error code on failure. - Connect(net.interfaces.IPEndPoint remote_addr, - UDPSocketOptions? socket_options) => - (int32 result, net.interfaces.IPEndPoint? local_addr_out); + // AddressFamily as |remote_addr|. Returns net::OK and the local address of + // socket on success. Subsequent packets received will be from |remote_addr|. + // Returns a negative net error code on failure. + Connect(IPEndPoint remote_addr, UDPSocketOptions? socket_options) => + (int32 result, IPEndPoint? local_addr_out); // Allows or disallows sending and receiving packets to and from broadcast // addresses. Returns a net error code. Should only be called after Bind(). @@ -111,13 +120,13 @@ interface UDPSocket { // Joins a multicast group. |group_address| is the group address to join, // could be either an IPv4 or IPv6 address. Returns a net error code. // See RFC 1112 for details on multicast. - JoinGroup(net.interfaces.IPAddress group_address) => (int32 result); + JoinGroup(IPAddress group_address) => (int32 result); // Leaves the multicast group. |group_address| is the group address to leave, // could be either an IPv4 or IPv6 address. If the socket hasn't joined the // group, this call will be ignored. It's optional to leave the multicast // group before destroying the socket. Returns a net error code. - LeaveGroup(net.interfaces.IPAddress group_address) => (int32 result); + LeaveGroup(IPAddress group_address) => (int32 result); // Notifies that the receiver is ready to accept |number| of datagrams. // Correspondingly, OnReceived() of the UDPSocketReceiver interface will be @@ -180,7 +189,7 @@ interface UDPSocket { // sufficient resource to complete the operation. When this happens, the // requests will be failed quickly (which might happen before the completion // of requests that were sent earlier). - SendTo(net.interfaces.IPEndPoint dest_addr, + SendTo(IPEndPoint dest_addr, mojo_base.mojom.ReadOnlyBuffer data, MutableNetworkTrafficAnnotationTag traffic_annotation) => (int32 result); @@ -214,6 +223,6 @@ interface UDPSocketReceiver { // Note that in both cases, |data| can be an empty buffer when |result| is // net::OK, which indicates a zero-byte payload. OnReceived(int32 result, - net.interfaces.IPEndPoint? src_addr, + IPEndPoint? src_addr, mojo_base.mojom.ReadOnlyBuffer? data); }; diff --git a/chromium/services/network/public/mojom/url_loader.mojom b/chromium/services/network/public/mojom/url_loader.mojom index c17e5bdd59d..f4ee750a37b 100644 --- a/chromium/services/network/public/mojom/url_loader.mojom +++ b/chromium/services/network/public/mojom/url_loader.mojom @@ -6,6 +6,7 @@ module network.mojom; import "services/network/public/mojom/http_request_headers.mojom"; import "services/network/public/mojom/network_param.mojom"; +import "url/mojom/url.mojom"; [Native] struct URLRequest; @@ -17,7 +18,7 @@ struct URLResponseHead; struct URLRequestRedirectInfo; [Native] -struct CORSErrorStatus; +struct CorsErrorStatus; [Native] struct URLLoaderCompletionStatus; @@ -46,9 +47,13 @@ interface URLLoader { // the redirect. This parameter is before |modified_request_headers| since // removing headers is applied first in the URLLoader::FollowRedirect(). // |modified_request_headers| can be used to add or override existing headers - // for the redirect. + // for the redirect. If |new_url| is specified, then the request will be made + // to it instead of the redirected URL. Note: it has to be in the same origin + // as the redirected URL, and this is only supported when the network service + // is enabled. FollowRedirect(array<string>? to_be_removed_request_headers, - network.mojom.HttpRequestHeaders? modified_request_headers); + network.mojom.HttpRequestHeaders? modified_request_headers, + url.mojom.Url? new_url); // Resumes loading the response body if the URLLoader paused the request upon // receiving the final response headers. diff --git a/chromium/services/network/public/mojom/url_loader_factory.mojom b/chromium/services/network/public/mojom/url_loader_factory.mojom index 139ad39ecc7..201fc38440c 100644 --- a/chromium/services/network/public/mojom/url_loader_factory.mojom +++ b/chromium/services/network/public/mojom/url_loader_factory.mojom @@ -22,6 +22,8 @@ const uint32 kURLLoadOptionSendSSLInfoForCertificateError = 8; // TODO(arthursonzogni): This is a temporary feature. Remove this as soon as // the InterceptingResourceHandler is removed. See https://crbug.com/791049. const uint32 kURLLoadOptionPauseOnResponseStarted = 16; +// Uses the header client set in URLLoaderFactoryParams for this request. +const uint32 kURLLoadOptionUseHeaderClient = 32; interface URLLoaderFactory { // Creates a URLLoader and starts loading with the given |request|. |client|'s diff --git a/chromium/services/network/public/mojom/websocket.mojom b/chromium/services/network/public/mojom/websocket.mojom index b007d38a7c4..bc9bde7ad03 100644 --- a/chromium/services/network/public/mojom/websocket.mojom +++ b/chromium/services/network/public/mojom/websocket.mojom @@ -52,8 +52,6 @@ interface WebSocketClient { OnFailChannel(string reason); // Notify the renderer that the browser has started an opening handshake. - // This message is for showing the request in the inspector and - // can be omitted if the inspector is not active. OnStartOpeningHandshake(WebSocketHandshakeRequest request); // Notify the renderer that the browser has finished an opening handshake. diff --git a/chromium/services/network/resource_scheduler.cc b/chromium/services/network/resource_scheduler.cc index bf92db77881..8448f97a50b 100644 --- a/chromium/services/network/resource_scheduler.cc +++ b/chromium/services/network/resource_scheduler.cc @@ -20,6 +20,8 @@ #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/supports_user_data.h" +#include "base/time/default_tick_clock.h" +#include "base/time/tick_clock.h" #include "base/trace_event/trace_event.h" #include "net/base/host_port_pair.h" #include "net/base/load_flags.h" @@ -51,18 +53,6 @@ const base::Feature kPrioritySupportedRequestsDelayable{ const base::Feature kHeadPrioritySupportedRequestsDelayable{ "HeadPriorityRequestsDelayable", base::FEATURE_DISABLED_BY_DEFAULT}; -// In the event that many resource requests are started quickly, this feature -// will periodically yield (e.g., delaying starting of requests) by posting a -// task and waiting for the task to run to resume. This allows other -// operations that rely on the IO thread (e.g., already running network -// requests) to make progress. -const base::Feature kNetworkSchedulerYielding{ - "NetworkSchedulerYielding", base::FEATURE_DISABLED_BY_DEFAULT}; -const char kMaxRequestsBeforeYieldingParam[] = "MaxRequestsBeforeYieldingParam"; -const int kMaxRequestsBeforeYieldingDefault = 5; -const char kYieldMsParam[] = "MaxYieldMs"; -const int kYieldMsDefault = 0; - enum StartMode { START_SYNC, START_ASYNC }; // Flags identifying various attributes of the request that are used @@ -82,7 +72,7 @@ enum class RequestStartTrigger { CLIENT_KILL, SPDY_PROXY_DETECTED, REQUEST_REPRIORITIZED, - START_WAS_YIELDED, + LONG_QUEUED_REQUESTS_TIMER_FIRED, }; const char* RequestStartTriggerString(RequestStartTrigger trigger) { @@ -101,11 +91,9 @@ const char* RequestStartTriggerString(RequestStartTrigger trigger) { return "SPDY_PROXY_DETECTED"; case RequestStartTrigger::REQUEST_REPRIORITIZED: return "REQUEST_REPRIORITIZED"; - case RequestStartTrigger::START_WAS_YIELDED: - return "START_WAS_YIELDED"; + case RequestStartTrigger::LONG_QUEUED_REQUESTS_TIMER_FIRED: + return "LONG_QUEUED_REQUESTS_TIMER_FIRED"; } - NOTREACHED(); - return "Unknown"; } } // namespace @@ -126,6 +114,15 @@ static const net::RequestPriority kDelayablePriorityThreshold = net::MEDIUM; // requests should be blocked. static const size_t kInFlightNonDelayableRequestCountPerClientThreshold = 1; +// Duration after which the timer to dispatch long queued requests should fire. +// The request needs to be queued for at least 15 seconds before it can be +// dispatched. Choosing 5 seconds as the checking interval ensures that the +// queue is not checked too frequently. The interval is also not too long, so +// we do not expect too many long queued requests to go on the network at the +// same time. +constexpr base::TimeDelta kLongQueuedRequestsDispatchPeriodicity = + base::TimeDelta::FromSeconds(5); + struct ResourceScheduler::RequestPriorityParams { RequestPriorityParams() : priority(net::DEFAULT_PRIORITY), intra_priority(0) {} @@ -379,16 +376,18 @@ void ResourceScheduler::RequestQueue::Insert( class ResourceScheduler::Client { public: Client(const net::NetworkQualityEstimator* const network_quality_estimator, - ResourceScheduler* resource_scheduler) + ResourceScheduler* resource_scheduler, + const base::TickClock* tick_clock) : deprecated_is_loaded_(false), in_flight_delayable_count_(0), total_layout_blocking_count_(0), num_skipped_scans_due_to_scheduled_start_(0), - started_requests_since_yielding_(0), - did_scheduler_yield_(false), network_quality_estimator_(network_quality_estimator), resource_scheduler_(resource_scheduler), + tick_clock_(tick_clock), weak_ptr_factory_(this) { + DCHECK(tick_clock_); + UpdateParamsForNetworkQuality(); // Must not run the conflicting experiments together. DCHECK(!params_for_network_quality_ @@ -410,8 +409,6 @@ class ResourceScheduler::Client { StartRequest(request, START_SYNC, RequestStartTrigger::NONE); } else { pending_requests_.Insert(request); - if (should_start == YIELD_SCHEDULER) - did_scheduler_yield_ = true; } } @@ -496,12 +493,18 @@ class ResourceScheduler::Client { : net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN); } + void OnLongQueuedRequestsDispatchTimerFired() { + LoadAnyStartablePendingRequests( + RequestStartTrigger::LONG_QUEUED_REQUESTS_TIMER_FIRED); + } + + bool HasNoPendingRequests() const { return pending_requests_.IsEmpty(); } + private: enum ShouldStartReqResult { DO_NOT_START_REQUEST_AND_STOP_SEARCHING, DO_NOT_START_REQUEST_AND_KEEP_SEARCHING, - START_REQUEST, - YIELD_SCHEDULER + START_REQUEST }; // Records the metrics related to number of requests in flight. @@ -689,21 +692,6 @@ class ResourceScheduler::Client { void StartRequest(ScheduledResourceRequestImpl* request, StartMode start_mode, RequestStartTrigger trigger) { - if (resource_scheduler_->yielding_scheduler_enabled()) { - started_requests_since_yielding_ += 1; - if (started_requests_since_yielding_ == 1) { - // This is the first started request since last yielding. Post a task to - // reset the counter and start any yielded tasks if necessary. We post - // this now instead of when we first yield so that if there is a pause - // between requests the counter is reset. - resource_scheduler_->task_runner()->PostDelayedTask( - FROM_HERE, - base::BindOnce(&Client::ResumeIfYielded, - weak_ptr_factory_.GetWeakPtr()), - resource_scheduler_->yield_time()); - } - } - // Only log on requests that were blocked by the ResourceScheduler. if (start_mode == START_ASYNC) { DCHECK_NE(RequestStartTrigger::NONE, trigger); @@ -782,6 +770,12 @@ class ResourceScheduler::Client { if (!url_request.url().SchemeIsHTTPOrHTTPS()) return START_REQUEST; + if (params_for_network_quality_.max_queuing_time && + tick_clock_->NowTicks() - url_request.creation_time() >= + params_for_network_quality_.max_queuing_time) { + return START_REQUEST; + } + const net::HostPortPair& host_port_pair = request->host_port_pair(); bool priority_delayable = @@ -798,7 +792,7 @@ class ResourceScheduler::Client { // https://crbug.com/164101. Also, theoretically we should not count a // request-priority capable request against the delayable requests limit. if (supports_priority) - return ShouldStartOrYieldRequest(request); + return START_REQUEST; } // Non-delayable requests. @@ -867,34 +861,6 @@ class ResourceScheduler::Client { num_skipped_scans_due_to_scheduled_start_ += 1; } - void ResumeIfYielded() { - bool yielded = did_scheduler_yield_; - started_requests_since_yielding_ = 0; - did_scheduler_yield_ = false; - - if (yielded) - LoadAnyStartablePendingRequests(RequestStartTrigger::START_WAS_YIELDED); - } - - // For a request that is ready to start, return START_REQUEST if the - // scheduler doesn't need to yield, else YIELD_SCHEDULER. - ShouldStartReqResult ShouldStartOrYieldRequest( - ScheduledResourceRequestImpl* request) const { - DCHECK_GE(started_requests_since_yielding_, 0); - - // Don't yield if: - // 1. The yielding scheduler isn't enabled - // 2. The resource is high priority - // 3. There haven't been enough recent requests to warrant yielding. - if (!resource_scheduler_->yielding_scheduler_enabled() || - request->url_request()->priority() >= kDelayablePriorityThreshold || - started_requests_since_yielding_ < - resource_scheduler_->max_requests_before_yielding()) { - return START_REQUEST; - } - return YIELD_SCHEDULER; - } - void LoadAnyStartablePendingRequests(RequestStartTrigger trigger) { // We iterate through all the pending requests, starting with the highest // priority one. For each entry, one of three things can happen: @@ -931,9 +897,6 @@ class ResourceScheduler::Client { } else if (query_result == DO_NOT_START_REQUEST_AND_KEEP_SEARCHING) { ++request_iter; continue; - } else if (query_result == YIELD_SCHEDULER) { - did_scheduler_yield_ = true; - break; } else { DCHECK(query_result == DO_NOT_START_REQUEST_AND_STOP_SEARCHING); break; @@ -957,14 +920,6 @@ class ResourceScheduler::Client { // to smarter task scheduling around reprioritization. int num_skipped_scans_due_to_scheduled_start_; - // The number of started requests since the last ResumeIfYielded task was - // run. - int started_requests_since_yielding_; - - // If the scheduler had to yield the start of a request since the last - // ResumeIfYielded task was run. - bool did_scheduler_yield_; - // Network quality estimator for network aware resource scheudling. This may // be null. const net::NetworkQualityEstimator* const network_quality_estimator_; @@ -978,29 +933,29 @@ class ResourceScheduler::Client { // configuration. ResourceScheduler* resource_scheduler_; + // Guaranteed to be non-null. + const base::TickClock* tick_clock_; + base::WeakPtrFactory<ResourceScheduler::Client> weak_ptr_factory_; }; -ResourceScheduler::ResourceScheduler(bool enabled) - : enabled_(enabled), +ResourceScheduler::ResourceScheduler(bool enabled, + const base::TickClock* tick_clock) + : tick_clock_(tick_clock ? tick_clock + : base::DefaultTickClock::GetInstance()), + enabled_(enabled), priority_requests_delayable_( base::FeatureList::IsEnabled(kPrioritySupportedRequestsDelayable)), head_priority_requests_delayable_(base::FeatureList::IsEnabled( kHeadPrioritySupportedRequestsDelayable)), - yielding_scheduler_enabled_( - base::FeatureList::IsEnabled(kNetworkSchedulerYielding)), - max_requests_before_yielding_(base::GetFieldTrialParamByFeatureAsInt( - kNetworkSchedulerYielding, - kMaxRequestsBeforeYieldingParam, - kMaxRequestsBeforeYieldingDefault)), - yield_time_(base::TimeDelta::FromMilliseconds( - base::GetFieldTrialParamByFeatureAsInt(kNetworkSchedulerYielding, - kYieldMsParam, - kYieldMsDefault))), task_runner_(base::ThreadTaskRunnerHandle::Get()) { + DCHECK(tick_clock_); + // Don't run the two experiments together. if (priority_requests_delayable_ && head_priority_requests_delayable_) priority_requests_delayable_ = false; + + StartLongQueuedRequestsDispatchTimerIfNeeded(); } ResourceScheduler::~ResourceScheduler() { @@ -1034,6 +989,10 @@ ResourceScheduler::ScheduleRequest(int child_id, Client* client = it->second.get(); client->ScheduleRequest(*url_request, request.get()); + + if (!IsLongQueuedRequestsDispatchTimerRunning()) + StartLongQueuedRequestsDispatchTimerIfNeeded(); + return std::move(request); } @@ -1061,16 +1020,25 @@ void ResourceScheduler::OnClientCreated( DCHECK(!base::ContainsKey(client_map_, client_id)); client_map_[client_id] = - std::make_unique<Client>(network_quality_estimator, this); + std::make_unique<Client>(network_quality_estimator, this, tick_clock_); } void ResourceScheduler::OnClientDeleted(int child_id, int route_id) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + ClientId client_id = MakeClientId(child_id, route_id); ClientMap::iterator it = client_map_.find(client_id); - DCHECK(it != client_map_.end()); + // TODO(crbug.com/873959): Turns this CHECK to DCHECK once the investigation + // is done. + CHECK(it != client_map_.end()); Client* client = it->second.get(); + // TODO(crbug.com/873959): Remove this CHECK once the investigation is done. + CHECK(client); + DCHECK(!base::FeatureList::IsEnabled( + features::kUnthrottleRequestsAfterLongQueuingDelay) || + client->HasNoPendingRequests() || + IsLongQueuedRequestsDispatchTimerRunning()); // ResourceDispatcherHost cancels all requests except for cross-renderer // navigations, async revalidations and detachable requests after // OnClientDeleted() returns. @@ -1122,6 +1090,40 @@ ResourceScheduler::Client* ResourceScheduler::GetClient(int child_id, return client_it->second.get(); } +void ResourceScheduler::StartLongQueuedRequestsDispatchTimerIfNeeded() { + if (!base::FeatureList::IsEnabled( + features::kUnthrottleRequestsAfterLongQueuingDelay)) { + return; + } + + bool pending_request_found = false; + for (const auto& client : client_map_) { + if (!client.second->HasNoPendingRequests()) { + pending_request_found = true; + break; + } + } + + // If there are no pending requests, then do not start the timer. This ensures + // that we are not running the periodic timer when Chrome is not being + // actively used (e.g., it's in background). + if (!pending_request_found) + return; + + long_queued_requests_dispatch_timer_.Start( + FROM_HERE, kLongQueuedRequestsDispatchPeriodicity, this, + &ResourceScheduler::OnLongQueuedRequestsDispatchTimerFired); +} + +void ResourceScheduler::OnLongQueuedRequestsDispatchTimerFired() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + for (auto& client : client_map_) + client.second->OnLongQueuedRequestsDispatchTimerFired(); + + StartLongQueuedRequestsDispatchTimerIfNeeded(); +} + void ResourceScheduler::ReprioritizeRequest(net::URLRequest* request, net::RequestPriority new_priority, int new_intra_priority_value) { @@ -1178,6 +1180,11 @@ ResourceScheduler::ClientId ResourceScheduler::MakeClientId(int child_id, return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id; } +bool ResourceScheduler::IsLongQueuedRequestsDispatchTimerRunning() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return long_queued_requests_dispatch_timer_.IsRunning(); +} + void ResourceScheduler::SetResourceSchedulerParamsManagerForTests( const ResourceSchedulerParamsManager& resource_scheduler_params_manager) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); @@ -1187,4 +1194,9 @@ void ResourceScheduler::SetResourceSchedulerParamsManagerForTests( } } +void ResourceScheduler::DispatchLongQueuedRequestsForTesting() { + long_queued_requests_dispatch_timer_.Stop(); + OnLongQueuedRequestsDispatchTimerFired(); +} + } // namespace network diff --git a/chromium/services/network/resource_scheduler.h b/chromium/services/network/resource_scheduler.h index 39500f29029..a4bfbfcef64 100644 --- a/chromium/services/network/resource_scheduler.h +++ b/chromium/services/network/resource_scheduler.h @@ -22,6 +22,7 @@ #include "base/memory/ref_counted.h" #include "base/sequence_checker.h" #include "base/time/time.h" +#include "base/timer/timer.h" #include "net/base/priority_queue.h" #include "net/base/request_priority.h" #include "net/nqe/effective_connection_type.h" @@ -29,6 +30,7 @@ namespace base { class SequencedTaskRunner; +class TickClock; } namespace net { @@ -81,7 +83,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ResourceScheduler { base::OnceClosure resume_callback_; }; - explicit ResourceScheduler(bool enabled); + explicit ResourceScheduler(bool enabled, + const base::TickClock* tick_clock = nullptr); ~ResourceScheduler(); // Requests that this ResourceScheduler schedule, and eventually loads, the @@ -136,29 +139,18 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ResourceScheduler { void ReprioritizeRequest(net::URLRequest* request, net::RequestPriority new_priority); + // Returns true if the timer that dispatches long queued requests is running. + bool IsLongQueuedRequestsDispatchTimerRunning() const; + bool priority_requests_delayable() const { return priority_requests_delayable_; } bool head_priority_requests_delayable() const { return head_priority_requests_delayable_; } - bool yielding_scheduler_enabled() const { - return yielding_scheduler_enabled_; - } - int max_requests_before_yielding() const { - return max_requests_before_yielding_; - } - base::TimeDelta yield_time() const { return yield_time_; } base::SequencedTaskRunner* task_runner() { return task_runner_.get(); } // Testing setters - void SetMaxRequestsBeforeYieldingForTesting( - int max_requests_before_yielding) { - max_requests_before_yielding_ = max_requests_before_yielding; - } - void SetYieldTimeForTesting(base::TimeDelta yield_time) { - yield_time_ = yield_time; - } void SetTaskRunnerForTesting( scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner) { task_runner_ = std::move(sequenced_task_runner); @@ -169,6 +161,9 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ResourceScheduler { void SetResourceSchedulerParamsManagerForTests( const ResourceSchedulerParamsManager& resource_scheduler_params_manager); + // Dispatch requests that have been queued for too long to network. + void DispatchLongQueuedRequestsForTesting(); + private: class Client; class RequestQueue; @@ -192,9 +187,22 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ResourceScheduler { // Returns the client for the given |child_id| and |route_id| combo. Client* GetClient(int child_id, int route_id); + // May start the timer that dispatches long queued requests + void StartLongQueuedRequestsDispatchTimerIfNeeded(); + + // Called when |long_queued_requests_dispatch_timer_| is fired. May start any + // pending requests that can be started. + void OnLongQueuedRequestsDispatchTimerFired(); + ClientMap client_map_; RequestSet unowned_requests_; + // Guaranteed to be non-null. + const base::TickClock* tick_clock_; + + // Timer to dispatch requests that may have been queued for too long. + base::OneShotTimer long_queued_requests_dispatch_timer_; + // Whether or not to enable ResourceScheduling. This will almost always be // enabled, except for some C++ headless embedders who may implement their own // resource scheduling via protocol handlers. @@ -208,12 +216,6 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ResourceScheduler { // be delayed while the parser is in head. bool head_priority_requests_delayable_; - // True if the scheduler should yield between several successive calls to - // start resource requests. - bool yielding_scheduler_enabled_; - int max_requests_before_yielding_; - base::TimeDelta yield_time_; - ResourceSchedulerParamsManager resource_scheduler_params_manager_; // The TaskRunner to post tasks on. Can be overridden for tests. diff --git a/chromium/services/network/resource_scheduler_params_manager.cc b/chromium/services/network/resource_scheduler_params_manager.cc index f363f2ab638..a8904a6aa98 100644 --- a/chromium/services/network/resource_scheduler_params_manager.cc +++ b/chromium/services/network/resource_scheduler_params_manager.cc @@ -10,120 +10,53 @@ #include "base/optional.h" #include "base/strings/string_number_conversions.h" #include "net/nqe/network_quality_estimator.h" +#include "net/nqe/network_quality_estimator_params.h" #include "services/network/public/cpp/features.h" +namespace network { + namespace { // The maximum number of delayable requests to allow to be in-flight at any // point in time (across all hosts). static const size_t kDefaultMaxNumDelayableRequestsPerClient = 10; -} // namespace - -namespace network { - -ResourceSchedulerParamsManager::ParamsForNetworkQuality:: - ParamsForNetworkQuality() - : ResourceSchedulerParamsManager::ParamsForNetworkQuality( - kDefaultMaxNumDelayableRequestsPerClient, - 0.0, - false) {} - -ResourceSchedulerParamsManager::ParamsForNetworkQuality:: - ParamsForNetworkQuality(size_t max_delayable_requests, - double non_delayable_weight, - bool delay_requests_on_multiplexed_connections) - : max_delayable_requests(max_delayable_requests), - non_delayable_weight(non_delayable_weight), - delay_requests_on_multiplexed_connections( - delay_requests_on_multiplexed_connections) {} - -ResourceSchedulerParamsManager::ResourceSchedulerParamsManager() - : ResourceSchedulerParamsManager( - GetParamsForDelayRequestsOnMultiplexedConnections( - GetParamsForNetworkQualityContainer())) {} - -ResourceSchedulerParamsManager::ResourceSchedulerParamsManager( - const ParamsForNetworkQualityContainer& - params_for_network_quality_container) - : params_for_network_quality_container_( - params_for_network_quality_container) {} - -ResourceSchedulerParamsManager::ResourceSchedulerParamsManager( - const ResourceSchedulerParamsManager& other) - : params_for_network_quality_container_( - other.params_for_network_quality_container_) {} - -ResourceSchedulerParamsManager::~ResourceSchedulerParamsManager() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -} - -ResourceSchedulerParamsManager::ParamsForNetworkQuality -ResourceSchedulerParamsManager::GetParamsForEffectiveConnectionType( - net::EffectiveConnectionType effective_connection_type) const { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - ParamsForNetworkQualityContainer::const_iterator iter = - params_for_network_quality_container_.find(effective_connection_type); - if (iter != params_for_network_quality_container_.end()) - return iter->second; - return ParamsForNetworkQuality(kDefaultMaxNumDelayableRequestsPerClient, 0.0, - false); -} - -// static +// Reads experiment parameters and returns them. ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer -ResourceSchedulerParamsManager:: - GetParamsForDelayRequestsOnMultiplexedConnections( - ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer - result) { - if (!base::FeatureList::IsEnabled( - features::kDelayRequestsOnMultiplexedConnections)) { - return result; - } - - base::Optional<net::EffectiveConnectionType> max_effective_connection_type = - net::GetEffectiveConnectionTypeForName( - base::GetFieldTrialParamValueByFeature( - features::kDelayRequestsOnMultiplexedConnections, - "MaxEffectiveConnectionType")); - - if (!max_effective_connection_type) { - // Use a default value if one is not set using field trial params. - max_effective_connection_type = net::EFFECTIVE_CONNECTION_TYPE_3G; - } - - for (int ect = net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; - ect <= max_effective_connection_type.value(); ++ect) { - net::EffectiveConnectionType effective_connection_type = - static_cast<net::EffectiveConnectionType>(ect); - ParamsForNetworkQualityContainer::iterator iter = - result.find(effective_connection_type); - if (iter != result.end()) { - iter->second.delay_requests_on_multiplexed_connections = true; - } else { - result.emplace(std::make_pair( - effective_connection_type, - ParamsForNetworkQuality(kDefaultMaxNumDelayableRequestsPerClient, 0.0, - true))); - } - } - return result; -} - -// static -ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer -ResourceSchedulerParamsManager::GetParamsForNetworkQualityContainer() { +GetParamsForNetworkQualityContainer() { + // Look for configuration parameters with sequential numeric suffixes, and + // stop looking after the first failure to find an experimetal parameter. + // A sample configuration is given below: + // "EffectiveConnectionType1": "Slow-2G", + // "MaxDelayableRequests1": "6", + // "NonDelayableWeight1": "2.0", + // "EffectiveConnectionType2": "3G", + // "MaxDelayableRequests2": "12", + // "NonDelayableWeight2": "3.0", + // This config implies that when Effective Connection Type (ECT) is Slow-2G, + // then the maximum number of non-delayable requests should be + // limited to 6, and the non-delayable request weight should be set to 2. + // When ECT is 3G, it should be limited to 12. For all other values of ECT, + // the default values are used. static const char kMaxDelayableRequestsBase[] = "MaxDelayableRequests"; static const char kEffectiveConnectionTypeBase[] = "EffectiveConnectionType"; static const char kNonDelayableWeightBase[] = "NonDelayableWeight"; + static constexpr base::TimeDelta kUpperBoundQueuingDuration = + base::TimeDelta::FromSeconds(120); + static constexpr base::TimeDelta kLowerBoundQueuingDuration = + base::TimeDelta::FromSeconds(15); ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer result; // Set the default params for networks with ECT Slow2G and 2G. These params // can still be overridden using the field trial. - result.emplace(std::make_pair(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, - ParamsForNetworkQuality(8, 3.0, false))); - result.emplace(std::make_pair(net::EFFECTIVE_CONNECTION_TYPE_2G, - ParamsForNetworkQuality(8, 3.0, false))); + result.emplace( + std::make_pair(net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, + ResourceSchedulerParamsManager::ParamsForNetworkQuality( + 8, 3.0, false, base::nullopt))); + result.emplace( + std::make_pair(net::EFFECTIVE_CONNECTION_TYPE_2G, + ResourceSchedulerParamsManager::ParamsForNetworkQuality( + 8, 3.0, false, base::nullopt))); for (int config_param_index = 1; config_param_index <= 20; ++config_param_index) { @@ -134,7 +67,7 @@ ResourceSchedulerParamsManager::GetParamsForNetworkQualityContainer() { kMaxDelayableRequestsBase + base::IntToString(config_param_index)), &max_delayable_requests)) { - return result; + break; } base::Optional<net::EffectiveConnectionType> effective_connection_type = @@ -151,21 +84,146 @@ ResourceSchedulerParamsManager::GetParamsForNetworkQualityContainer() { // Check if the entry is already present. This will happen if the default // params are being overridden by the field trial. - ParamsForNetworkQualityContainer::iterator iter = - result.find(effective_connection_type.value()); + ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer::iterator + iter = result.find(effective_connection_type.value()); if (iter != result.end()) { iter->second.max_delayable_requests = max_delayable_requests; iter->second.non_delayable_weight = non_delayable_weight; } else { - result.emplace( - std::make_pair(effective_connection_type.value(), - ParamsForNetworkQuality(max_delayable_requests, - non_delayable_weight, false))); + result.emplace(std::make_pair( + effective_connection_type.value(), + ResourceSchedulerParamsManager::ParamsForNetworkQuality( + max_delayable_requests, non_delayable_weight, false, + base::nullopt))); } } - // There should not have been more than 20 params indices specified. - NOTREACHED(); + + // Next, read the experiments params for + // DelayRequestsOnMultiplexedConnections finch experiment, and modify |result| + // based on the experiment params. + if (base::FeatureList::IsEnabled( + features::kDelayRequestsOnMultiplexedConnections)) { + base::Optional<net::EffectiveConnectionType> max_effective_connection_type = + net::GetEffectiveConnectionTypeForName( + base::GetFieldTrialParamValueByFeature( + features::kDelayRequestsOnMultiplexedConnections, + "MaxEffectiveConnectionType")); + + if (!max_effective_connection_type) { + // Use a default value if one is not set using field trial params. + max_effective_connection_type = net::EFFECTIVE_CONNECTION_TYPE_3G; + } + + for (int ect = net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; + ect <= max_effective_connection_type.value(); ++ect) { + net::EffectiveConnectionType effective_connection_type = + static_cast<net::EffectiveConnectionType>(ect); + ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer::iterator + iter = result.find(effective_connection_type); + if (iter != result.end()) { + iter->second.delay_requests_on_multiplexed_connections = true; + } else { + result.emplace(std::make_pair( + effective_connection_type, + ResourceSchedulerParamsManager::ParamsForNetworkQuality( + kDefaultMaxNumDelayableRequestsPerClient, 0.0, true, + base::nullopt))); + } + } + } + + if (base::FeatureList::IsEnabled( + features::kUnthrottleRequestsAfterLongQueuingDelay)) { + int http_rtt_multiplier = base::GetFieldTrialParamByFeatureAsInt( + features::kUnthrottleRequestsAfterLongQueuingDelay, + "http_rtt_multiplier", -1); + + if (http_rtt_multiplier > 0) { + for (int ect = net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; + ect <= net::EFFECTIVE_CONNECTION_TYPE_4G; ++ect) { + net::EffectiveConnectionType effective_connection_type = + static_cast<net::EffectiveConnectionType>(ect); + base::TimeDelta http_rtt = + net::NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt( + effective_connection_type); + base::TimeDelta max_queuing_time = http_rtt * http_rtt_multiplier; + if (max_queuing_time < kLowerBoundQueuingDuration) + max_queuing_time = kLowerBoundQueuingDuration; + if (max_queuing_time > kUpperBoundQueuingDuration) + max_queuing_time = kUpperBoundQueuingDuration; + + ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer:: + iterator iter = result.find(effective_connection_type); + if (iter != result.end()) { + iter->second.max_queuing_time = max_queuing_time; + } else { + result.emplace(std::make_pair( + effective_connection_type, + ResourceSchedulerParamsManager::ParamsForNetworkQuality( + kDefaultMaxNumDelayableRequestsPerClient, 0.0, false, + max_queuing_time))); + } + } + } + } + return result; } +} // namespace + +ResourceSchedulerParamsManager::ParamsForNetworkQuality:: + ParamsForNetworkQuality() + : ResourceSchedulerParamsManager::ParamsForNetworkQuality( + kDefaultMaxNumDelayableRequestsPerClient, + 0.0, + false, + base::nullopt) {} + +ResourceSchedulerParamsManager::ParamsForNetworkQuality:: + ParamsForNetworkQuality(size_t max_delayable_requests, + double non_delayable_weight, + bool delay_requests_on_multiplexed_connections, + base::Optional<base::TimeDelta> max_queuing_time) + : max_delayable_requests(max_delayable_requests), + non_delayable_weight(non_delayable_weight), + delay_requests_on_multiplexed_connections( + delay_requests_on_multiplexed_connections), + max_queuing_time(max_queuing_time) {} + +ResourceSchedulerParamsManager::ParamsForNetworkQuality:: + ParamsForNetworkQuality( + const ResourceSchedulerParamsManager::ParamsForNetworkQuality& other) = + default; + +ResourceSchedulerParamsManager::ResourceSchedulerParamsManager() + : ResourceSchedulerParamsManager(GetParamsForNetworkQualityContainer()) {} + +ResourceSchedulerParamsManager::ResourceSchedulerParamsManager( + const ParamsForNetworkQualityContainer& + params_for_network_quality_container) + : params_for_network_quality_container_( + params_for_network_quality_container) {} + +ResourceSchedulerParamsManager::ResourceSchedulerParamsManager( + const ResourceSchedulerParamsManager& other) + : params_for_network_quality_container_( + other.params_for_network_quality_container_) {} + +ResourceSchedulerParamsManager::~ResourceSchedulerParamsManager() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +} + +ResourceSchedulerParamsManager::ParamsForNetworkQuality +ResourceSchedulerParamsManager::GetParamsForEffectiveConnectionType( + net::EffectiveConnectionType effective_connection_type) const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + ParamsForNetworkQualityContainer::const_iterator iter = + params_for_network_quality_container_.find(effective_connection_type); + if (iter != params_for_network_quality_container_.end()) + return iter->second; + return ParamsForNetworkQuality(kDefaultMaxNumDelayableRequestsPerClient, 0.0, + false, base::nullopt); +} + } // namespace network diff --git a/chromium/services/network/resource_scheduler_params_manager.h b/chromium/services/network/resource_scheduler_params_manager.h index 40249bc1eaa..ab866e26a32 100644 --- a/chromium/services/network/resource_scheduler_params_manager.h +++ b/chromium/services/network/resource_scheduler_params_manager.h @@ -11,6 +11,7 @@ #include <map> #include "base/component_export.h" +#include "base/optional.h" #include "base/sequence_checker.h" #include "net/nqe/effective_connection_type.h" @@ -27,7 +28,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ResourceSchedulerParamsManager { ParamsForNetworkQuality(size_t max_delayable_requests, double non_delayable_weight, - bool delay_requests_on_multiplexed_connections); + bool delay_requests_on_multiplexed_connections, + base::Optional<base::TimeDelta> max_queuing_time); + + ParamsForNetworkQuality(const ParamsForNetworkQuality& other); // The maximum number of delayable requests allowed. size_t max_delayable_requests; @@ -39,6 +43,10 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ResourceSchedulerParamsManager { // True if requests to servers that support prioritization (e.g., // H2/SPDY/QUIC) should be delayed similar to other HTTP 1.1 requests. bool delay_requests_on_multiplexed_connections; + + // The maximum duration for which a request is queued after after which the + // request is dispatched to the network. + base::Optional<base::TimeDelta> max_queuing_time; }; ResourceSchedulerParamsManager(); @@ -73,31 +81,6 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) ResourceSchedulerParamsManager { } private: - // Reads the experiments params for DelayRequestsOnMultiplexedConnections - // finch experiment, modifies |result| based on the experiment params, and - // returns the modified |result|. - static ParamsForNetworkQualityContainer - GetParamsForDelayRequestsOnMultiplexedConnections( - ParamsForNetworkQualityContainer result); - - // Reads experiment parameters and populates - // |params_for_network_quality_container_|. It looks for configuration - // parameters with sequential numeric suffixes, and stops looking after the - // first failure to find an experimetal parameter. A sample configuration is - // given below: - // "EffectiveConnectionType1": "Slow-2G", - // "MaxDelayableRequests1": "6", - // "NonDelayableWeight1": "2.0", - // "EffectiveConnectionType2": "3G", - // "MaxDelayableRequests2": "12", - // "NonDelayableWeight2": "3.0", - // This config implies that when Effective Connection Type (ECT) is Slow-2G, - // then the maximum number of non-delayable requests should be - // limited to 6, and the non-delayable request weight should be set to 2. - // When ECT is 3G, it should be limited to 12. For all other values of ECT, - // the default values are used. - static ParamsForNetworkQualityContainer GetParamsForNetworkQualityContainer(); - // The number of delayable requests in-flight for different ranges of the // network quality. ParamsForNetworkQualityContainer params_for_network_quality_container_; diff --git a/chromium/services/network/resource_scheduler_params_manager_unittest.cc b/chromium/services/network/resource_scheduler_params_manager_unittest.cc index 742767bde60..64883df20d8 100644 --- a/chromium/services/network/resource_scheduler_params_manager_unittest.cc +++ b/chromium/services/network/resource_scheduler_params_manager_unittest.cc @@ -12,6 +12,7 @@ #include "base/metrics/field_trial_params.h" #include "base/strings/string_number_conversions.h" #include "base/test/scoped_feature_list.h" +#include "net/nqe/network_quality_estimator_params.h" #include "services/network/public/cpp/features.h" #include "testing/gtest/include/gtest/gtest.h" @@ -19,6 +20,9 @@ namespace network { namespace { +static constexpr base::TimeDelta kLowerBoundQueuingDuration = + base::TimeDelta::FromSeconds(15); + class ResourceSchedulerParamsManagerTest : public testing::Test { public: ResourceSchedulerParamsManagerTest() : field_trial_list_(nullptr) {} @@ -100,7 +104,12 @@ class ResourceSchedulerParamsManagerTest : public testing::Test { resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(effective_connection_type) .delay_requests_on_multiplexed_connections); + EXPECT_FALSE( + resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(effective_connection_type) + .max_queuing_time.has_value()); return; + case net::EFFECTIVE_CONNECTION_TYPE_3G: EXPECT_EQ(10u, resource_scheduler_params_manager .GetParamsForEffectiveConnectionType( @@ -114,7 +123,12 @@ class ResourceSchedulerParamsManagerTest : public testing::Test { resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(effective_connection_type) .delay_requests_on_multiplexed_connections); + EXPECT_FALSE( + resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(effective_connection_type) + .max_queuing_time.has_value()); return; + case net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G: case net::EFFECTIVE_CONNECTION_TYPE_2G: EXPECT_EQ(8u, resource_scheduler_params_manager @@ -129,7 +143,12 @@ class ResourceSchedulerParamsManagerTest : public testing::Test { resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(effective_connection_type) .delay_requests_on_multiplexed_connections); + EXPECT_FALSE( + resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(effective_connection_type) + .max_queuing_time.has_value()); return; + case net::EFFECTIVE_CONNECTION_TYPE_LAST: NOTREACHED(); return; @@ -195,6 +214,9 @@ TEST_F(ResourceSchedulerParamsManagerTest, EXPECT_TRUE(resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(ect) .delay_requests_on_multiplexed_connections); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time.has_value()); } else if (effective_connection_type == net::EFFECTIVE_CONNECTION_TYPE_3G) { EXPECT_EQ(10u, resource_scheduler_params_manager @@ -206,6 +228,9 @@ TEST_F(ResourceSchedulerParamsManagerTest, EXPECT_FALSE(resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(ect) .delay_requests_on_multiplexed_connections); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time.has_value()); } else { VerifyDefaultParams( @@ -215,6 +240,99 @@ TEST_F(ResourceSchedulerParamsManagerTest, } } +TEST_F(ResourceSchedulerParamsManagerTest, MaxQueuingTime) { + base::FieldTrialParamAssociator::GetInstance()->ClearAllParamsForTesting(); + const std::string kTrialName = "TrialFoo"; + const std::string kGroupName = "GroupFoo"; // Value not used + base::test::ScopedFeatureList scoped_feature_list; + + scoped_refptr<base::FieldTrial> trial = + base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName); + + std::map<std::string, std::string> params; + params["http_rtt_multiplier"] = "20"; + ASSERT_TRUE( + base::FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams( + kTrialName, kGroupName, params)); + + std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList); + feature_list->RegisterFieldTrialOverride( + features::kUnthrottleRequestsAfterLongQueuingDelay.name, + base::FeatureList::OVERRIDE_ENABLE_FEATURE, trial.get()); + scoped_feature_list.InitWithFeatureList(std::move(feature_list)); + + ResourceSchedulerParamsManager resource_scheduler_params_manager; + + for (int effective_connection_type = net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; + effective_connection_type < net::EFFECTIVE_CONNECTION_TYPE_LAST; + ++effective_connection_type) { + net::EffectiveConnectionType ect = + static_cast<net::EffectiveConnectionType>(effective_connection_type); + base::TimeDelta typical_http_rtt = + net::NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt(ect); + + if (effective_connection_type == net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G || + effective_connection_type == net::EFFECTIVE_CONNECTION_TYPE_2G) { + EXPECT_EQ(8u, resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_delayable_requests); + EXPECT_EQ(3.0, resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .non_delayable_weight); + EXPECT_TRUE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .delay_requests_on_multiplexed_connections); + EXPECT_EQ(typical_http_rtt * 20, + resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time); + + } else if (effective_connection_type == net::EFFECTIVE_CONNECTION_TYPE_3G) { + EXPECT_EQ(10u, resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_delayable_requests); + EXPECT_EQ(0.0, resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .non_delayable_weight); + EXPECT_TRUE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .delay_requests_on_multiplexed_connections); + EXPECT_EQ(kLowerBoundQueuingDuration, + resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time); + + } else if (effective_connection_type == net::EFFECTIVE_CONNECTION_TYPE_4G) { + EXPECT_EQ(10u, resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_delayable_requests); + EXPECT_EQ(0.0, resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .non_delayable_weight); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .delay_requests_on_multiplexed_connections); + EXPECT_EQ(kLowerBoundQueuingDuration, + resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time); + } else { + EXPECT_EQ(10u, resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_delayable_requests); + EXPECT_EQ(0.0, resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .non_delayable_weight); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .delay_requests_on_multiplexed_connections); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time.has_value()); + } + } +} + // Verify that the params are parsed correctly when // kDelayRequestsOnMultiplexedConnections and kThrottleDelayable are enabled. TEST_F(ResourceSchedulerParamsManagerTest, MultipleFieldTrialsEnabled) { @@ -275,6 +393,9 @@ TEST_F(ResourceSchedulerParamsManagerTest, MultipleFieldTrialsEnabled) { EXPECT_TRUE(resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(ect) .delay_requests_on_multiplexed_connections); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time.has_value()); } else if (effective_connection_type == net::EFFECTIVE_CONNECTION_TYPE_3G) { EXPECT_EQ(12u, resource_scheduler_params_manager @@ -286,6 +407,10 @@ TEST_F(ResourceSchedulerParamsManagerTest, MultipleFieldTrialsEnabled) { EXPECT_TRUE(resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(ect) .delay_requests_on_multiplexed_connections); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time.has_value()); + } else if (effective_connection_type == net::EFFECTIVE_CONNECTION_TYPE_4G) { EXPECT_EQ(14u, resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(ect) @@ -296,6 +421,10 @@ TEST_F(ResourceSchedulerParamsManagerTest, MultipleFieldTrialsEnabled) { EXPECT_FALSE(resource_scheduler_params_manager .GetParamsForEffectiveConnectionType(ect) .delay_requests_on_multiplexed_connections); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType(ect) + .max_queuing_time.has_value()); + } else { VerifyDefaultParams(resource_scheduler_params_manager, ect); } @@ -447,6 +576,10 @@ TEST_F(ResourceSchedulerParamsManagerTest, .GetParamsForEffectiveConnectionType( net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G) .non_delayable_weight); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType( + net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G) + .max_queuing_time.has_value()); VerifyDefaultParams(resource_scheduler_params_manager, net::EFFECTIVE_CONNECTION_TYPE_2G); @@ -460,6 +593,10 @@ TEST_F(ResourceSchedulerParamsManagerTest, .GetParamsForEffectiveConnectionType( net::EFFECTIVE_CONNECTION_TYPE_3G) .non_delayable_weight); + EXPECT_FALSE(resource_scheduler_params_manager + .GetParamsForEffectiveConnectionType( + net::EFFECTIVE_CONNECTION_TYPE_3G) + .max_queuing_time.has_value()); VerifyDefaultParams(resource_scheduler_params_manager, net::EFFECTIVE_CONNECTION_TYPE_4G); diff --git a/chromium/services/network/resource_scheduler_unittest.cc b/chromium/services/network/resource_scheduler_unittest.cc index 15ea7c162d2..ce618ff0d19 100644 --- a/chromium/services/network/resource_scheduler_unittest.cc +++ b/chromium/services/network/resource_scheduler_unittest.cc @@ -22,9 +22,11 @@ #include "base/test/metrics/histogram_tester.h" #include "base/test/mock_entropy_provider.h" #include "base/test/scoped_feature_list.h" +#include "base/test/simple_test_tick_clock.h" #include "base/test/test_mock_time_task_runner.h" #include "base/timer/timer.h" #include "net/base/host_port_pair.h" +#include "net/base/load_timing_info.h" #include "net/base/request_priority.h" #include "net/http/http_server_properties_impl.h" #include "net/nqe/network_quality_estimator_test_util.h" @@ -32,6 +34,7 @@ #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 "services/network/public/cpp/features.h" #include "services/network/resource_scheduler_params_manager.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -57,35 +60,8 @@ const char kPrioritySupportedRequestsDelayable[] = "PrioritySupportedRequestsDelayable"; const char kHeadPrioritySupportedRequestsDelayable[] = "HeadPriorityRequestsDelayable"; -const char kNetworkSchedulerYielding[] = "NetworkSchedulerYielding"; const size_t kMaxNumDelayableRequestsPerHostPerClient = 6; -void ConfigureYieldFieldTrial( - int max_requests_before_yielding, - int max_yield_ms, - base::test::ScopedFeatureList* scoped_feature_list) { - const std::string kTrialName = "TrialName"; - const std::string kGroupName = "GroupName"; // Value not used - const std::string kNetworkSchedulerYielding = "NetworkSchedulerYielding"; - - scoped_refptr<base::FieldTrial> trial = - base::FieldTrialList::CreateFieldTrial(kTrialName, kGroupName); - - std::map<std::string, std::string> params; - params["MaxRequestsBeforeYieldingParam"] = - base::IntToString(max_requests_before_yielding); - params["MaxYieldMs"] = base::IntToString(max_yield_ms); - ASSERT_TRUE( - base::FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams( - kTrialName, kGroupName, params)); - - std::unique_ptr<base::FeatureList> feature_list(new base::FeatureList); - feature_list->RegisterFieldTrialOverride( - kNetworkSchedulerYielding, base::FeatureList::OVERRIDE_ENABLE_FEATURE, - trial.get()); - scoped_feature_list->InitWithFeatureList(std::move(feature_list)); -} - class TestRequest { public: TestRequest(std::unique_ptr<net::URLRequest> url_request, @@ -171,7 +147,7 @@ class ResourceSchedulerTest : public testing::Test { CleanupScheduler(); // Destroys previous scheduler. - scheduler_.reset(new ResourceScheduler(enabled)); + scheduler_.reset(new ResourceScheduler(enabled, &tick_clock_)); scheduler()->SetResourceSchedulerParamsManagerForTests( resource_scheduler_params_manager_); @@ -188,7 +164,7 @@ class ResourceSchedulerTest : public testing::Test { for (int i = 0; i != net::EFFECTIVE_CONNECTION_TYPE_LAST; ++i) { auto type = static_cast<net::EffectiveConnectionType>(i); c[type] = ResourceSchedulerParamsManager::ParamsForNetworkQuality( - max_delayable_requests, 0.0, false); + max_delayable_requests, 0.0, false, base::nullopt); } return ResourceSchedulerParamsManager(std::move(c)); } @@ -357,9 +333,9 @@ class ResourceSchedulerTest : public testing::Test { ResourceSchedulerParamsManager::ParamsForNetworkQuality> params_for_network_quality_container; ResourceSchedulerParamsManager::ParamsForNetworkQuality params_slow_2g( - 8, 3.0, true); - ResourceSchedulerParamsManager::ParamsForNetworkQuality params_2g(8, 3.0, - true); + 8, 3.0, true, base::nullopt); + ResourceSchedulerParamsManager::ParamsForNetworkQuality params_2g( + 8, 3.0, true, base::nullopt); params_for_network_quality_container [net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G] = params_slow_2g; @@ -376,9 +352,9 @@ class ResourceSchedulerTest : public testing::Test { ResourceSchedulerParamsManager::ParamsForNetworkQuality> params_for_network_quality_container; ResourceSchedulerParamsManager::ParamsForNetworkQuality params_slow_2g( - 8, 3.0, false); - ResourceSchedulerParamsManager::ParamsForNetworkQuality params_3g(10, 0.0, - false); + 8, 3.0, false, base::nullopt); + ResourceSchedulerParamsManager::ParamsForNetworkQuality params_3g( + 10, 0.0, false, base::nullopt); if (lower_delayable_count_enabled) { params_slow_2g.max_delayable_requests = 2; @@ -401,6 +377,21 @@ class ResourceSchedulerTest : public testing::Test { params_for_network_quality_container); } + void InitializeMaxQueuingDelayExperiment(base::TimeDelta max_queuing_time) { + std::map<net::EffectiveConnectionType, + ResourceSchedulerParamsManager::ParamsForNetworkQuality> + params_for_network_quality_container; + + ResourceSchedulerParamsManager::ParamsForNetworkQuality params_slow_2g( + 8, 3.0, true, base::nullopt); + params_slow_2g.max_queuing_time = max_queuing_time; + params_for_network_quality_container + [net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G] = params_slow_2g; + + resource_scheduler_params_manager_.Reset( + params_for_network_quality_container); + } + void NonDelayableThrottlesDelayableHelper(double non_delayable_weight) { // Should be in sync with .cc for ECT SLOW_2G, const int kDefaultMaxNumDelayableRequestsPerClient = 8; @@ -438,6 +429,7 @@ class ResourceSchedulerTest : public testing::Test { net::TestURLRequestContext context_; ResourceSchedulerParamsManager resource_scheduler_params_manager_; base::FieldTrialList field_trial_list_; + base::SimpleTestTickClock tick_clock_; }; TEST_F(ResourceSchedulerTest, OneIsolatedLowRequest) { @@ -476,189 +468,6 @@ TEST_F(ResourceSchedulerTest, OneLowLoadsUntilCriticalComplete) { 2); } -TEST_F(ResourceSchedulerTest, SchedulerYieldsOnSpdy) { - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitFromCommandLine(kNetworkSchedulerYielding, ""); - InitializeScheduler(); - - // The second low-priority request should yield. - scheduler_->SetMaxRequestsBeforeYieldingForTesting(1); - - // Set a custom yield time. - scheduler_->SetYieldTimeForTesting(base::TimeDelta::FromMilliseconds(42)); - - // Use a testing task runner so that we can control time. - auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(); - scheduler_->SetTaskRunnerForTesting(task_runner); - - http_server_properties_.SetSupportsSpdy( - url::SchemeHostPort("https", "spdyhost", 443), true); - - std::unique_ptr<TestRequest> request( - NewRequest("https://spdyhost/low", net::LOWEST)); - std::unique_ptr<TestRequest> request2( - NewRequest("https://spdyhost/low", net::LOWEST)); - std::unique_ptr<TestRequest> request3( - NewRequest("https://spdyhost/low", net::LOWEST)); - - // Just before the yield task runs, only the first request should have - // started. - task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(41)); - EXPECT_TRUE(request->started()); - EXPECT_FALSE(request2->started()); - EXPECT_FALSE(request3->started()); - - // Yield is done, run the next task. - task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(1)); - EXPECT_TRUE(request2->started()); - EXPECT_FALSE(request3->started()); - - // Just before the yield task runs, only the first two requests should have - // started. - task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(41)); - EXPECT_FALSE(request3->started()); - - // Yield is done, run the next task. - task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(1)); - EXPECT_TRUE(request3->started()); -} - -// Same as SchedulerYieldsOnSpdy but uses FieldTrial Parameters for -// configuration. -TEST_F(ResourceSchedulerTest, SchedulerYieldFieldTrialParams) { - base::test::ScopedFeatureList scoped_feature_list; - - ConfigureYieldFieldTrial(1 /* requests before yielding */, - 42 /* yield time */, &scoped_feature_list); - InitializeScheduler(); - - // Make sure the parameters were properly set. - EXPECT_EQ(42, scheduler_->yield_time().InMilliseconds()); - EXPECT_EQ(1, scheduler_->max_requests_before_yielding()); - - // Use a testing task runner so that we can control time. - auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(); - scheduler_->SetTaskRunnerForTesting(task_runner); - - http_server_properties_.SetSupportsSpdy( - url::SchemeHostPort("https", "spdyhost", 443), true); - - std::unique_ptr<TestRequest> request( - NewRequest("https://spdyhost/low", net::LOWEST)); - std::unique_ptr<TestRequest> request2( - NewRequest("https://spdyhost/low", net::LOWEST)); - - // Just before the yield task runs, only the first request should have - // started. - task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(41)); - EXPECT_TRUE(request->started()); - EXPECT_FALSE(request2->started()); - - // Yield is done, run the next task. - task_runner->FastForwardBy(base::TimeDelta::FromMilliseconds(1)); - EXPECT_TRUE(request2->started()); -} - -TEST_F(ResourceSchedulerTest, YieldingDisabled) { - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitFromCommandLine("", kNetworkSchedulerYielding); - InitializeScheduler(); - - // We're setting a yield parameter, but no yielding will happen since it's - // disabled. - scheduler_->SetMaxRequestsBeforeYieldingForTesting(1); - - http_server_properties_.SetSupportsSpdy( - url::SchemeHostPort("https", "spdyhost", 443), true); - - std::unique_ptr<TestRequest> request( - NewRequest("https://spdyhost/low", net::LOWEST)); - std::unique_ptr<TestRequest> request2( - NewRequest("https://spdyhost/low", net::LOWEST)); - EXPECT_TRUE(request->started()); - EXPECT_TRUE(request2->started()); -} - -TEST_F(ResourceSchedulerTest, SchedulerDoesNotYieldH1) { - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitFromCommandLine(kNetworkSchedulerYielding, ""); - InitializeScheduler(); - SetMaxDelayableRequests(1); - - // Use a testing task runner so that we can control time. - auto task_runner = base::MakeRefCounted<base::TestMockTimeTaskRunner>(); - scheduler_->SetTaskRunnerForTesting(task_runner); - - // Yield after each request. - scheduler_->SetMaxRequestsBeforeYieldingForTesting(1); - scheduler_->SetYieldTimeForTesting(base::TimeDelta::FromMilliseconds(42)); - - std::unique_ptr<TestRequest> request( - NewRequest("https://host/low", net::LOWEST)); - std::unique_ptr<TestRequest> request2( - NewRequest("https://host/low", net::LOWEST)); - - EXPECT_TRUE(request->started()); - EXPECT_FALSE(request2->started()); - - // Finish the first task so that the second can start. - request = nullptr; - - // Run tasks without advancing time, if there were yielding the next task - // wouldn't start. - task_runner->RunUntilIdle(); - - // The next task started, so there was no yielding. - EXPECT_TRUE(request2->started()); -} - -TEST_F(ResourceSchedulerTest, SchedulerDoesNotYieldAltSchemes) { - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitFromCommandLine(kNetworkSchedulerYielding, ""); - InitializeScheduler(); - - // Yield after each request. - scheduler_->SetMaxRequestsBeforeYieldingForTesting(1); - scheduler_->SetYieldTimeForTesting(base::TimeDelta::FromMilliseconds(42)); - - std::unique_ptr<TestRequest> request( - NewRequest("yyy://host/low", net::LOWEST)); - std::unique_ptr<TestRequest> request2( - NewRequest("zzz://host/low", net::LOWEST)); - - EXPECT_TRUE(request->started()); - EXPECT_TRUE(request2->started()); -} - -TEST_F(ResourceSchedulerTest, SchedulerDoesNotYieldSyncRequests) { - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitFromCommandLine(kNetworkSchedulerYielding, ""); - InitializeScheduler(); - - // The second low-priority request should yield. - scheduler_->SetMaxRequestsBeforeYieldingForTesting(1); - - // Use spdy so that we don't throttle. - http_server_properties_.SetSupportsSpdy( - url::SchemeHostPort("https", "spdyhost", 443), true); - - std::unique_ptr<TestRequest> request( - NewRequest("https://spdyhost/low", net::LOWEST)); - std::unique_ptr<TestRequest> request2( - NewRequest("https://spdyhost/low", net::LOWEST)); // yields - - // Add a synchronous request, it shouldn't yield. - std::unique_ptr<TestRequest> sync_request( - NewSyncRequest("http://spdyhost/low", net::LOWEST)); - - EXPECT_TRUE(request->started()); - EXPECT_FALSE(request2->started()); - EXPECT_TRUE(sync_request->started()); // The sync request started. - - base::RunLoop().RunUntilIdle(); - EXPECT_TRUE(request2->started()); -} - TEST_F(ResourceSchedulerTest, MaxRequestsPerHostForSpdyWhenNotDelayable) { base::test::ScopedFeatureList scoped_feature_list; scoped_feature_list.InitFromCommandLine("", @@ -786,11 +595,6 @@ TEST_F(ResourceSchedulerTest, CancelOtherRequestsWhileResuming) { } TEST_F(ResourceSchedulerTest, LimitedNumberOfDelayableRequestsInFlight) { - // The yielding feature will sometimes yield requests before they get a - // chance to start, which conflicts this test. So disable the feature. - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitFromCommandLine("", kNetworkSchedulerYielding); - // Throw in one high priority request to make sure that's not a factor. std::unique_ptr<TestRequest> high( NewRequest("http://host/high", net::HIGHEST)); @@ -1612,7 +1416,8 @@ TEST_F(ResourceSchedulerTest, SchedulerDisabled) { TEST_F(ResourceSchedulerTest, MultipleInstances_1) { SetMaxDelayableRequests(1); // In some circumstances there may exist multiple instances. - ResourceScheduler another_scheduler(false); + ResourceScheduler another_scheduler(false, + base::DefaultTickClock::GetInstance()); std::unique_ptr<TestRequest> high( NewRequest("http://host/high", net::HIGHEST)); @@ -1628,7 +1433,8 @@ TEST_F(ResourceSchedulerTest, MultipleInstances_1) { TEST_F(ResourceSchedulerTest, MultipleInstances_2) { SetMaxDelayableRequests(1); - ResourceScheduler another_scheduler(true); + ResourceScheduler another_scheduler(true, + base::DefaultTickClock::GetInstance()); another_scheduler.OnClientCreated(kChildId, kRouteId, &network_quality_estimator_); @@ -1797,6 +1603,280 @@ TEST_F(ResourceSchedulerTest, EXPECT_FALSE(last_singlehost->started()); } +// Verify that when |max_queuing_time| is set, requests queued for too long +// duration are dispatched to the network. +TEST_F(ResourceSchedulerTest, MaxQueuingDelaySet) { + base::TimeDelta max_queuing_time = base::TimeDelta::FromSeconds(15); + InitializeMaxQueuingDelayExperiment(max_queuing_time); + network_quality_estimator_.set_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G); + + InitializeScheduler(); + // The limit will matter only once the page has a body, since delayable + // requests are not loaded before that. + scheduler()->DeprecatedOnNavigate(kChildId, kRouteId); + + // Throw in one high priority request to ensure that it does not matter once + // a body exists. + std::unique_ptr<TestRequest> high( + NewRequest("http://host/high", net::HIGHEST)); + EXPECT_TRUE(high->started()); + + // Should be in sync with resource_scheduler.cc for effective connection type + // (ECT) 2G. For ECT of 2G, number of low priority requests allowed are: + // 8 - 3 * count of high priority requests in flight. That expression computes + // to 8 - 3 * 1 = 5. + const int max_low_priority_requests_allowed = 5; + + std::vector<std::unique_ptr<TestRequest>> lows_singlehost; + // Queue up to the maximum limit. Use different host names to prevent the + // per host limit from kicking in. + for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) { + // Keep unique hostnames to prevent the per host limit from kicking in. + std::string url = "http://host" + base::IntToString(i) + "/low"; + lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST)); + EXPECT_EQ(i < max_low_priority_requests_allowed, + lows_singlehost[i]->started()); + } + + // Advance the clock by more than |max_queuing_time|. + tick_clock_.SetNowTicks(base::DefaultTickClock::GetInstance()->NowTicks() + + max_queuing_time + base::TimeDelta::FromSeconds(1)); + + // Since the requests have been queued for too long, they should now be + // dispatched. Trigger the calculation of queuing time by Triggering the + // finish of a single request. + lows_singlehost[0].reset(); + base::RunLoop().RunUntilIdle(); + + for (int i = 1; i < max_low_priority_requests_allowed + 10; ++i) { + EXPECT_TRUE(lows_singlehost[i]->started()); + } +} + +// Verify that when |max_queuing_time| is not set, requests queued for too long +// duration are not dispatched to the network. +TEST_F(ResourceSchedulerTest, MaxQueuingDelayNotSet) { + base::TimeDelta max_queuing_time = base::TimeDelta::FromSeconds(15); + network_quality_estimator_.set_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G); + + InitializeScheduler(); + // The limit will matter only once the page has a body, since delayable + // requests are not loaded before that. + scheduler()->DeprecatedOnNavigate(kChildId, kRouteId); + + // Throw in one high priority request to ensure that it does not matter once + // a body exists. + std::unique_ptr<TestRequest> high( + NewRequest("http://host/high", net::HIGHEST)); + EXPECT_TRUE(high->started()); + + // Should be in sync with resource_scheduler.cc for effective connection type + // (ECT) 2G. For ECT of 2G, number of low priority requests allowed are: + // 8 - 3 * count of high priority requests in flight. That expression computes + // to 8 - 3 * 1 = 5. + const int max_low_priority_requests_allowed = 5; + + std::vector<std::unique_ptr<TestRequest>> lows_singlehost; + // Queue up to the maximum limit. Use different host names to prevent the + // per host limit from kicking in. + for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) { + // Keep unique hostnames to prevent the per host limit from kicking in. + std::string url = "http://host" + base::IntToString(i) + "/low"; + lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST)); + EXPECT_EQ(i < max_low_priority_requests_allowed, + lows_singlehost[i]->started()); + } + + // Advance the clock by more than |max_queuing_time|. + tick_clock_.SetNowTicks(base::DefaultTickClock::GetInstance()->NowTicks() + + max_queuing_time + base::TimeDelta::FromSeconds(1)); + + // Triggering the finish of a single request should not trigger dispatch of + // requests that have been queued for too long. + lows_singlehost[0].reset(); + base::RunLoop().RunUntilIdle(); + + // Starting at i=1 since the request at index 0 has been deleted. + for (int i = 1; i < max_low_priority_requests_allowed + 10; ++i) { + EXPECT_EQ(i < max_low_priority_requests_allowed + 1, + lows_singlehost[i]->started()); + } +} + +// Verify that when the timer for dispatching long queued requests is fired, +// then the long queued requests are dispatched to the network. +TEST_F(ResourceSchedulerTest, MaxQueuingDelayTimerFires) { + base::TimeDelta max_queuing_time = base::TimeDelta::FromSeconds(15); + InitializeMaxQueuingDelayExperiment(max_queuing_time); + network_quality_estimator_.set_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G); + + InitializeScheduler(); + // The limit will matter only once the page has a body, since delayable + // requests are not loaded before that. + scheduler()->DeprecatedOnNavigate(kChildId, kRouteId); + + // Throw in one high priority request to ensure that it does not matter once + // a body exists. + std::unique_ptr<TestRequest> high( + NewRequest("http://host/high", net::HIGHEST)); + EXPECT_TRUE(high->started()); + + // Should be in sync with resource_scheduler.cc for effective connection type + // (ECT) 2G. For ECT of 2G, number of low priority requests allowed are: + // 8 - 3 * count of high priority requests in flight. That expression computes + // to 8 - 3 * 1 = 5. + const int max_low_priority_requests_allowed = 5; + + std::vector<std::unique_ptr<TestRequest>> lows_singlehost; + // Queue up to the maximum limit. Use different host names to prevent the + // per host limit from kicking in. + for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) { + // Keep unique hostnames to prevent the per host limit from kicking in. + std::string url = "http://host" + base::IntToString(i) + "/low"; + lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST)); + EXPECT_EQ(i < max_low_priority_requests_allowed, + lows_singlehost[i]->started()); + } + + // Advance the clock by more than |max_queuing_time|. + tick_clock_.SetNowTicks(base::DefaultTickClock::GetInstance()->NowTicks() + + max_queuing_time + base::TimeDelta::FromSeconds(1)); + + // Since the requests have been queued for too long, they should now be + // dispatched. Trigger the calculation of queuing time by calling + // DispatchLongQueuedRequestsForTesting(). + scheduler()->DispatchLongQueuedRequestsForTesting(); + base::RunLoop().RunUntilIdle(); + + for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) { + EXPECT_TRUE(lows_singlehost[i]->started()); + } +} + +// Verify that when the timer for dispatching long queued requests is not fired, +// then the long queued requests are not dispatched to the network. +TEST_F(ResourceSchedulerTest, MaxQueuingDelayTimerNotFired) { + base::TimeDelta max_queuing_time = base::TimeDelta::FromSeconds(15); + InitializeMaxQueuingDelayExperiment(max_queuing_time); + network_quality_estimator_.set_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G); + + InitializeScheduler(); + // The limit will matter only once the page has a body, since delayable + // requests are not loaded before that. + scheduler()->DeprecatedOnNavigate(kChildId, kRouteId); + + // Throw in one high priority request to ensure that it does not matter once + // a body exists. + std::unique_ptr<TestRequest> high( + NewRequest("http://host/high", net::HIGHEST)); + EXPECT_TRUE(high->started()); + + // Should be in sync with resource_scheduler.cc for effective connection type + // (ECT) 2G. For ECT of 2G, number of low priority requests allowed are: + // 8 - 3 * count of high priority requests in flight. That expression computes + // to 8 - 3 * 1 = 5. + const int max_low_priority_requests_allowed = 5; + + std::vector<std::unique_ptr<TestRequest>> lows_singlehost; + // Queue up to the maximum limit. Use different host names to prevent the + // per host limit from kicking in. + for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) { + // Keep unique hostnames to prevent the per host limit from kicking in. + std::string url = "http://host" + base::IntToString(i) + "/low"; + lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST)); + EXPECT_EQ(i < max_low_priority_requests_allowed, + lows_singlehost[i]->started()); + } + + // Advance the clock by more than |max_queuing_time|. + tick_clock_.SetNowTicks(base::DefaultTickClock::GetInstance()->NowTicks() + + max_queuing_time + base::TimeDelta::FromSeconds(1)); + + // Since the requests have been queued for too long, they are now eligible for + // disptaching. However, since the timer is not fired, the requests would not + // be dispatched. + base::RunLoop().RunUntilIdle(); + + for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) { + EXPECT_EQ(i < max_low_priority_requests_allowed, + lows_singlehost[i]->started()); + } +} + +// Verify that the timer to dispatch long queued requests starts only when there +// are requests in-flight. +TEST_F(ResourceSchedulerTest, MaxQueuingDelayTimerRunsOnRequestSchedule) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitFromCommandLine( + features::kUnthrottleRequestsAfterLongQueuingDelay.name, ""); + base::TimeDelta max_queuing_time = base::TimeDelta::FromSeconds(15); + InitializeMaxQueuingDelayExperiment(max_queuing_time); + network_quality_estimator_.set_effective_connection_type( + net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G); + // Should be in sync with resource_scheduler.cc for effective connection type + // (ECT) 2G. For ECT of 2G, number of low priority requests allowed are: + // 8 - 3 * count of high priority requests in flight. That expression computes + // to 8 - 3 * 1 = 5. + const int max_low_priority_requests_allowed = 5; + + std::vector<std::unique_ptr<TestRequest>> lows_singlehost; + + InitializeScheduler(); + EXPECT_FALSE(scheduler()->IsLongQueuedRequestsDispatchTimerRunning()); + // The limit will matter only once the page has a body, since delayable + // requests are not loaded before that. + scheduler()->DeprecatedOnNavigate(kChildId, kRouteId); + + // Throw in one high priority request to ensure that it does not matter once + // a body exists. + std::unique_ptr<TestRequest> high( + NewRequest("http://host/high", net::HIGHEST)); + EXPECT_TRUE(high->started()); + + for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) { + // Keep unique hostnames to prevent the per host limit from kicking in. + std::string url = "http://host" + base::IntToString(i) + "/low"; + lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST)); + EXPECT_EQ(i < max_low_priority_requests_allowed, + lows_singlehost[i]->started()); + } + // Timer should be running since there are pending requests. + EXPECT_TRUE(scheduler()->IsLongQueuedRequestsDispatchTimerRunning()); + + // Simulate firing of timer. The timer should restart since there is at least + // one request in flight. + scheduler()->DispatchLongQueuedRequestsForTesting(); + EXPECT_TRUE(scheduler()->IsLongQueuedRequestsDispatchTimerRunning()); + + // Simulate firing of timer. The timer should not restart since there is no + // request in flight. + high.reset(); + for (auto& request : lows_singlehost) { + request.reset(); + } + scheduler()->DispatchLongQueuedRequestsForTesting(); + EXPECT_FALSE(scheduler()->IsLongQueuedRequestsDispatchTimerRunning()); + + // Start a new set of requests, and verify timer still works correctly. + std::unique_ptr<TestRequest> high2( + NewRequest("http://host/high", net::HIGHEST)); + EXPECT_TRUE(high2->started()); + // Timer not started because there is no pending requests. + EXPECT_FALSE(scheduler()->IsLongQueuedRequestsDispatchTimerRunning()); + + // Start some requests which end up pending. + for (int i = 0; i < max_low_priority_requests_allowed + 10; ++i) { + // Keep unique hostnames to prevent the per host limit from kicking in. + std::string url = "http://host" + base::IntToString(i) + "/low"; + lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST)); + } + EXPECT_TRUE(scheduler()->IsLongQueuedRequestsDispatchTimerRunning()); +} + } // unnamed namespace } // namespace network diff --git a/chromium/services/network/ssl_config_type_converter.cc b/chromium/services/network/ssl_config_type_converter.cc index 41f718d1329..81d7cc06c32 100644 --- a/chromium/services/network/ssl_config_type_converter.cc +++ b/chromium/services/network/ssl_config_type_converter.cc @@ -34,7 +34,7 @@ int MojoSSLVersionToNetSSLVersion(network::mojom::SSLVersion mojo_version) { return net::SSL_PROTOCOL_VERSION_TLS1_3; } NOTREACHED(); - return net::SSL_PROTOCOL_VERSION_TLS1_2; + return net::SSL_PROTOCOL_VERSION_TLS1_3; } net::SSLConfig diff --git a/chromium/services/network/tcp_connected_socket.h b/chromium/services/network/tcp_connected_socket.h index ee89dac1a6f..46b9a9b0d8c 100644 --- a/chromium/services/network/tcp_connected_socket.h +++ b/chromium/services/network/tcp_connected_socket.h @@ -13,11 +13,11 @@ #include "mojo/public/cpp/system/data_pipe.h" #include "net/base/address_family.h" #include "net/base/ip_endpoint.h" -#include "net/interfaces/address_family.mojom.h" -#include "net/interfaces/ip_endpoint.mojom.h" #include "net/socket/tcp_client_socket.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "services/network/public/cpp/net_adapters.h" +#include "services/network/public/mojom/address_family.mojom.h" +#include "services/network/public/mojom/ip_endpoint.mojom.h" #include "services/network/public/mojom/network_context.mojom.h" #include "services/network/public/mojom/tcp_socket.mojom.h" #include "services/network/socket_data_pump.h" diff --git a/chromium/services/network/tls_client_socket.h b/chromium/services/network/tls_client_socket.h index 70a46c0c718..898c1e5b120 100644 --- a/chromium/services/network/tls_client_socket.h +++ b/chromium/services/network/tls_client_socket.h @@ -10,10 +10,10 @@ #include "base/component_export.h" #include "base/macros.h" #include "net/base/address_family.h" -#include "net/interfaces/address_family.mojom.h" -#include "net/interfaces/ip_endpoint.mojom.h" #include "net/socket/ssl_client_socket.h" #include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/public/mojom/address_family.mojom.h" +#include "services/network/public/mojom/ip_endpoint.mojom.h" #include "services/network/public/mojom/tcp_socket.mojom.h" #include "services/network/public/mojom/tls_socket.mojom.h" #include "services/network/socket_data_pump.h" diff --git a/chromium/services/network/tls_client_socket_unittest.cc b/chromium/services/network/tls_client_socket_unittest.cc index 7d869b7448e..42ced521bb3 100644 --- a/chromium/services/network/tls_client_socket_unittest.cc +++ b/chromium/services/network/tls_client_socket_unittest.cc @@ -167,7 +167,7 @@ class TLSClientSocketTestBase { base::RunLoop run_loop; int net_error = net::ERR_FAILED; proxy_resolving_factory_->CreateProxyResolvingSocket( - url, false /* use_tls */, + url, nullptr /* options */, net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS), std::move(request), nullptr /* observer */, base::BindLambdaForTesting( @@ -893,8 +893,8 @@ class TLSCLientSocketProxyTest : public ::testing::Test, TEST_F(TLSCLientSocketProxyTest, UpgradeToTLS) { const char kConnectRequest[] = - "CONNECT 127.0.0.1:1234 HTTP/1.1\r\n" - "Host: 127.0.0.1:1234\r\n" + "CONNECT 192.168.1.1:1234 HTTP/1.1\r\n" + "Host: 192.168.1.1:1234\r\n" "Proxy-Connection: keep-alive\r\n\r\n"; const char kConnectResponse[] = "HTTP/1.1 200 OK\r\n\r\n"; @@ -912,7 +912,7 @@ TEST_F(TLSCLientSocketProxyTest, UpgradeToTLS) { mock_client_socket_factory()->AddSSLSocketDataProvider(&ssl_socket); SocketHandle client_socket; - net::IPEndPoint server_addr(net::IPAddress::IPv4Localhost(), 1234); + net::IPEndPoint server_addr(net::IPAddress(192, 168, 1, 1), 1234); EXPECT_EQ(net::OK, CreateSocketSync(MakeRequest(&client_socket), server_addr)); diff --git a/chromium/services/network/transitional_url_loader_factory_owner.cc b/chromium/services/network/transitional_url_loader_factory_owner.cc index 908451d7ee1..0eec76622d2 100644 --- a/chromium/services/network/transitional_url_loader_factory_owner.cc +++ b/chromium/services/network/transitional_url_loader_factory_owner.cc @@ -105,6 +105,12 @@ TransitionalURLLoaderFactoryOwner::GetURLLoaderFactory() { return shared_url_loader_factory_; } +network::mojom::NetworkContext* +TransitionalURLLoaderFactoryOwner::GetNetworkContext() { + GetURLLoaderFactory(); + return network_context_pipe_.get(); +} + void TransitionalURLLoaderFactoryOwner::DisallowUsageInProcess() { disallowed_in_process().Set(); } diff --git a/chromium/services/network/transitional_url_loader_factory_owner.h b/chromium/services/network/transitional_url_loader_factory_owner.h index 5bdeb7da05b..3105b250983 100644 --- a/chromium/services/network/transitional_url_loader_factory_owner.h +++ b/chromium/services/network/transitional_url_loader_factory_owner.h @@ -42,6 +42,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) TransitionalURLLoaderFactoryOwner { scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory(); + network::mojom::NetworkContext* GetNetworkContext(); + // If this is called, any creation, use, or destruction of a // TransitionalURLLoaderFactoryOwner will DCHECK-fail. static void DisallowUsageInProcess(); diff --git a/chromium/services/network/udp_socket.cc b/chromium/services/network/udp_socket.cc index c2f6e707fd4..5847ba9df6f 100644 --- a/chromium/services/network/udp_socket.cc +++ b/chromium/services/network/udp_socket.cc @@ -116,6 +116,8 @@ class SocketWrapperImpl : public UDPSocket::SocketWrapper { int result = net::OK; if (options->allow_address_reuse) result = socket_.AllowAddressReuse(); + if (result == net::OK && options->allow_address_sharing_for_multicast) + result = socket_.AllowAddressSharingForMulticast(); if (result == net::OK && options->allow_broadcast) result = socket_.SetBroadcast(true); if (result == net::OK && options->multicast_interface != 0) diff --git a/chromium/services/network/udp_socket.h b/chromium/services/network/udp_socket.h index 5bd5ff3d51a..f08520230c1 100644 --- a/chromium/services/network/udp_socket.h +++ b/chromium/services/network/udp_socket.h @@ -16,9 +16,9 @@ #include "net/base/address_family.h" #include "net/base/completion_once_callback.h" #include "net/base/ip_endpoint.h" -#include "net/interfaces/address_family.mojom.h" -#include "net/interfaces/ip_endpoint.mojom.h" #include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/public/mojom/address_family.mojom.h" +#include "services/network/public/mojom/ip_endpoint.mojom.h" #include "services/network/public/mojom/udp_socket.mojom.h" namespace net { diff --git a/chromium/services/network/udp_socket_unittest.cc b/chromium/services/network/udp_socket_unittest.cc index a33ff221a69..0191117e45f 100644 --- a/chromium/services/network/udp_socket_unittest.cc +++ b/chromium/services/network/udp_socket_unittest.cc @@ -688,11 +688,13 @@ TEST_F(UDPSocketTest, MAYBE_JoinMulticastGroup) { // See https://fuchsia.atlassian.net/browse/NET-195 . options->multicast_interface = 1; #endif // defined(OS_FUCHSIA) + options->allow_address_sharing_for_multicast = true; net::IPAddress bind_ip_address; EXPECT_TRUE(bind_ip_address.AssignFromIPLiteral("0.0.0.0")); net::IPEndPoint socket_address(bind_ip_address, 0); - ASSERT_EQ(net::OK, helper.BindSync(socket_address, nullptr, &socket_address)); + ASSERT_EQ(net::OK, helper.BindSync(socket_address, std::move(options), + &socket_address)); int port = socket_address.port(); EXPECT_NE(0, port); EXPECT_EQ(net::OK, helper.JoinGroupSync(group_ip)); diff --git a/chromium/services/network/url_loader.cc b/chromium/services/network/url_loader.cc index 335f445e3bc..3a164319007 100644 --- a/chromium/services/network/url_loader.cc +++ b/chromium/services/network/url_loader.cc @@ -9,6 +9,9 @@ #include <utility> #include <vector> +#include "base/command_line.h" +#include "base/debug/alias.h" +#include "base/debug/dump_without_crashing.h" #include "base/files/file.h" #include "base/memory/weak_ptr.h" #include "base/metrics/histogram_macros.h" @@ -22,7 +25,9 @@ #include "net/base/upload_file_element_reader.h" #include "net/cert/symantec_certs.h" #include "net/ssl/client_cert_store.h" +#include "net/ssl/ssl_connection_status_flags.h" #include "net/ssl/ssl_private_key.h" +#include "net/traffic_annotation/network_traffic_annotation.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" #include "services/network/chunked_data_pipe_upload_data_stream.h" @@ -32,6 +37,7 @@ #include "services/network/network_usage_accumulator.h" #include "services/network/public/cpp/features.h" #include "services/network/public/cpp/net_adapters.h" +#include "services/network/public/cpp/network_switches.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/resource_response.h" #include "services/network/public/mojom/url_loader_factory.mojom.h" @@ -41,12 +47,16 @@ namespace network { namespace { + constexpr size_t kDefaultAllocationSize = 512 * 1024; // Cannot use 0, because this means "default" in // mojo::core::Core::CreateDataPipe constexpr size_t kBlockedBodyAllocationSize = 1; +// Used to dump when we get too many requests, once. +bool g_reported_too_many_requests = false; + // TODO: this duplicates some of PopulateResourceResponse in // content/browser/loader/resource_loader.cc void PopulateResourceResponse(net::URLRequest* request, @@ -67,7 +77,6 @@ void PopulateResourceResponse(net::URLRequest* request, response->head.connection_info = response_info.connection_info; response->head.socket_address = response_info.socket_address; response->head.was_fetched_via_cache = request->was_cached(); - response->head.was_fetched_via_proxy = request->was_fetched_via_proxy(); response->head.proxy_server = request->proxy_server(); response->head.network_accessed = response_info.network_accessed; response->head.async_revalidation_requested = @@ -87,6 +96,11 @@ void PopulateResourceResponse(net::URLRequest* request, net::IsCertStatusMinorError(response->head.cert_status)) && net::IsLegacySymantecCert(request->ssl_info().public_key_hashes); response->head.cert_status = request->ssl_info().cert_status; + net::SSLVersion ssl_version = net::SSLConnectionStatusToVersion( + request->ssl_info().connection_status); + response->head.is_legacy_tls_version = + ssl_version == net::SSLVersion::SSL_CONNECTION_VERSION_TLS1 || + ssl_version == net::SSLVersion::SSL_CONNECTION_VERSION_TLS1_1; if (include_ssl_info) response->head.ssl_info = request->ssl_info(); @@ -228,9 +242,11 @@ std::unique_ptr<net::UploadDataStream> CreateUploadDataStream( class SSLPrivateKeyInternal : public net::SSLPrivateKey { public: - SSLPrivateKeyInternal(const std::vector<uint16_t>& algorithm_perferences, + SSLPrivateKeyInternal(const std::string& provider_name, + const std::vector<uint16_t>& algorithm_preferences, mojom::SSLPrivateKeyPtr ssl_private_key) - : algorithm_perferences_(algorithm_perferences), + : provider_name_(provider_name), + algorithm_preferences_(algorithm_preferences), ssl_private_key_(std::move(ssl_private_key)) { ssl_private_key_.set_connection_error_handler( base::BindOnce(&SSLPrivateKeyInternal::HandleSSLPrivateKeyError, @@ -238,8 +254,10 @@ class SSLPrivateKeyInternal : public net::SSLPrivateKey { } // net::SSLPrivateKey: + std::string GetProviderName() override { return provider_name_; } + std::vector<uint16_t> GetAlgorithmPreferences() override { - return algorithm_perferences_; + return algorithm_preferences_; } void Sign(uint16_t algorithm, @@ -273,7 +291,8 @@ class SSLPrivateKeyInternal : public net::SSLPrivateKey { std::move(callback).Run(static_cast<net::Error>(net_error), input); } - std::vector<uint16_t> algorithm_perferences_; + std::string provider_name_; + std::vector<uint16_t> algorithm_preferences_; mojom::SSLPrivateKeyPtr ssl_private_key_; DISALLOW_COPY_AND_ASSIGN(SSLPrivateKeyInternal); @@ -288,14 +307,14 @@ URLLoader::URLLoader( mojom::URLLoaderRequest url_loader_request, int32_t options, const ResourceRequest& request, - bool report_raw_headers, mojom::URLLoaderClientPtr url_loader_client, const net::NetworkTrafficAnnotationTag& traffic_annotation, const mojom::URLLoaderFactoryParams* factory_params, uint32_t request_id, scoped_refptr<ResourceSchedulerClient> resource_scheduler_client, base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder, - base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator) + base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator, + mojom::TrustedURLLoaderHeaderClient* header_client) : url_request_context_(url_request_context), network_service_client_(network_service_client), delete_callback_(std::move(delete_callback)), @@ -316,14 +335,18 @@ URLLoader::URLLoader( peer_closed_handle_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL, base::SequencedTaskRunnerHandle::Get()), - report_raw_headers_(report_raw_headers), + want_raw_headers_(request.report_raw_headers), + report_raw_headers_(false), resource_scheduler_client_(std::move(resource_scheduler_client)), keepalive_statistics_recorder_(std::move(keepalive_statistics_recorder)), network_usage_accumulator_(std::move(network_usage_accumulator)), first_auth_attempt_(true), custom_proxy_pre_cache_headers_(request.custom_proxy_pre_cache_headers), custom_proxy_post_cache_headers_(request.custom_proxy_post_cache_headers), + custom_proxy_use_alternate_proxy_list_( + request.custom_proxy_use_alternate_proxy_list), fetch_window_id_(request.fetch_window_id), + header_client_(header_client), weak_ptr_factory_(this) { DCHECK(delete_callback_); if (!base::FeatureList::IsEnabled(features::kNetworkService)) { @@ -335,13 +358,12 @@ URLLoader::URLLoader( << "disabled, as that skips security checks in ResourceDispatcherHost. " << "The only acceptable usage is the browser using SimpleURLLoader."; } - if (report_raw_headers_) { + if (want_raw_headers_) { options_ |= mojom::kURLLoadOptionSendSSLInfoWithResponse | mojom::kURLLoadOptionSendSSLInfoForCertificateError; } binding_.set_connection_error_handler( base::BindOnce(&URLLoader::OnConnectionError, base::Unretained(this))); - url_request_ = url_request_context_->CreateRequest( GURL(request.url), request.priority, this, traffic_annotation); url_request_->set_method(request.method); @@ -350,10 +372,16 @@ URLLoader::URLLoader( url_request_->SetReferrer(ComputeReferrer(request.referrer)); url_request_->set_referrer_policy(request.referrer_policy); url_request_->SetExtraRequestHeaders(request.headers); - if (!request.requested_with.empty()) { - // X-Requested-With header must be set here to avoid breaking CORS checks. - url_request_->SetExtraRequestHeaderByName("X-Requested-With", - request.requested_with, true); + // X-Requested-With and X-Client-Data header must be set here to avoid + // breaking CORS checks. They are non-empty when the values are given by the + // UA code, therefore they should be ignored by CORS checks. + if (!request.requested_with_header.empty()) { + url_request_->SetExtraRequestHeaderByName( + "X-Requested-With", request.requested_with_header, true); + } + if (!request.client_data_header.empty()) { + url_request_->SetExtraRequestHeaderByName("X-Client-Data", + request.client_data_header, true); } url_request_->set_upgrade_if_insecure(request.upgrade_if_insecure); @@ -362,7 +390,7 @@ URLLoader::URLLoader( is_nocors_corb_excluded_request_ = resource_type_ == factory_params_->corb_excluded_resource_type && - request.fetch_request_mode == mojom::FetchRequestMode::kNoCORS && + request.fetch_request_mode == mojom::FetchRequestMode::kNoCors && CrossOriginReadBlocking::ShouldAllowForPlugin( factory_params_->process_id); @@ -387,7 +415,7 @@ URLLoader::URLLoader( url_request_->set_allow_credentials(false); } - if (report_raw_headers_) { + if (want_raw_headers_) { url_request_->SetRequestHeadersCallback( base::Bind(&net::HttpRawRequestHeaders::Assign, base::Unretained(&raw_request_headers_))); @@ -398,6 +426,18 @@ URLLoader::URLLoader( if (keepalive_ && keepalive_statistics_recorder_) keepalive_statistics_recorder_->OnLoadStarted(factory_params_->process_id); + // Record some debug info in hope of tracing down leaks. + int32_t annotation_hash = + url_request_->traffic_annotation().unique_id_hash_code; + size_t num_running_requests = url_request_context_->url_requests()->size(); + base::debug::Alias(&annotation_hash); + base::debug::Alias(&num_running_requests); + DEBUG_ALIAS_FOR_GURL(url_buf, url_request_->url()); + if (!g_reported_too_many_requests && num_running_requests > 10000) { + g_reported_too_many_requests = true; + base::debug::DumpWithoutCrashing(); + } + // Resolve elements from request_body and prepare upload data. if (request.request_body.get()) { OpenFilesForUpload(request); @@ -502,19 +542,29 @@ const void* const URLLoader::kUserDataKey = &URLLoader::kUserDataKey; void URLLoader::FollowRedirect( const base::Optional<std::vector<std::string>>& to_be_removed_request_headers, - const base::Optional<net::HttpRequestHeaders>& modified_request_headers) { + const base::Optional<net::HttpRequestHeaders>& modified_request_headers, + const base::Optional<GURL>& new_url) { if (!url_request_) { NotifyCompleted(net::ERR_UNEXPECTED); // |this| may have been deleted. return; } - if (!deferred_redirect_) { + if (!deferred_redirect_url_) { NOTREACHED(); return; } - deferred_redirect_ = false; + if (new_url && + (new_url->GetOrigin() != deferred_redirect_url_->GetOrigin())) { + NOTREACHED() << "Can only change the URL within the same origin."; + NotifyCompleted(net::ERR_UNEXPECTED); + // |this| may have been deleted. + return; + } + + deferred_redirect_url_.reset(); + new_redirect_url_ = new_url; if (to_be_removed_request_headers.has_value()) { for (const std::string& key : to_be_removed_request_headers.value()) @@ -522,6 +572,7 @@ void URLLoader::FollowRedirect( } url_request_->FollowDeferredRedirect(modified_request_headers); + new_redirect_url_.reset(); } void URLLoader::ProceedWithResponse() { @@ -581,8 +632,8 @@ void URLLoader::OnReceivedRedirect(net::URLRequest* url_request, DCHECK(url_request == url_request_.get()); DCHECK(url_request->status().is_success()); - DCHECK(!deferred_redirect_); - deferred_redirect_ = true; + DCHECK(!deferred_redirect_url_); + deferred_redirect_url_ = std::make_unique<GURL>(redirect_info.new_url); // Send the redirect response to the client, allowing them to inspect it and // optionally follow the redirect. @@ -634,8 +685,17 @@ void URLLoader::OnAuthRequired(net::URLRequest* url_request, void URLLoader::OnCertificateRequested(net::URLRequest* unused, net::SSLCertRequestInfo* cert_info) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kIgnoreUrlFetcherCertRequests) && + factory_params_->process_id == 0 && + render_frame_id_ == MSG_ROUTING_NONE) { + url_request_->ContinueWithCertificate(nullptr, nullptr); + return; + } + if (!network_service_client_) { - OnCertificateRequestedResponse(nullptr, std::vector<uint16_t>(), nullptr, + OnCertificateRequestedResponse(nullptr, std::string(), + std::vector<uint16_t>(), nullptr, true /* cancel_certificate_selection */); return; } @@ -669,11 +729,6 @@ void URLLoader::OnSSLCertificateError(net::URLRequest* request, weak_ptr_factory_.GetWeakPtr(), ssl_info)); } -void URLLoader::ResumeStart() { - url_request_->LogUnblocked(); - url_request_->Start(); -} - void URLLoader::OnResponseStarted(net::URLRequest* url_request, int net_error) { DCHECK(url_request == url_request_.get()); @@ -728,10 +783,7 @@ void URLLoader::OnResponseStarted(net::URLRequest* url_request, int net_error) { base::Unretained(this))); // Figure out if we need to sniff (for MIME type detection or for CORB). - if (factory_params_->is_corb_enabled && !is_nocors_corb_excluded_request_ && - (factory_params_->corb_excluded_initiator_scheme.empty() || - factory_params_->corb_excluded_initiator_scheme != - url_request->initiator().value_or(url::Origin()).scheme())) { + if (factory_params_->is_corb_enabled && !is_nocors_corb_excluded_request_) { CrossOriginReadBlocking::LogAction( CrossOriginReadBlocking::Action::kResponseStarted); @@ -838,35 +890,40 @@ void URLLoader::DidRead(int num_bytes, bool completed_synchronously) { bool complete_read = true; if (consumer_handle_.is_valid()) { - // Limit sniffing to the first net::kMaxBytesToSniff. - size_t data_length = pending_write_buffer_offset_; - if (data_length > net::kMaxBytesToSniff) - data_length = net::kMaxBytesToSniff; - base::StringPiece data(pending_write_->buffer(), data_length); - - if (is_more_mime_sniffing_needed_) { - const std::string& type_hint = response_->head.mime_type; - std::string new_type; - is_more_mime_sniffing_needed_ = !net::SniffMimeType( - data.data(), data.size(), url_request_->url(), type_hint, - net::ForceSniffFileUrlsForHtml::kDisabled, &new_type); - // SniffMimeType() returns false if there is not enough data to determine - // the mime type. However, even if it returns false, it returns a new type - // that is probably better than the current one. - response_->head.mime_type.assign(new_type); - response_->head.did_mime_sniff = true; - } + // |pending_write_| may be null if the job self-aborts due to a suspend; + // this will have |consumer_handle_| valid when the loader is paused. + if (pending_write_) { + // Limit sniffing to the first net::kMaxBytesToSniff. + size_t data_length = pending_write_buffer_offset_; + if (data_length > net::kMaxBytesToSniff) + data_length = net::kMaxBytesToSniff; + + base::StringPiece data(pending_write_->buffer(), data_length); + + if (is_more_mime_sniffing_needed_) { + const std::string& type_hint = response_->head.mime_type; + std::string new_type; + is_more_mime_sniffing_needed_ = !net::SniffMimeType( + data.data(), data.size(), url_request_->url(), type_hint, + net::ForceSniffFileUrlsForHtml::kDisabled, &new_type); + // SniffMimeType() returns false if there is not enough data to + // determine the mime type. However, even if it returns false, it + // returns a new type that is probably better than the current one. + response_->head.mime_type.assign(new_type); + response_->head.did_mime_sniff = true; + } - if (is_more_corb_sniffing_needed_) { - corb_analyzer_->SniffResponseBody(data, new_data_offset); - if (corb_analyzer_->ShouldBlock()) { - corb_analyzer_->LogBlockedResponse(); - is_more_corb_sniffing_needed_ = false; - if (BlockResponseForCorb() == kWillCancelRequest) - return; - } else if (corb_analyzer_->ShouldAllow()) { - corb_analyzer_->LogAllowedResponse(); - is_more_corb_sniffing_needed_ = false; + if (is_more_corb_sniffing_needed_) { + corb_analyzer_->SniffResponseBody(data, new_data_offset); + if (corb_analyzer_->ShouldBlock()) { + corb_analyzer_->LogBlockedResponse(); + is_more_corb_sniffing_needed_ = false; + if (BlockResponseForCorb() == kWillCancelRequest) + return; + } else if (corb_analyzer_->ShouldAllow()) { + corb_analyzer_->LogAllowedResponse(); + is_more_corb_sniffing_needed_ = false; + } } } @@ -922,6 +979,35 @@ void URLLoader::OnReadCompleted(net::URLRequest* url_request, int bytes_read) { // |this| may have been deleted. } +int URLLoader::OnBeforeStartTransaction(net::CompletionOnceCallback callback, + net::HttpRequestHeaders* headers) { + if (header_client_ && (options_ & mojom::kURLLoadOptionUseHeaderClient)) { + header_client_->OnBeforeSendHeaders( + request_id_, *headers, + base::BindOnce(&URLLoader::OnBeforeSendHeadersComplete, + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + headers)); + return net::ERR_IO_PENDING; + } + return net::OK; +} + +int URLLoader::OnHeadersReceived( + net::CompletionOnceCallback callback, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers, + GURL* allowed_unsafe_redirect_url) { + if (header_client_ && (options_ & mojom::kURLLoadOptionUseHeaderClient)) { + header_client_->OnHeadersReceived( + request_id_, original_response_headers->raw_headers(), + base::BindOnce(&URLLoader::OnHeadersReceivedComplete, + weak_ptr_factory_.GetWeakPtr(), std::move(callback), + override_response_headers, allowed_unsafe_redirect_url)); + return net::ERR_IO_PENDING; + } + return net::OK; +} + net::LoadState URLLoader::GetLoadStateForTesting() const { if (!url_request_) return net::LOAD_STATE_IDLE; @@ -936,6 +1022,10 @@ uint32_t URLLoader::GetProcessId() const { return factory_params_->process_id; } +void URLLoader::SetAllowReportingRawHeaders(bool allow) { + report_raw_headers_ = want_raw_headers_ && allow; +} + // static URLLoader* URLLoader::ForRequest(const net::URLRequest& request) { auto* pointer = @@ -974,6 +1064,13 @@ void URLLoader::NotifyCompleted(int error_code) { url_request_->GetTotalReceivedBytes(), url_request_->GetTotalSentBytes()); } + if (network_service_client_ && (url_request_->GetTotalReceivedBytes() > 0 || + url_request_->GetTotalSentBytes() > 0)) { + network_service_client_->OnDataUseUpdate( + url_request_->traffic_annotation().unique_id_hash_code, + url_request_->GetTotalReceivedBytes(), + url_request_->GetTotalSentBytes()); + } if (url_loader_client_) { if (consumer_handle_.is_valid()) @@ -1091,6 +1188,7 @@ void URLLoader::OnSSLCertificateErrorResponse(const net::SSLInfo& ssl_info, void URLLoader::OnCertificateRequestedResponse( const scoped_refptr<net::X509Certificate>& x509_certificate, + const std::string& provider_name, const std::vector<uint16_t>& algorithm_preferences, mojom::SSLPrivateKeyPtr ssl_private_key, bool cancel_certificate_selection) { @@ -1098,8 +1196,8 @@ void URLLoader::OnCertificateRequestedResponse( url_request_->CancelWithError(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED); } else { if (x509_certificate) { - scoped_refptr<net::SSLPrivateKey> key(new SSLPrivateKeyInternal( - algorithm_preferences, std::move(ssl_private_key))); + auto key = base::MakeRefCounted<SSLPrivateKeyInternal>( + provider_name, algorithm_preferences, std::move(ssl_private_key)); url_request_->ContinueWithCertificate(std::move(x509_certificate), std::move(key)); } else { @@ -1131,6 +1229,36 @@ void URLLoader::RecordBodyReadFromNetBeforePausedIfNeeded() { } } +void URLLoader::ResumeStart() { + url_request_->LogUnblocked(); + url_request_->Start(); +} + +void URLLoader::OnBeforeSendHeadersComplete( + net::CompletionOnceCallback callback, + net::HttpRequestHeaders* out_headers, + int result, + const base::Optional<net::HttpRequestHeaders>& headers) { + if (headers) + *out_headers = headers.value(); + std::move(callback).Run(result); +} + +void URLLoader::OnHeadersReceivedComplete( + net::CompletionOnceCallback callback, + scoped_refptr<net::HttpResponseHeaders>* out_headers, + GURL* out_allowed_unsafe_redirect_url, + int result, + const base::Optional<std::string>& headers, + const GURL& allowed_unsafe_redirect_url) { + if (headers) { + *out_headers = + base::MakeRefCounted<net::HttpResponseHeaders>(headers.value()); + } + *out_allowed_unsafe_redirect_url = allowed_unsafe_redirect_url; + std::move(callback).Run(result); +} + URLLoader::BlockResponseForCorbResult URLLoader::BlockResponseForCorb() { // The response headers and body shouldn't yet be sent to the URLLoaderClient. DCHECK(response_); diff --git a/chromium/services/network/url_loader.h b/chromium/services/network/url_loader.h index e30c8041c96..aedaec52752 100644 --- a/chromium/services/network/url_loader.h +++ b/chromium/services/network/url_loader.h @@ -60,21 +60,22 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader mojom::URLLoaderRequest url_loader_request, int32_t options, const ResourceRequest& request, - bool report_raw_headers, mojom::URLLoaderClientPtr url_loader_client, const net::NetworkTrafficAnnotationTag& traffic_annotation, const mojom::URLLoaderFactoryParams* factory_params, uint32_t request_id, scoped_refptr<ResourceSchedulerClient> resource_scheduler_client, base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder, - base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator); + base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator, + mojom::TrustedURLLoaderHeaderClient* header_client); ~URLLoader() override; // mojom::URLLoader implementation: - void FollowRedirect(const base::Optional<std::vector<std::string>>& - to_be_removed_request_headers, - const base::Optional<net::HttpRequestHeaders>& - modified_request_headers) override; + void FollowRedirect( + const base::Optional<std::vector<std::string>>& + to_be_removed_request_headers, + const base::Optional<net::HttpRequestHeaders>& modified_request_headers, + const base::Optional<GURL>& new_url) override; void ProceedWithResponse() override; void SetPriority(net::RequestPriority priority, int32_t intra_priority_value) override; @@ -95,6 +96,16 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader void OnResponseStarted(net::URLRequest* url_request, int net_error) override; void OnReadCompleted(net::URLRequest* url_request, int bytes_read) override; + // These methods are called by the network delegate to forward these events to + // the |header_client_|. + int OnBeforeStartTransaction(net::CompletionOnceCallback callback, + net::HttpRequestHeaders* headers); + int OnHeadersReceived( + net::CompletionOnceCallback callback, + const net::HttpResponseHeaders* original_response_headers, + scoped_refptr<net::HttpResponseHeaders>* override_response_headers, + GURL* allowed_unsafe_redirect_url); + // mojom::AuthChallengeResponder: void OnAuthCredentials( const base::Optional<net::AuthCredentials>& credentials) override; @@ -112,6 +123,16 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader return custom_proxy_post_cache_headers_; } + bool custom_proxy_use_alternate_proxy_list() const { + return custom_proxy_use_alternate_proxy_list_; + } + + const base::Optional<GURL>& new_redirect_url() const { + return new_redirect_url_; + } + + void SetAllowReportingRawHeaders(bool allow); + // Gets the URLLoader associated with this request. static URLLoader* ForRequest(const net::URLRequest& request); @@ -159,12 +180,25 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader int net_error); void OnCertificateRequestedResponse( const scoped_refptr<net::X509Certificate>& x509_certificate, + const std::string& provider_name, const std::vector<uint16_t>& algorithm_preferences, mojom::SSLPrivateKeyPtr ssl_private_key, bool cancel_certificate_selection); bool HasDataPipe() const; void RecordBodyReadFromNetBeforePausedIfNeeded(); void ResumeStart(); + void OnBeforeSendHeadersComplete( + net::CompletionOnceCallback callback, + net::HttpRequestHeaders* out_headers, + int result, + const base::Optional<net::HttpRequestHeaders>& headers); + void OnHeadersReceivedComplete( + net::CompletionOnceCallback callback, + scoped_refptr<net::HttpResponseHeaders>* out_headers, + GURL* out_allowed_unsafe_redirect_url, + int result, + const base::Optional<std::string>& headers, + const GURL& allowed_unsafe_redirect_url); enum BlockResponseForCorbResult { // Returned when caller of BlockResponseForCorb doesn't need to continue, @@ -189,7 +223,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader // store a raw pointer to mojom::URLLoaderFactoryParams. const mojom::URLLoaderFactoryParams* const factory_params_; - uint32_t render_frame_id_; + int render_frame_id_; uint32_t request_id_; const bool keepalive_; const bool do_not_prompt_for_login_; @@ -220,14 +254,22 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader std::unique_ptr<ResourceScheduler::ScheduledResourceRequest> resource_scheduler_request_handle_; + // Whether client requested raw headers. + const bool want_raw_headers_; + // Whether we actually should report them. bool report_raw_headers_; net::HttpRawRequestHeaders raw_request_headers_; scoped_refptr<const net::HttpResponseHeaders> raw_response_headers_; std::unique_ptr<UploadProgressTracker> upload_progress_tracker_; - // Whether a redirect is currently deferred. - bool deferred_redirect_ = false; + // Holds the URL of a redirect if it's currently deferred. + std::unique_ptr<GURL> deferred_redirect_url_; + + // If |new_url| is given to FollowRedirect() it's saved here, so that it can + // be later referred to from NetworkContext::OnBeforeURLRequestInternal, which + // is called from NetworkDelegate::NotifyBeforeURLRequest. + base::Optional<GURL> new_redirect_url_; bool should_pause_reading_body_ = false; // The response body stream is open, but transferring data is paused. @@ -266,11 +308,14 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) URLLoader net::HttpRequestHeaders custom_proxy_pre_cache_headers_; net::HttpRequestHeaders custom_proxy_post_cache_headers_; + bool custom_proxy_use_alternate_proxy_list_ = false; // Indicates the originating frame of the request, see // network::ResourceRequest::fetch_window_id for details. base::Optional<base::UnguessableToken> fetch_window_id_; + mojom::TrustedURLLoaderHeaderClient* header_client_ = nullptr; + base::WeakPtrFactory<URLLoader> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(URLLoader); diff --git a/chromium/services/network/url_loader_factory.cc b/chromium/services/network/url_loader_factory.cc index da6f7ef7f0d..0b93d46285a 100644 --- a/chromium/services/network/url_loader_factory.cc +++ b/chromium/services/network/url_loader_factory.cc @@ -31,10 +31,11 @@ URLLoaderFactory::URLLoaderFactory( NetworkContext* context, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr<ResourceSchedulerClient> resource_scheduler_client, - cors::CORSURLLoaderFactory* cors_url_loader_factory) + cors::CorsURLLoaderFactory* cors_url_loader_factory) : context_(context), params_(std::move(params)), resource_scheduler_client_(std::move(resource_scheduler_client)), + header_client_(std::move(params_->header_client)), cors_url_loader_factory_(cors_url_loader_factory) { DCHECK(context); DCHECK_NE(mojom::kInvalidProcessId, params_->process_id); @@ -82,16 +83,6 @@ void URLLoaderFactory::CreateLoaderAndStart( origin_head_same_as_request_origin); } - bool report_raw_headers = false; - if (url_request.report_raw_headers) { - const NetworkService* service = context_->network_service(); - report_raw_headers = - service && service->HasRawHeadersAccess(params_->process_id); - if (!report_raw_headers) - DLOG(ERROR) << "Denying raw headers request by process " - << params_->process_id; - } - mojom::NetworkServiceClient* network_service_client = nullptr; base::WeakPtr<KeepaliveStatisticsRecorder> keepalive_statistics_recorder; base::WeakPtr<NetworkUsageAccumulator> network_usage_accumulator; @@ -140,14 +131,13 @@ void URLLoaderFactory::CreateLoaderAndStart( auto loader = std::make_unique<URLLoader>( context_->url_request_context(), network_service_client, - base::BindOnce(&cors::CORSURLLoaderFactory::DestroyURLLoader, + base::BindOnce(&cors::CorsURLLoaderFactory::DestroyURLLoader, base::Unretained(cors_url_loader_factory_)), - std::move(request), options, url_request, report_raw_headers, - std::move(client), + std::move(request), options, url_request, std::move(client), static_cast<net::NetworkTrafficAnnotationTag>(traffic_annotation), params_.get(), request_id, resource_scheduler_client_, std::move(keepalive_statistics_recorder), - std::move(network_usage_accumulator)); + std::move(network_usage_accumulator), header_client_.get()); cors_url_loader_factory_->OnLoaderCreated(std::move(loader)); } diff --git a/chromium/services/network/url_loader_factory.h b/chromium/services/network/url_loader_factory.h index 7031e603a92..8a23061ff12 100644 --- a/chromium/services/network/url_loader_factory.h +++ b/chromium/services/network/url_loader_factory.h @@ -21,7 +21,7 @@ class ResourceSchedulerClient; class URLLoader; namespace cors { -class CORSURLLoaderFactory; +class CorsURLLoaderFactory; } // namespace cors // This class is an implementation of mojom::URLLoaderFactory that @@ -33,9 +33,9 @@ class CORSURLLoaderFactory; // works on each frame. // A URLLoaderFactory can be created with null ResourceSchedulerClient, in which // case requests constructed by the factory will not be throttled. -// The CORS related part is implemented in CORSURLLoader[Factory] until -// kOutOfBlinkCORS and kNetworkService is fully enabled. Note that -// NetworkContext::CreateURLLoaderFactory returns a CORSURLLoaderFactory, +// The CORS related part is implemented in CorsURLLoader[Factory] until +// kOutOfBlinkCors and kNetworkService is fully enabled. Note that +// NetworkContext::CreateURLLoaderFactory returns a CorsURLLoaderFactory, // instead of a URLLoaderFactory. class URLLoaderFactory : public mojom::URLLoaderFactory { public: @@ -44,7 +44,7 @@ class URLLoaderFactory : public mojom::URLLoaderFactory { NetworkContext* context, mojom::URLLoaderFactoryParamsPtr params, scoped_refptr<ResourceSchedulerClient> resource_scheduler_client, - cors::CORSURLLoaderFactory* cors_url_loader_factory); + cors::CorsURLLoaderFactory* cors_url_loader_factory); ~URLLoaderFactory() override; @@ -68,9 +68,10 @@ class URLLoaderFactory : public mojom::URLLoaderFactory { NetworkContext* const context_; mojom::URLLoaderFactoryParamsPtr params_; scoped_refptr<ResourceSchedulerClient> resource_scheduler_client_; + mojom::TrustedURLLoaderHeaderClientPtr header_client_; // |cors_url_loader_factory_| owns this. - cors::CORSURLLoaderFactory* cors_url_loader_factory_; + cors::CorsURLLoaderFactory* cors_url_loader_factory_; DISALLOW_COPY_AND_ASSIGN(URLLoaderFactory); }; diff --git a/chromium/services/network/url_loader_unittest.cc b/chromium/services/network/url_loader_unittest.cc index 0211387904c..0112c579acd 100644 --- a/chromium/services/network/url_loader_unittest.cc +++ b/chromium/services/network/url_loader_unittest.cc @@ -277,9 +277,11 @@ class URLRequestSimulatedCacheJob : public net::URLRequestJob { URLRequestSimulatedCacheJob( net::URLRequest* request, net::NetworkDelegate* network_delegate, - scoped_refptr<net::IOBuffer>* simulated_cache_dest) + scoped_refptr<net::IOBuffer>* simulated_cache_dest, + bool use_text_plain) : URLRequestJob(request, network_delegate), simulated_cache_dest_(simulated_cache_dest), + use_text_plain_(use_text_plain), weak_factory_(this) {} // net::URLRequestJob implementation: @@ -289,6 +291,15 @@ class URLRequestSimulatedCacheJob : public net::URLRequestJob { weak_factory_.GetWeakPtr())); } + void GetResponseInfo(net::HttpResponseInfo* info) override { + if (!use_text_plain_) + return URLRequestJob::GetResponseInfo(info); + if (!info->headers) { + info->headers = net::HttpResponseHeaders::TryToCreate( + "HTTP/1.1 200 OK\r\nContent-Type: text/plain"); + } + } + int ReadRawData(net::IOBuffer* buf, int buf_size) override { DCHECK_GT(buf_size, 0); @@ -307,6 +318,7 @@ class URLRequestSimulatedCacheJob : public net::URLRequestJob { void StartAsync() { NotifyHeadersComplete(); } scoped_refptr<net::IOBuffer>* simulated_cache_dest_; + bool use_text_plain_; base::WeakPtrFactory<URLRequestSimulatedCacheJob> weak_factory_; DISALLOW_COPY_AND_ASSIGN(URLRequestSimulatedCacheJob); @@ -315,18 +327,21 @@ class URLRequestSimulatedCacheJob : public net::URLRequestJob { class SimulatedCacheInterceptor : public net::URLRequestInterceptor { public: explicit SimulatedCacheInterceptor( - scoped_refptr<net::IOBuffer>* simulated_cache_dest) - : simulated_cache_dest_(simulated_cache_dest) {} + scoped_refptr<net::IOBuffer>* simulated_cache_dest, + bool use_text_plain) + : simulated_cache_dest_(simulated_cache_dest), + use_text_plain_(use_text_plain) {} net::URLRequestJob* MaybeInterceptRequest( net::URLRequest* request, net::NetworkDelegate* network_delegate) const override { - return new URLRequestSimulatedCacheJob(request, network_delegate, - simulated_cache_dest_); + return new URLRequestSimulatedCacheJob( + request, network_delegate, simulated_cache_dest_, use_text_plain_); } private: scoped_refptr<net::IOBuffer>* simulated_cache_dest_; + bool use_text_plain_; DISALLOW_COPY_AND_ASSIGN(SimulatedCacheInterceptor); }; @@ -419,16 +434,16 @@ class URLLoaderTest : public testing::Test { url_loader = std::make_unique<URLLoader>( context(), network_service_client.get(), DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), options, request, false, + mojo::MakeRequest(&loader), options, request, client_.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); ran_ = true; if (expect_redirect_) { client_.RunUntilRedirectReceived(); - loader->FollowRedirect(base::nullopt, base::nullopt); + loader->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); } if (body) { @@ -947,10 +962,10 @@ TEST_F(URLLoaderTest, DestroyOnURLLoaderPipeClosed) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); // Run until the response body pipe arrives, to make sure that a live body // pipe does not result in keeping the loader alive when the URLLoader pipe is @@ -999,10 +1014,10 @@ TEST_F(URLLoaderTest, CloseResponseBodyConsumerBeforeProducer) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilResponseBodyArrived(); EXPECT_TRUE(client()->has_received_response()); @@ -1053,10 +1068,10 @@ TEST_F(URLLoaderTest, PauseReadingBodyFromNetBeforeResponseHeaders) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); // Pausing reading response body from network stops future reads from the // underlying URLRequest. So no data should be sent using the response body @@ -1129,10 +1144,10 @@ TEST_F(URLLoaderTest, PauseReadingBodyFromNetWhenReadIsPending) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); response_controller.WaitForRequest(); response_controller.Send( @@ -1194,10 +1209,10 @@ TEST_F(URLLoaderTest, ResumeReadingBodyFromNetAfterClosingConsumer) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); loader->PauseReadingBodyFromNet(); loader.FlushForTesting(); @@ -1254,10 +1269,10 @@ TEST_F(URLLoaderTest, MultiplePauseResumeReadingBodyFromNet) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); // It is okay to call ResumeReadingBodyFromNet() even if there is no prior // PauseReadingBodyFromNet(). @@ -1436,10 +1451,10 @@ TEST_F(URLLoaderTest, UploadFileCanceled) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), network_service_client.get(), DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); mojom::NetworkServiceClient::OnFileUploadRequestedCallback callback; network_service_client->RunUntilUploadRequested(&callback); @@ -1620,11 +1635,11 @@ TEST_F(URLLoaderTest, UploadChunkedDataPipe) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false /* report_raw_headers */, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, nullptr /* resource_scheduler_client */, + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + nullptr /* resource_scheduler_client */, nullptr /* keepalive_statistics_reporter */, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); mojom::ChunkedDataPipeGetter::GetSizeCallback get_size_callback = data_pipe_getter.WaitForGetSize(); @@ -1688,10 +1703,10 @@ TEST_F(URLLoaderTest, RedirectModifiedHeaders) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, false, + mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilRedirectReceived(); @@ -1705,7 +1720,7 @@ TEST_F(URLLoaderTest, RedirectModifiedHeaders) { net::HttpRequestHeaders redirect_headers; redirect_headers.SetHeader("Header2", ""); redirect_headers.SetHeader("Header3", "Value3"); - loader->FollowRedirect(base::nullopt, redirect_headers); + loader->FollowRedirect(base::nullopt, redirect_headers, base::nullopt); client()->RunUntilComplete(); delete_run_loop.Run(); @@ -1733,10 +1748,10 @@ TEST_F(URLLoaderTest, RedirectRemoveHeader) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, false, + mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilRedirectReceived(); @@ -1747,7 +1762,8 @@ TEST_F(URLLoaderTest, RedirectRemoveHeader) { // Remove Header1. std::vector<std::string> to_be_removed_request_headers = {"Header1"}; - loader->FollowRedirect(to_be_removed_request_headers, base::nullopt); + loader->FollowRedirect(to_be_removed_request_headers, base::nullopt, + base::nullopt); client()->RunUntilComplete(); delete_run_loop.Run(); @@ -1774,10 +1790,10 @@ TEST_F(URLLoaderTest, RedirectRemoveHeaderAndAddItBack) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, false, + mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilRedirectReceived(); @@ -1790,7 +1806,8 @@ TEST_F(URLLoaderTest, RedirectRemoveHeaderAndAddItBack) { std::vector<std::string> to_be_removed_request_headers = {"Header1"}; net::HttpRequestHeaders redirect_headers; redirect_headers.SetHeader("Header1", "NewValue1"); - loader->FollowRedirect(to_be_removed_request_headers, redirect_headers); + loader->FollowRedirect(to_be_removed_request_headers, redirect_headers, + base::nullopt); client()->RunUntilComplete(); delete_run_loop.Run(); @@ -1891,10 +1908,10 @@ TEST_F(URLLoaderTest, ResourceSchedulerIntegration) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, NeverInvokedDeleteLoaderCallback(), - mojo::MakeRequest(&loaderInterfacePtr), 0, request, false, + mojo::MakeRequest(&loaderInterfacePtr), 0, request, client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); loaders.emplace_back( std::make_pair(std::move(url_loader), std::move(loaderInterfacePtr))); @@ -1912,10 +1929,10 @@ TEST_F(URLLoaderTest, ResourceSchedulerIntegration) { std::unique_ptr<URLLoader> loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, NeverInvokedDeleteLoaderCallback(), - mojo::MakeRequest(&loader_interface_ptr), 0, request, false, + mojo::MakeRequest(&loader_interface_ptr), 0, request, client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); base::RunLoop().RunUntilIdle(); // Make sure that the ResourceScheduler throttles this request. @@ -1947,10 +1964,10 @@ TEST_F(URLLoaderTest, ReadPipeClosedWhileReadTaskPosted) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, false, + mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilResponseBodyArrived(); client()->response_body_release(); @@ -2001,10 +2018,10 @@ TEST_F(URLLoaderTest, EnterSuspendModeWhileNoPendingRead) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, false, + mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); // This will spin the run loop until the Mojo read buffer is full. The // URLLoader will end up waiting for Mojo to give it more read buffer space. @@ -2019,6 +2036,52 @@ TEST_F(URLLoaderTest, EnterSuspendModeWhileNoPendingRead) { unowned_power_monitor_source->Resume(); } +// This tests the case where suspend mode is entered when a job is trying to do +// mime detection, but is paused and therefore does not have a pending read to +// provide partial data. +TEST_F(URLLoaderTest, EnterSuspendModePaused) { + GURL url("http://www.example.com"); + scoped_refptr<net::IOBuffer> simulated_cache_dest; + // Using SimulatedCacheInterceptor here since it marks the read pending, + // which avoids races between various events. + net::URLRequestFilter::GetInstance()->AddUrlInterceptor( + url, std::make_unique<SimulatedCacheInterceptor>( + &simulated_cache_dest, true /* use_text_plain */)); + + std::unique_ptr<TestPowerMonitorSource> power_monitor_source = + std::make_unique<TestPowerMonitorSource>(); + TestPowerMonitorSource* unowned_power_monitor_source = + power_monitor_source.get(); + base::PowerMonitor power_monitor(std::move(power_monitor_source)); + + ResourceRequest request = CreateResourceRequest("GET", url); + + base::RunLoop delete_run_loop; + mojom::URLLoaderPtr loader; + std::unique_ptr<URLLoader> url_loader; + mojom::URLLoaderFactoryParams params; + params.process_id = mojom::kBrowserProcessId; + params.is_corb_enabled = false; + url_loader = std::make_unique<URLLoader>( + context(), nullptr /* network_service_client */, + DeleteLoaderCallback(&delete_run_loop, &url_loader), + mojo::MakeRequest(&loader), mojom::kURLLoadOptionSniffMimeType, request, + client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, + 0 /* request_id */, resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); + + url_loader->PauseReadingBodyFromNet(); + base::RunLoop().RunUntilIdle(); + + unowned_power_monitor_source->Suspend(); + + client()->RunUntilComplete(); + EXPECT_EQ(net::ERR_ABORTED, client()->completion_status().error_code); + delete_run_loop.Run(); + + unowned_power_monitor_source->Resume(); +} + TEST_F(URLLoaderTest, EnterSuspendDiskCacheWriteQueued) { // Test to make sure that fetch abort on suspend doesn't yank out the backing // for IOBuffer for an issued disk_cache Write. @@ -2026,7 +2089,8 @@ TEST_F(URLLoaderTest, EnterSuspendDiskCacheWriteQueued) { GURL url("http://www.example.com"); scoped_refptr<net::IOBuffer> simulated_cache_dest; net::URLRequestFilter::GetInstance()->AddUrlInterceptor( - url, std::make_unique<SimulatedCacheInterceptor>(&simulated_cache_dest)); + url, std::make_unique<SimulatedCacheInterceptor>( + &simulated_cache_dest, false /* use_text_plain */)); std::unique_ptr<TestPowerMonitorSource> power_monitor_source = std::make_unique<TestPowerMonitorSource>(); @@ -2045,10 +2109,10 @@ TEST_F(URLLoaderTest, EnterSuspendDiskCacheWriteQueued) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, false, + mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); // Spin until the job has produced a (simulated) cache write. base::RunLoop().RunUntilIdle(); @@ -2168,16 +2232,19 @@ class MockNetworkServiceClient : public mojom::NetworkServiceClient { url_loader_ptr_->reset(); break; case CertificateResponse::CANCEL_CERTIFICATE_SELECTION: - std::move(callback).Run(nullptr, std::vector<uint16_t>(), nullptr, + std::move(callback).Run(nullptr, std::string(), std::vector<uint16_t>(), + nullptr, true /* cancel_certificate_selection */); break; case CertificateResponse::NULL_CERTIFICATE: - std::move(callback).Run(nullptr, std::vector<uint16_t>(), nullptr, + std::move(callback).Run(nullptr, std::string(), std::vector<uint16_t>(), + nullptr, false /* cancel_certificate_selection */); break; case CertificateResponse::VALID_CERTIFICATE_SIGNATURE: case CertificateResponse::INVALID_CERTIFICATE_SIGNATURE: - std::move(callback).Run(std::move(certificate_), algorithm_preferences_, + std::move(callback).Run(std::move(certificate_), provider_name_, + algorithm_preferences_, std::move(ssl_private_key_ptr_), false /* cancel_certificate_selection */); break; @@ -2212,6 +2279,12 @@ class MockNetworkServiceClient : public mojom::NetworkServiceClient { NOTREACHED(); } +#if defined(OS_CHROMEOS) + void OnTrustAnchorUsed(const std::string& username_hash) override { + NOTREACHED(); + } +#endif + void OnFileUploadRequested(uint32_t process_id, bool async, const std::vector<base::FilePath>& file_paths, @@ -2233,6 +2306,10 @@ class MockNetworkServiceClient : public mojom::NetworkServiceClient { NOTREACHED(); } + void OnDataUseUpdate(int32_t network_traffic_annotation_id_hash, + int64_t recv_bytes, + int64_t sent_bytes) override {} + void set_credentials_response(CredentialsResponse credentials_response) { credentials_response_ = credentials_response; } @@ -2253,6 +2330,7 @@ class MockNetworkServiceClient : public mojom::NetworkServiceClient { void set_private_key(scoped_refptr<net::SSLPrivateKey> ssl_private_key) { ssl_private_key_ = std::move(ssl_private_key); + provider_name_ = ssl_private_key_->GetProviderName(); algorithm_preferences_ = ssl_private_key_->GetAlgorithmPreferences(); auto ssl_private_key_request = mojo::MakeRequest(&ssl_private_key_ptr_); mojo::MakeStrongBinding( @@ -2278,6 +2356,7 @@ class MockNetworkServiceClient : public mojom::NetworkServiceClient { scoped_refptr<net::SSLPrivateKey> ssl_private_key_; scoped_refptr<net::X509Certificate> certificate_; network::mojom::SSLPrivateKeyPtr ssl_private_key_ptr_; + std::string provider_name_; std::vector<uint16_t> algorithm_preferences_; int on_certificate_requested_counter_ = 0; @@ -2299,10 +2378,10 @@ TEST_F(URLLoaderTest, SetAuth) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(url_loader); @@ -2340,10 +2419,10 @@ TEST_F(URLLoaderTest, CancelAuth) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(url_loader); @@ -2382,10 +2461,10 @@ TEST_F(URLLoaderTest, TwoChallenges) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(url_loader); @@ -2425,10 +2504,10 @@ TEST_F(URLLoaderTest, NoAuthRequiredForFavicon) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(url_loader); @@ -2467,10 +2546,10 @@ TEST_F(URLLoaderTest, HttpAuthResponseHeadersAvailable) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); base::RunLoop().RunUntilIdle(); ASSERT_TRUE(url_loader); @@ -2496,7 +2575,7 @@ TEST_F(URLLoaderTest, CorbEffectiveWithCors) { ResourceRequest request = CreateResourceRequest("GET", test_server()->GetURL("/hello.html")); request.resource_type = kResourceType; - request.fetch_request_mode = mojom::FetchRequestMode::kCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kCors; request.request_initiator = url::Origin::Create(GURL("http://foo.com/")); base::RunLoop delete_run_loop; @@ -2507,10 +2586,10 @@ TEST_F(URLLoaderTest, CorbEffectiveWithCors) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilResponseBodyArrived(); std::string body = ReadBody(); @@ -2532,7 +2611,7 @@ TEST_F(URLLoaderTest, CorbExcludedWithNoCors) { ResourceRequest request = CreateResourceRequest("GET", test_server()->GetURL("/hello.html")); request.resource_type = kResourceType; - request.fetch_request_mode = mojom::FetchRequestMode::kNoCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kNoCors; request.request_initiator = url::Origin::Create(GURL("http://foo.com/")); base::RunLoop delete_run_loop; @@ -2545,10 +2624,10 @@ TEST_F(URLLoaderTest, CorbExcludedWithNoCors) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilResponseBodyArrived(); std::string body = ReadBody(); @@ -2572,7 +2651,7 @@ TEST_F(URLLoaderTest, CorbEffectiveWithNoCorsWhenNoActualPlugin) { ResourceRequest request = CreateResourceRequest("GET", test_server()->GetURL("/hello.html")); request.resource_type = kResourceType; - request.fetch_request_mode = mojom::FetchRequestMode::kNoCORS; + request.fetch_request_mode = mojom::FetchRequestMode::kNoCors; request.request_initiator = url::Origin::Create(GURL("http://foo.com/")); base::RunLoop delete_run_loop; @@ -2586,10 +2665,10 @@ TEST_F(URLLoaderTest, CorbEffectiveWithNoCorsWhenNoActualPlugin) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilResponseBodyArrived(); std::string body = ReadBody(); @@ -2619,15 +2698,16 @@ TEST_F(URLLoaderTest, FollowRedirectTwice) { url_loader = std::make_unique<URLLoader>( context(), nullptr /* network_service_client */, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, false, + mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request, client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + nullptr /* network_usage_accumulator */, nullptr /* header_client */); client()->RunUntilRedirectReceived(); - url_loader->FollowRedirect(base::nullopt, base::nullopt); - EXPECT_DCHECK_DEATH(url_loader->FollowRedirect(base::nullopt, base::nullopt)); + url_loader->FollowRedirect(base::nullopt, base::nullopt, base::nullopt); + EXPECT_DCHECK_DEATH( + url_loader->FollowRedirect(base::nullopt, base::nullopt, base::nullopt)); client()->RunUntilComplete(); delete_run_loop.Run(); @@ -2641,6 +2721,7 @@ class TestSSLPrivateKey : public net::SSLPrivateKey { void set_fail_signing(bool fail_signing) { fail_signing_ = fail_signing; } int sign_count() const { return sign_count_; } + std::string GetProviderName() override { return key_->GetProviderName(); } std::vector<uint16_t> GetAlgorithmPreferences() override { return key_->GetAlgorithmPreferences(); } @@ -2694,10 +2775,10 @@ TEST_F(URLLoaderTest, ClientAuthCancelConnection) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); network_service_client.set_url_loader_ptr(&loader); RunUntilIdle(); @@ -2733,10 +2814,10 @@ TEST_F(URLLoaderTest, ClientAuthCancelCertificateSelection) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); RunUntilIdle(); ASSERT_TRUE(url_loader); @@ -2755,6 +2836,13 @@ TEST_F(URLLoaderTest, ClientAuthNoCertificate) { net::SSLServerConfig ssl_config; ssl_config.client_cert_type = net::SSLServerConfig::ClientCertType::REQUIRE_CLIENT_CERT; + + // TLS 1.3 client auth errors show up post-handshake, resulting in a read + // error which on Windows causes the socket to shutdown immediately before the + // error is read. + // TODO(crbug.com/906668): Add support for testing this in TLS 1.3. + ssl_config.version_max = net::SSL_PROTOCOL_VERSION_TLS1_2; + test_server.SetSSLConfig(net::EmbeddedTestServer::CERT_OK, ssl_config); test_server.AddDefaultHandlers( base::FilePath(FILE_PATH_LITERAL("services/test/data"))); @@ -2774,10 +2862,10 @@ TEST_F(URLLoaderTest, ClientAuthNoCertificate) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); RunUntilIdle(); ASSERT_TRUE(url_loader); @@ -2828,10 +2916,10 @@ TEST_F(URLLoaderTest, ClientAuthCertificateWithValidSignature) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); RunUntilIdle(); ASSERT_TRUE(url_loader); @@ -2884,10 +2972,10 @@ TEST_F(URLLoaderTest, ClientAuthCertificateWithInvalidSignature) { std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>( context(), &network_service_client, DeleteLoaderCallback(&delete_run_loop, &url_loader), - mojo::MakeRequest(&loader), 0, request, false, - client()->CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, - 0 /* request_id */, resource_scheduler_client(), nullptr, - nullptr /* network_usage_accumulator */); + mojo::MakeRequest(&loader), 0, request, client()->CreateInterfacePtr(), + TRAFFIC_ANNOTATION_FOR_TESTS, ¶ms, 0 /* request_id */, + resource_scheduler_client(), nullptr, + nullptr /* network_usage_accumulator */, nullptr /* header_client */); RunUntilIdle(); ASSERT_TRUE(url_loader); diff --git a/chromium/services/network/websocket.cc b/chromium/services/network/websocket.cc index 55e853a4d27..feda8658aae 100644 --- a/chromium/services/network/websocket.cc +++ b/chromium/services/network/websocket.cc @@ -204,37 +204,44 @@ void WebSocket::WebSocketEventHandler::OnFailChannel( void WebSocket::WebSocketEventHandler::OnStartOpeningHandshake( std::unique_ptr<net::WebSocketHandshakeRequestInfo> request) { - bool should_send = impl_->delegate_->CanReadRawCookies(); + bool can_read_raw_cookies = impl_->delegate_->CanReadRawCookies(request->url); DVLOG(3) << "WebSocketEventHandler::OnStartOpeningHandshake @" - << reinterpret_cast<void*>(this) << " should_send=" << should_send; - - if (!should_send) - return; + << reinterpret_cast<void*>(this) + << " can_read_raw_cookies =" << can_read_raw_cookies; mojom::WebSocketHandshakeRequestPtr request_to_pass( mojom::WebSocketHandshakeRequest::New()); request_to_pass->url.Swap(&request->url); + std::string headers_text = base::StringPrintf( + "GET %s HTTP/1.1\r\n", request_to_pass->url.spec().c_str()); net::HttpRequestHeaders::Iterator it(request->headers); while (it.GetNext()) { + if (!can_read_raw_cookies && + base::EqualsCaseInsensitiveASCII(it.name(), + net::HttpRequestHeaders::kCookie)) { + continue; + } mojom::HttpHeaderPtr header(mojom::HttpHeader::New()); header->name = it.name(); header->value = it.value(); request_to_pass->headers.push_back(std::move(header)); + headers_text.append(base::StringPrintf("%s: %s\r\n", it.name().c_str(), + it.value().c_str())); } - request_to_pass->headers_text = - base::StringPrintf("GET %s HTTP/1.1\r\n", - request_to_pass->url.spec().c_str()) + - request->headers.ToString(); + headers_text.append("\r\n"); + request_to_pass->headers_text = std::move(headers_text); impl_->client_->OnStartOpeningHandshake(std::move(request_to_pass)); } void WebSocket::WebSocketEventHandler::OnFinishOpeningHandshake( std::unique_ptr<net::WebSocketHandshakeResponseInfo> response) { + bool can_read_raw_cookies = + impl_->delegate_->CanReadRawCookies(response->url); DVLOG(3) << "WebSocketEventHandler::OnFinishOpeningHandshake " << reinterpret_cast<void*>(this) - << " CanReadRawCookies=" << impl_->delegate_->CanReadRawCookies(); + << " CanReadRawCookies=" << can_read_raw_cookies; mojom::WebSocketHandshakeResponsePtr response_to_pass( mojom::WebSocketHandshakeResponse::New()); @@ -246,7 +253,7 @@ void WebSocket::WebSocketEventHandler::OnFinishOpeningHandshake( size_t iter = 0; std::string name, value; while (response->headers->EnumerateHeaderLines(&iter, &name, &value)) { - if (impl_->delegate_->CanReadRawCookies() || + if (can_read_raw_cookies || !net::HttpResponseHeaders::IsCookieResponseHeader(name)) { // We drop cookie-related headers such as "set-cookie" when the // renderer doesn't have access. diff --git a/chromium/services/network/websocket.h b/chromium/services/network/websocket.h index c54b9b2114d..8b736249cff 100644 --- a/chromium/services/network/websocket.h +++ b/chromium/services/network/websocket.h @@ -55,7 +55,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) WebSocket : public mojom::WebSocket { bool fatal) = 0; // This function may delete |impl|. virtual void ReportBadMessage(BadMessageReason reason, WebSocket* impl) = 0; - virtual bool CanReadRawCookies() = 0; + virtual bool CanReadRawCookies(const GURL& url) = 0; virtual void OnCreateURLRequest(int child_id, int frame_id, net::URLRequest* request) = 0; diff --git a/chromium/services/network/websocket_factory.cc b/chromium/services/network/websocket_factory.cc index 75e9516ff7a..fd21e41daec 100644 --- a/chromium/services/network/websocket_factory.cc +++ b/chromium/services/network/websocket_factory.cc @@ -59,9 +59,9 @@ class WebSocketFactory::Delegate final : public WebSocket::Delegate { OnLostConnectionToClient(impl); } - bool CanReadRawCookies() override { + bool CanReadRawCookies(const GURL& url) override { return factory_->context_->network_service()->HasRawHeadersAccess( - process_id_); + process_id_, url); } void OnCreateURLRequest(int child_id, |