diff options
Diffstat (limited to 'chromium/services/network/public')
142 files changed, 4523 insertions, 3920 deletions
diff --git a/chromium/services/network/public/cpp/BUILD.gn b/chromium/services/network/public/cpp/BUILD.gn index 1ba78613e28..1e3bd0ffecc 100644 --- a/chromium/services/network/public/cpp/BUILD.gn +++ b/chromium/services/network/public/cpp/BUILD.gn @@ -43,10 +43,6 @@ component("cpp") { "cors/origin_access_entry.h", "cors/origin_access_list.cc", "cors/origin_access_list.h", - "cors/preflight_cache.cc", - "cors/preflight_cache.h", - "cors/preflight_result.cc", - "cors/preflight_result.h", "cross_origin_embedder_policy_parser.cc", "cross_origin_embedder_policy_parser.h", "cross_origin_opener_policy_parser.cc", @@ -83,8 +79,10 @@ component("cpp") { "network_switches.h", "not_implemented_url_loader_factory.cc", "not_implemented_url_loader_factory.h", - "origin_isolation_parser.cc", - "origin_isolation_parser.h", + "opaque_response_blocking.cc", + "opaque_response_blocking.h", + "origin_agent_cluster_parser.cc", + "origin_agent_cluster_parser.h", "parsed_headers.cc", "parsed_headers.h", "request_destination.cc", @@ -93,6 +91,8 @@ component("cpp") { "request_mode.h", "resolve_host_client_base.cc", "resolve_host_client_base.h", + "self_deleting_url_loader_factory.cc", + "self_deleting_url_loader_factory.h", "session_cookie_delete_predicate.h", "shared_url_loader_factory.cc", "shared_url_loader_factory.h", @@ -114,6 +114,8 @@ component("cpp") { "web_sandbox_flags.h", "wrapper_shared_url_loader_factory.cc", "wrapper_shared_url_loader_factory.h", + "x_frame_options_parser.cc", + "x_frame_options_parser.h", ] if (!is_ios) { @@ -179,12 +181,31 @@ component("cookies_mojom_support") { ] deps = [ ":crash_keys", + ":schemeful_site_mojom_support", "//net", "//services/network/public/mojom:cookies_mojom_shared", ] defines = [ "IS_NETWORK_CPP_BASE_IMPL" ] } +component("network_param_mojom_support") { + sources = [ + "net_ipc_param_traits.cc", + "net_ipc_param_traits.h", + "network_param_mojom_traits.cc", + "network_param_mojom_traits.h", + ] + deps = [ + "//ipc", + "//mojo/public/cpp/base:shared_typemap_traits", + "//net", + "//services/network/public/mojom:mojom_network_param_shared", + "//url/ipc:url_ipc", + "//url/mojom:url_mojom_origin", + ] + defines = [ "IS_NETWORK_CPP_BASE_IMPL" ] +} + # This component is separate from cpp_base as it is a dependency of # //services/network/public/mojom:url_loader_base. component("cross_origin_embedder_policy") { @@ -237,12 +258,8 @@ component("cpp_base") { "http_request_headers_mojom_traits.h", "isolation_info_mojom_traits.cc", "isolation_info_mojom_traits.h", - "isolation_opt_in_hints.cc", - "isolation_opt_in_hints.h", "mutable_network_traffic_annotation_tag_mojom_traits.h", "mutable_partial_network_traffic_annotation_tag_mojom_traits.h", - "net_ipc_param_traits.cc", - "net_ipc_param_traits.h", "network_interface_mojom_traits.cc", "network_interface_mojom_traits.h", "network_ipc_param_traits.cc", @@ -280,6 +297,7 @@ component("cpp_base") { ":crash_keys", ":cross_origin_embedder_policy", ":ip_address_mojom_support", + ":network_param_mojom_support", ":schemeful_site_mojom_support", "//services/network/public/mojom:url_loader_base", "//third_party/webrtc_overrides:webrtc_component", @@ -303,6 +321,21 @@ mojom("test_interfaces") { public_deps = [ "//services/network/public/mojom" ] } +source_set("test_support") { + testonly = true + + sources = [ "is_potentially_trustworthy_unittest.h" ] + + public_deps = [ + ":cpp", + "//base", + "//base/test:test_support", + "//testing/gmock", + "//testing/gtest", + "//url:url_test_support", + ] +} + source_set("tests") { testonly = true @@ -316,8 +349,6 @@ source_set("tests") { "cors/cors_unittest.cc", "cors/origin_access_entry_unittest.cc", "cors/origin_access_list_unittest.cc", - "cors/preflight_cache_unittest.cc", - "cors/preflight_result_unittest.cc", "cross_origin_embedder_policy_parser_unittest.cc", "cross_origin_opener_policy_parser_unittest.cc", "cross_origin_read_blocking_unittest.cc", @@ -340,8 +371,9 @@ source_set("tests") { "network_isolation_key_mojom_traits_unittest.cc", "network_mojom_traits_unittest.cc", "network_quality_tracker_unittest.cc", + "opaque_response_blocking_unittest.cc", "optional_trust_token_params_unittest.cc", - "origin_isolation_parser_unittest.cc", + "origin_agent_cluster_parser_unittest.cc", "proxy_config_mojom_traits_unittest.cc", "schemeful_site_mojom_traits_unittest.cc", "simple_url_loader_unittest.cc", @@ -350,6 +382,7 @@ source_set("tests") { "supports_loading_mode/supports_loading_mode_parser_unittest.cc", "url_request_mojom_traits_unittest.cc", "web_sandbox_flags_unittests.cc", + "x_frame_options_parser_unittest.cc", ] if (!is_ios) { @@ -359,6 +392,7 @@ source_set("tests") { deps = [ ":cpp", ":test_interfaces", + ":test_support", "//base", "//mojo/public/cpp/bindings", "//mojo/public/cpp/test_support:test_utils", @@ -373,6 +407,12 @@ source_set("tests") { public_deps = [ ":buildflags" ] } +fuzzer_test("xfo_fuzzer") { + sources = [ "x_frame_options_parser_fuzzer.cc" ] + dict = "x_frame_options.dict" + deps = [ ":cpp" ] +} + fuzzer_test("cors_fuzzer") { sources = [ "cors/cors_fuzzer.cc" ] deps = [ ":cpp" ] diff --git a/chromium/services/network/public/cpp/address_list_mojom_traits.cc b/chromium/services/network/public/cpp/address_list_mojom_traits.cc index 0dac1c7c001..795bbb9a856 100644 --- a/chromium/services/network/public/cpp/address_list_mojom_traits.cc +++ b/chromium/services/network/public/cpp/address_list_mojom_traits.cc @@ -16,10 +16,10 @@ bool StructTraits<network::mojom::AddressListDataView, net::AddressList>::Read( if (!data.ReadAddresses(&out->endpoints())) return false; - std::string canonical_name; - if (!data.ReadCanonicalName(&canonical_name)) + std::vector<std::string> dns_aliases; + if (!data.ReadDnsAliases(&dns_aliases)) return false; - out->set_canonical_name(canonical_name); + out->SetDnsAliases(std::move(dns_aliases)); return true; } diff --git a/chromium/services/network/public/cpp/address_list_mojom_traits.h b/chromium/services/network/public/cpp/address_list_mojom_traits.h index 6c47b7c7aa4..7186789268a 100644 --- a/chromium/services/network/public/cpp/address_list_mojom_traits.h +++ b/chromium/services/network/public/cpp/address_list_mojom_traits.h @@ -22,8 +22,9 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) return obj.endpoints(); } - static const std::string& canonical_name(const net::AddressList& obj) { - return obj.canonical_name(); + static const std::vector<std::string>& dns_aliases( + const net::AddressList& obj) { + return obj.dns_aliases(); } static bool Read(network::mojom::AddressListDataView data, diff --git a/chromium/services/network/public/cpp/cert_verifier/BUILD.gn b/chromium/services/network/public/cpp/cert_verifier/BUILD.gn index cf8e80d2d6f..a0e55c95f7c 100644 --- a/chromium/services/network/public/cpp/cert_verifier/BUILD.gn +++ b/chromium/services/network/public/cpp/cert_verifier/BUILD.gn @@ -22,12 +22,9 @@ source_set("mojo_cert_verifier") { } source_set("cert_verifier_creation") { - sources = [ - "cert_verifier_creation.cc", - "cert_verifier_creation.h", - ] + sources = [] - if (is_ash) { + if (is_chromeos_ash) { sources += [ "system_trust_store_provider_chromeos.cc", "system_trust_store_provider_chromeos.h", diff --git a/chromium/services/network/public/cpp/cert_verifier/DIR_METADATA b/chromium/services/network/public/cpp/cert_verifier/DIR_METADATA index c97d1840b0a..14a5f5e3989 100644 --- a/chromium/services/network/public/cpp/cert_verifier/DIR_METADATA +++ b/chromium/services/network/public/cpp/cert_verifier/DIR_METADATA @@ -8,4 +8,5 @@ monorail { component: "Internals>Network>Certificate" -}
\ No newline at end of file +} +team_email: "trusty-transport@chromium.org" diff --git a/chromium/services/network/public/cpp/cert_verifier/cert_verifier_creation.cc b/chromium/services/network/public/cpp/cert_verifier/cert_verifier_creation.cc deleted file mode 100644 index c1974d1533d..00000000000 --- a/chromium/services/network/public/cpp/cert_verifier/cert_verifier_creation.cc +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2020 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/cert_verifier/cert_verifier_creation.h" - -#include "build/build_config.h" -#include "build/chromeos_buildflags.h" -#include "net/base/features.h" -#include "net/cert_net/cert_net_fetcher_url_request.h" -#include "net/net_buildflags.h" - -#if BUILDFLAG(IS_ASH) -#include "crypto/nss_util_internal.h" -#include "net/cert/cert_verify_proc.h" -#include "net/cert/cert_verify_proc_builtin.h" -#include "net/cert/internal/system_trust_store.h" -#include "net/cert/multi_threaded_cert_verifier.h" -#include "services/network/public/cpp/cert_verifier/system_trust_store_provider_chromeos.h" -#endif - -#if BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED) || \ - BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED) -#include "net/cert/cert_verify_proc.h" -#include "net/cert/cert_verify_proc_builtin.h" -#include "net/cert/multi_threaded_cert_verifier.h" -#endif - -#if BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED) -#include "services/network/public/cpp/cert_verifier/trial_comparison_cert_verifier_mojo.h" -#endif - -namespace network { - -namespace { - -#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED) -bool UsingBuiltinCertVerifier( - mojom::CertVerifierCreationParams::CertVerifierImpl mode) { - switch (mode) { - case mojom::CertVerifierCreationParams::CertVerifierImpl::kDefault: - return base::FeatureList::IsEnabled( - net::features::kCertVerifierBuiltinFeature); - case mojom::CertVerifierCreationParams::CertVerifierImpl::kBuiltin: - return true; - case mojom::CertVerifierCreationParams::CertVerifierImpl::kSystem: - return false; - } -} -#endif - -#if BUILDFLAG(IS_ASH) -scoped_refptr<net::CertVerifyProc> CreateCertVerifyProcForUser( - scoped_refptr<net::CertNetFetcher> net_fetcher, - crypto::ScopedPK11Slot user_public_slot) { - return net::CreateCertVerifyProcBuiltin( - std::move(net_fetcher), - std::make_unique<SystemTrustStoreProviderChromeOS>( - std::move(user_public_slot))); -} - -scoped_refptr<net::CertVerifyProc> CreateCertVerifyProcWithoutUserSlots( - scoped_refptr<net::CertNetFetcher> net_fetcher) { - return net::CreateCertVerifyProcBuiltin( - std::move(net_fetcher), - std::make_unique<SystemTrustStoreProviderChromeOS>()); -} -#endif // BUILDFLAG(IS_ASH) - -} // namespace - -bool IsUsingCertNetFetcher() { -#if defined(OS_ANDROID) || defined(OS_FUCHSIA) || defined(OS_CHROMEOS) || \ - defined(OS_LINUX) || \ - BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED) || \ - BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED) - return true; -#else - return false; -#endif -} - -std::unique_ptr<net::CertVerifier> CreateCertVerifier( - mojom::CertVerifierCreationParams* creation_params, - scoped_refptr<net::CertNetFetcher> cert_net_fetcher) { - DCHECK(cert_net_fetcher || !IsUsingCertNetFetcher()); - - std::unique_ptr<net::CertVerifier> cert_verifier; - - bool use_builtin_cert_verifier; -#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED) - use_builtin_cert_verifier = - creation_params - ? UsingBuiltinCertVerifier(creation_params->use_builtin_cert_verifier) - : UsingBuiltinCertVerifier( - mojom::CertVerifierCreationParams::CertVerifierImpl::kDefault); -#else - use_builtin_cert_verifier = false; -#endif - -#if BUILDFLAG(IS_ASH) - scoped_refptr<net::CertVerifyProc> verify_proc; - if (!creation_params || creation_params->username_hash.empty()) { - verify_proc = - CreateCertVerifyProcWithoutUserSlots(std::move(cert_net_fetcher)); - } else { - // Make sure NSS is initialized for the user. - crypto::InitializeNSSForChromeOSUser(creation_params->username_hash, - creation_params->nss_path.value()); - - crypto::ScopedPK11Slot public_slot = - crypto::GetPublicSlotForChromeOSUser(creation_params->username_hash); - verify_proc = CreateCertVerifyProcForUser(std::move(cert_net_fetcher), - std::move(public_slot)); - } - cert_verifier = - std::make_unique<net::MultiThreadedCertVerifier>(std::move(verify_proc)); -#endif -#if BUILDFLAG(TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED) - if (!cert_verifier && creation_params && - creation_params->trial_comparison_cert_verifier_params) { - cert_verifier = std::make_unique<TrialComparisonCertVerifierMojo>( - creation_params->trial_comparison_cert_verifier_params->initial_allowed, - std::move(creation_params->trial_comparison_cert_verifier_params - ->config_client_receiver), - std::move(creation_params->trial_comparison_cert_verifier_params - ->report_client), - net::CertVerifyProc::CreateSystemVerifyProc(cert_net_fetcher), - net::CertVerifyProc::CreateBuiltinVerifyProc(cert_net_fetcher)); - } -#endif -#if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED) - if (!cert_verifier) { - cert_verifier = std::make_unique<net::MultiThreadedCertVerifier>( - use_builtin_cert_verifier - ? net::CertVerifyProc::CreateBuiltinVerifyProc( - std::move(cert_net_fetcher)) - : net::CertVerifyProc::CreateSystemVerifyProc( - std::move(cert_net_fetcher))); - } -#endif - - if (!cert_verifier) { - cert_verifier = net::CertVerifier::CreateDefaultWithoutCaching( - std::move(cert_net_fetcher)); - } - - return cert_verifier; -} -} // namespace network diff --git a/chromium/services/network/public/cpp/cert_verifier/cert_verifier_creation.h b/chromium/services/network/public/cpp/cert_verifier/cert_verifier_creation.h deleted file mode 100644 index ea6a8120661..00000000000 --- a/chromium/services/network/public/cpp/cert_verifier/cert_verifier_creation.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020 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_CERT_VERIFIER_CERT_VERIFIER_CREATION_H_ -#define SERVICES_NETWORK_PUBLIC_CPP_CERT_VERIFIER_CERT_VERIFIER_CREATION_H_ - -#include <memory> - -#include "base/component_export.h" -#include "base/memory/scoped_refptr.h" -#include "net/cert/cert_net_fetcher.h" -#include "net/cert/cert_verifier.h" -#include "services/network/public/mojom/network_context.mojom.h" - -namespace network { - -// Certain platforms and build configurations require a net::CertNetFetcher in -// order to instantiate a net::CertVerifier. Callers of CreateCertVerifier() can -// call IsUsingCertNetFetcher() to decide whether or not to pass a -// net::CertNetFetcher. -bool IsUsingCertNetFetcher(); - -// Creates a concrete net::CertVerifier based on the platform and the particular -// build configuration. |creation_params| is optional. -std::unique_ptr<net::CertVerifier> CreateCertVerifier( - mojom::CertVerifierCreationParams* creation_params, - scoped_refptr<net::CertNetFetcher> cert_net_fetcher); - -} // namespace network - -#endif // SERVICES_NETWORK_PUBLIC_CPP_CERT_VERIFIER_CERT_VERIFIER_CREATION_H_ diff --git a/chromium/services/network/public/cpp/cert_verifier/cert_verifier_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/cert_verifier/cert_verifier_mojom_traits_unittest.cc index 3388e84eb7a..790d06e3023 100644 --- a/chromium/services/network/public/cpp/cert_verifier/cert_verifier_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/cert_verifier/cert_verifier_mojom_traits_unittest.cc @@ -34,7 +34,7 @@ TEST(CertVerifierMojomTraitsTest, RequestParams) { net::CertVerifier::RequestParams out_params; ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::RequestParams>( - ¶ms, &out_params)); + params, out_params)); ASSERT_EQ(params, out_params); } @@ -114,7 +114,7 @@ TEST(CertVerifierMojomTraitsTest, ConfigBasic) { net::CertVerifier::Config out_config; ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CertVerifierConfig>( - &config, &out_config)); + config, out_config)); ASSERT_TRUE(ConfigsEqual(config, out_config)); } @@ -128,19 +128,16 @@ TEST(CertVerifierMojomTraitsTest, ConfigTrue) { net::CertVerifier::Config out_config; ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CertVerifierConfig>( - &config, &out_config)); + config, out_config)); ASSERT_TRUE(ConfigsEqual(config, out_config)); } TEST(CertVerifierMojomTraitsTest, ConfigCRLAndAdditionalCerts) { std::string crl_set; - { - base::ScopedAllowBlockingForTesting allow_blocking; - base::ReadFileToString( - net::GetTestCertsDirectory().AppendASCII("crlset_by_leaf_spki.raw"), - &crl_set); - } + base::ReadFileToString( + net::GetTestCertsDirectory().AppendASCII("crlset_by_leaf_spki.raw"), + &crl_set); const base::FilePath certs_dir = net::GetTestCertsDirectory(); @@ -157,7 +154,7 @@ TEST(CertVerifierMojomTraitsTest, ConfigCRLAndAdditionalCerts) { net::CertVerifier::Config out_config; ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CertVerifierConfig>( - &config, &out_config)); + config, out_config)); ASSERT_TRUE(ConfigsEqual(config, out_config)); } diff --git a/chromium/services/network/public/cpp/cert_verifier/mojo_cert_verifier.cc b/chromium/services/network/public/cpp/cert_verifier/mojo_cert_verifier.cc index 767d136b405..b562727948d 100644 --- a/chromium/services/network/public/cpp/cert_verifier/mojo_cert_verifier.cc +++ b/chromium/services/network/public/cpp/cert_verifier/mojo_cert_verifier.cc @@ -18,10 +18,12 @@ class CertVerifierRequestImpl : public mojom::CertVerifierRequest, public: CertVerifierRequestImpl( mojo::PendingReceiver<mojom::CertVerifierRequest> receiver, + scoped_refptr<net::X509Certificate> cert, net::CertVerifyResult* verify_result, net::CompletionOnceCallback callback, const net::NetLogWithSource& net_log) : receiver_(this, std::move(receiver)), + cert_(cert), cert_verify_result_(verify_result), completion_callback_(std::move(callback)), net_log_(net_log) { @@ -50,12 +52,15 @@ class CertVerifierRequestImpl : public mojom::CertVerifierRequest, // The CertVerifierRequest disconnected. DCHECK(completion_callback_); *cert_verify_result_ = net::CertVerifyResult(); + cert_verify_result_->verified_cert = cert_; cert_verify_result_->cert_status = net::CERT_STATUS_INVALID; std::move(completion_callback_).Run(net::ERR_ABORTED); } private: mojo::Receiver<mojom::CertVerifierRequest> receiver_; + // Certificate being verified. + scoped_refptr<net::X509Certificate> cert_; // Out parameter for the result. net::CertVerifyResult* cert_verify_result_; // Callback to call once the result is available. @@ -119,8 +124,8 @@ int MojoCertVerifier::Verify( cert_verifier_request.InitWithNewPipeAndPassReceiver(); mojo_cert_verifier_->Verify(params, std::move(cert_verifier_request)); *out_req = std::make_unique<CertVerifierRequestImpl>( - std::move(cert_verifier_receiver), verify_result, std::move(callback), - net_log); + std::move(cert_verifier_receiver), params.certificate(), verify_result, + std::move(callback), net_log); return net::ERR_IO_PENDING; } diff --git a/chromium/services/network/public/cpp/client_hints.cc b/chromium/services/network/public/cpp/client_hints.cc index db8bb0d2315..6beb9b69a88 100644 --- a/chromium/services/network/public/cpp/client_hints.cc +++ b/chromium/services/network/public/cpp/client_hints.cc @@ -26,13 +26,13 @@ const char* const kClientHintsNameMapping[] = {"device-memory", "downlink", "ect", "lang", - "ua", - "ua-arch", - "ua-platform", - "ua-model", - "ua-mobile", - "ua-full-version", - "ua-platform-version"}; + "sec-ch-ua", + "sec-ch-ua-arch", + "sec-ch-ua-platform", + "sec-ch-ua-model", + "sec-ch-ua-mobile", + "sec-ch-ua-full-version", + "sec-ch-ua-platform-version"}; const size_t kClientHintsMappingsCount = base::size(kClientHintsNameMapping); diff --git a/chromium/services/network/public/cpp/constants.cc b/chromium/services/network/public/cpp/constants.cc index c766f565c6e..d9090e19bed 100644 --- a/chromium/services/network/public/cpp/constants.cc +++ b/chromium/services/network/public/cpp/constants.cc @@ -6,9 +6,6 @@ namespace network { -const char kFrameAcceptHeaderValue[] = - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp," - "image/apng,*/*;q=0.8"; const char kDefaultAcceptHeaderValue[] = "*/*"; } // namespace network diff --git a/chromium/services/network/public/cpp/constants.h b/chromium/services/network/public/cpp/constants.h index 55772c98fa9..0008d465666 100644 --- a/chromium/services/network/public/cpp/constants.h +++ b/chromium/services/network/public/cpp/constants.h @@ -14,10 +14,6 @@ namespace network { // The default buffer size of DataPipe which is used to send the content body. static constexpr size_t kDataPipeDefaultAllocationSize = 512 * 1024; -// Accept header used for frame requests. -COMPONENT_EXPORT(NETWORK_CPP) -extern const char kFrameAcceptHeaderValue[]; - // The default Accept header value to use if none were specified. COMPONENT_EXPORT(NETWORK_CPP) extern const char kDefaultAcceptHeaderValue[]; diff --git a/chromium/services/network/public/cpp/content_security_policy/content_security_policy.cc b/chromium/services/network/public/cpp/content_security_policy/content_security_policy.cc index d69c1fc5eda..7450e790226 100644 --- a/chromium/services/network/public/cpp/content_security_policy/content_security_policy.cc +++ b/chromium/services/network/public/cpp/content_security_policy/content_security_policy.cc @@ -6,8 +6,10 @@ #include <sstream> #include <string> +#include "base/base64url.h" #include "base/containers/flat_set.h" #include "base/ranges/algorithm.h" +#include "base/strings/strcat.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/string_split.h" @@ -18,7 +20,6 @@ #include "services/network/public/cpp/content_security_policy/csp_context.h" #include "services/network/public/cpp/content_security_policy/csp_source.h" #include "services/network/public/cpp/content_security_policy/csp_source_list.h" -#include "services/network/public/cpp/features.h" #include "services/network/public/cpp/is_potentially_trustworthy.h" #include "services/network/public/cpp/web_sandbox_flags.h" #include "url/gurl.h" @@ -39,73 +40,9 @@ bool IsDirectiveNameCharacter(char c) { } bool IsDirectiveValueCharacter(char c) { + // Whitespace + VCHAR, but not ',' and ';' return base::IsAsciiWhitespace(c) || - base::IsAsciiPrintable(c); // Whitespace + VCHAR -} - -static CSPDirectiveName CSPFallback(CSPDirectiveName directive, - CSPDirectiveName original_directive) { - switch (directive) { - case CSPDirectiveName::ConnectSrc: - case CSPDirectiveName::FontSrc: - case CSPDirectiveName::ImgSrc: - case CSPDirectiveName::ManifestSrc: - case CSPDirectiveName::MediaSrc: - case CSPDirectiveName::PrefetchSrc: - case CSPDirectiveName::ObjectSrc: - case CSPDirectiveName::ScriptSrc: - case CSPDirectiveName::StyleSrc: - return CSPDirectiveName::DefaultSrc; - - case CSPDirectiveName::ScriptSrcAttr: - case CSPDirectiveName::ScriptSrcElem: - return CSPDirectiveName::ScriptSrc; - - case CSPDirectiveName::StyleSrcAttr: - case CSPDirectiveName::StyleSrcElem: - return CSPDirectiveName::StyleSrc; - - case CSPDirectiveName::FrameSrc: - case CSPDirectiveName::WorkerSrc: - return CSPDirectiveName::ChildSrc; - - // Because the fallback chain of child-src can be different if we are - // checking a worker or a frame request, we need to know the original type - // of the request to decide. These are the fallback chains for worker-src - // and frame-src specifically. - - // worker-src > child-src > script-src > default-src - // frame-src > child-src > default-src - - // Since there are some situations and tests that will operate on the - // `child-src` directive directly (like for example the EE subsumption - // algorithm), we consider the child-src > default-src fallback path as the - // "default" and the worker-src fallback path as an exception. - case CSPDirectiveName::ChildSrc: - if (original_directive == CSPDirectiveName::WorkerSrc) - return CSPDirectiveName::ScriptSrc; - - return CSPDirectiveName::DefaultSrc; - - case CSPDirectiveName::BaseURI: - case CSPDirectiveName::BlockAllMixedContent: - case CSPDirectiveName::DefaultSrc: - case CSPDirectiveName::FormAction: - case CSPDirectiveName::FrameAncestors: - case CSPDirectiveName::NavigateTo: - case CSPDirectiveName::PluginTypes: - case CSPDirectiveName::ReportTo: - case CSPDirectiveName::ReportURI: - case CSPDirectiveName::RequireTrustedTypesFor: - case CSPDirectiveName::Sandbox: - case CSPDirectiveName::TreatAsPublicAddress: - case CSPDirectiveName::TrustedTypes: - case CSPDirectiveName::UpgradeInsecureRequests: - return CSPDirectiveName::Unknown; - case CSPDirectiveName::Unknown: - NOTREACHED(); - return CSPDirectiveName::Unknown; - } + (base::IsAsciiPrintable(c) && c != ',' && c != ';'); } std::string ElideURLForReportViolation(const GURL& url) { @@ -135,7 +72,6 @@ bool SupportedInReportOnly(CSPDirectiveName directive) { case CSPDirectiveName::MediaSrc: case CSPDirectiveName::NavigateTo: case CSPDirectiveName::ObjectSrc: - case CSPDirectiveName::PluginTypes: case CSPDirectiveName::PrefetchSrc: case CSPDirectiveName::ReportTo: case CSPDirectiveName::ReportURI: @@ -181,7 +117,6 @@ const char* ErrorMessage(CSPDirectiveName directive) { case CSPDirectiveName::ManifestSrc: case CSPDirectiveName::MediaSrc: case CSPDirectiveName::ObjectSrc: - case CSPDirectiveName::PluginTypes: case CSPDirectiveName::PrefetchSrc: case CSPDirectiveName::ReportTo: case CSPDirectiveName::ReportURI: @@ -215,7 +150,7 @@ void ReportViolation(CSPContext* context, // ensure that these are not transmitted between different cross-origin // renderers. GURL blocked_url = (directive_name == CSPDirectiveName::FrameAncestors) - ? GURL(ToString(context->self_source())) + ? GURL(ToString(*policy->self_origin)) : url; auto safe_source_location = source_location ? source_location->Clone() : mojom::SourceLocation::New(); @@ -303,8 +238,10 @@ DirectivesMap ParseHeaderValue(base::StringPiece header) { // 5. Let directive value be the result of splitting token on ASCII // whitespace. base::StringPiece value; - if (pos != std::string::npos) - value = directive.substr(pos + 1); + if (pos != std::string::npos) { + value = base::TrimString(directive.substr(pos + 1), + base::kWhitespaceASCII, base::TRIM_ALL); + } // 6. Let directive be a new directive whose name is directive name, // and value is directive value. @@ -562,7 +499,18 @@ bool ParseHash(base::StringPiece expression, mojom::CSPHashSource* hash) { return false; hash->algorithm = item.type; - hash->value = subexpression.as_string(); + + // We lazily accept both base64url and base64-encoded data. + std::string normalized_value; + base::ReplaceChars(subexpression, "+", "-", &normalized_value); + base::ReplaceChars(normalized_value, "/", "_", &normalized_value); + + std::string out; + if (!base::Base64UrlDecode(normalized_value, + base::Base64UrlDecodePolicy::IGNORE_PADDING, + &out)) + return false; + hash->value = std::vector<uint8_t>(out.begin(), out.end()); return true; } } @@ -694,35 +642,79 @@ mojom::CSPSourceListPtr ParseSourceList( return directive; } -// Checks whether |expression| is a plugin type matching the regex: -// [^\s/]+\/[^\s/]+ -// We assume |expression| does not contain any whitespaces. -bool IsPluginType(base::StringPiece expression) { - auto* it = expression.begin(); - auto* end = expression.end(); +// Parse the 'required-trusted-types-for' directive. +// https://w3c.github.io/webappsec-trusted-types/dist/spec/#require-trusted-types-for-csp-directive +network::mojom::CSPRequireTrustedTypesFor ParseRequireTrustedTypesFor( + base::StringPiece value, + std::vector<std::string>& parsing_errors) { + network::mojom::CSPRequireTrustedTypesFor out = + network::mojom::CSPRequireTrustedTypesFor::None; + for (const auto expression : base::SplitStringPiece( + value, base::kWhitespaceASCII, base::TRIM_WHITESPACE, + base::SPLIT_WANT_NONEMPTY)) { + if (expression == "'script'") { + out = network::mojom::CSPRequireTrustedTypesFor::Script; + } else { + const char* hint = nullptr; + if (expression == "script" || expression == "scripts" || + expression == "'scripts'") { + hint = " Did you mean 'script'?"; + } - int count_1 = EatChar(&it, end, [](char c) { return c != '/'; }); - if (it == end || *it != '/') - return false; - ++it; - int count_2 = EatChar(&it, end, [](char c) { return c != '/'; }); + parsing_errors.emplace_back(base::StringPrintf( + "Invalid expression in 'require-trusted-types-for' " + "Content Security Policy directive: %s.%s\n", + expression.as_string().c_str(), hint)); + } + } + if (out == network::mojom::CSPRequireTrustedTypesFor::None) + parsing_errors.emplace_back(base::StringPrintf( + "'require-trusted-types-for' Content Security Policy " + "directive is empty; The directive has no effect.\n")); + return out; +} - return count_1 >= 1 && count_2 >= 1 && it == end; +// This implements tt-policy-name from +// https://w3c.github.io/webappsec-trusted-types/dist/spec/#trusted-types-csp-directive/ +bool IsValidTrustedTypesPolicyName(base::StringPiece value) { + return base::ranges::all_of(value, [](char c) { + return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || + base::Contains("-#=_/@.%", c); + }); } -std::vector<std::string> ParsePluginTypes( +// Parse the 'trusted-types' directive. +// https://w3c.github.io/webappsec-trusted-types/dist/spec/#trusted-types-csp-directive +network::mojom::CSPTrustedTypesPtr ParseTrustedTypes( base::StringPiece value, std::vector<std::string>& parsing_errors) { - std::vector<std::string> out; - for (const auto expression : base::SplitStringPiece( - value, base::kWhitespaceASCII, base::TRIM_WHITESPACE, - base::SPLIT_WANT_NONEMPTY)) { - if (IsPluginType(expression)) - out.emplace_back(expression.as_string()); + auto out = network::mojom::CSPTrustedTypes::New(); + std::vector<base::StringPiece> pieces = + base::SplitStringPiece(value, base::kWhitespaceASCII, + base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + + if (pieces.size() == 1 && pieces[0] == "'none'") + return out; + + for (const auto expression : pieces) { + if (expression == "*") + out->allow_any = true; + else if (expression == "'allow-duplicates'") + out->allow_duplicates = true; + else if (expression == "'none'") { + parsing_errors.emplace_back( + "The value of the Content Security Policy directive " + "'trusted_types' contains an invalid policy: 'none'. " + "It will be ignored. " + "Note that 'none' has no effect unless it is the only " + "expression in the directive value."); + } else if (IsValidTrustedTypesPolicyName(expression)) + out->list.emplace_back(expression); else { parsing_errors.emplace_back(base::StringPrintf( - "Invalid plugin type in 'plugin-types' Content Security Policy " - "directive: '%s'.", + "The value of the Content Security Policy directive " + "'trusted_types' contains an invalid policy: '%s'. " + "It will be ignored.", expression.as_string().c_str())); } } @@ -766,6 +758,47 @@ void ParseReportDirective(const GURL& request_url, } } +void WarnIfDirectiveValueNotEmpty( + const std::pair<base::StringPiece, base::StringPiece>& directive, + std::vector<std::string>& parsing_errors) { + if (!directive.second.empty()) { + parsing_errors.emplace_back(base::StringPrintf( + "The Content Security Policy directive '%s' should be empty, but was " + "delivered with a value of '%s'. The directive has been applied, and " + "the value ignored.", + directive.first.as_string().c_str(), + directive.second.as_string().c_str())); + } +} + +mojom::CSPSourcePtr ComputeSelfOrigin(const GURL& url) { + if (url.scheme() == url::kFileScheme) { + // Forget the host for file schemes. Host can anyway only be `localhost` or + // empty and this is platform dependent. + // + // TODO(antoniosartori): Consider returning mojom::CSPSource::New() for + // file: urls, so that 'self' for file: would match nothing. + return mojom::CSPSource::New(url::kFileScheme, "", url::PORT_UNSPECIFIED, + "", false, false); + } + return mojom::CSPSource::New(url.scheme(), url.host(), url.EffectiveIntPort(), + "", false, false); +} + +std::string UnrecognizedDirectiveErrorMessage( + const std::string& directive_name) { + if (base::EqualsCaseInsensitiveASCII(directive_name, "plugin-types")) { + return "The Content-Security-Policy directive 'plugin-types' has been " + "removed from the " + "specification. If you want to block plugins, consider specifying " + "\"object-src 'none'\" instead."; + } + + return base::StringPrintf( + "Unrecognized Content-Security-Policy directive '%s'.", + directive_name.c_str()); +} + void AddContentSecurityPolicyFromHeader(base::StringPiece header, mojom::ContentSecurityPolicyType type, const GURL& base_url, @@ -773,6 +806,7 @@ void AddContentSecurityPolicyFromHeader(base::StringPiece header, DirectivesMap directives = ParseHeaderValue(header); out->header = mojom::ContentSecurityPolicyHeader::New( header.as_string(), type, mojom::ContentSecurityPolicySource::kHTTP); + out->self_origin = ComputeSelfOrigin(base_url); for (auto directive : directives) { if (!base::ranges::all_of(directive.first, IsDirectiveNameCharacter)) { @@ -784,28 +818,39 @@ void AddContentSecurityPolicyFromHeader(base::StringPiece header, continue; } - if (!base::ranges::all_of(directive.second, IsDirectiveValueCharacter)) { - out->parsing_errors.emplace_back(base::StringPrintf( - "The value for the Content-Security-Policy directive '%s' contains " - "one or more invalid characters. Non-whitespace characters outside " - "ASCII 0x21-0x7E must be percent-encoded, as described in RFC 3986, " - "section 2.1: http://tools.ietf.org/html/rfc3986#section-2.1.", - directive.first.as_string().c_str())); - continue; - } - CSPDirectiveName directive_name = ToCSPDirectiveName(directive.first.as_string()); + if (directive_name == CSPDirectiveName::Unknown) { + out->parsing_errors.emplace_back( + UnrecognizedDirectiveErrorMessage(directive.first.as_string())); + continue; + } + // A directive with this name has already been parsed. Skip further // directives per // https://www.w3.org/TR/CSP3/#parse-serialized-policy. - if (out->directives.count(directive_name)) { + if (out->raw_directives.count(directive_name)) { out->parsing_errors.emplace_back(base::StringPrintf( "Ignoring duplicate Content-Security-Policy directive '%s'.", directive.first.as_string().c_str())); continue; } + out->raw_directives[directive_name] = directive.second.as_string(); + + if (!base::ranges::all_of(directive.second, IsDirectiveValueCharacter)) { + out->parsing_errors.emplace_back(base::StringPrintf( + "The value for the Content-Security-Policy directive '%s' contains " + "one or more invalid characters. In a source expression, " + "non-whitespace characters outside ASCII " + "0x21-0x7E must be Punycode-encoded, as described in RFC 3492 " + "(https://tools.ietf.org/html/rfc3492), if part of the hostname and " + "percent-encoded, as described in RFC 3986, section 2.1 " + "(http://tools.ietf.org/html/rfc3986#section-2.1), if part of the " + "path.", + directive.first.as_string().c_str())); + continue; + } if (type == mojom::ContentSecurityPolicyType::kReport && !SupportedInReportOnly(directive_name)) { @@ -842,51 +887,38 @@ void AddContentSecurityPolicyFromHeader(base::StringPiece header, directive_name, directive.second, out->parsing_errors); break; case CSPDirectiveName::Sandbox: - // Note: |ParseSandboxPolicy(...).error_message| is ignored here. - // Blink's CSP parser is already in charge of displaying it. + // Note: Outside of CSP embedded enforcement, + // |ParseSandboxPolicy(...).error_message| isn't displayed to the user. + // Blink's CSP parser is already in charge of it. { auto sandbox = ParseWebSandboxPolicy(directive.second, mojom::WebSandboxFlags::kNone); out->sandbox = sandbox.flags; - out->parsing_errors.emplace_back(std::move(sandbox.error_message)); + if (!sandbox.error_message.empty()) + out->parsing_errors.emplace_back(std::move(sandbox.error_message)); } break; case CSPDirectiveName::UpgradeInsecureRequests: out->upgrade_insecure_requests = true; - if (!directive.second.empty()) { - out->parsing_errors.emplace_back(base::StringPrintf( - "The Content Security Policy directive " - "'upgrade-insecure-requests' should be empty, but was delivered " - "with a value of '%s'. The directive has been applied, and the " - "value ignored.", - directive.second.as_string().c_str())); - } + WarnIfDirectiveValueNotEmpty(directive, out->parsing_errors); break; case CSPDirectiveName::TreatAsPublicAddress: out->treat_as_public_address = true; - if (!directive.second.empty()) { - out->parsing_errors.emplace_back(base::StringPrintf( - "The Content Security Policy directive 'treat-as-public-address' " - "should be empty, but was delivered with a value of '%s'. The " - "directive has been applied, and the value ignored.", - directive.second.as_string().c_str())); - } + WarnIfDirectiveValueNotEmpty(directive, out->parsing_errors); break; - case CSPDirectiveName::PluginTypes: - // If the plugin-types directive is present, then always initialize - // `out->plugin_types` to be non-null, since only the plugin types - // explicitly listed will be allowed.. - out->plugin_types = - ParsePluginTypes(directive.second, out->parsing_errors); + case CSPDirectiveName::RequireTrustedTypesFor: + out->require_trusted_types_for = + ParseRequireTrustedTypesFor(directive.second, out->parsing_errors); break; - // We check the following three directives so that we do not trigger a - // warning because of an unrecognized directive. However, we skip - // parsing them for now since we do not need these directives here (they - // are parsed and enforced in the blink CSP parser). - case CSPDirectiveName::BlockAllMixedContent: - case CSPDirectiveName::RequireTrustedTypesFor: case CSPDirectiveName::TrustedTypes: + out->trusted_types = + ParseTrustedTypes(directive.second, out->parsing_errors); + break; + + case CSPDirectiveName::BlockAllMixedContent: + out->block_all_mixed_content = true; + WarnIfDirectiveValueNotEmpty(directive, out->parsing_errors); break; case CSPDirectiveName::ReportTo: @@ -902,9 +934,6 @@ void AddContentSecurityPolicyFromHeader(base::StringPiece header, &(out->report_endpoints)); break; case CSPDirectiveName::Unknown: - out->parsing_errors.emplace_back(base::StringPrintf( - "Unrecognized Content-Security-Policy directive '%s'.", - directive.first.as_string().c_str())); break; } } @@ -915,7 +944,8 @@ std::pair<CSPDirectiveName, const mojom::CSPSourceList*> GetSourceList( const mojom::ContentSecurityPolicy& policy) { for (CSPDirectiveName effective_directive = directive; effective_directive != CSPDirectiveName::Unknown; - effective_directive = CSPFallback(effective_directive, directive)) { + effective_directive = + CSPFallbackDirective(effective_directive, directive)) { auto value = policy.directives.find(effective_directive); if (value != policy.directives.end()) return std::make_pair(effective_directive, value->second.get()); @@ -923,57 +953,71 @@ std::pair<CSPDirectiveName, const mojom::CSPSourceList*> GetSourceList( return std::make_pair(CSPDirectiveName::Unknown, nullptr); } -// Check that all plugin-types allowed by the intersection of the policies in -// |policies_b| are also allowed by |policy_a|. -bool PluginTypesSubsumes( - const mojom::ContentSecurityPolicy& policy_a, - const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b) { - // Note that `policy->plugin_types == base::nullopt` means all plugin-types - // are allowed, while if `policy->plugin_types` is the empty vector than no - // plugin-types are allowed. - - if (!policy_a.plugin_types.has_value()) - // |types_a| allows everything. - return true; +} // namespace - if (policies_b.empty()) - return false; +CSPDirectiveName CSPFallbackDirective(CSPDirectiveName directive, + CSPDirectiveName original_directive) { + switch (directive) { + case CSPDirectiveName::ConnectSrc: + case CSPDirectiveName::FontSrc: + case CSPDirectiveName::ImgSrc: + case CSPDirectiveName::ManifestSrc: + case CSPDirectiveName::MediaSrc: + case CSPDirectiveName::PrefetchSrc: + case CSPDirectiveName::ObjectSrc: + case CSPDirectiveName::ScriptSrc: + case CSPDirectiveName::StyleSrc: + return CSPDirectiveName::DefaultSrc; - // Compute the intersection of the allowed plugin-types from |policies_b|. - // First, find the first non-null plugin-types entry in |policies_b|. - base::Optional<base::flat_set<std::string>> types_b; - auto it = policies_b.begin(); - for (; it != policies_b.end(); ++it) { - if ((*it)->plugin_types.has_value()) { - types_b = base::flat_set<std::string>((*it)->plugin_types.value()); - break; - } - } + case CSPDirectiveName::ScriptSrcAttr: + case CSPDirectiveName::ScriptSrcElem: + return CSPDirectiveName::ScriptSrc; - // If |types_b| is base::nullopt, then no policy in |policies_b| specified - // any plugin-types, so |policies_b| allows everything. - if (!types_b.has_value()) - return false; + case CSPDirectiveName::StyleSrcAttr: + case CSPDirectiveName::StyleSrcElem: + return CSPDirectiveName::StyleSrc; - // Now complete the intersection by considering the remaining policies of - // |policies_b|. - for (; it != policies_b.end(); ++it) { - if ((*it)->plugin_types.has_value()) { - base::flat_set<std::string> set((*it)->plugin_types.value()); - base::EraseIf(types_b.value(), - [&set](const auto& type) { return !set.contains(type); }); - } - } + case CSPDirectiveName::FrameSrc: + case CSPDirectiveName::WorkerSrc: + return CSPDirectiveName::ChildSrc; - // Check that every plugin-type in |types_b| is allowed by |types_a|. - return base::ranges::all_of(types_b.value(), [&](const std::string& type_b) { - return base::ranges::any_of( - policy_a.plugin_types.value(), - [&](const std::string& type_a) { return type_a == type_b; }); - }); -} + // Because the fallback chain of child-src can be different if we are + // checking a worker or a frame request, we need to know the original type + // of the request to decide. These are the fallback chains for worker-src + // and frame-src specifically. -} // namespace + // worker-src > child-src > script-src > default-src + // frame-src > child-src > default-src + + // Since there are some situations and tests that will operate on the + // `child-src` directive directly (like for example the EE subsumption + // algorithm), we consider the child-src > default-src fallback path as the + // "default" and the worker-src fallback path as an exception. + case CSPDirectiveName::ChildSrc: + if (original_directive == CSPDirectiveName::WorkerSrc) + return CSPDirectiveName::ScriptSrc; + + return CSPDirectiveName::DefaultSrc; + + case CSPDirectiveName::BaseURI: + case CSPDirectiveName::BlockAllMixedContent: + case CSPDirectiveName::DefaultSrc: + case CSPDirectiveName::FormAction: + case CSPDirectiveName::FrameAncestors: + case CSPDirectiveName::NavigateTo: + case CSPDirectiveName::ReportTo: + case CSPDirectiveName::ReportURI: + case CSPDirectiveName::RequireTrustedTypesFor: + case CSPDirectiveName::Sandbox: + case CSPDirectiveName::TreatAsPublicAddress: + case CSPDirectiveName::TrustedTypes: + case CSPDirectiveName::UpgradeInsecureRequests: + return CSPDirectiveName::Unknown; + case CSPDirectiveName::Unknown: + NOTREACHED(); + return CSPDirectiveName::Unknown; + } +} void AddContentSecurityPolicyFromHeaders( const net::HttpResponseHeaders& headers, @@ -1015,9 +1059,6 @@ void AddContentSecurityPolicyFromHeaders( mojom::AllowCSPFromHeaderValuePtr ParseAllowCSPFromHeader( const net::HttpResponseHeaders& headers) { - if (!base::FeatureList::IsEnabled(features::kOutOfBlinkCSPEE)) - return nullptr; - std::string allow_csp_from; if (!headers.GetNormalizedHeader("Allow-CSP-From", &allow_csp_from)) return nullptr; @@ -1045,6 +1086,8 @@ bool CheckContentSecurityPolicy(const mojom::ContentSecurityPolicyPtr& policy, CSPContext* context, const mojom::SourceLocationPtr& source_location, bool is_form_submission) { + DCHECK(policy->self_origin); + if (ShouldBypassContentSecurityPolicy(context, directive_name, url)) return true; @@ -1058,13 +1101,13 @@ bool CheckContentSecurityPolicy(const mojom::ContentSecurityPolicyPtr& policy, for (CSPDirectiveName effective_directive_name = directive_name; effective_directive_name != CSPDirectiveName::Unknown; effective_directive_name = - CSPFallback(effective_directive_name, directive_name)) { + CSPFallbackDirective(effective_directive_name, directive_name)) { const auto& directive = policy->directives.find(effective_directive_name); if (directive == policy->directives.end()) continue; const auto& source_list = directive->second; - bool allowed = CheckCSPSourceList(source_list, url, context, + bool allowed = CheckCSPSourceList(*source_list, url, *(policy->self_origin), has_followed_redirect, is_response_check); if (!allowed) { @@ -1118,28 +1161,23 @@ void UpgradeInsecureRequest(GURL* url) { bool IsValidRequiredCSPAttr( const std::vector<mojom::ContentSecurityPolicyPtr>& policy, const mojom::ContentSecurityPolicy* context, - const url::Origin& origin, std::string& error_message) { DCHECK(policy.size() == 1); if (!policy[0]) return false; - if (!policy[0]->parsing_errors.empty()) { - error_message = - "Parsing the csp attribute into a Content-Security-Policy returned one " - "or more parsing errors: " + - base::JoinString(policy[0]->parsing_errors, " "); - return false; - } - - if (!policy[0]->report_endpoints.empty()) { + if (!policy[0]->report_endpoints.empty() || + // We really don't want any report directives, even with invalid/missing + // endpoints. + policy[0]->raw_directives.contains(mojom::CSPDirectiveName::ReportURI) || + policy[0]->raw_directives.contains(mojom::CSPDirectiveName::ReportTo)) { error_message = "The csp attribute cannot contain the directives 'report-to' or " "'report-uri'."; return false; } - if (context && !Subsumes(*context, policy, origin)) { + if (context && !Subsumes(*context, policy)) { error_message = "The csp attribute Content-Security-Policy is not subsumed by the " "frame's parent csp attribute Content-Security-Policy."; @@ -1150,19 +1188,19 @@ bool IsValidRequiredCSPAttr( } bool Subsumes(const mojom::ContentSecurityPolicy& policy_a, - const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b, - const url::Origin& origin_b) { + const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b) { if (policy_a.header->type == mojom::ContentSecurityPolicyType::kReport) return true; - if (!PluginTypesSubsumes(policy_a, policies_b)) - return false; if (policy_a.directives.empty()) return true; if (policies_b.empty()) return false; + // All policies in |policies_b| must have the same self_origin. + mojom::CSPSource* origin_b = policies_b[0]->self_origin.get(); + // A list of directives that we consider for subsumption. // See more about source lists here: // https://w3c.github.io/webappsec-csp/#framework-directive-source-list @@ -1230,8 +1268,6 @@ CSPDirectiveName ToCSPDirectiveName(const std::string& name) { return CSPDirectiveName::MediaSrc; if (name == "object-src") return CSPDirectiveName::ObjectSrc; - if (name == "plugin-types") - return CSPDirectiveName::PluginTypes; if (name == "prefetch-src") return CSPDirectiveName::PrefetchSrc; if (name == "report-uri") @@ -1296,8 +1332,6 @@ std::string ToString(CSPDirectiveName name) { return "media-src"; case CSPDirectiveName::ObjectSrc: return "object-src"; - case CSPDirectiveName::PluginTypes: - return "plugin-types"; case CSPDirectiveName::PrefetchSrc: return "prefetch-src"; case CSPDirectiveName::ReportURI: @@ -1337,4 +1371,31 @@ std::string ToString(CSPDirectiveName name) { return ""; } +bool AllowsBlanketEnforcementOfRequiredCSP( + const url::Origin& request_origin, + const GURL& response_url, + const network::mojom::AllowCSPFromHeaderValue* allow_csp_from) { + if (response_url.SchemeIs(url::kAboutScheme) || + response_url.SchemeIs(url::kDataScheme) || response_url.SchemeIsFile() || + response_url.SchemeIsFileSystem() || response_url.SchemeIsBlob()) { + return true; + } + + if (request_origin.IsSameOriginWith(url::Origin::Create(response_url))) + return true; + + if (!allow_csp_from) + return false; + + if (allow_csp_from->is_allow_star()) + return true; + + if (allow_csp_from->is_origin() && + request_origin.IsSameOriginWith(allow_csp_from->get_origin())) { + return true; + } + + return false; +} + } // namespace network diff --git a/chromium/services/network/public/cpp/content_security_policy/content_security_policy.h b/chromium/services/network/public/cpp/content_security_policy/content_security_policy.h index 3f2eaa6ad10..e420a0277d8 100644 --- a/chromium/services/network/public/cpp/content_security_policy/content_security_policy.h +++ b/chromium/services/network/public/cpp/content_security_policy/content_security_policy.h @@ -22,6 +22,14 @@ class HttpResponseHeaders; namespace network { class CSPContext; +// Return the next Content Security Policy directive after |directive| in +// |original_directive|'s fallback list: +// https://w3c.github.io/webappsec-csp/#directive-fallback-list. +COMPONENT_EXPORT(NETWORK_CPP) +mojom::CSPDirectiveName CSPFallbackDirective( + mojom::CSPDirectiveName directive, + mojom::CSPDirectiveName original_directive); + // Parses the Content-Security-Policy headers specified in |headers| and appends // the results into |out|. // @@ -83,16 +91,13 @@ COMPONENT_EXPORT(NETWORK_CPP) bool IsValidRequiredCSPAttr( const std::vector<mojom::ContentSecurityPolicyPtr>& policy, const mojom::ContentSecurityPolicy* context, - const url::Origin& url, std::string& error_message); -// Checks whether |policy_a| subsumes the policy list -// |policies_b| with origin |origin_b| according to the algorithm -// https://w3c.github.io/webappsec-cspee/#subsume-policy-list. +// Checks whether |policy_a| subsumes the policy list |policies_b| according to +// the algorithm https://w3c.github.io/webappsec-cspee/#subsume-policy-list. COMPONENT_EXPORT(NETWORK_CPP) bool Subsumes(const mojom::ContentSecurityPolicy& policy_a, - const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b, - const url::Origin& origin_b); + const std::vector<mojom::ContentSecurityPolicyPtr>& policies_b); COMPONENT_EXPORT(NETWORK_CPP) mojom::CSPDirectiveName ToCSPDirectiveName(const std::string& name); @@ -100,6 +105,15 @@ mojom::CSPDirectiveName ToCSPDirectiveName(const std::string& name); COMPONENT_EXPORT(NETWORK_CPP) std::string ToString(mojom::CSPDirectiveName name); +// Return true if the response allows the embedder to enforce arbitrary policy +// on its behalf. +// Specification: https://w3c.github.io/webappsec-cspee/#origin-allowed +COMPONENT_EXPORT(NETWORK_CPP) +bool AllowsBlanketEnforcementOfRequiredCSP( + const url::Origin& request_origin, + const GURL& response_url, + const network::mojom::AllowCSPFromHeaderValue* allow_csp_from); + } // namespace network #endif // SERVICES_NETWORK_PUBLIC_CPP_CONTENT_SECURITY_POLICY_CONTENT_SECURITY_POLICY_H_ diff --git a/chromium/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc b/chromium/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc index 1913361f19b..36878ad5118 100644 --- a/chromium/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc +++ b/chromium/services/network/public/cpp/content_security_policy/content_security_policy_unittest.cc @@ -4,7 +4,8 @@ #include "services/network/public/cpp/content_security_policy/content_security_policy.h" -#include "base/stl_util.h" +#include "base/containers/contains.h" +#include "base/strings/stringprintf.h" #include "net/http/http_response_headers.h" #include "services/network/public/cpp/content_security_policy/csp_context.h" #include "services/network/public/cpp/web_sandbox_flags.h" @@ -62,6 +63,9 @@ static void TestFrameAncestorsCSPParser(const std::string& header, policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors]; EXPECT_EQ(frame_ancestors->sources.size(), expected_result->parsed_sources.size()); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + header); for (size_t i = 0; i < expected_result->parsed_sources.size(); i++) { EXPECT_EQ(frame_ancestors->sources[i]->scheme, expected_result->parsed_sources[i].scheme); @@ -110,6 +114,8 @@ class CSPContextTest : public CSPContext { mojom::ContentSecurityPolicyPtr EmptyCSP() { auto policy = mojom::ContentSecurityPolicy::New(); policy->header = mojom::ContentSecurityPolicyHeader::New(); + policy->self_origin = network::mojom::CSPSource::New( + "", "", url::PORT_UNSPECIFIED, "", false, false); return policy; } @@ -267,7 +273,11 @@ TEST(ContentSecurityPolicy, ParseDirectives) { AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); EXPECT_EQ(2U, policies[0]->directives.size()); + EXPECT_EQ(2U, policies[0]->raw_directives.size()); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "example.com"); auto& frame_ancestors = policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors]; EXPECT_EQ(frame_ancestors->sources.size(), 1U); @@ -279,6 +289,8 @@ TEST(ContentSecurityPolicy, ParseDirectives) { EXPECT_EQ(frame_ancestors->sources[0]->is_port_wildcard, false); EXPECT_EQ(frame_ancestors->allow_self, false); + EXPECT_EQ(policies[0]->raw_directives[mojom::CSPDirectiveName::ScriptSrc], + "example2.com"); auto& script_src = policies[0]->directives[mojom::CSPDirectiveName::ScriptSrc]; EXPECT_EQ(script_src->sources.size(), 1U); @@ -308,7 +320,11 @@ TEST(ContentSecurityPolicy, ParseDirectives) { AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); EXPECT_EQ(1U, policies[0]->directives.size()); + EXPECT_EQ(1U, policies[0]->raw_directives.size()); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "example.org"); auto& frame_ancestors = policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors]; EXPECT_EQ(frame_ancestors->sources.size(), 1U); @@ -337,6 +353,7 @@ TEST(ContentSecurityPolicy, ParseDirectives) { AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); EXPECT_TRUE(policies[0]->directives.empty()); + EXPECT_TRUE(policies[0]->raw_directives.empty()); EXPECT_EQ(1U, policies[0]->parsing_errors.size()); EXPECT_EQ( @@ -359,9 +376,13 @@ TEST(ContentSecurityPolicy, ParseDirectives) { EXPECT_EQ(1U, policies[0]->parsing_errors.size()); EXPECT_EQ( "The value for the Content-Security-Policy directive 'frame-ancestors' " - "contains one or more invalid characters. Non-whitespace characters " - "outside ASCII 0x21-0x7E must be percent-encoded, as described in RFC " - "3986, section 2.1: http://tools.ietf.org/html/rfc3986#section-2.1.", + "contains one or more invalid characters. In a source expression, " + "non-whitespace characters outside ASCII 0x21-0x7E must be " + "Punycode-encoded, as described in RFC 3492 " + "(https://tools.ietf.org/html/rfc3492), if part of the hostname and " + "percent-encoded, as described in RFC 3986, section 2.1 " + "(http://tools.ietf.org/html/rfc3986#section-2.1), if part of the " + "path.", policies[0]->parsing_errors[0]); } @@ -374,7 +395,11 @@ TEST(ContentSecurityPolicy, ParseDirectives) { AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); EXPECT_EQ(1U, policies[0]->directives.size()); + EXPECT_EQ(1U, policies[0]->raw_directives.size()); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "object-src"); auto& frame_ancestors = policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors]; EXPECT_EQ(frame_ancestors->sources.size(), 1U); @@ -405,7 +430,11 @@ TEST(ContentSecurityPolicy, ParseDirectives) { AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); EXPECT_EQ(1U, policies[0]->directives.size()); + EXPECT_EQ(1U, policies[0]->raw_directives.size()); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "http://example.org/index.html?a=b"); auto& frame_ancestors = policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors]; EXPECT_EQ(frame_ancestors->sources.size(), 1U); @@ -437,7 +466,11 @@ TEST(ContentSecurityPolicy, ParseDirectives) { AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); EXPECT_EQ(1U, policies[0]->directives.size()); + EXPECT_EQ(1U, policies[0]->raw_directives.size()); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "http://example.org/index.html#a"); auto& frame_ancestors = policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors]; EXPECT_EQ(frame_ancestors->sources.size(), 1U); @@ -473,6 +506,14 @@ TEST(ContentSecurityPolicy, ParseDirectives) { &policies); EXPECT_EQ(2U, policies.size()); + + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "example.com"); + EXPECT_EQ( + policies[1]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "example.org"); + auto& frame_ancestors0 = policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors]; auto& frame_ancestors1 = @@ -509,6 +550,10 @@ TEST(ContentSecurityPolicy, ParseDirectives) { &policies); EXPECT_EQ(2U, policies.size()); + EXPECT_EQ( + policies[1]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "example.org"); + auto& frame_ancestors1 = policies[1]->directives[mojom::CSPDirectiveName::FrameAncestors]; EXPECT_EQ(frame_ancestors1->sources.size(), 1U); @@ -535,6 +580,14 @@ TEST(ContentSecurityPolicy, ParseDirectives) { &policies); EXPECT_EQ(2U, policies.size()); + + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "example.com"); + EXPECT_EQ( + policies[1]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "example.org"); + auto& frame_ancestors0 = policies[0]->directives[mojom::CSPDirectiveName::FrameAncestors]; auto& frame_ancestors1 = @@ -571,6 +624,10 @@ TEST(ContentSecurityPolicy, ParseDirectives) { AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::FrameAncestors], + "example.com"); + auto& report_endpoints = policies[0]->report_endpoints; EXPECT_EQ(report_endpoints.size(), 1U); EXPECT_EQ(report_endpoints[0], "http://example.com/report"); @@ -591,48 +648,140 @@ TEST(ContentSecurityPolicy, ParseDirectives) { } TEST(ContentSecurityPolicy, ParsePluginTypes) { + std::vector<mojom::ContentSecurityPolicyPtr> policies = + ParseCSP("plugin-types application/pdf text/plain"); + EXPECT_EQ(policies[0]->directives.size(), 0u); + EXPECT_EQ(policies[0]->parsing_errors[0], + "The Content-Security-Policy directive 'plugin-types' has been " + "removed from the " + "specification. If you want to block plugins, consider specifying " + "\"object-src 'none'\" instead."); +} + +TEST(ContentSecurityPolicy, ParseRequireTrustedTypesFor) { + struct { + const char* input; + const unsigned long errors; + network::mojom::CSPRequireTrustedTypesFor expected; + } cases[]{ + { + "", + 1u, + network::mojom::CSPRequireTrustedTypesFor::None, + }, + { + "'script'", + 0u, + network::mojom::CSPRequireTrustedTypesFor::Script, + }, + { + "'wasm' 'script'", + 1u, + network::mojom::CSPRequireTrustedTypesFor::Script, + }, + { + "'script' 'wasm' 'script'", + 1u, + network::mojom::CSPRequireTrustedTypesFor::Script, + }, + { + "'wasm'", + 2u, + network::mojom::CSPRequireTrustedTypesFor::None, + }, + }; + + for (const auto& testCase : cases) { + std::vector<mojom::ContentSecurityPolicyPtr> policies = ParseCSP( + base::StringPrintf("require-trusted-types-for %s", testCase.input)); + EXPECT_EQ( + policies[0] + ->raw_directives[mojom::CSPDirectiveName::RequireTrustedTypesFor], + testCase.input); + EXPECT_EQ(policies[0]->directives.size(), 0u); + EXPECT_EQ(policies[0]->parsing_errors.size(), testCase.errors); + EXPECT_EQ(policies[0]->require_trusted_types_for, testCase.expected); + } +} + +TEST(ContentSecurityPolicy, ParseTrustedTypes) { { std::vector<mojom::ContentSecurityPolicyPtr> policies = - ParseCSP("plugin-types application/pdf text/plain invalid a/a/a"); - EXPECT_EQ(policies[0]->directives.size(), 0u); - EXPECT_TRUE(policies[0]->plugin_types.has_value()); - EXPECT_EQ(policies[0]->plugin_types.value().size(), 2u); - EXPECT_EQ(policies[0]->plugin_types.value()[0], "application/pdf"); - EXPECT_EQ(policies[0]->plugin_types.value()[1], "text/plain"); - EXPECT_EQ(policies[0]->parsing_errors.size(), 2u); - EXPECT_EQ(policies[0]->parsing_errors[0], - "Invalid plugin type in 'plugin-types' Content Security Policy " - "directive: 'invalid'."); - EXPECT_EQ(policies[0]->parsing_errors[1], - "Invalid plugin type in 'plugin-types' Content Security Policy " - "directive: 'a/a/a'."); + ParseCSP("script-src 'none'"); + EXPECT_EQ(policies[0]->directives.size(), 1u); + EXPECT_FALSE(policies[0]->trusted_types); } { std::vector<mojom::ContentSecurityPolicyPtr> policies = - ParseCSP("plugin-types ; default-src 'self'"); - EXPECT_TRUE(policies[0]->plugin_types.has_value()); - EXPECT_EQ(policies[0]->plugin_types.value().size(), 0u); + ParseCSP("trusted-types 'none'"); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::TrustedTypes], + "'none'"); + EXPECT_EQ(policies[0]->directives.size(), 0u); + EXPECT_TRUE(policies[0]->trusted_types); + EXPECT_EQ(policies[0]->trusted_types->list.size(), 0u); + EXPECT_FALSE(policies[0]->trusted_types->allow_any); + EXPECT_FALSE(policies[0]->trusted_types->allow_duplicates); + EXPECT_EQ(policies[0]->trusted_types->list.size(), 0u); EXPECT_EQ(policies[0]->parsing_errors.size(), 0u); } { std::vector<mojom::ContentSecurityPolicyPtr> policies = - ParseCSP("plugin-types 'self' ; default-src 'self'"); - EXPECT_TRUE(policies[0]->plugin_types.has_value()); - EXPECT_EQ(policies[0]->plugin_types.value().size(), 0u); - EXPECT_EQ(policies[0]->parsing_errors.size(), 1u); - EXPECT_EQ(policies[0]->parsing_errors[0], - "Invalid plugin type in 'plugin-types' Content Security Policy " - "directive: ''self''."); + ParseCSP("trusted-types policy 'none' other_policy@ invalid~policy"); + EXPECT_EQ( + policies[0]->raw_directives[mojom::CSPDirectiveName::TrustedTypes], + "policy 'none' other_policy@ invalid~policy"); + EXPECT_EQ(policies[0]->directives.size(), 0u); + EXPECT_TRUE(policies[0]->trusted_types); + EXPECT_EQ(policies[0]->trusted_types->list.size(), 2u); + EXPECT_EQ(policies[0]->trusted_types->list[0], "policy"); + EXPECT_EQ(policies[0]->trusted_types->list[1], "other_policy@"); + EXPECT_FALSE(policies[0]->trusted_types->allow_any); + EXPECT_FALSE(policies[0]->trusted_types->allow_duplicates); + EXPECT_EQ(policies[0]->parsing_errors.size(), 2u); + EXPECT_EQ( + policies[0]->parsing_errors[0], + "The value of the Content Security Policy directive 'trusted_types' " + "contains an invalid policy: 'none'. It will be ignored. " + "Note that 'none' has no effect unless it is the only " + "expression in the directive value."); + EXPECT_EQ( + policies[0]->parsing_errors[1], + "The value of the Content Security Policy directive 'trusted_types' " + "contains an invalid policy: 'invalid~policy'. It will be ignored."); + } +} + +TEST(ContentSecurityPolicy, ParseBlockAllMixedContent) { + { + std::vector<mojom::ContentSecurityPolicyPtr> policies = + ParseCSP("script-src 'none'"); + EXPECT_EQ(policies[0]->directives.size(), 1u); + EXPECT_FALSE(policies[0]->block_all_mixed_content); } { std::vector<mojom::ContentSecurityPolicyPtr> policies = - ParseCSP("default-src 'self'"); - EXPECT_FALSE(policies[0]->plugin_types.has_value()); + ParseCSP("block-all-mixed-content"); + EXPECT_EQ(policies[0]->directives.size(), 0u); + EXPECT_TRUE(policies[0]->block_all_mixed_content); EXPECT_EQ(policies[0]->parsing_errors.size(), 0u); } + + { + std::vector<mojom::ContentSecurityPolicyPtr> policies = + ParseCSP("block-all-mixed-content true"); + EXPECT_EQ(policies[0]->directives.size(), 0u); + EXPECT_TRUE(policies[0]->block_all_mixed_content); + EXPECT_EQ(policies[0]->parsing_errors.size(), 1u); + EXPECT_EQ(policies[0]->parsing_errors[0], + "The Content Security Policy directive " + "'block-all-mixed-content' should be empty, but was delivered " + "with a value of 'true'. The directive has been applied, and the " + "value ignored."); + } } TEST(ContentSecurityPolicy, ParseReportEndpoint) { @@ -703,6 +852,40 @@ TEST(ContentSecurityPolicy, ParseReportEndpoint) { } } +TEST(ContentSecurityPolicy, ParseStoresSelfOrigin) { + struct { + const char* url; + network::mojom::CSPSourcePtr self_origin; + } testCases[]{ + { + "https://example.com", + network::mojom::CSPSource::New("https", "example.com", 443, "", false, + false), + }, + { + "http://example.com/main/index.html", + network::mojom::CSPSource::New("http", "example.com", 80, "", false, + false), + }, + { + "file://localhost/var/www/index.html", + network::mojom::CSPSource::New("file", "", url::PORT_UNSPECIFIED, "", + false, false), + }, + }; + + for (const auto& testCase : testCases) { + scoped_refptr<net::HttpResponseHeaders> headers( + new net::HttpResponseHeaders("HTTP/1.1 200 OK")); + headers->SetHeader("Content-Security-Policy", "default-src 'none'"); + std::vector<mojom::ContentSecurityPolicyPtr> policies; + AddContentSecurityPolicyFromHeaders(*headers, GURL(testCase.url), + &policies); + + EXPECT_TRUE(testCase.self_origin.Equals(policies[0]->self_origin)); + } +} + // Check URL are upgraded iif "upgrade-insecure-requests" directive is defined. TEST(ContentSecurityPolicy, ShouldUpgradeInsecureRequest) { std::vector<mojom::ContentSecurityPolicyPtr> policies; @@ -958,7 +1141,6 @@ TEST(ContentSecurityPolicy, NavigateToChecks) { csp->allow_response_redirects = true; return csp; }; - context.SetSelf(source_a()); struct TestCase { mojom::CSPSourceListPtr navigate_to_list; @@ -997,6 +1179,7 @@ TEST(ContentSecurityPolicy, NavigateToChecks) { for (auto& test : cases) { auto policy = EmptyCSP(); + policy->self_origin = source_a().Clone(); policy->directives[CSPDirectiveName::NavigateTo] = std::move(test.navigate_to_list); @@ -1024,6 +1207,8 @@ TEST(ContentSecurityPolicy, ParseSandbox) { std::vector<mojom::ContentSecurityPolicyPtr> policies; AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); + EXPECT_EQ(policies[0]->raw_directives[mojom::CSPDirectiveName::Sandbox], + "allow-downloads allow-scripts"); EXPECT_EQ(policies[0]->sandbox, ~mojom::WebSandboxFlags::kDownloads & ~mojom::WebSandboxFlags::kScripts & @@ -1033,13 +1218,13 @@ TEST(ContentSecurityPolicy, ParseSandbox) { TEST(ContentSecurityPolicy, ParseSerializedSourceList) { struct TestCase { std::string directive_value; - base::Callback<mojom::CSPSourceListPtr()> expected; + base::OnceCallback<mojom::CSPSourceListPtr()> expected; std::string expected_error; } cases[] = { { "'nonce-a' 'nonce-a=' 'nonce-a==' 'nonce-a===' 'nonce-==' 'nonce-' " "'nonce 'nonce-cde' 'nonce-cde=' 'nonce-cde==' 'nonce-cde==='", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->nonces.push_back("a"); csp->nonces.push_back("a="); @@ -1052,37 +1237,28 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { "", }, { - "'sha256-abc' 'sha256-ABC' 'sha256 'sha256-' 'sha384-abc' " - "'sha512-abc' 'sha-abc' 'sha256-*' 'sha-256-cde' 'sha-384-cde' " - "'sha-512-cde'", - base::Bind([] { + "'sha256-YWJj' 'nonce-cde' 'sha256-QUJD'", + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); - csp->hashes.push_back(mojom::CSPHashSource::New( - mojom::CSPHashAlgorithm::SHA256, "abc")); - csp->hashes.push_back(mojom::CSPHashSource::New( - mojom::CSPHashAlgorithm::SHA256, "ABC")); - csp->hashes.push_back(mojom::CSPHashSource::New( - mojom::CSPHashAlgorithm::SHA384, "abc")); - csp->hashes.push_back(mojom::CSPHashSource::New( - mojom::CSPHashAlgorithm::SHA512, "abc")); - csp->hashes.push_back(mojom::CSPHashSource::New( - mojom::CSPHashAlgorithm::SHA256, "cde")); - csp->hashes.push_back(mojom::CSPHashSource::New( - mojom::CSPHashAlgorithm::SHA384, "cde")); - csp->hashes.push_back(mojom::CSPHashSource::New( - mojom::CSPHashAlgorithm::SHA512, "cde")); + csp->hashes.push_back( + mojom::CSPHashSource::New(mojom::CSPHashAlgorithm::SHA256, + std::vector<uint8_t>{'a', 'b', 'c'})); + csp->hashes.push_back( + mojom::CSPHashSource::New(mojom::CSPHashAlgorithm::SHA256, + std::vector<uint8_t>{'A', 'B', 'C'})); + csp->nonces.push_back("cde"); return csp; }), "", }, { "'none' ", - base::Bind([] { return mojom::CSPSourceList::New(); }), + base::BindOnce([] { return mojom::CSPSourceList::New(); }), "", }, { "'none' 'self'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_self = true; return csp; @@ -1094,7 +1270,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'self' 'none'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_self = true; return csp; @@ -1106,7 +1282,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'self'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_self = true; return csp; @@ -1114,7 +1290,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'wrong' *", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_star = true; return csp; @@ -1124,7 +1300,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'wrong' 'unsafe-inline'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_inline = true; return csp; @@ -1134,7 +1310,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'wrong' 'unsafe-eval'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_eval = true; return csp; @@ -1144,7 +1320,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'wrong' 'wasm-eval'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_wasm_eval = true; return csp; @@ -1154,7 +1330,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'wrong' 'strict-dynamic'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_dynamic = true; return csp; @@ -1164,7 +1340,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'wrong' 'unsafe-hashes'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->allow_unsafe_hashes = true; return csp; @@ -1174,7 +1350,7 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { }, { "'wrong' 'report-sample'", - base::Bind([] { + base::BindOnce([] { auto csp = mojom::CSPSourceList::New(); csp->report_sample = true; return csp; @@ -1193,27 +1369,86 @@ TEST(ContentSecurityPolicy, ParseSerializedSourceList) { std::vector<mojom::ContentSecurityPolicyPtr> policies; AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), &policies); - EXPECT_TRUE(test.expected.Run().Equals( - policies[0]->directives[mojom::CSPDirectiveName::ScriptSrc])); + EXPECT_TRUE( + std::move(test.expected) + .Run() + .Equals( + policies[0]->directives[mojom::CSPDirectiveName::ScriptSrc])); + + EXPECT_EQ(policies[0]->raw_directives[mojom::CSPDirectiveName::ScriptSrc], + base::TrimString(test.directive_value, " ", base::TRIM_ALL) + .as_string()); if (!test.expected_error.empty()) EXPECT_EQ(test.expected_error, policies[0]->parsing_errors[0]); } } +TEST(ContentSecurityPolicy, ParseHash) { + using Algo = mojom::CSPHashAlgorithm; + struct TestCase { + std::string hash; + Algo expected_algorithm; + std::vector<uint8_t> expected_hash; + } cases[] = { + // For this test, we have the following base64 encoding: + // abc => YWJj ABC => QUJD cd => Y2Q= abcd => YWJjZA== + // We also test base64 without padding. + {"'sha256-YWJj'", Algo::SHA256, {'a', 'b', 'c'}}, + {"'sha256-QUJD'", Algo::SHA256, {'A', 'B', 'C'}}, + {"'sha256", Algo::None, {}}, + {"'sha256-'", Algo::None, {}}, + {"'sha384-YWJj'", Algo::SHA384, {'a', 'b', 'c'}}, + {"'sha512-YWJjZA'", Algo::SHA512, {'a', 'b', 'c', 'd'}}, + {"'sha-YWJj'", Algo::None, {}}, + {"'sha256-*'", Algo::None, {}}, + {"'sha-256-Y2Q'", Algo::SHA256, {'c', 'd'}}, + {"'sha-384-Y2Q='", Algo::SHA384, {'c', 'd'}}, + {"'sha-512-Y2Q='", Algo::SHA512, {'c', 'd'}}, + // "ABCDE" is not valid base64 and should be ignored. + {"'sha256-ABCDE'", Algo::None, {}}, + {"'sha256--__'", Algo::SHA256, {0xfb, 0xff}}, + {"'sha256-++/'", Algo::SHA256, {0xfb, 0xef}}, + // Other invalid hashes should be ignored. + {"'sha256-YWJj", Algo::None, {}}, + {"'sha111-YWJj'", Algo::None, {}}, + {"'sha256-ABC('", Algo::None, {}}, + }; + + for (auto& test : cases) { + scoped_refptr<net::HttpResponseHeaders> headers( + new net::HttpResponseHeaders("HTTP/1.1 200 OK")); + headers->SetHeader("Content-Security-Policy", "script-src " + test.hash); + std::vector<mojom::ContentSecurityPolicyPtr> policies; + AddContentSecurityPolicyFromHeaders(*headers, GURL("https://example.com/"), + &policies); + const std::vector<mojom::CSPHashSourcePtr>& hashes = + policies[0]->directives[mojom::CSPDirectiveName::ScriptSrc]->hashes; + if (test.expected_algorithm != Algo::None) { + EXPECT_EQ(1u, hashes.size()) << test.hash << " should parse to one hash"; + EXPECT_EQ(test.expected_algorithm, hashes[0]->algorithm) + << test.hash << " should have algorithm " << test.expected_algorithm; + EXPECT_EQ(test.expected_hash, hashes[0]->value) + << test.hash << " has not been base64decoded correctly"; + } else { + EXPECT_TRUE(hashes.empty()) << test.hash << " should be an invalid hash"; + } + } +} + TEST(ContentSecurityPolicy, IsValidRequiredCSPAttr) { struct TestCase { const char* csp; bool expected; std::string expected_error; - } cases[] = {{"script-src 'none'", true, ""}, - {"script-src 'none'; invalid-directive", false, - "Parsing the csp attribute into a Content-Security-Policy " - "returned one or more parsing errors: Unrecognized " - "Content-Security-Policy directive 'invalid-directive'."}, - {"script-src 'none'; report-uri https://www.example.com", false, - "The csp attribute cannot contain the directives 'report-to' " - "or 'report-uri'."}}; + } cases[] = { + {" script-src 'none' https://www.google.com ;; ; invalid-directive " + "invalid-value ;", + true, ""}, + {"script-src 'none'; report-uri https://www.example.com", false, + "The csp attribute cannot contain the directives 'report-to' " + "or 'report-uri'."}, + }; for (auto& test : cases) { SCOPED_TRACE(test.csp); @@ -1223,11 +1458,14 @@ TEST(ContentSecurityPolicy, IsValidRequiredCSPAttr) { required_csp_headers->SetHeader("Content-Security-Policy", test.csp); AddContentSecurityPolicyFromHeaders(*required_csp_headers, GURL("https://example.com/"), &csp); + + // Overwrite the header_value artificially. At the moment, our header parser + // takes already care of some parts (like removing commas). But we want to + // be sure that header values with commas or other invalid header values are + // blocked by our validation mechanism anyway. + csp[0]->header->header_value = test.csp; std::string out; - EXPECT_EQ( - test.expected, - IsValidRequiredCSPAttr( - csp, nullptr, url::Origin::Create(GURL("https://a.com")), out)); + EXPECT_EQ(test.expected, IsValidRequiredCSPAttr(csp, nullptr, out)); EXPECT_EQ(test.expected_error, out); } } @@ -1277,9 +1515,7 @@ TEST(ContentSecurityPolicy, Subsumes) { std::vector<mojom::ContentSecurityPolicyPtr> returned_csp; AddContentSecurityPolicyFromHeaders( *returned_csp_headers, GURL("https://example.com/"), &returned_csp); - EXPECT_EQ(test.expected, - Subsumes(*required_csp[0], returned_csp, - url::Origin::Create(GURL("https://a.com")))) + EXPECT_EQ(test.expected, Subsumes(*required_csp[0], returned_csp)) << test.name; } } @@ -1343,16 +1579,13 @@ TEST(ContentSecurityPolicy, SubsumesBasedOnCSPSourcesOnly) { for (const auto& test : cases) { std::vector<mojom::ContentSecurityPolicyPtr> policies_b = ParseCSP(test.policies); - EXPECT_EQ(Subsumes(*policy_a[0], policies_b, - url::Origin::Create(GURL("https://a.com"))), - test.expected) + EXPECT_EQ(Subsumes(*policy_a[0], policies_b), test.expected) << csp_a << " should " << (test.expected ? "" : "not ") << "subsume " << test.policies; if (!policies_b.empty()) { // Check if first policy of `listB` subsumes `A`. - EXPECT_EQ(Subsumes(*policies_b[0], policy_a, - url::Origin::Create(GURL("https://a.com"))), + EXPECT_EQ(Subsumes(*policies_b[0], policy_a), test.expected_first_policy_opposite) << csp_a << " should " << (test.expected_first_policy_opposite ? "" : "not ") << "subsume " @@ -1441,81 +1674,7 @@ TEST(ContentSecurityPolicy, SubsumesIfNoneIsPresent) { ParseCSP(test.policy_a); std::vector<mojom::ContentSecurityPolicyPtr> policies_b = ParseCSP(test.policies_b); - EXPECT_EQ(Subsumes(*policy_a[0], policies_b, - url::Origin::Create(GURL("https://a.com"))), - test.expected) - << test.policy_a << " should " << (test.expected ? "" : "not ") - << "subsume " << test.policies_b; - } -} - -TEST(ContentSecurityPolicy, SubsumesPluginTypes) { - struct TestCase { - const char* policy_a; - const char* policies_b; - bool expected; - } cases[] = { - // `policyA` subsumes `policiesB`. - {"script-src 'unsafe-inline'", - "script-src , script-src http://example.com, plugin-types text/plain", - true}, - {"script-src http://example.com", - "script-src http://example.com; plugin-types ", true}, - {"script-src http://example.com", - "script-src http://example.com; plugin-types text/plain", true}, - {"script-src http://example.com; plugin-types text/plain", - "script-src http://example.com; plugin-types text/plain", true}, - {"script-src http://example.com; plugin-types text/plain", - "script-src http://example.com; plugin-types ", true}, - {"script-src http://example.com; plugin-types text/plain", - "script-src http://example.com; plugin-types , plugin-types ", true}, - {"plugin-types application/pdf text/plain", - "plugin-types application/pdf text/plain, plugin-types " - "application/x-blink-test-plugin", - true}, - {"plugin-types application/pdf text/plain", - "plugin-types application/pdf text/plain," - "plugin-types application/pdf text/plain " - "application/x-blink-test-plugin", - true}, - {"plugin-types application/x-shockwave-flash application/pdf text/plain", - "plugin-types application/x-shockwave-flash application/pdf text/plain, " - "plugin-types application/x-shockwave-flash", - true}, - {"plugin-types application/x-shockwave-flash", - "plugin-types application/x-shockwave-flash application/pdf text/plain, " - "plugin-types application/x-shockwave-flash", - true}, - // `policyA` does not subsume `policiesB`. - {"script-src http://example.com; plugin-types text/plain", "", false}, - {"script-src http://example.com; plugin-types text/plain", - "script-src http://example.com", false}, - {"plugin-types random-value", - "script-src 'unsafe-inline', plugin-types text/plain", false}, - {"plugin-types random-value", - "script-src http://example.com, script-src http://example.com", false}, - {"plugin-types random-value", - "plugin-types text/plain, plugin-types text/plain", false}, - {"script-src http://example.com; plugin-types text/plain", - "plugin-types , plugin-types ", false}, - {"plugin-types application/pdf text/plain", - "plugin-types application/x-blink-test-plugin," - "plugin-types application/x-blink-test-plugin", - false}, - {"plugin-types application/pdf text/plain", - "plugin-types application/pdf application/x-blink-test-plugin, " - "plugin-types application/x-blink-test-plugin", - false}, - }; - - for (const auto& test : cases) { - std::vector<mojom::ContentSecurityPolicyPtr> policy_a = - ParseCSP(test.policy_a); - std::vector<mojom::ContentSecurityPolicyPtr> policies_b = - ParseCSP(test.policies_b); - EXPECT_EQ(Subsumes(*policy_a[0], policies_b, - url::Origin::Create(GURL("https://a.com"))), - test.expected) + EXPECT_EQ(Subsumes(*policy_a[0], policies_b), test.expected) << test.policy_a << " should " << (test.expected ? "" : "not ") << "subsume " << test.policies_b; } @@ -1556,4 +1715,120 @@ TEST(ContentSecurityPolicy, InvalidPolicyInReportTreatAsPublicAddress) { policy->parsing_errors[0]); } +TEST(ContentSecurityPolicy, AllowsBlanketEnforcementOfRequiredCSP) { + struct TestCase { + const char* name; + const char* request_origin; + const char* response_origin; + const char* allow_csp_from; + bool expected_result; + } cases[] = { + { + "About scheme allows", + "http://example.com", + "about://me", + nullptr, + true, + }, + { + "File scheme allows", + "http://example.com", + "file://me", + nullptr, + true, + }, + { + "Data scheme allows", + "http://example.com", + "data://me", + nullptr, + true, + }, + { + "Filesystem scheme allows", + "http://example.com", + "filesystem://me", + nullptr, + true, + }, + { + "Blob scheme allows", + "http://example.com", + "blob://me", + nullptr, + true, + }, + { + "Same origin allows", + "http://example.com", + "http://example.com", + nullptr, + true, + }, + { + "Same origin allows independently of header", + "http://example.com", + "http://example.com", + "http://not-example.com", + true, + }, + { + "Different origin does not allow", + "http://example.com", + "http://not.example.com", + nullptr, + false, + }, + { + "Different origin with right header allows", + "http://example.com", + "http://not-example.com", + "http://example.com", + true, + }, + { + "Different origin with right header 2 allows", + "http://example.com", + "http://not-example.com", + "http://example.com/", + true, + }, + { + "Different origin with wrong header does not allow", + "http://example.com", + "http://not-example.com", + "http://not-example.com", + false, + }, + { + "Wildcard header allows", + "http://example.com", + "http://not-example.com", + "*", + true, + }, + { + "Malformed header does not allow", + "http://example.com", + "http://not-example.com", + "*; http://example.com", + false, + }, + }; + + for (const auto& test : cases) { + SCOPED_TRACE(test.name); + auto headers = + base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK"); + if (test.allow_csp_from) + headers->AddHeader("allow-csp-from", test.allow_csp_from); + auto allow_csp_from = network::ParseAllowCSPFromHeader(*headers); + + bool actual = AllowsBlanketEnforcementOfRequiredCSP( + url::Origin::Create(GURL(test.request_origin)), + GURL(test.response_origin), allow_csp_from.get()); + EXPECT_EQ(test.expected_result, actual); + } +} + } // namespace network diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_context.cc b/chromium/services/network/public/cpp/content_security_policy/csp_context.cc index ee5a5d548c5..a1ee0020048 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_context.cc +++ b/chromium/services/network/public/cpp/content_security_policy/csp_context.cc @@ -3,9 +3,10 @@ // found in the LICENSE file. #include "services/network/public/cpp/content_security_policy/csp_context.h" -#include "services/network/public/cpp/content_security_policy/content_security_policy.h" -#include "url/origin.h" +#include "base/containers/contains.h" +#include "services/network/public/cpp/content_security_policy/content_security_policy.h" +#include "url/url_util.h" namespace network { @@ -53,34 +54,16 @@ bool CSPContext::IsAllowedByCsp(mojom::CSPDirectiveName directive_name, return allow; } -void CSPContext::SetSelf(const url::Origin& origin) { - self_source_.reset(); - - // When the origin is unique, no URL should match with 'self'. That's why - // |self_source_| stays undefined here. - if (origin.opaque()) - return; - - if (origin.scheme() == url::kFileScheme) { - self_source_ = mojom::CSPSource::New( - url::kFileScheme, "", url::PORT_UNSPECIFIED, "", false, false); - return; - } - - self_source_ = mojom::CSPSource::New( - origin.scheme(), origin.host(), - origin.port() == 0 ? url::PORT_UNSPECIFIED : origin.port(), "", false, - false); - - DCHECK_NE("", self_source_->scheme); -} - -void CSPContext::SetSelf(mojom::CSPSourcePtr self_source) { - self_source_ = std::move(self_source); -} - bool CSPContext::SchemeShouldBypassCSP(const base::StringPiece& scheme) { - return false; + // Blink uses its SchemeRegistry to check if a scheme should be bypassed. + // It can't be used on the browser process. It is used for two things: + // 1) Bypassing the "chrome-extension" scheme when chrome is built with the + // extensions support. + // 2) Bypassing arbitrary scheme for testing purpose only in blink and in V8. + // TODO(arthursonzogni): url::GetBypassingCSPScheme() is used instead of the + // blink::SchemeRegistry. It contains 1) but not 2). + const auto& bypassing_schemes = url::GetCSPBypassingSchemes(); + return base::Contains(bypassing_schemes, scheme); } void CSPContext::SanitizeDataForUseInCspViolation( diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_context.h b/chromium/services/network/public/cpp/content_security_policy/csp_context.h index 968b39daeb3..4f9fd68ac03 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_context.h +++ b/chromium/services/network/public/cpp/content_security_policy/csp_context.h @@ -9,10 +9,6 @@ class GURL; -namespace url { -class Origin; -} - namespace network { // A CSPContext represents the Document where the Content-Security-Policy are @@ -75,6 +71,8 @@ class COMPONENT_EXPORT(NETWORK_CPP) CSPContext { // HTTPS) according to the CSP. bool ShouldModifyRequestUrlForCsp(bool is_subresource_or_form_submssion); + // This is declared virtual only so that it can be overridden for unit + // testing. virtual bool SchemeShouldBypassCSP(const base::StringPiece& scheme); // TODO(arthursonzogni): This is an interface. Stop storing object in it. @@ -87,20 +85,8 @@ class COMPONENT_EXPORT(NETWORK_CPP) CSPContext { return policies_; } - void SetSelf(const url::Origin& origin); - void SetSelf(mojom::CSPSourcePtr self_source); - - // When a CSPSourceList contains 'self', the url is allowed when it match the - // CSPSource returned by this function. - // Sometimes there is no 'self' source. It means that the current origin is - // unique and no urls will match 'self' whatever they are. - // Note: When there is a 'self' source, its scheme is guaranteed to be - // non-empty. - const mojom::CSPSourcePtr& self_source() { return self_source_; } - private: // TODO(arthursonzogni): This is an interface. Stop storing object in it. - mojom::CSPSourcePtr self_source_; // Nullable. std::vector<mojom::ContentSecurityPolicyPtr> policies_; }; diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_context_unittest.cc b/chromium/services/network/public/cpp/content_security_policy/csp_context_unittest.cc index 39168c0b5a3..151bec3d684 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_context_unittest.cc +++ b/chromium/services/network/public/cpp/content_security_policy/csp_context_unittest.cc @@ -66,19 +66,22 @@ mojom::ContentSecurityPolicyPtr EmptyCSP() { } // Build a new policy made of only one directive and no report endpoints. -mojom::ContentSecurityPolicyPtr BuildPolicy(CSPDirectiveName directive_name, +mojom::ContentSecurityPolicyPtr BuildPolicy(mojom::CSPSourcePtr self_source, + CSPDirectiveName directive_name, mojom::CSPSourcePtr source) { auto source_list = mojom::CSPSourceList::New(); source_list->sources.push_back(std::move(source)); auto policy = EmptyCSP(); policy->directives[directive_name] = std::move(source_list); + policy->self_origin = std::move(self_source); return policy; } // Build a new policy made of only one directive and no report endpoints. -mojom::ContentSecurityPolicyPtr BuildPolicy(CSPDirectiveName directive_name, +mojom::ContentSecurityPolicyPtr BuildPolicy(mojom::CSPSourcePtr self_source, + CSPDirectiveName directive_name, mojom::CSPSourcePtr source_1, mojom::CSPSourcePtr source_2) { auto source_list = mojom::CSPSourceList::New(); @@ -87,6 +90,7 @@ mojom::ContentSecurityPolicyPtr BuildPolicy(CSPDirectiveName directive_name, auto policy = EmptyCSP(); policy->directives[directive_name] = std::move(source_list); + policy->self_origin = std::move(self_source); return policy; } @@ -104,8 +108,11 @@ network::mojom::SourceLocationPtr SourceLocation() { TEST(CSPContextTest, SchemeShouldBypassCSP) { CSPContextTest context; - context.AddContentSecurityPolicy(BuildPolicy( - CSPDirectiveName::DefaultSrc, BuildCSPSource("", "example.com"))); + auto self_source = network::mojom::CSPSource::New("http", "example.com", 80, + "", false, false); + context.AddContentSecurityPolicy( + BuildPolicy(self_source.Clone(), CSPDirectiveName::DefaultSrc, + BuildCSPSource("", "example.com"))); EXPECT_FALSE(context.IsAllowedByCsp( CSPDirectiveName::FrameSrc, GURL("data:text/html,<html></html>"), false, @@ -120,14 +127,15 @@ TEST(CSPContextTest, SchemeShouldBypassCSP) { TEST(CSPContextTest, MultiplePolicies) { CSPContextTest context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); + auto self_source = network::mojom::CSPSource::New("http", "example.com", 80, + "", false, false); - context.AddContentSecurityPolicy(BuildPolicy(CSPDirectiveName::FrameSrc, - BuildCSPSource("", "a.com"), - BuildCSPSource("", "b.com"))); - context.AddContentSecurityPolicy(BuildPolicy(CSPDirectiveName::FrameSrc, - BuildCSPSource("", "a.com"), - BuildCSPSource("", "c.com"))); + context.AddContentSecurityPolicy( + BuildPolicy(self_source.Clone(), CSPDirectiveName::FrameSrc, + BuildCSPSource("", "a.com"), BuildCSPSource("", "b.com"))); + context.AddContentSecurityPolicy( + BuildPolicy(self_source.Clone(), CSPDirectiveName::FrameSrc, + BuildCSPSource("", "a.com"), BuildCSPSource("", "c.com"))); EXPECT_TRUE(context.IsAllowedByCsp( CSPDirectiveName::FrameSrc, GURL("http://a.com"), false, false, @@ -145,11 +153,12 @@ TEST(CSPContextTest, MultiplePolicies) { TEST(CSPContextTest, SanitizeDataForUseInCspViolation) { CSPContextTest context; - context.SetSelf(url::Origin::Create(GURL("http://a.com"))); + auto self_source = + network::mojom::CSPSource::New("http", "a.com", 80, "", false, false); // Content-Security-Policy: frame-src "a.com/iframe" context.AddContentSecurityPolicy( - BuildPolicy(CSPDirectiveName::FrameSrc, + BuildPolicy(self_source.Clone(), CSPDirectiveName::FrameSrc, mojom::CSPSource::New("", "a.com", url::PORT_UNSPECIFIED, "/iframe", false, false))); @@ -196,14 +205,18 @@ TEST(CSPContextTest, SanitizeDataForUseInCspViolation) { // When several policies are infringed, all of them must be reported. TEST(CSPContextTest, MultipleInfringement) { CSPContextTest context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); - - context.AddContentSecurityPolicy( - BuildPolicy(CSPDirectiveName::FrameSrc, BuildCSPSource("", "a.com"))); - context.AddContentSecurityPolicy( - BuildPolicy(CSPDirectiveName::FrameSrc, BuildCSPSource("", "b.com"))); - context.AddContentSecurityPolicy( - BuildPolicy(CSPDirectiveName::FrameSrc, BuildCSPSource("", "c.com"))); + auto self_source = network::mojom::CSPSource::New("http", "example.com", 80, + "", false, false); + + context.AddContentSecurityPolicy(BuildPolicy(self_source.Clone(), + CSPDirectiveName::FrameSrc, + BuildCSPSource("", "a.com"))); + context.AddContentSecurityPolicy(BuildPolicy(self_source.Clone(), + CSPDirectiveName::FrameSrc, + BuildCSPSource("", "b.com"))); + context.AddContentSecurityPolicy(BuildPolicy(self_source.Clone(), + CSPDirectiveName::FrameSrc, + BuildCSPSource("", "c.com"))); EXPECT_FALSE(context.IsAllowedByCsp( CSPDirectiveName::FrameSrc, GURL("http://c.com"), false, false, @@ -222,13 +235,17 @@ TEST(CSPContextTest, MultipleInfringement) { // Tests that the CheckCSPDisposition parameter is obeyed. TEST(CSPContextTest, CheckCSPDisposition) { CSPContextTest context; + auto self_source = network::mojom::CSPSource::New("http", "example.com", 80, + "", false, false); // Add an enforced policy. - auto enforce_csp = BuildPolicy(CSPDirectiveName::FrameSrc, - BuildCSPSource("", "example.com")); + auto enforce_csp = + BuildPolicy(self_source.Clone(), CSPDirectiveName::FrameSrc, + BuildCSPSource("", "example.com")); // Add a report-only policy. - auto report_only_csp = BuildPolicy(CSPDirectiveName::DefaultSrc, - BuildCSPSource("", "example.com")); + auto report_only_csp = + BuildPolicy(self_source.Clone(), CSPDirectiveName::DefaultSrc, + BuildCSPSource("", "example.com")); report_only_csp->header->type = mojom::ContentSecurityPolicyType::kReport; context.AddContentSecurityPolicy(std::move(enforce_csp)); diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_source.cc b/chromium/services/network/public/cpp/content_security_policy/csp_source.cc index 70a11e6550b..3fe19c79071 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_source.cc +++ b/chromium/services/network/public/cpp/content_security_policy/csp_source.cc @@ -9,7 +9,6 @@ #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "services/network/public/cpp/content_security_policy/content_security_policy.h" -#include "services/network/public/cpp/content_security_policy/csp_context.h" #include "services/network/public/mojom/content_security_policy.mojom.h" #include "url/url_canon.h" #include "url/url_util.h" @@ -18,8 +17,8 @@ namespace network { namespace { -bool HasHost(const mojom::CSPSourcePtr& source) { - return !source->host.empty() || source->is_host_wildcard; +bool HasHost(const mojom::CSPSource& source) { + return !source.host.empty() || source.is_host_wildcard; } bool DecodePath(const base::StringPiece& path, std::string* output) { @@ -55,66 +54,65 @@ SchemeMatchingResult MatchScheme(const std::string& scheme_a, return SchemeMatchingResult::NotMatching; } -SchemeMatchingResult SourceAllowScheme(const mojom::CSPSourcePtr& source, +SchemeMatchingResult SourceAllowScheme(const mojom::CSPSource& source, const GURL& url, - CSPContext* context) { + const mojom::CSPSource& self_source) { // The source doesn't specify a scheme and the current origin is unique. In // this case, the url doesn't match regardless of its scheme. - if (source->scheme.empty() && !context->self_source()) + if (source.scheme.empty() && self_source.scheme.empty()) return SchemeMatchingResult::NotMatching; // |allowed_scheme| is guaranteed to be non-empty. const std::string& allowed_scheme = - source->scheme.empty() ? context->self_source()->scheme : source->scheme; + source.scheme.empty() ? self_source.scheme : source.scheme; return MatchScheme(allowed_scheme, url.scheme()); } -bool SourceAllowHost(const mojom::CSPSourcePtr& source, - const std::string& host) { - if (source->is_host_wildcard) { - if (source->host.empty()) +bool SourceAllowHost(const mojom::CSPSource& source, const std::string& host) { + if (source.is_host_wildcard) { + if (source.host.empty()) return true; // TODO(arthursonzogni): Chrome used to, incorrectly, match *.x.y to x.y. // The renderer version of this function counts how many times it happens. // It might be useful to do it outside of blink too. // See third_party/blink/renderer/core/frame/csp/csp_source.cc - return base::EndsWith(host, '.' + source->host, + return base::EndsWith(host, '.' + source.host, base::CompareCase::INSENSITIVE_ASCII); } else { - return base::EqualsCaseInsensitiveASCII(host, source->host); + return base::EqualsCaseInsensitiveASCII(host, source.host); } } -bool SourceAllowHost(const mojom::CSPSourcePtr& source, const GURL& url) { +bool SourceAllowHost(const mojom::CSPSource& source, const GURL& url) { return SourceAllowHost(source, url.host()); } -PortMatchingResult SourceAllowPort(const mojom::CSPSourcePtr& source, +PortMatchingResult SourceAllowPort(const mojom::CSPSource& source, int port, const std::string& scheme) { - if (source->is_port_wildcard) + if (source.is_port_wildcard) return PortMatchingResult::MatchingWildcard; - if (source->port == port) { - if (source->port == url::PORT_UNSPECIFIED) + if (source.port == port) { + if (source.port == url::PORT_UNSPECIFIED) return PortMatchingResult::MatchingWildcard; return PortMatchingResult::MatchingExact; } - if (source->port == url::PORT_UNSPECIFIED) { + if (source.port == url::PORT_UNSPECIFIED) { if (DefaultPortForScheme(scheme) == port) return PortMatchingResult::MatchingWildcard; } if (port == url::PORT_UNSPECIFIED) { - if (source->port == DefaultPortForScheme(scheme)) + if (source.port == DefaultPortForScheme(scheme)) return PortMatchingResult::MatchingWildcard; } - int source_port = source->port; + int source_port = source.port; if (source_port == url::PORT_UNSPECIFIED) - source_port = DefaultPortForScheme(source->scheme); + source_port = DefaultPortForScheme(source.scheme); if (port == url::PORT_UNSPECIFIED) port = DefaultPortForScheme(scheme); @@ -125,13 +123,12 @@ PortMatchingResult SourceAllowPort(const mojom::CSPSourcePtr& source, return PortMatchingResult::NotMatching; } -PortMatchingResult SourceAllowPort(const mojom::CSPSourcePtr& source, +PortMatchingResult SourceAllowPort(const mojom::CSPSource& source, const GURL& url) { return SourceAllowPort(source, url.EffectiveIntPort(), url.scheme()); } -bool SourceAllowPath(const mojom::CSPSourcePtr& source, - const std::string& path) { +bool SourceAllowPath(const mojom::CSPSource& source, const std::string& path) { std::string path_decoded; if (!DecodePath(path, &path_decoded)) { // TODO(arthursonzogni): try to figure out if that could happen and how to @@ -139,20 +136,20 @@ bool SourceAllowPath(const mojom::CSPSourcePtr& source, return false; } - if (source->path.empty() || (source->path == "/" && path_decoded.empty())) + if (source.path.empty() || (source.path == "/" && path_decoded.empty())) return true; // If the path represents a directory. - if (base::EndsWith(source->path, "/", base::CompareCase::SENSITIVE)) { - return base::StartsWith(path_decoded, source->path, + if (base::EndsWith(source.path, "/", base::CompareCase::SENSITIVE)) { + return base::StartsWith(path_decoded, source.path, base::CompareCase::SENSITIVE); } // The path represents a file. - return source->path == path_decoded; + return source.path == path_decoded; } -bool SourceAllowPath(const mojom::CSPSourcePtr& source, +bool SourceAllowPath(const mojom::CSPSource& source, const GURL& url, bool has_followed_redirect) { if (has_followed_redirect) @@ -180,20 +177,21 @@ bool canUpgrade(const SchemeMatchingResult result) { } // namespace -bool CSPSourceIsSchemeOnly(const mojom::CSPSourcePtr& source) { +bool CSPSourceIsSchemeOnly(const mojom::CSPSource& source) { return !HasHost(source); } -bool CheckCSPSource(const mojom::CSPSourcePtr& source, +bool CheckCSPSource(const mojom::CSPSource& source, const GURL& url, - CSPContext* context, + const mojom::CSPSource& self_source, bool has_followed_redirect) { if (CSPSourceIsSchemeOnly(source)) { - return SourceAllowScheme(source, url, context) != + return SourceAllowScheme(source, url, self_source) != SchemeMatchingResult::NotMatching; } PortMatchingResult portResult = SourceAllowPort(source, url); - SchemeMatchingResult schemeResult = SourceAllowScheme(source, url, context); + SchemeMatchingResult schemeResult = + SourceAllowScheme(source, url, self_source); if (requiresUpgrade(schemeResult) && !canUpgrade(portResult)) return false; if (requiresUpgrade(portResult) && !canUpgrade(schemeResult)) @@ -204,71 +202,71 @@ bool CheckCSPSource(const mojom::CSPSourcePtr& source, SourceAllowPath(source, url, has_followed_redirect); } -mojom::CSPSourcePtr CSPSourcesIntersect(const mojom::CSPSourcePtr& source_a, - const mojom::CSPSourcePtr& source_b) { +mojom::CSPSourcePtr CSPSourcesIntersect(const mojom::CSPSource& source_a, + const mojom::CSPSource& source_b) { // If the original source expressions didn't have a scheme, we should have // filled that already with origin's scheme. - DCHECK(!source_a->scheme.empty()); - DCHECK(!source_b->scheme.empty()); + DCHECK(!source_a.scheme.empty()); + DCHECK(!source_b.scheme.empty()); auto result = mojom::CSPSource::New(); - if (MatchScheme(source_a->scheme, source_b->scheme) != + if (MatchScheme(source_a.scheme, source_b.scheme) != SchemeMatchingResult::NotMatching) { - result->scheme = source_b->scheme; - } else if (MatchScheme(source_b->scheme, source_a->scheme) != + result->scheme = source_b.scheme; + } else if (MatchScheme(source_b.scheme, source_a.scheme) != SchemeMatchingResult::NotMatching) { - result->scheme = source_a->scheme; + result->scheme = source_a.scheme; } else { return nullptr; } if (CSPSourceIsSchemeOnly(source_a)) { - auto new_result = source_b->Clone(); + auto new_result = source_b.Clone(); new_result->scheme = result->scheme; return new_result; } else if (CSPSourceIsSchemeOnly(source_b)) { - auto new_result = source_a->Clone(); + auto new_result = source_a.Clone(); new_result->scheme = result->scheme; return new_result; } const std::string host_a = - (source_a->is_host_wildcard ? "*." : "") + source_a->host; + (source_a.is_host_wildcard ? "*." : "") + source_a.host; const std::string host_b = - (source_b->is_host_wildcard ? "*." : "") + source_b->host; + (source_b.is_host_wildcard ? "*." : "") + source_b.host; if (SourceAllowHost(source_a, host_b)) { - result->host = source_b->host; - result->is_host_wildcard = source_b->is_host_wildcard; + result->host = source_b.host; + result->is_host_wildcard = source_b.is_host_wildcard; } else if (SourceAllowHost(source_b, host_a)) { - result->host = source_a->host; - result->is_host_wildcard = source_a->is_host_wildcard; + result->host = source_a.host; + result->is_host_wildcard = source_a.is_host_wildcard; } else { return nullptr; } - if (source_b->is_port_wildcard) { - result->port = source_a->port; - result->is_port_wildcard = source_a->is_port_wildcard; - } else if (source_a->is_port_wildcard) { - result->port = source_b->port; - } else if (SourceAllowPort(source_a, source_b->port, source_b->scheme) != + if (source_b.is_port_wildcard) { + result->port = source_a.port; + result->is_port_wildcard = source_a.is_port_wildcard; + } else if (source_a.is_port_wildcard) { + result->port = source_b.port; + } else if (SourceAllowPort(source_a, source_b.port, source_b.scheme) != PortMatchingResult::NotMatching && // If port_a is explicitly specified but port_b is omitted, then we // should take port_a instead of port_b, since port_a is stricter. - !(source_a->port != url::PORT_UNSPECIFIED && - source_b->port == url::PORT_UNSPECIFIED)) { - result->port = source_b->port; - } else if (SourceAllowPort(source_b, source_a->port, source_a->scheme) != + !(source_a.port != url::PORT_UNSPECIFIED && + source_b.port == url::PORT_UNSPECIFIED)) { + result->port = source_b.port; + } else if (SourceAllowPort(source_b, source_a.port, source_a.scheme) != PortMatchingResult::NotMatching) { - result->port = source_a->port; + result->port = source_a.port; } else { return nullptr; } - if (SourceAllowPath(source_a, source_b->path)) - result->path = source_b->path; - else if (SourceAllowPath(source_b, source_a->path)) - result->path = source_a->path; + if (SourceAllowPath(source_a, source_b.path)) + result->path = source_b.path; + else if (SourceAllowPath(source_b, source_a.path)) + result->path = source_a.path; else return nullptr; @@ -276,14 +274,14 @@ mojom::CSPSourcePtr CSPSourcesIntersect(const mojom::CSPSourcePtr& source_a, } // Check whether |source_a| subsumes |source_b|. -bool CSPSourceSubsumes(const mojom::CSPSourcePtr& source_a, - const mojom::CSPSourcePtr& source_b) { +bool CSPSourceSubsumes(const mojom::CSPSource& source_a, + const mojom::CSPSource& source_b) { // If the original source expressions didn't have a scheme, we should have // filled that already with origin's scheme. - DCHECK(!source_a->scheme.empty()); - DCHECK(!source_b->scheme.empty()); + DCHECK(!source_a.scheme.empty()); + DCHECK(!source_b.scheme.empty()); - if (MatchScheme(source_a->scheme, source_b->scheme) == + if (MatchScheme(source_a.scheme, source_b.scheme) == SchemeMatchingResult::NotMatching) { return false; } @@ -293,51 +291,51 @@ bool CSPSourceSubsumes(const mojom::CSPSourcePtr& source_a, if (CSPSourceIsSchemeOnly(source_b)) return false; - if (!SourceAllowHost(source_a, (source_b->is_host_wildcard ? "*." : "") + - source_b->host)) { + if (!SourceAllowHost( + source_a, (source_b.is_host_wildcard ? "*." : "") + source_b.host)) { return false; } - if (source_b->is_port_wildcard && !source_a->is_port_wildcard) + if (source_b.is_port_wildcard && !source_a.is_port_wildcard) return false; PortMatchingResult port_matching = - SourceAllowPort(source_a, source_b->port, source_b->scheme); + SourceAllowPort(source_a, source_b.port, source_b.scheme); if (port_matching == PortMatchingResult::NotMatching) return false; - if (!SourceAllowPath(source_a, source_b->path)) + if (!SourceAllowPath(source_a, source_b.path)) return false; return true; } -std::string ToString(const mojom::CSPSourcePtr& source) { +std::string ToString(const mojom::CSPSource& source) { // scheme if (CSPSourceIsSchemeOnly(source)) - return source->scheme + ":"; + return source.scheme + ":"; std::stringstream text; - if (!source->scheme.empty()) - text << source->scheme << "://"; + if (!source.scheme.empty()) + text << source.scheme << "://"; // host - if (source->is_host_wildcard) { - if (source->host.empty()) + if (source.is_host_wildcard) { + if (source.host.empty()) text << "*"; else - text << "*." << source->host; + text << "*." << source.host; } else { - text << source->host; + text << source.host; } // port - if (source->is_port_wildcard) + if (source.is_port_wildcard) text << ":*"; - if (source->port != url::PORT_UNSPECIFIED) - text << ":" << source->port; + if (source.port != url::PORT_UNSPECIFIED) + text << ":" << source.port; // path - text << source->path; + text << source.path; return text.str(); } diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_source.h b/chromium/services/network/public/cpp/content_security_policy/csp_source.h index 8e65b494693..49f9948ff6e 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_source.h +++ b/chromium/services/network/public/cpp/content_security_policy/csp_source.h @@ -13,34 +13,32 @@ class GURL; namespace network { -class CSPContext; - // Check if a CSP |source| matches the scheme-source grammar. -bool CSPSourceIsSchemeOnly(const mojom::CSPSourcePtr& source); +bool CSPSourceIsSchemeOnly(const mojom::CSPSource& source); // Check if a |url| matches with a CSP |source| matches. COMPONENT_EXPORT(NETWORK_CPP) -bool CheckCSPSource(const mojom::CSPSourcePtr& source, +bool CheckCSPSource(const mojom::CSPSource& source, const GURL& url, - CSPContext* context, + const mojom::CSPSource& self_source, bool has_followed_redirect = false); // Compute the source intersection of |source_a| and |source_b|. // https://w3c.github.io/webappsec-cspee/#intersection-source-expressions COMPONENT_EXPORT(NETWORK_CPP) -mojom::CSPSourcePtr CSPSourcesIntersect(const mojom::CSPSourcePtr& source_a, - const mojom::CSPSourcePtr& source_b); +mojom::CSPSourcePtr CSPSourcesIntersect(const mojom::CSPSource& source_a, + const mojom::CSPSource& source_b); // Check if |source_a| subsumes |source_b| according to // https://w3c.github.io/webappsec-cspee/#subsume-source-expressions COMPONENT_EXPORT(NETWORK_CPP) -bool CSPSourceSubsumes(const mojom::CSPSourcePtr& source_a, - const mojom::CSPSourcePtr& source_b); +bool CSPSourceSubsumes(const mojom::CSPSource& source_a, + const mojom::CSPSource& source_b); // Serialize the CSPSource |source| as a string. This is used for reporting // violations. COMPONENT_EXPORT(NETWORK_CPP) -std::string ToString(const mojom::CSPSourcePtr& source); +std::string ToString(const mojom::CSPSource& source); } // namespace network diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_source_list.cc b/chromium/services/network/public/cpp/content_security_policy/csp_source_list.cc index 60443a74b7e..d20cd476017 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_source_list.cc +++ b/chromium/services/network/public/cpp/content_security_policy/csp_source_list.cc @@ -7,7 +7,6 @@ #include "base/containers/flat_set.h" #include "base/ranges/algorithm.h" #include "services/network/public/cpp/content_security_policy/content_security_policy.h" -#include "services/network/public/cpp/content_security_policy/csp_context.h" #include "services/network/public/cpp/content_security_policy/csp_source.h" namespace network { @@ -18,10 +17,10 @@ namespace { bool AllowFromSources(const GURL& url, const std::vector<mojom::CSPSourcePtr>& sources, - CSPContext* context, + const mojom::CSPSource& self_source, bool has_followed_redirect) { for (const auto& source : sources) { - if (CheckCSPSource(source, url, context, has_followed_redirect)) + if (CheckCSPSource(*source, url, self_source, has_followed_redirect)) return true; } return false; @@ -73,14 +72,14 @@ base::flat_set<std::string> IntersectSchemesOnly( const std::vector<mojom::CSPSourcePtr>& list_b) { base::flat_set<std::string> schemes_a; for (const auto& source_a : list_a) { - if (CSPSourceIsSchemeOnly(source_a)) { + if (CSPSourceIsSchemeOnly(*source_a)) { AddSourceSchemesToSet(schemes_a, source_a.get()); } } base::flat_set<std::string> intersection; for (const auto& source_b : list_b) { - if (CSPSourceIsSchemeOnly(source_b)) { + if (CSPSourceIsSchemeOnly(*source_b)) { if (schemes_a.contains(source_b->scheme)) AddSourceSchemesToSet(intersection, source_b.get()); else if (source_b->scheme == url::kHttpScheme && @@ -98,14 +97,13 @@ base::flat_set<std::string> IntersectSchemesOnly( std::vector<mojom::CSPSourcePtr> ExpandSchemeStarAndSelf( const mojom::CSPSourceList& source_list, - const mojom::CSPSource& self) { + const mojom::CSPSource* self) { std::vector<mojom::CSPSourcePtr> result; for (const mojom::CSPSourcePtr& item : source_list.sources) { mojom::CSPSourcePtr new_item = item->Clone(); if (new_item->scheme.empty()) { - if (self.scheme.empty()) - continue; - new_item->scheme = self.scheme; + if (self && !self->scheme.empty()) + new_item->scheme = self->scheme; } result.push_back(std::move(new_item)); } @@ -117,15 +115,16 @@ std::vector<mojom::CSPSourcePtr> ExpandSchemeStarAndSelf( url::kWsScheme, "", url::PORT_UNSPECIFIED, "", false, false)); result.push_back(mojom::CSPSource::New( url::kHttpScheme, "", url::PORT_UNSPECIFIED, "", false, false)); - if (!self.scheme.empty()) { + if (self && !self->scheme.empty()) { result.push_back(mojom::CSPSource::New( - self.scheme, "", url::PORT_UNSPECIFIED, "", false, false)); + self->scheme, "", url::PORT_UNSPECIFIED, "", false, false)); } } - if (source_list.allow_self && !self.scheme.empty() && !self.host.empty()) { + if (source_list.allow_self && self && !self->scheme.empty() && + !self->host.empty()) { // If |self| is an opaque origin we should ignore it. - result.push_back(self.Clone()); + result.push_back(self->Clone()); } return result; } @@ -133,7 +132,7 @@ std::vector<mojom::CSPSourcePtr> ExpandSchemeStarAndSelf( std::vector<mojom::CSPSourcePtr> IntersectSources( const mojom::CSPSourceList& source_list_a, const std::vector<mojom::CSPSourcePtr>& source_list_b, - const mojom::CSPSource& self) { + const mojom::CSPSource* self) { auto schemes = IntersectSchemesOnly(source_list_a.sources, source_list_b); std::vector<mojom::CSPSourcePtr> normalized; @@ -160,7 +159,7 @@ std::vector<mojom::CSPSourcePtr> IntersectSources( if (schemes.contains(source_b->scheme)) continue; if (mojom::CSPSourcePtr local_match = - CSPSourcesIntersect(source_a, source_b)) { + CSPSourcesIntersect(*source_a, *source_b)) { normalized.emplace_back(std::move(local_match)); } } @@ -179,21 +178,21 @@ bool UrlSourceListSubsumes( // |source_list_a|. return base::ranges::all_of(source_list_b, [&](const auto& source_b) { return base::ranges::any_of(source_list_a, [&](const auto& source_a) { - return CSPSourceSubsumes(source_a, source_b); + return CSPSourceSubsumes(*source_a, *source_b); }); }); } } // namespace -bool CheckCSPSourceList(const mojom::CSPSourceListPtr& source_list, +bool CheckCSPSourceList(const mojom::CSPSourceList& source_list, const GURL& url, - CSPContext* context, + const mojom::CSPSource& self_source, bool has_followed_redirect, bool is_response_check) { // If the source list allows all redirects, the decision can't be made until // the response is received. - if (source_list->allow_response_redirects && !is_response_check) + if (source_list.allow_response_redirects && !is_response_check) return true; // Wildcards match network schemes ('http', 'https', 'ftp', 'ws', 'wss'), and @@ -201,22 +200,21 @@ bool CheckCSPSourceList(const mojom::CSPSourceListPtr& source_list, // https://w3c.github.io/webappsec-csp/#match-url-to-source-expression. Other // schemes, including custom schemes, must be explicitly listed in a source // list. - if (source_list->allow_star) { + if (source_list.allow_star) { if (url.SchemeIsHTTPOrHTTPS() || url.SchemeIsWSOrWSS() || url.SchemeIs("ftp")) { return true; } - if (context->self_source() && url.SchemeIs(context->self_source()->scheme)) + if (!self_source.scheme.empty() && url.SchemeIs(self_source.scheme)) return true; } - if (source_list->allow_self && context->self_source() && - CheckCSPSource(context->self_source(), url, context, - has_followed_redirect)) { + if (source_list.allow_self && + CheckCSPSource(self_source, url, self_source, has_followed_redirect)) { return true; } - return AllowFromSources(url, source_list->sources, context, + return AllowFromSources(url, source_list.sources, self_source, has_followed_redirect); } @@ -224,7 +222,7 @@ bool CSPSourceListSubsumes( const mojom::CSPSourceList& source_list_a, const std::vector<const mojom::CSPSourceList*>& source_list_b, CSPDirectiveName directive, - const url::Origin& origin_b) { + const mojom::CSPSource* origin_b) { if (source_list_b.empty()) return false; @@ -239,10 +237,8 @@ bool CSPSourceListSubsumes( base::flat_set<std::string> nonces_b((*it)->nonces); base::flat_set<mojom::CSPHashSourcePtr> hashes_b(mojo::Clone((*it)->hashes)); - auto origin_b_as_csp_source = mojom::CSPSource::New( - origin_b.scheme(), origin_b.host(), origin_b.port(), "", false, false); std::vector<mojom::CSPSourcePtr> normalized_sources_b = - ExpandSchemeStarAndSelf(**it, *origin_b_as_csp_source); + ExpandSchemeStarAndSelf(**it, origin_b); ++it; for (; it != source_list_b.end(); ++it) { @@ -262,7 +258,7 @@ bool CSPSourceListSubsumes( mojo::Clone((*it)->hashes)); IntersectHashes(hashes_b, item_hashes); normalized_sources_b = - IntersectSources(**it, normalized_sources_b, *origin_b_as_csp_source); + IntersectSources(**it, normalized_sources_b, origin_b); } // If source_list_b enforces some nonce, then source_list_a must contain @@ -310,7 +306,7 @@ bool CSPSourceListSubsumes( // If embedding CSP specifies `self`, `self` refers to the embedee's origin. std::vector<mojom::CSPSourcePtr> normalized_sources_a = - ExpandSchemeStarAndSelf(source_list_a, *origin_b_as_csp_source); + ExpandSchemeStarAndSelf(source_list_a, origin_b); return UrlSourceListSubsumes(normalized_sources_a, normalized_sources_b); } @@ -332,7 +328,7 @@ std::string ToString(const mojom::CSPSourceListPtr& source_list) { for (const auto& source : source_list->sources) { if (!is_empty) text << " "; - text << ToString(source); + text << ToString(*source); is_empty = false; } diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_source_list.h b/chromium/services/network/public/cpp/content_security_policy/csp_source_list.h index d2bc9742197..141b233d47f 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_source_list.h +++ b/chromium/services/network/public/cpp/content_security_policy/csp_source_list.h @@ -13,22 +13,17 @@ class GURL; -namespace url { -class Origin; -} - namespace network { -class CSPContext; COMPONENT_EXPORT(NETWORK_CPP) std::string ToString(const mojom::CSPSourceListPtr& source_list); // Return true when at least one source in the |source_list| matches the -// |url| for a given |context|. +// |url|. COMPONENT_EXPORT(NETWORK_CPP) -bool CheckCSPSourceList(const mojom::CSPSourceListPtr& source_list, +bool CheckCSPSourceList(const mojom::CSPSourceList& source_list, const GURL& url, - CSPContext* context, + const mojom::CSPSource& self_source, bool has_followed_redirect = false, bool is_response_check = false); @@ -40,7 +35,7 @@ bool CSPSourceListSubsumes( const mojom::CSPSourceList& source_list_a, const std::vector<const mojom::CSPSourceList*>& source_list_b, mojom::CSPDirectiveName directive, - const url::Origin& origin_b); + const mojom::CSPSource* origin_b); } // namespace network #endif // SERVICES_NETWORK_PUBLIC_CPP_CONTENT_SECURITY_POLICY_CSP_SOURCE_LIST_H_ diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_source_list_unittest.cc b/chromium/services/network/public/cpp/content_security_policy/csp_source_list_unittest.cc index 7831ed6ffcc..4f9c3293011 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_source_list_unittest.cc +++ b/chromium/services/network/public/cpp/content_security_policy/csp_source_list_unittest.cc @@ -6,7 +6,6 @@ #include "base/strings/stringprintf.h" #include "net/http/http_response_headers.h" #include "services/network/public/cpp/content_security_policy/content_security_policy.h" -#include "services/network/public/cpp/content_security_policy/csp_context.h" #include "services/network/public/mojom/content_security_policy.mojom.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/origin.h" @@ -19,10 +18,10 @@ namespace { // test expectations on one line. bool Allow(const mojom::CSPSourceListPtr& source_list, const GURL& url, - CSPContext* context, + const mojom::CSPSource& self, bool is_redirect = false, bool is_response_check = false) { - return CheckCSPSourceList(source_list, url, context, is_redirect, + return CheckCSPSourceList(*source_list, url, self, is_redirect, is_response_check); } @@ -77,8 +76,8 @@ std::vector<const mojom::CSPSourceList*> ToRawPointers( } // namespace TEST(CSPSourceList, MultipleSource) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); + auto self = network::mojom::CSPSource::New("http", "example.com", 80, "", + false, false); std::vector<mojom::CSPSourcePtr> sources; sources.push_back(mojom::CSPSource::New("", "a.com", url::PORT_UNSPECIFIED, "", false, false)); @@ -86,92 +85,96 @@ TEST(CSPSourceList, MultipleSource) { "", false, false)); auto source_list = mojom::CSPSourceList::New(); source_list->sources = std::move(sources); - EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), &context)); - EXPECT_FALSE(Allow(source_list, GURL("http://c.com"), &context)); + EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self)); + EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self)); + EXPECT_FALSE(Allow(source_list, GURL("http://c.com"), *self)); } TEST(CSPSourceList, AllowStar) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); + auto self = network::mojom::CSPSource::New("http", "example.com", 80, "", + false, false); auto source_list = mojom::CSPSourceList::New(); source_list->allow_star = true; - EXPECT_TRUE(Allow(source_list, GURL("http://not-example.com"), &context)); - EXPECT_TRUE(Allow(source_list, GURL("https://not-example.com"), &context)); - EXPECT_TRUE(Allow(source_list, GURL("ws://not-example.com"), &context)); - EXPECT_TRUE(Allow(source_list, GURL("wss://not-example.com"), &context)); - EXPECT_TRUE(Allow(source_list, GURL("ftp://not-example.com"), &context)); + EXPECT_TRUE(Allow(source_list, GURL("http://not-example.com"), *self)); + EXPECT_TRUE(Allow(source_list, GURL("https://not-example.com"), *self)); + EXPECT_TRUE(Allow(source_list, GURL("ws://not-example.com"), *self)); + EXPECT_TRUE(Allow(source_list, GURL("wss://not-example.com"), *self)); + EXPECT_TRUE(Allow(source_list, GURL("ftp://not-example.com"), *self)); - EXPECT_FALSE(Allow(source_list, GURL("file://not-example.com"), &context)); - EXPECT_FALSE(Allow(source_list, GURL("applewebdata://a.test"), &context)); + EXPECT_FALSE(Allow(source_list, GURL("file://not-example.com"), *self)); + EXPECT_FALSE(Allow(source_list, GURL("applewebdata://a.test"), *self)); - // With a protocol of 'file', '*' allow 'file:' - context.SetSelf(url::Origin::Create(GURL("file://example.com"))); - EXPECT_TRUE(Allow(source_list, GURL("file://not-example.com"), &context)); - EXPECT_FALSE(Allow(source_list, GURL("applewebdata://a.test"), &context)); + { + // With a protocol of 'file', '*' allow 'file:' + auto self = network::mojom::CSPSource::New( + "file", "example.com", url::PORT_UNSPECIFIED, "", false, false); + EXPECT_TRUE(Allow(source_list, GURL("file://not-example.com"), *self)); + EXPECT_FALSE(Allow(source_list, GURL("applewebdata://a.test"), *self)); + } } TEST(CSPSourceList, AllowSelf) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); + auto self = network::mojom::CSPSource::New("http", "example.com", 80, "", + false, false); auto source_list = mojom::CSPSourceList::New(); source_list->allow_self = true; - EXPECT_TRUE(Allow(source_list, GURL("http://example.com"), &context)); - EXPECT_FALSE(Allow(source_list, GURL("http://not-example.com"), &context)); - EXPECT_TRUE(Allow(source_list, GURL("https://example.com"), &context)); - EXPECT_FALSE(Allow(source_list, GURL("ws://example.com"), &context)); + EXPECT_TRUE(Allow(source_list, GURL("http://example.com"), *self)); + EXPECT_FALSE(Allow(source_list, GURL("http://not-example.com"), *self)); + EXPECT_TRUE(Allow(source_list, GURL("https://example.com"), *self)); + EXPECT_FALSE(Allow(source_list, GURL("ws://example.com"), *self)); } TEST(CSPSourceList, AllowStarAndSelf) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("https://a.com"))); + auto self = + network::mojom::CSPSource::New("https", "a.com", 443, "", false, false); auto source_list = mojom::CSPSourceList::New(); // If the request is allowed by {*} and not by {'self'} then it should be // allowed by the union {*,'self'}. source_list->allow_self = true; source_list->allow_star = false; - EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), &context)); + EXPECT_FALSE(Allow(source_list, GURL("http://b.com"), *self)); source_list->allow_self = false; source_list->allow_star = true; - EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), &context)); + EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self)); source_list->allow_self = true; source_list->allow_star = true; - EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), &context)); + EXPECT_TRUE(Allow(source_list, GURL("http://b.com"), *self)); } TEST(CSPSourceList, AllowSelfWithUnspecifiedPort) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("https://example.com/"))); + auto self = network::mojom::CSPSource::New("https", "example.com", 443, "", + false, false); auto source_list = mojom::CSPSourceList::New(); source_list->allow_self = true; - EXPECT_TRUE( - Allow(source_list, GURL("https://example.com/print.pdf"), &context)); + EXPECT_TRUE(Allow(source_list, GURL("https://example.com/print.pdf"), *self)); } TEST(CSPSourceList, AllowNone) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); + auto self = network::mojom::CSPSource::New("http", "example.com", 80, "", + false, false); auto source_list = mojom::CSPSourceList::New(); - EXPECT_FALSE(Allow(source_list, GURL("http://example.com"), &context)); - EXPECT_FALSE(Allow(source_list, GURL("https://example.test/"), &context)); + EXPECT_FALSE(Allow(source_list, GURL("http://example.com"), *self)); + EXPECT_FALSE(Allow(source_list, GURL("https://example.test/"), *self)); } TEST(CSPSourceTest, SelfIsUnique) { // Policy: 'self' auto source_list = mojom::CSPSourceList::New(); source_list->allow_self = true; - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("http://a.com"))); - EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), &context)); - EXPECT_FALSE(Allow(source_list, GURL("data:text/html,hello"), &context)); + auto self = + network::mojom::CSPSource::New("http", "a.com", 80, "", false, false); + EXPECT_TRUE(Allow(source_list, GURL("http://a.com"), *self)); + EXPECT_FALSE(Allow(source_list, GURL("data:text/html,hello"), *self)); - context.SetSelf( - url::Origin::Create(GURL("data:text/html,<iframe src=[...]>"))); - EXPECT_FALSE(Allow(source_list, GURL("http://a.com"), &context)); - EXPECT_FALSE(Allow(source_list, GURL("data:text/html,hello"), &context)); + // Self doesn't match anything. + auto no_self_source = network::mojom::CSPSource::New( + "", "", url::PORT_UNSPECIFIED, "", false, false); + EXPECT_FALSE(Allow(source_list, GURL("http://a.com"), *no_self_source)); + EXPECT_FALSE( + Allow(source_list, GURL("data:text/html,hello"), *no_self_source)); } TEST(CSPSourceList, Subsume) { @@ -257,6 +260,8 @@ TEST(CSPSourceList, Subsume) { {{"http://*", "http://*.com http://*.example3.com:*/bar/"}, false}, }; + auto origin_b = + mojom::CSPSource::New("https", "frame.test", 443, "", false, false); for (const auto& test : cases) { auto response_sources = ParseToVectorOfSourceLists( mojom::CSPDirectiveName::ScriptSrc, test.response_csp); @@ -264,8 +269,7 @@ TEST(CSPSourceList, Subsume) { EXPECT_EQ(test.expected, CSPSourceListSubsumes( *required_sources, ToRawPointers(response_sources), - mojom::CSPDirectiveName::ScriptSrc, - url::Origin::Create(GURL("https://frame.test")))) + mojom::CSPDirectiveName::ScriptSrc, origin_b.get())) << required << " should " << (test.expected ? "" : "not ") << "subsume " << base::JoinString(test.response_csp, ", "); } @@ -383,11 +387,14 @@ TEST(CSPSourceList, SubsumeWithSelf) { auto response_sources = ParseToVectorOfSourceLists( mojom::CSPDirectiveName::ScriptSrc, test.response_csp); + GURL parsed_test_origin(test.origin); + auto origin_b = mojom::CSPSource::New( + parsed_test_origin.scheme(), parsed_test_origin.host(), + parsed_test_origin.EffectiveIntPort(), "", false, false); EXPECT_EQ(test.expected, - CSPSourceListSubsumes(*required_sources, - ToRawPointers(response_sources), - mojom::CSPDirectiveName::ScriptSrc, - url::Origin::Create(GURL(test.origin)))) + CSPSourceListSubsumes( + *required_sources, ToRawPointers(response_sources), + mojom::CSPDirectiveName::ScriptSrc, origin_b.get())) << required << "from origin " << test.origin << " should " << (test.expected ? "" : "not ") << "subsume " << base::JoinString(test.response_csp, ", "); @@ -489,17 +496,18 @@ TEST(CSPSourceList, SubsumeAllowAllInline) { true}, }; + auto origin_b = + mojom::CSPSource::New("https", "frame.test", 443, "", false, false); for (const auto& test : cases) { mojom::CSPSourceListPtr required_sources = ParseToSourceList(test.directive, test.required); auto response_sources = ParseToVectorOfSourceLists(test.directive, test.response_csp); - EXPECT_EQ( - test.expected, - CSPSourceListSubsumes(*required_sources, - ToRawPointers(response_sources), test.directive, - url::Origin::Create(GURL("https://frame.test")))) + EXPECT_EQ(test.expected, + CSPSourceListSubsumes(*required_sources, + ToRawPointers(response_sources), + test.directive, origin_b.get())) << test.required << " should " << (test.expected ? "" : "not ") << "subsume " << base::JoinString(test.response_csp, ", "); } @@ -576,17 +584,18 @@ TEST(CSPSourceList, SubsumeUnsafeAttributes) { false}, }; + auto origin_b = + mojom::CSPSource::New("https", "frame.test", 443, "", false, false); for (const auto& test : cases) { mojom::CSPSourceListPtr required_sources = ParseToSourceList(test.directive, test.required); auto response_sources = ParseToVectorOfSourceLists(test.directive, test.response_csp); - EXPECT_EQ( - test.expected, - CSPSourceListSubsumes(*required_sources, - ToRawPointers(response_sources), test.directive, - url::Origin::Create(GURL("https://frame.test")))) + EXPECT_EQ(test.expected, + CSPSourceListSubsumes(*required_sources, + ToRawPointers(response_sources), + test.directive, origin_b.get())) << test.required << " should " << (test.expected ? "" : "not ") << "subsume " << base::JoinString(test.response_csp, ", "); } @@ -728,17 +737,18 @@ TEST(CSPSourceList, SubsumeNoncesAndHashes) { false}, }; + auto origin_b = + mojom::CSPSource::New("https", "frame.test", 443, "", false, false); for (const auto& test : cases) { mojom::CSPSourceListPtr required_sources = ParseToSourceList(test.directive, test.required); auto response_sources = ParseToVectorOfSourceLists(test.directive, test.response_csp); - EXPECT_EQ( - test.expected, - CSPSourceListSubsumes(*required_sources, - ToRawPointers(response_sources), test.directive, - url::Origin::Create(GURL("https://frame.test")))) + EXPECT_EQ(test.expected, + CSPSourceListSubsumes(*required_sources, + ToRawPointers(response_sources), + test.directive, origin_b.get())) << test.required << " should " << (test.expected ? "" : "not ") << "subsume " << base::JoinString(test.response_csp, ", "); } @@ -911,17 +921,18 @@ TEST(CSPSourceList, SubsumeStrictDynamic) { false}, }; + auto origin_b = + mojom::CSPSource::New("https", "frame.test", 443, "", false, false); for (const auto& test : cases) { mojom::CSPSourceListPtr required_sources = ParseToSourceList(test.directive, test.required); auto response_sources = ParseToVectorOfSourceLists(test.directive, test.response_csp); - EXPECT_EQ( - test.expected, - CSPSourceListSubsumes(*required_sources, - ToRawPointers(response_sources), test.directive, - url::Origin::Create(GURL("https://frame.test")))) + EXPECT_EQ(test.expected, + CSPSourceListSubsumes(*required_sources, + ToRawPointers(response_sources), + test.directive, origin_b.get())) << test.required << " should " << (test.expected ? "" : "not ") << "subsume " << base::JoinString(test.response_csp, ", "); } @@ -982,6 +993,8 @@ TEST(CSPSourceList, SubsumeListWildcard) { false}, }; + auto origin_b = + mojom::CSPSource::New("https", "another.test", 443, "", false, false); for (const auto& test : cases) { mojom::CSPSourceListPtr required_sources = ParseToSourceList(mojom::CSPDirectiveName::ScriptSrc, test.required); @@ -991,33 +1004,36 @@ TEST(CSPSourceList, SubsumeListWildcard) { EXPECT_EQ(test.expected, CSPSourceListSubsumes( *required_sources, ToRawPointers(response_sources), - mojom::CSPDirectiveName::ScriptSrc, - url::Origin::Create(GURL("https://another.test/image.png")))) + mojom::CSPDirectiveName::ScriptSrc, origin_b.get())) << test.required << " should " << (test.expected ? "" : "not ") << "subsume " << base::JoinString(test.response_csp, ", "); } } TEST(CSPSourceList, SubsumeListNoScheme) { + auto origin_http = + mojom::CSPSource::New("http", "example.org", 80, "", false, false); + auto origin_https = + mojom::CSPSource::New("https", "example.org", 443, "", false, false); struct TestCase { std::string required; std::vector<std::string> response_csp; - std::string origin; + mojom::CSPSource* origin; bool expected; } cases[] = { - {"http://a.com", {"a.com"}, "https://example.org", true}, - {"https://a.com", {"a.com"}, "https://example.org", true}, - {"https://a.com", {"a.com"}, "http://example.org", false}, - {"data://a.com", {"a.com"}, "https://example.org", false}, - {"a.com", {"a.com"}, "https://example.org", true}, - {"a.com", {"a.com"}, "http://example.org", true}, - {"a.com", {"https://a.com"}, "http://example.org", true}, - {"a.com", {"https://a.com"}, "https://example.org", true}, - {"a.com", {"http://a.com"}, "https://example.org", false}, - {"https:", {"a.com"}, "http://example.org", false}, - {"http:", {"a.com"}, "http://example.org", true}, - {"https:", {"a.com", "https:"}, "http://example.org", true}, - {"https:", {"a.com"}, "https://example.org", true}, + {"http://a.com", {"a.com"}, origin_https.get(), true}, + {"https://a.com", {"a.com"}, origin_https.get(), true}, + {"https://a.com", {"a.com"}, origin_http.get(), false}, + {"data://a.com", {"a.com"}, origin_https.get(), false}, + {"a.com", {"a.com"}, origin_https.get(), true}, + {"a.com", {"a.com"}, origin_http.get(), true}, + {"a.com", {"https://a.com"}, origin_http.get(), true}, + {"a.com", {"https://a.com"}, origin_https.get(), true}, + {"a.com", {"http://a.com"}, origin_https.get(), false}, + {"https:", {"a.com"}, origin_http.get(), false}, + {"http:", {"a.com"}, origin_http.get(), true}, + {"https:", {"a.com", "https:"}, origin_http.get(), true}, + {"https:", {"a.com"}, origin_https.get(), true}, }; for (const auto& test : cases) { @@ -1027,12 +1043,11 @@ TEST(CSPSourceList, SubsumeListNoScheme) { mojom::CSPDirectiveName::ScriptSrc, test.response_csp); EXPECT_EQ(test.expected, - CSPSourceListSubsumes(*required_sources, - ToRawPointers(response_sources), - mojom::CSPDirectiveName::ScriptSrc, - url::Origin::Create(GURL(test.origin)))) - << test.required << " on origin " << test.origin << " should " - << (test.expected ? "" : "not ") << "subsume " + CSPSourceListSubsumes( + *required_sources, ToRawPointers(response_sources), + mojom::CSPDirectiveName::ScriptSrc, test.origin)) + << test.required << " on origin with scheme " << test.origin->scheme + << " should " << (test.expected ? "" : "not ") << "subsume " << base::JoinString(test.response_csp, ", "); } } diff --git a/chromium/services/network/public/cpp/content_security_policy/csp_source_unittest.cc b/chromium/services/network/public/cpp/content_security_policy/csp_source_unittest.cc index 88f4e53f051..7ee11c202dd 100644 --- a/chromium/services/network/public/cpp/content_security_policy/csp_source_unittest.cc +++ b/chromium/services/network/public/cpp/content_security_policy/csp_source_unittest.cc @@ -5,7 +5,6 @@ #include "services/network/public/cpp/content_security_policy/csp_source.h" #include "net/http/http_response_headers.h" #include "services/network/public/cpp/content_security_policy/content_security_policy.h" -#include "services/network/public/cpp/content_security_policy/csp_context.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/origin.h" @@ -15,11 +14,11 @@ namespace { // Allow() is an abbreviation of CSPSource::Allow(). Useful for writing test // expectations on one line. -bool Allow(const network::mojom::CSPSourcePtr& source, +bool Allow(const network::mojom::CSPSource& source, const GURL& url, - CSPContext* context, + const network::mojom::CSPSource& self_source, bool is_redirect = false) { - return CheckCSPSource(source, url, context, is_redirect); + return CheckCSPSource(source, url, self_source, is_redirect); } network::mojom::CSPSourcePtr CSPSource(const std::string& raw) { @@ -36,261 +35,291 @@ network::mojom::CSPSourcePtr CSPSource(const std::string& raw) { } // namespace TEST(CSPSourceTest, BasicMatching) { - CSPContext context; - auto source = network::mojom::CSPSource::New("http", "example.com", 8000, "/foo/", false, false); - EXPECT_TRUE(Allow(source, GURL("http://example.com:8000/foo/"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://example.com:8000/foo/bar"), &context)); - EXPECT_TRUE(Allow(source, GURL("HTTP://EXAMPLE.com:8000/foo/BAR"), &context)); + // Self doesn't match anything. + auto self_source = network::mojom::CSPSource::New( + "", "", url::PORT_UNSPECIFIED, "", false, false); + + EXPECT_TRUE( + Allow(*source, GURL("http://example.com:8000/foo/"), *self_source)); + EXPECT_TRUE( + Allow(*source, GURL("http://example.com:8000/foo/bar"), *self_source)); + EXPECT_TRUE( + Allow(*source, GURL("HTTP://EXAMPLE.com:8000/foo/BAR"), *self_source)); - EXPECT_FALSE(Allow(source, GURL("http://example.com:8000/bar/"), &context)); - EXPECT_FALSE(Allow(source, GURL("https://example.com:8000/bar/"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://example.com:9000/bar/"), &context)); EXPECT_FALSE( - Allow(source, GURL("HTTP://example.com:8000/FOO/bar"), &context)); + Allow(*source, GURL("http://example.com:8000/bar/"), *self_source)); + EXPECT_FALSE( + Allow(*source, GURL("https://example.com:8000/bar/"), *self_source)); + EXPECT_FALSE( + Allow(*source, GURL("http://example.com:9000/bar/"), *self_source)); EXPECT_FALSE( - Allow(source, GURL("HTTP://example.com:8000/FOO/BAR"), &context)); + Allow(*source, GURL("HTTP://example.com:8000/FOO/bar"), *self_source)); + EXPECT_FALSE( + Allow(*source, GURL("HTTP://example.com:8000/FOO/BAR"), *self_source)); } TEST(CSPSourceTest, AllowScheme) { - CSPContext context; + // Self doesn't match anything. + auto no_self_source = network::mojom::CSPSource::New( + "", "", url::PORT_UNSPECIFIED, "", false, false); // http -> {http, https}. { auto source = network::mojom::CSPSource::New( "http", "", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *no_self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com"), *no_self_source)); // This passes because the source is "scheme only" so the upgrade is // allowed. - EXPECT_TRUE(Allow(source, GURL("https://a.com:80"), &context)); - EXPECT_FALSE(Allow(source, GURL("ftp://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("ws://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("wss://a.com"), &context)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com:80"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("ftp://a.com"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("ws://a.com"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("wss://a.com"), *no_self_source)); } // ws -> {ws, wss}. { auto source = network::mojom::CSPSource::New( "ws", "", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("https://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("ftp://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("ws://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("wss://a.com"), &context)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("https://a.com"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("ftp://a.com"), *no_self_source)); + EXPECT_TRUE(Allow(*source, GURL("ws://a.com"), *no_self_source)); + EXPECT_TRUE(Allow(*source, GURL("wss://a.com"), *no_self_source)); } // Exact matches required (ftp) { auto source = network::mojom::CSPSource::New( "ftp", "", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_TRUE(Allow(source, GURL("ftp://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); + EXPECT_TRUE(Allow(*source, GURL("ftp://a.com"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *no_self_source)); } // Exact matches required (https) { auto source = network::mojom::CSPSource::New( "https", "", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *no_self_source)); } // Exact matches required (wss) { auto source = network::mojom::CSPSource::New( "wss", "", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_TRUE(Allow(source, GURL("wss://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("ws://a.com"), &context)); + EXPECT_TRUE(Allow(*source, GURL("wss://a.com"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("ws://a.com"), *no_self_source)); } // Scheme is empty (ProtocolMatchesSelf). { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); - - // Self's scheme is http. - context.SetSelf(url::Origin::Create(GURL("http://a.com"))); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("ftp://a.com"), &context)); - - // Self's is https. - context.SetSelf(url::Origin::Create(GURL("https://a.com"))); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("ftp://a.com"), &context)); - - // Self's scheme is not in the http familly. - context.SetSelf(url::Origin::Create(GURL("ftp://a.com/"))); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("ftp://a.com"), &context)); - - // Self's scheme is unique (non standard scheme). - context.SetSelf(url::Origin::Create(GURL("non-standard-scheme://a.com"))); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("non-standard-scheme://a.com"), &context)); - - // Self's scheme is unique (data-url). - context.SetSelf( - url::Origin::Create(GURL("data:text/html,<iframe src=[...]>"))); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("data:text/html,hello"), &context)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *no_self_source)); + + { + // Self's scheme is http. + auto self_source = + network::mojom::CSPSource::New("http", "a.com", 80, "", false, false); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("ftp://a.com"), *self_source)); + } + + { + // Self's is https. + auto self_source = network::mojom::CSPSource::New("https", "a.com", 443, + "", false, false); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("ftp://a.com"), *self_source)); + } + + { + // Self's scheme is not in the http family. + auto self_source = + network::mojom::CSPSource::New("ftp", "a.com", 21, "", false, false); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("ftp://a.com"), *self_source)); + } + + { + // Self's scheme is unique (non standard scheme). + auto self_source = network::mojom::CSPSource::New( + "non-standard-scheme", "a.com", url::PORT_UNSPECIFIED, "", false, + false); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_FALSE( + Allow(*source, GURL("non-standard-scheme://a.com"), *self_source)); + } + + // Self's scheme is unique (e.g. data-url). + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *no_self_source)); + EXPECT_FALSE(Allow(*source, GURL("data:text/html,hello"), *no_self_source)); } } TEST(CSPSourceTest, AllowHost) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); + auto self_source = network::mojom::CSPSource::New("http", "example.com", 80, + "", false, false); + + // Self doesn't match anything. + auto no_self_source = network::mojom::CSPSource::New( + "", "", url::PORT_UNSPECIFIED, "", false, false); // Host is * (source-expression = "http://*") { auto source = network::mojom::CSPSource::New( "http", "", url::PORT_UNSPECIFIED, "", true, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://."), &context)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://."), *self_source)); } // Host is *.foo.bar { auto source = network::mojom::CSPSource::New( "", "foo.bar", url::PORT_UNSPECIFIED, "", true, false); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://bar"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://foo.bar"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://o.bar"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://*.foo.bar"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://sub.foo.bar"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://sub.sub.foo.bar"), &context)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://bar"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://foo.bar"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://o.bar"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://*.foo.bar"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://sub.foo.bar"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://sub.sub.foo.bar"), *self_source)); // Please see http://crbug.com/692505 - EXPECT_TRUE(Allow(source, GURL("http://.foo.bar"), &context)); + EXPECT_TRUE(Allow(*source, GURL("http://.foo.bar"), *self_source)); } // Host is exact. { auto source = network::mojom::CSPSource::New( "", "foo.bar", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_TRUE(Allow(source, GURL("http://foo.bar"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://sub.foo.bar"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://bar"), &context)); + EXPECT_TRUE(Allow(*source, GURL("http://foo.bar"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://sub.foo.bar"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://bar"), *self_source)); // Please see http://crbug.com/692505 - EXPECT_FALSE(Allow(source, GURL("http://.foo.bar"), &context)); + EXPECT_FALSE(Allow(*source, GURL("http://.foo.bar"), *self_source)); } } TEST(CSPSourceTest, AllowPort) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); + auto self_source = network::mojom::CSPSource::New("http", "example.com", 80, + "", false, false); // Source's port unspecified. { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com:80"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com:8080"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com:443"), &context)); - EXPECT_FALSE(Allow(source, GURL("https://a.com:80"), &context)); - EXPECT_FALSE(Allow(source, GURL("https://a.com:8080"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com:443"), &context)); - EXPECT_FALSE(Allow(source, GURL("unknown://a.com:80"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com:80"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com:8080"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com:443"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("https://a.com:80"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("https://a.com:8080"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com:443"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("unknown://a.com:80"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com"), *self_source)); } // Source's port is "*". { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "", false, true); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com:80"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com:8080"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com:8080"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com:0"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com:80"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com:8080"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com:8080"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com:0"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com"), *self_source)); } // Source has a port. { auto source = network::mojom::CSPSource::New("", "a.com", 80, "", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com:80"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com:8080"), &context)); - EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com:80"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com:8080"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com"), *self_source)); } // Allow upgrade from :80 to :443 { auto source = network::mojom::CSPSource::New("", "a.com", 80, "", false, false); - EXPECT_TRUE(Allow(source, GURL("https://a.com:443"), &context)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com:443"), *self_source)); // Should not allow scheme upgrades unless both port and scheme are // upgraded. - EXPECT_FALSE(Allow(source, GURL("http://a.com:443"), &context)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com:443"), *self_source)); } // Host is * but port is specified { auto source = network::mojom::CSPSource::New("http", "", 111, "", true, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com:111"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com:222"), &context)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com:111"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com:222"), *self_source)); } } TEST(CSPSourceTest, AllowPath) { - CSPContext context; - context.SetSelf(url::Origin::Create(GURL("http://example.com"))); + auto self_source = network::mojom::CSPSource::New("http", "example.com", 80, + "", false, false); // Path to a file { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "/path/to/file", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/file"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com/path/to/"), &context)); - EXPECT_FALSE( - Allow(source, GURL("http://a.com/path/to/file/subpath"), &context)); + EXPECT_TRUE( + Allow(*source, GURL("http://a.com/path/to/file"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com/path/to/"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com/path/to/file/subpath"), + *self_source)); EXPECT_FALSE( - Allow(source, GURL("http://a.com/path/to/something"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com/"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); + Allow(*source, GURL("http://a.com/path/to/something"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com/"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *self_source)); } // Path to a directory { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "/path/to/", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/file"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com/path/"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com/path/to"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com/path/to"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com/"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com"), &context)); + EXPECT_TRUE( + Allow(*source, GURL("http://a.com/path/to/file"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com/path/to/"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com/path/"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com/path/to"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com/path/to"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com/"), *self_source)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com"), *self_source)); } // Empty path { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/file"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com/"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); + EXPECT_TRUE( + Allow(*source, GURL("http://a.com/path/to/file"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com/path/to/"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com/"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *self_source)); } // Almost empty path { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "/", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/file"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com/path/to/"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com/"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com"), &context)); + EXPECT_TRUE( + Allow(*source, GURL("http://a.com/path/to/file"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com/path/to/"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com/"), *self_source)); + EXPECT_TRUE(Allow(*source, GURL("http://a.com"), *self_source)); } // Path encoded. @@ -298,29 +327,39 @@ TEST(CSPSourceTest, AllowPath) { auto source = network::mojom::CSPSource::New( "http", "a.com", url::PORT_UNSPECIFIED, "/Hello Günter", false, false); EXPECT_TRUE( - Allow(source, GURL("http://a.com/Hello%20G%C3%BCnter"), &context)); - EXPECT_TRUE(Allow(source, GURL("http://a.com/Hello Günter"), &context)); + Allow(*source, GURL("http://a.com/Hello%20G%C3%BCnter"), *self_source)); + EXPECT_TRUE( + Allow(*source, GURL("http://a.com/Hello Günter"), *self_source)); } // Host is * but path is specified. { auto source = network::mojom::CSPSource::New( "http", "", url::PORT_UNSPECIFIED, "/allowed-path", true, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com/allowed-path"), &context)); - EXPECT_FALSE(Allow(source, GURL("http://a.com/disallowed-path"), &context)); + EXPECT_TRUE( + Allow(*source, GURL("http://a.com/allowed-path"), *self_source)); + EXPECT_FALSE( + Allow(*source, GURL("http://a.com/disallowed-path"), *self_source)); } } TEST(CSPSourceTest, RedirectMatching) { - CSPContext context; auto source = network::mojom::CSPSource::New("http", "a.com", 8000, "/bar/", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com:8000/"), &context, true)); - EXPECT_TRUE(Allow(source, GURL("http://a.com:8000/foo"), &context, true)); - EXPECT_FALSE(Allow(source, GURL("https://a.com:8000/foo"), &context, true)); + + // Self doesn't match anything. + auto self_source = network::mojom::CSPSource::New( + "", "", url::PORT_UNSPECIFIED, "", false, false); + + EXPECT_TRUE(Allow(*source, GURL("http://a.com:8000/"), *self_source, true)); + EXPECT_TRUE( + Allow(*source, GURL("http://a.com:8000/foo"), *self_source, true)); + EXPECT_FALSE( + Allow(*source, GURL("https://a.com:8000/foo"), *self_source, true)); EXPECT_FALSE( - Allow(source, GURL("http://not-a.com:8000/foo"), &context, true)); - EXPECT_FALSE(Allow(source, GURL("http://a.com:9000/foo/"), &context, false)); + Allow(*source, GURL("http://not-a.com:8000/foo"), *self_source, true)); + EXPECT_FALSE( + Allow(*source, GURL("http://a.com:9000/foo/"), *self_source, false)); } TEST(CSPSourceTest, Intersect) { @@ -373,14 +412,14 @@ TEST(CSPSourceTest, Intersect) { auto a = CSPSource(test.a); auto b = CSPSource(test.b); - auto a_intersect_b = CSPSourcesIntersect(a, b); - auto b_intersect_a = CSPSourcesIntersect(b, a); + auto a_intersect_b = CSPSourcesIntersect(*a, *b); + auto b_intersect_a = CSPSourcesIntersect(*b, *a); if (test.intersection) { - EXPECT_EQ(test.intersection, ToString(a_intersect_b)) + EXPECT_EQ(test.intersection, ToString(*a_intersect_b)) << "The intersection of " << test.a << " and " << test.b << " should be " << test.intersection; // Intersection should be symmetric. - EXPECT_EQ(test.intersection, ToString(b_intersect_a)) + EXPECT_EQ(test.intersection, ToString(*b_intersect_a)) << "The intersection of " << test.b << " and " << test.a << " should be " << test.intersection; } else { @@ -416,9 +455,9 @@ TEST(CSPSourceTest, DoesNotSubsume) { auto a = CSPSource(test.a); auto b = CSPSource(test.b); - EXPECT_FALSE(CSPSourceSubsumes(a, b)) + EXPECT_FALSE(CSPSourceSubsumes(*a, *b)) << test.a << " should not subsume " << test.b; - EXPECT_FALSE(CSPSourceSubsumes(b, a)) + EXPECT_FALSE(CSPSourceSubsumes(*b, *a)) << test.b << " should not subsume " << test.a; } } @@ -466,22 +505,22 @@ TEST(CSPSourceTest, Subsumes) { auto a = CSPSource(test.a); auto b = CSPSource(test.b); - EXPECT_EQ(CSPSourceSubsumes(a, b), test.expected_a_subsumes_b) + EXPECT_EQ(CSPSourceSubsumes(*a, *b), test.expected_a_subsumes_b) << test.a << " subsumes " << test.b << " should return " << test.expected_a_subsumes_b; - EXPECT_EQ(CSPSourceSubsumes(b, a), test.expected_b_subsumes_a) + EXPECT_EQ(CSPSourceSubsumes(*b, *a), test.expected_b_subsumes_a) << test.b << " subsumes " << test.a << " should return " << test.expected_b_subsumes_a; a->is_host_wildcard = true; - EXPECT_FALSE(CSPSourceSubsumes(b, a)) - << test.b << " should not subsume " << ToString(a); + EXPECT_FALSE(CSPSourceSubsumes(*b, *a)) + << test.b << " should not subsume " << ToString(*a); // If also |b| has a wildcard host, then the result should be the expected // one. b->is_host_wildcard = true; - EXPECT_EQ(CSPSourceSubsumes(b, a), test.expected_b_subsumes_a) - << ToString(b) << " subsumes " << ToString(a) << " should return " + EXPECT_EQ(CSPSourceSubsumes(*b, *a), test.expected_b_subsumes_a) + << ToString(*b) << " subsumes " << ToString(*a) << " should return " << test.expected_b_subsumes_a; } } @@ -498,21 +537,21 @@ TEST(CSPSourceTest, HostWildcardSubsumes) { auto source_d = CSPSource(d); // *.example.com subsumes www.example.com. - EXPECT_TRUE(CSPSourceSubsumes(source_a, source_b)) + EXPECT_TRUE(CSPSourceSubsumes(*source_a, *source_b)) << a << " should subsume " << b; - EXPECT_FALSE(CSPSourceSubsumes(source_b, source_a)) + EXPECT_FALSE(CSPSourceSubsumes(*source_b, *source_a)) << b << " should not subsume " << a; // *.example.com and example.com have no relations. - EXPECT_FALSE(CSPSourceSubsumes(source_a, source_c)) + EXPECT_FALSE(CSPSourceSubsumes(*source_a, *source_c)) << a << " should not subsume " << c; - EXPECT_FALSE(CSPSourceSubsumes(source_c, source_a)) + EXPECT_FALSE(CSPSourceSubsumes(*source_c, *source_a)) << c << " should not subsume " << a; // https://*.example.com and http://www.example.com have no relations. - EXPECT_FALSE(CSPSourceSubsumes(source_d, source_b)) + EXPECT_FALSE(CSPSourceSubsumes(*source_d, *source_b)) << d << " should not subsume " << b; - EXPECT_FALSE(CSPSourceSubsumes(source_b, source_d)) + EXPECT_FALSE(CSPSourceSubsumes(*source_b, *source_d)) << b << " should not subsume " << d; } @@ -525,15 +564,15 @@ TEST(CSPSourceTest, PortWildcardSubsumes) { auto source_b = CSPSource(b); auto source_c = CSPSource(c); - EXPECT_TRUE(CSPSourceSubsumes(source_a, source_b)) + EXPECT_TRUE(CSPSourceSubsumes(*source_a, *source_b)) << a << " should subsume " << b; - EXPECT_FALSE(CSPSourceSubsumes(source_b, source_a)) + EXPECT_FALSE(CSPSourceSubsumes(*source_b, *source_a)) << b << " should not subsume " << a; // https://example.com:* and http://example.com have no relations. - EXPECT_FALSE(CSPSourceSubsumes(source_b, source_c)) + EXPECT_FALSE(CSPSourceSubsumes(*source_b, *source_c)) << b << " should not subsume " << c; - EXPECT_FALSE(CSPSourceSubsumes(source_c, source_b)) + EXPECT_FALSE(CSPSourceSubsumes(*source_c, *source_b)) << c << " should not subsume " << b; } @@ -564,7 +603,7 @@ TEST(CSPSourceTest, SchemesOnlySubsumes) { for (const auto& test : cases) { auto source_a = CSPSource(test.a); auto source_b = CSPSource(test.b); - EXPECT_EQ(CSPSourceSubsumes(source_a, source_b), test.expected) + EXPECT_EQ(CSPSourceSubsumes(*source_a, *source_b), test.expected) << test.a << " subsumes " << test.b << " should return " << test.expected; } @@ -574,54 +613,58 @@ TEST(CSPSourceTest, ToString) { { auto source = network::mojom::CSPSource::New( "http", "", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_EQ("http:", ToString(source)); + EXPECT_EQ("http:", ToString(*source)); } { auto source = network::mojom::CSPSource::New( "http", "a.com", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_EQ("http://a.com", ToString(source)); + EXPECT_EQ("http://a.com", ToString(*source)); } { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "", false, false); - EXPECT_EQ("a.com", ToString(source)); + EXPECT_EQ("a.com", ToString(*source)); } { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "", true, false); - EXPECT_EQ("*.a.com", ToString(source)); + EXPECT_EQ("*.a.com", ToString(*source)); } { auto source = network::mojom::CSPSource::New("", "", url::PORT_UNSPECIFIED, "", true, false); - EXPECT_EQ("*", ToString(source)); + EXPECT_EQ("*", ToString(*source)); } { auto source = network::mojom::CSPSource::New("", "a.com", 80, "", false, false); - EXPECT_EQ("a.com:80", ToString(source)); + EXPECT_EQ("a.com:80", ToString(*source)); } { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "", false, true); - EXPECT_EQ("a.com:*", ToString(source)); + EXPECT_EQ("a.com:*", ToString(*source)); } { auto source = network::mojom::CSPSource::New( "", "a.com", url::PORT_UNSPECIFIED, "/path", false, false); - EXPECT_EQ("a.com/path", ToString(source)); + EXPECT_EQ("a.com/path", ToString(*source)); } } TEST(CSPSourceTest, UpgradeRequests) { - CSPContext context; auto source = network::mojom::CSPSource::New("http", "a.com", 80, "", false, false); - EXPECT_TRUE(Allow(source, GURL("http://a.com:80"), &context, true)); - EXPECT_FALSE(Allow(source, GURL("https://a.com:80"), &context, true)); - EXPECT_FALSE(Allow(source, GURL("http://a.com:443"), &context, true)); - EXPECT_TRUE(Allow(source, GURL("https://a.com:443"), &context, true)); - EXPECT_TRUE(Allow(source, GURL("https://a.com"), &context, true)); + + // Self doesn't match anything. + auto self_source = network::mojom::CSPSource::New( + "", "", url::PORT_UNSPECIFIED, "", false, false); + + EXPECT_TRUE(Allow(*source, GURL("http://a.com:80"), *self_source, true)); + EXPECT_FALSE(Allow(*source, GURL("https://a.com:80"), *self_source, true)); + EXPECT_FALSE(Allow(*source, GURL("http://a.com:443"), *self_source, true)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com:443"), *self_source, true)); + EXPECT_TRUE(Allow(*source, GURL("https://a.com"), *self_source, true)); } } // namespace content diff --git a/chromium/services/network/public/cpp/cookie_manager_mojom_traits.cc b/chromium/services/network/public/cpp/cookie_manager_mojom_traits.cc index 215258c78ac..da8102781ae 100644 --- a/chromium/services/network/public/cpp/cookie_manager_mojom_traits.cc +++ b/chromium/services/network/public/cpp/cookie_manager_mojom_traits.cc @@ -320,6 +320,35 @@ bool StructTraits<network::mojom::CookieSameSiteContextDataView, return true; } +bool EnumTraits<network::mojom::SamePartyCookieContextType, + net::CookieOptions::SamePartyCookieContextType>:: + FromMojom(network::mojom::SamePartyCookieContextType context_type, + net::CookieOptions::SamePartyCookieContextType* out) { + switch (context_type) { + case network::mojom::SamePartyCookieContextType::kCrossParty: + *out = net::CookieOptions::SamePartyCookieContextType::kCrossParty; + return true; + case network::mojom::SamePartyCookieContextType::kSameParty: + *out = net::CookieOptions::SamePartyCookieContextType::kSameParty; + return true; + } + return false; +} + +network::mojom::SamePartyCookieContextType +EnumTraits<network::mojom::SamePartyCookieContextType, + net::CookieOptions::SamePartyCookieContextType>:: + ToMojom(net::CookieOptions::SamePartyCookieContextType context_type) { + switch (context_type) { + case net::CookieOptions::SamePartyCookieContextType::kCrossParty: + return network::mojom::SamePartyCookieContextType::kCrossParty; + case net::CookieOptions::SamePartyCookieContextType::kSameParty: + return network::mojom::SamePartyCookieContextType::kSameParty; + } + NOTREACHED(); + return network::mojom::SamePartyCookieContextType::kCrossParty; +} + bool StructTraits<network::mojom::CookieOptionsDataView, net::CookieOptions>:: Read(network::mojom::CookieOptionsDataView mojo_options, net::CookieOptions* cookie_options) { @@ -343,17 +372,18 @@ bool StructTraits<network::mojom::CookieOptionsDataView, net::CookieOptions>:: else cookie_options->unset_return_excluded_cookies(); - base::Optional<std::vector<net::SchemefulSite>> mojo_full_party_context; - if (!mojo_options.ReadFullPartyContext(&mojo_full_party_context)) + net::CookieOptions::SamePartyCookieContextType same_party_cookie_context_type; + if (!mojo_options.ReadSamePartyCookieContextType( + &same_party_cookie_context_type)) return false; - base::Optional<std::set<net::SchemefulSite>> full_party_context; - if (mojo_full_party_context.has_value()) { - full_party_context.emplace(mojo_full_party_context->begin(), - mojo_full_party_context->end()); - if (mojo_full_party_context->size() != full_party_context->size()) - return false; - } - cookie_options->set_full_party_context(full_party_context); + cookie_options->set_same_party_cookie_context_type( + same_party_cookie_context_type); + + cookie_options->set_full_party_context_size( + mojo_options.full_party_context_size()); + + cookie_options->set_is_in_nontrivial_first_party_set( + mojo_options.is_in_nontrivial_first_party_set()); return true; } @@ -457,7 +487,8 @@ bool StructTraits< if (!c.ReadAccessSemantics(&access_semantics)) return false; - *out = {effective_same_site, status, access_semantics}; + *out = {effective_same_site, status, access_semantics, + c.is_allowed_to_access_secure_cookies()}; return true; } diff --git a/chromium/services/network/public/cpp/cookie_manager_mojom_traits.h b/chromium/services/network/public/cpp/cookie_manager_mojom_traits.h index cde54d9247c..d8cd73f3e66 100644 --- a/chromium/services/network/public/cpp/cookie_manager_mojom_traits.h +++ b/chromium/services/network/public/cpp/cookie_manager_mojom_traits.h @@ -13,13 +13,8 @@ #include "net/cookies/cookie_constants.h" #include "net/cookies/cookie_inclusion_status.h" #include "net/cookies/cookie_options.h" -#include "services/network/public/cpp/schemeful_site_mojom_traits.h" #include "services/network/public/mojom/cookie_manager.mojom.h" -namespace net { -class SchemefulSite; -} // namespace net - namespace mojo { template <> @@ -100,6 +95,16 @@ struct StructTraits<network::mojom::CookieSameSiteContextDataView, }; template <> +struct EnumTraits<network::mojom::SamePartyCookieContextType, + net::CookieOptions::SamePartyCookieContextType> { + static network::mojom::SamePartyCookieContextType ToMojom( + net::CookieOptions::SamePartyCookieContextType context_type); + + static bool FromMojom(network::mojom::SamePartyCookieContextType context_type, + net::CookieOptions::SamePartyCookieContextType* out); +}; + +template <> struct StructTraits<network::mojom::CookieOptionsDataView, net::CookieOptions> { static bool exclude_httponly(const net::CookieOptions& o) { return o.exclude_httponly(); @@ -115,9 +120,17 @@ struct StructTraits<network::mojom::CookieOptionsDataView, net::CookieOptions> { return o.return_excluded_cookies(); } - static const base::Optional<std::set<net::SchemefulSite>>& full_party_context( - const net::CookieOptions& o) { - return o.full_party_context(); + static net::CookieOptions::SamePartyCookieContextType + same_party_cookie_context_type(const net::CookieOptions& o) { + return o.same_party_cookie_context_type(); + } + + static uint32_t full_party_context_size(const net::CookieOptions& o) { + return o.full_party_context_size(); + } + + static bool is_in_nontrivial_first_party_set(const net::CookieOptions& o) { + return o.is_in_nontrivial_first_party_set(); } static bool Read(network::mojom::CookieOptionsDataView mojo_options, @@ -217,6 +230,10 @@ struct StructTraits<network::mojom::CookieAccessResultDataView, const net::CookieAccessResult& c) { return c.access_semantics; } + static bool is_allowed_to_access_secure_cookies( + const net::CookieAccessResult& c) { + return c.is_allowed_to_access_secure_cookies; + } static bool Read(network::mojom::CookieAccessResultDataView access_result, net::CookieAccessResult* out); }; diff --git a/chromium/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc index 639592a4392..6164206812a 100644 --- a/chromium/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/cookie_manager_mojom_traits_unittest.cc @@ -4,13 +4,11 @@ #include "services/network/public/cpp/cookie_manager_mojom_traits.h" -#include <set> #include <vector> #include "base/test/gtest_util.h" #include "mojo/public/cpp/base/time_mojom_traits.h" #include "mojo/public/cpp/test_support/test_utils.h" -#include "net/base/schemeful_site.h" #include "net/cookies/cookie_constants.h" #include "services/network/public/cpp/cookie_manager_mojom_traits.h" #include "services/network/public/mojom/cookie_manager.mojom.h" @@ -20,14 +18,8 @@ namespace network { namespace { -template <typename MojoType, typename NativeType> -bool SerializeAndDeserializeEnum(NativeType in, NativeType* out) { - MojoType intermediate = mojo::EnumTraits<MojoType, NativeType>::ToMojom(in); - return mojo::EnumTraits<MojoType, NativeType>::FromMojom(intermediate, out); -} - TEST(CookieManagerTraitsTest, Roundtrips_CanonicalCookie) { - net::CanonicalCookie original( + auto original = net::CanonicalCookie::CreateUnsafeCookieForTesting( "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(), false, false, net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_LOW, false, net::CookieSourceScheme::kSecure, 8433); @@ -35,57 +27,60 @@ TEST(CookieManagerTraitsTest, Roundtrips_CanonicalCookie) { net::CanonicalCookie copied; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CanonicalCookie>( - &original, &copied)); - - EXPECT_EQ(original.Name(), copied.Name()); - EXPECT_EQ(original.Value(), copied.Value()); - EXPECT_EQ(original.Domain(), copied.Domain()); - EXPECT_EQ(original.Path(), copied.Path()); - EXPECT_EQ(original.CreationDate(), copied.CreationDate()); - EXPECT_EQ(original.LastAccessDate(), copied.LastAccessDate()); - EXPECT_EQ(original.ExpiryDate(), copied.ExpiryDate()); - EXPECT_EQ(original.IsSecure(), copied.IsSecure()); - EXPECT_EQ(original.IsHttpOnly(), copied.IsHttpOnly()); - EXPECT_EQ(original.SameSite(), copied.SameSite()); - EXPECT_EQ(original.Priority(), copied.Priority()); - EXPECT_EQ(original.IsSameParty(), copied.IsSameParty()); - EXPECT_EQ(original.SourceScheme(), copied.SourceScheme()); - EXPECT_EQ(original.SourcePort(), copied.SourcePort()); + *original, copied)); + + EXPECT_EQ(original->Name(), copied.Name()); + EXPECT_EQ(original->Value(), copied.Value()); + EXPECT_EQ(original->Domain(), copied.Domain()); + EXPECT_EQ(original->Path(), copied.Path()); + EXPECT_EQ(original->CreationDate(), copied.CreationDate()); + EXPECT_EQ(original->LastAccessDate(), copied.LastAccessDate()); + EXPECT_EQ(original->ExpiryDate(), copied.ExpiryDate()); + EXPECT_EQ(original->IsSecure(), copied.IsSecure()); + EXPECT_EQ(original->IsHttpOnly(), copied.IsHttpOnly()); + EXPECT_EQ(original->SameSite(), copied.SameSite()); + EXPECT_EQ(original->Priority(), copied.Priority()); + EXPECT_EQ(original->IsSameParty(), copied.IsSameParty()); + EXPECT_EQ(original->SourceScheme(), copied.SourceScheme()); + EXPECT_EQ(original->SourcePort(), copied.SourcePort()); // Test port edge cases: unspecified. - net::CanonicalCookie original_unspecified( - "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(), false, - false, net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_LOW, - false, net::CookieSourceScheme::kSecure, url::PORT_UNSPECIFIED); + auto original_unspecified = + net::CanonicalCookie::CreateUnsafeCookieForTesting( + "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(), + false, false, net::CookieSameSite::NO_RESTRICTION, + net::COOKIE_PRIORITY_LOW, false, net::CookieSourceScheme::kSecure, + url::PORT_UNSPECIFIED); net::CanonicalCookie copied_unspecified; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CanonicalCookie>( - &original_unspecified, &copied_unspecified)); + *original_unspecified, copied_unspecified)); - EXPECT_EQ(original_unspecified.SourcePort(), copied_unspecified.SourcePort()); + EXPECT_EQ(original_unspecified->SourcePort(), + copied_unspecified.SourcePort()); // Test port edge cases: invalid. - net::CanonicalCookie original_invalid( + auto original_invalid = net::CanonicalCookie::CreateUnsafeCookieForTesting( "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(), false, false, net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_LOW, false, net::CookieSourceScheme::kSecure, url::PORT_INVALID); net::CanonicalCookie copied_invalid; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CanonicalCookie>( - &original_invalid, &copied_invalid)); + *original_invalid, copied_invalid)); - EXPECT_EQ(original_invalid.SourcePort(), copied_invalid.SourcePort()); + EXPECT_EQ(original_invalid->SourcePort(), copied_invalid.SourcePort()); // Serializer returns false if cookie is non-canonical. // Example is non-canonical because of newline in name. - original = net::CanonicalCookie("A\n", "B", "x.y", "/path", base::Time(), - base::Time(), base::Time(), false, false, - net::CookieSameSite::NO_RESTRICTION, - net::COOKIE_PRIORITY_LOW, false); + original = net::CanonicalCookie::CreateUnsafeCookieForTesting( + "A\n", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(), + false, false, net::CookieSameSite::NO_RESTRICTION, + net::COOKIE_PRIORITY_LOW, false); EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::CanonicalCookie>( - &original, &copied)); + *original, copied)); } TEST(CookieManagerTraitsTest, Roundtrips_CookieInclusionStatus) { @@ -105,7 +100,7 @@ TEST(CookieManagerTraitsTest, Roundtrips_CookieInclusionStatus) { net::CookieInclusionStatus copied; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieInclusionStatus>( - &original, &copied)); + original, copied)); EXPECT_TRUE(copied.HasExactlyExclusionReasonsForTesting( {net::CookieInclusionStatus::EXCLUDE_SAMESITE_LAX, net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX, @@ -121,10 +116,10 @@ TEST(CookieManagerTraitsTest, Roundtrips_CookieInclusionStatus) { EXPECT_FALSE( mojo::test::SerializeAndDeserialize<mojom::CookieInclusionStatus>( - &invalid, &copied)); + invalid, copied)); } -TEST(CookieManagerTraitsTest, Rountrips_CookieAccessResult) { +TEST(CookieManagerTraitsTest, Roundtrips_CookieAccessResult) { net::CookieAccessResult original = net::CookieAccessResult( net::CookieEffectiveSameSite::LAX_MODE, net::CookieInclusionStatus( @@ -132,11 +127,12 @@ TEST(CookieManagerTraitsTest, Rountrips_CookieAccessResult) { EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX, net::CookieInclusionStatus:: WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT), - net::CookieAccessSemantics::LEGACY); + net::CookieAccessSemantics::LEGACY, + true /* is_allowed_to_access_secure_cookies */); net::CookieAccessResult copied; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieAccessResult>( - &original, &copied)); + original, copied)); EXPECT_EQ(original.effective_same_site, copied.effective_same_site); EXPECT_TRUE(copied.status.HasExactlyExclusionReasonsForTesting( @@ -145,21 +141,23 @@ TEST(CookieManagerTraitsTest, Rountrips_CookieAccessResult) { EXPECT_TRUE(copied.status.HasExactlyWarningReasonsForTesting( {net::CookieInclusionStatus:: WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT})); + EXPECT_EQ(original.is_allowed_to_access_secure_cookies, + copied.is_allowed_to_access_secure_cookies); } TEST(CookieManagerTraitsTest, Rountrips_CookieWithAccessResult) { - net::CanonicalCookie original_cookie( + auto original_cookie = net::CanonicalCookie::CreateUnsafeCookieForTesting( "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(), - /* secure = */ true, /* http_only = */ false, + /* secure = */ true, /* httponly = */ false, net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_LOW, false); - net::CookieWithAccessResult original = {original_cookie, + net::CookieWithAccessResult original = {*original_cookie, net::CookieAccessResult()}; net::CookieWithAccessResult copied; EXPECT_TRUE( mojo::test::SerializeAndDeserialize<mojom::CookieWithAccessResult>( - &original, &copied)); + original, copied)); EXPECT_EQ(original.cookie.Name(), copied.cookie.Name()); EXPECT_EQ(original.cookie.Value(), copied.cookie.Value()); @@ -178,19 +176,19 @@ TEST(CookieManagerTraitsTest, Rountrips_CookieWithAccessResult) { EXPECT_EQ(original.access_result.status, copied.access_result.status); } -TEST(CookieManagerTraitsTest, Rountrips_CookieAndLineWithAccessResult) { - net::CanonicalCookie original_cookie( +TEST(CookieManagerTraitsTest, Roundtrips_CookieAndLineWithAccessResult) { + auto original_cookie = net::CanonicalCookie::CreateUnsafeCookieForTesting( "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(), - /* secure = */ true, /* http_only = */ false, + /* secure = */ true, /* httponly = */ false, net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_LOW, false); - net::CookieAndLineWithAccessResult original(original_cookie, "cookie-string", + net::CookieAndLineWithAccessResult original(*original_cookie, "cookie-string", net::CookieAccessResult()); net::CookieAndLineWithAccessResult copied; EXPECT_TRUE( mojo::test::SerializeAndDeserialize<mojom::CookieAndLineWithAccessResult>( - &original, &copied)); + original, copied)); EXPECT_EQ(original.cookie->Name(), copied.cookie->Name()); EXPECT_EQ(original.cookie->Value(), copied.cookie->Value()); @@ -213,8 +211,8 @@ TEST(CookieManagerTraitsTest, Roundtrips_CookieSameSite) { {net::CookieSameSite::NO_RESTRICTION, net::CookieSameSite::LAX_MODE, net::CookieSameSite::STRICT_MODE, net::CookieSameSite::UNSPECIFIED}) { net::CookieSameSite roundtrip; - ASSERT_TRUE(SerializeAndDeserializeEnum<mojom::CookieSameSite>(cookie_state, - &roundtrip)); + ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieSameSite>( + cookie_state, roundtrip)); EXPECT_EQ(cookie_state, roundtrip); } } @@ -227,8 +225,9 @@ TEST(CookieManagerTraitsTest, Roundtrips_CookieEffectiveSameSite) { net::CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE, net::CookieEffectiveSameSite::UNDEFINED}) { net::CookieEffectiveSameSite roundtrip; - ASSERT_TRUE(SerializeAndDeserializeEnum<mojom::CookieEffectiveSameSite>( - cookie_state, &roundtrip)); + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::CookieEffectiveSameSite>( + cookie_state, roundtrip)); EXPECT_EQ(cookie_state, roundtrip); } } @@ -239,8 +238,8 @@ TEST(CookieManagerTraitsTest, Roundtrips_ContextType) { {ContextType::CROSS_SITE, ContextType::SAME_SITE_LAX_METHOD_UNSAFE, ContextType::SAME_SITE_LAX, ContextType::SAME_SITE_STRICT}) { ContextType roundtrip; - ASSERT_TRUE(SerializeAndDeserializeEnum<mojom::ContextType>(context_type, - &roundtrip)); + ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::ContextType>( + context_type, roundtrip)); EXPECT_EQ(context_type, roundtrip); } } @@ -251,12 +250,24 @@ TEST(CookieManagerTraitsTest, Roundtrips_CookieAccessSemantics) { net::CookieAccessSemantics::NONLEGACY, net::CookieAccessSemantics::LEGACY}) { net::CookieAccessSemantics roundtrip; - ASSERT_TRUE(SerializeAndDeserializeEnum<mojom::CookieAccessSemantics>( - access_semantics, &roundtrip)); + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::CookieAccessSemantics>( + access_semantics, roundtrip)); EXPECT_EQ(access_semantics, roundtrip); } } +TEST(CookieManagerTraitsTest, Roundtrips_CookieSourceScheme) { + for (net::CookieSourceScheme source_scheme : + {net::CookieSourceScheme::kUnset, net::CookieSourceScheme::kNonSecure, + net::CookieSourceScheme::kSecure}) { + net::CookieSourceScheme roundtrip; + ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieSourceScheme>( + source_scheme, roundtrip)); + EXPECT_EQ(source_scheme, roundtrip); + } +} + TEST(CookieManagerTraitsTest, Roundtrips_CookieChangeCause) { for (net::CookieChangeCause change_cause : {net::CookieChangeCause::INSERTED, net::CookieChangeCause::EXPLICIT, @@ -265,8 +276,8 @@ TEST(CookieManagerTraitsTest, Roundtrips_CookieChangeCause) { net::CookieChangeCause::EVICTED, net::CookieChangeCause::EXPIRED_OVERWRITE}) { net::CookieChangeCause roundtrip; - ASSERT_TRUE(SerializeAndDeserializeEnum<mojom::CookieChangeCause>( - change_cause, &roundtrip)); + ASSERT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieChangeCause>( + change_cause, roundtrip)); EXPECT_EQ(change_cause, roundtrip); } } @@ -288,7 +299,7 @@ TEST(CookieManagerTraitsTest, Roundtrips_CookieSameSiteContext) { EXPECT_EQ( mojo::test::SerializeAndDeserialize<mojom::CookieSameSiteContext>( - &context_in, ©), + context_in, copy), schemeful_context_type <= context_type); if (schemeful_context_type <= context_type) @@ -297,90 +308,82 @@ TEST(CookieManagerTraitsTest, Roundtrips_CookieSameSiteContext) { } } +TEST(CookieManagerTraitsTest, Roundtrips_SamePartyCookieContextType) { + using ContextType = net::CookieOptions::SamePartyCookieContextType; + for (ContextType context_type : + {ContextType::kCrossParty, ContextType::kSameParty}) { + ContextType roundtrip; + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::SamePartyCookieContextType>( + context_type, roundtrip)); + EXPECT_EQ(context_type, roundtrip); + } +} + TEST(CookieManagerTraitsTest, Roundtrips_CookieOptions) { { net::CookieOptions least_trusted, copy; EXPECT_FALSE(least_trusted.return_excluded_cookies()); - least_trusted.set_return_excluded_cookies(); // differ from default. + least_trusted.set_full_party_context_size(10u); + least_trusted.set_is_in_nontrivial_first_party_set( + true); // differ from default. EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieOptions>( - &least_trusted, ©)); + least_trusted, copy)); EXPECT_TRUE(copy.exclude_httponly()); EXPECT_EQ( net::CookieOptions::SameSiteCookieContext( net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE), copy.same_site_cookie_context()); EXPECT_TRUE(copy.return_excluded_cookies()); + EXPECT_EQ(net::CookieOptions::SamePartyCookieContextType::kCrossParty, + copy.same_party_cookie_context_type()); + EXPECT_EQ(10u, copy.full_party_context_size()); + EXPECT_TRUE(copy.is_in_nontrivial_first_party_set()); } { net::CookieOptions very_trusted, copy; - auto kPartyContext = std::set<net::SchemefulSite>{ - net::SchemefulSite(url::Origin::Create(GURL("https://a.test")))}; very_trusted.set_include_httponly(); very_trusted.set_same_site_cookie_context( net::CookieOptions::SameSiteCookieContext::MakeInclusive()); - very_trusted.set_full_party_context(kPartyContext); + very_trusted.set_same_party_cookie_context_type( + net::CookieOptions::SamePartyCookieContextType::kSameParty); + very_trusted.set_full_party_context_size(1u); + very_trusted.set_is_in_nontrivial_first_party_set(true); EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieOptions>( - &very_trusted, ©)); + very_trusted, copy)); EXPECT_FALSE(copy.exclude_httponly()); EXPECT_EQ(net::CookieOptions::SameSiteCookieContext::MakeInclusive(), copy.same_site_cookie_context()); EXPECT_FALSE(copy.return_excluded_cookies()); - EXPECT_EQ(kPartyContext, copy.full_party_context()); - } -} - -TEST(CookieManagerTraitsTest, Roundtrips_FullPartyContext) { - { - std::vector<std::set<net::SchemefulSite>> kTestCases = { - std::set<net::SchemefulSite>(), - std::set<net::SchemefulSite>{net::SchemefulSite()}, - std::set<net::SchemefulSite>{ - net::SchemefulSite(url::Origin::Create(GURL("https://a.test")))}, - std::set<net::SchemefulSite>{ - net::SchemefulSite(url::Origin::Create(GURL("http://a.test"))), - net::SchemefulSite(url::Origin::Create(GURL("http://b.test")))}, - }; - - for (const std::set<net::SchemefulSite>& fpc : kTestCases) { - net::CookieOptions options, copy; - options.set_full_party_context(fpc); - EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieOptions>( - &options, ©)); - EXPECT_EQ(fpc, copy.full_party_context()); - } - } - { - base::Optional<std::set<net::SchemefulSite>> kFullPartyContext = - base::nullopt; - net::CookieOptions options, copy; - options.set_full_party_context(kFullPartyContext); - EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieOptions>( - &options, ©)); - EXPECT_EQ(kFullPartyContext, copy.full_party_context()); + EXPECT_EQ(net::CookieOptions::SamePartyCookieContextType::kSameParty, + copy.same_party_cookie_context_type()); + EXPECT_EQ(1u, copy.full_party_context_size()); + EXPECT_TRUE(copy.is_in_nontrivial_first_party_set()); } } TEST(CookieManagerTraitsTest, Roundtrips_CookieChangeInfo) { - net::CanonicalCookie original_cookie( + auto original_cookie = net::CanonicalCookie::CreateUnsafeCookieForTesting( "A", "B", "x.y", "/path", base::Time(), base::Time(), base::Time(), - /* secure = */ false, /* http_only = */ false, + /* secure = */ false, /* httponly = */ false, net::CookieSameSite::UNSPECIFIED, net::COOKIE_PRIORITY_LOW, false); net::CookieChangeInfo original( - original_cookie, + *original_cookie, net::CookieAccessResult(net::CookieEffectiveSameSite::UNDEFINED, net::CookieInclusionStatus(), - net::CookieAccessSemantics::LEGACY), + net::CookieAccessSemantics::LEGACY, + false /* is_allowed_to_access_secure_cookies */), net::CookieChangeCause::EXPLICIT); net::CookieChangeInfo copied; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::CookieChangeInfo>( - &original, &copied)); + original, copied)); EXPECT_EQ(original.cookie.Name(), copied.cookie.Name()); EXPECT_EQ(original.cookie.Value(), copied.cookie.Value()); diff --git a/chromium/services/network/public/cpp/cors/cors.cc b/chromium/services/network/public/cpp/cors/cors.cc index 7189eaa1871..f148465db9e 100644 --- a/chromium/services/network/public/cpp/cors/cors.cc +++ b/chromium/services/network/public/cpp/cors/cors.cc @@ -473,10 +473,11 @@ bool IsCorsSafelistedHeader(const std::string& name, const std::string& value) { } if (lower_name == "accept-language" || lower_name == "content-language") { - return (value.end() == std::find_if(value.begin(), value.end(), [](char c) { - return !isalnum(c) && c != 0x20 && c != 0x2a && c != 0x2c && - c != 0x2d && c != 0x2e && c != 0x3b && c != 0x3d; - })); + return std::all_of(value.begin(), value.end(), [](char c) { + return (0x30 <= c && c <= 0x39) || (0x41 <= c && c <= 0x5a) || + (0x61 <= c && c <= 0x7a) || c == 0x20 || c == 0x2a || c == 0x2c || + c == 0x2d || c == 0x2e || c == 0x3b || c == 0x3d; + }); } if (lower_name == "content-type") diff --git a/chromium/services/network/public/cpp/cors/cors_legacy.cc b/chromium/services/network/public/cpp/cors/cors_legacy.cc deleted file mode 100644 index f01af63619b..00000000000 --- a/chromium/services/network/public/cpp/cors/cors_legacy.cc +++ /dev/null @@ -1,40 +0,0 @@ -// 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/cors/cors_legacy.h" - -#include <algorithm> - -#include "url/gurl.h" -#include "url/url_util.h" - -namespace { - -std::vector<std::string>* secure_origins = nullptr; - -} // namespace - -namespace network { - -namespace cors { - -namespace legacy { - -void RegisterSecureOrigins(const std::vector<std::string>& origins) { - delete secure_origins; - secure_origins = new std::vector<std::string>(origins.size()); - std::copy(origins.begin(), origins.end(), secure_origins->begin()); -} - -const std::vector<std::string>& GetSecureOrigins() { - if (!secure_origins) - secure_origins = new std::vector<std::string>; - return *secure_origins; -} - -} // namespace legacy - -} // namespace cors - -} // namespace network diff --git a/chromium/services/network/public/cpp/cors/cors_legacy.h b/chromium/services/network/public/cpp/cors/cors_legacy.h deleted file mode 100644 index 6da3b705e1b..00000000000 --- a/chromium/services/network/public/cpp/cors/cors_legacy.h +++ /dev/null @@ -1,39 +0,0 @@ -// 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_CORS_CORS_LEGACY_H_ -#define SERVICES_NETWORK_PUBLIC_CPP_CORS_CORS_LEGACY_H_ - -#include <string> -#include <vector> - -#include "base/component_export.h" - -namespace network { -namespace cors { - -// Functions in namespace legacy are for legacy code path. Pure Network -// Service code should not use it. Since files in public/cpp/cors are shared -// between Network Service and legacy code path in content, but Network Service -// should not know content dependent concepts, we need to provide abstracted -// interfaces to implement extra checks for the case code runs in content. -// -// TODO(toyoshim): Remove all functions after Network Service is enabled. -namespace legacy { - -// Registers allowlisted secure origins and hostname patterns for CORS checks in -// CorsURLLoader. -COMPONENT_EXPORT(NETWORK_CPP) -void RegisterSecureOrigins(const std::vector<std::string>& secure_origins); - -// Refers the registered allowlisted secure origins and hostname patterns. -COMPONENT_EXPORT(NETWORK_CPP) -const std::vector<std::string>& GetSecureOrigins(); - -} // namespace legacy - -} // namespace cors -} // namespace network - -#endif // SERVICES_NETWORK_PUBLIC_CPP_CORS_CORS_LEGACY_H_ diff --git a/chromium/services/network/public/cpp/cors/cors_unittest.cc b/chromium/services/network/public/cpp/cors/cors_unittest.cc index 2113d78f370..2af600635ac 100644 --- a/chromium/services/network/public/cpp/cors/cors_unittest.cc +++ b/chromium/services/network/public/cpp/cors/cors_unittest.cc @@ -6,6 +6,7 @@ #include <limits.h> +#include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" diff --git a/chromium/services/network/public/cpp/cors/preflight_cache.cc b/chromium/services/network/public/cpp/cors/preflight_cache.cc deleted file mode 100644 index 0049fa1bc3d..00000000000 --- a/chromium/services/network/public/cpp/cors/preflight_cache.cc +++ /dev/null @@ -1,127 +0,0 @@ -// 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/cors/preflight_cache.h" - -#include <iterator> - -#include "base/metrics/histogram_macros.h" -#include "base/rand_util.h" -#include "base/time/time.h" -#include "url/gurl.h" - -namespace network { - -namespace cors { - -namespace { - -constexpr size_t kMaxCacheEntries = 1024u; -constexpr size_t kMaxKeyLength = 1024u; -constexpr size_t kPurgeUnit = 10u; - -// These values are persisted to logs. Entries should not be renumbered and -// numeric values should never be reused. -enum class CacheMetric { - kHitAndPass = 0, - kHitAndFail = 1, - kMiss = 2, - kStale = 3, - - kMaxValue = kStale, -}; - -void ReportCacheMetric(CacheMetric metric) { - UMA_HISTOGRAM_ENUMERATION("Net.Cors.PreflightCacheResult", metric); -} - -} // namespace - -PreflightCache::PreflightCache() = default; -PreflightCache::~PreflightCache() = default; - -void PreflightCache::AppendEntry( - const url::Origin& origin, - const GURL& url, - const net::NetworkIsolationKey& network_isolation_key, - std::unique_ptr<PreflightResult> preflight_result) { - DCHECK(preflight_result); - - // Do not cache |preflight_result| if |url| is too long. - const std::string url_spec = url.spec(); - if (url_spec.length() >= kMaxKeyLength) - return; - - auto key = std::make_tuple(origin, url_spec, network_isolation_key); - const auto existing_entry = cache_.find(key); - if (existing_entry == cache_.end()) { - // Since one new entry is always added below, let's purge one cache entry - // if cache size is larger than kMaxCacheEntries - 1 so that the size to be - // kMaxCacheEntries at maximum. - MayPurge(kMaxCacheEntries - 1, kPurgeUnit); - } - UMA_HISTOGRAM_COUNTS_10000("Net.Cors.PreflightCacheKeySize", - url_spec.length()); - - cache_[key] = std::move(preflight_result); -} - -bool PreflightCache::CheckIfRequestCanSkipPreflight( - const url::Origin& origin, - const GURL& url, - const net::NetworkIsolationKey& network_isolation_key, - mojom::CredentialsMode credentials_mode, - const std::string& method, - const net::HttpRequestHeaders& request_headers, - bool is_revalidating) { - // Check if the entry exists in the cache. - auto key = std::make_tuple(origin, url.spec(), network_isolation_key); - auto cache_entry = cache_.find(key); - if (cache_entry == cache_.end()) { - ReportCacheMetric(CacheMetric::kMiss); - return false; - } - - // Check if the entry is still valid. - if (!cache_entry->second->IsExpired()) { - // Both |origin| and |url| are in cache. Check if the entry is sufficient to - // skip CORS-preflight. - if (cache_entry->second->EnsureAllowedRequest( - credentials_mode, method, request_headers, is_revalidating)) { - ReportCacheMetric(CacheMetric::kHitAndPass); - return true; - } - ReportCacheMetric(CacheMetric::kHitAndFail); - } else { - ReportCacheMetric(CacheMetric::kStale); - } - - // The cache entry is either stale or not sufficient. Remove the item from the - // cache. - cache_.erase(cache_entry); - return false; -} - -size_t PreflightCache::CountEntriesForTesting() const { - return cache_.size(); -} - -void PreflightCache::MayPurgeForTesting(size_t max_entries, size_t purge_unit) { - MayPurge(max_entries, purge_unit); -} - -void PreflightCache::MayPurge(size_t max_entries, size_t purge_unit) { - if (cache_.size() <= max_entries) - return; - DCHECK_GE(cache_.size(), purge_unit); - auto purge_begin_entry = cache_.begin(); - std::advance(purge_begin_entry, base::RandInt(0, cache_.size() - purge_unit)); - auto purge_end_entry = purge_begin_entry; - std::advance(purge_end_entry, purge_unit); - cache_.erase(purge_begin_entry, purge_end_entry); -} - -} // namespace cors - -} // namespace network diff --git a/chromium/services/network/public/cpp/cors/preflight_cache.h b/chromium/services/network/public/cpp/cors/preflight_cache.h deleted file mode 100644 index 1d357965b56..00000000000 --- a/chromium/services/network/public/cpp/cors/preflight_cache.h +++ /dev/null @@ -1,79 +0,0 @@ -// 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_CORS_PREFLIGHT_CACHE_H_ -#define SERVICES_NETWORK_PUBLIC_CPP_CORS_PREFLIGHT_CACHE_H_ - -#include <map> -#include <memory> -#include <string> -#include <tuple> - -#include "base/component_export.h" -#include "base/macros.h" -#include "net/base/network_isolation_key.h" -#include "net/http/http_request_headers.h" -#include "services/network/public/cpp/cors/preflight_result.h" -#include "services/network/public/mojom/fetch_api.mojom-shared.h" -#include "url/origin.h" - -class GURL; - -namespace network { - -namespace cors { - -// A class to implement CORS-preflight cache that is defined in the fetch spec, -// https://fetch.spec.whatwg.org/#concept-cache. -// TODO(toyoshim): We may consider to clear all cached entries when users' -// network configuration is changed. -class COMPONENT_EXPORT(NETWORK_CPP) PreflightCache final { - public: - PreflightCache(); - ~PreflightCache(); - - // Appends new |preflight_result| entry to the cache for a specified |origin| - // and |url|. - void AppendEntry(const url::Origin& origin, - const GURL& url, - const net::NetworkIsolationKey& network_isolation_key, - std::unique_ptr<PreflightResult> preflight_result); - - // Consults with cached results, and decides if we can skip CORS-preflight or - // not. - bool CheckIfRequestCanSkipPreflight( - const url::Origin& origin, - const GURL& url, - const net::NetworkIsolationKey& network_isolation_key, - mojom::CredentialsMode credentials_mode, - const std::string& method, - const net::HttpRequestHeaders& headers, - bool is_revalidating); - - // Counts cached entries for testing. - size_t CountEntriesForTesting() const; - - // Purges one cache entry if number of entries is larger than |max_entries| - // for testing. - void MayPurgeForTesting(size_t max_entries, size_t purge_unit); - - private: - void MayPurge(size_t max_entries, size_t purge_unit); - - // A map for caching. This is accessed by a tuple of origin, - // url string, and NetworkIsolationKey to find a cached entry. - std::map<std::tuple<url::Origin /* origin */, - std::string /* url */, - net::NetworkIsolationKey /* NIK */>, - std::unique_ptr<PreflightResult>> - cache_; - - DISALLOW_COPY_AND_ASSIGN(PreflightCache); -}; - -} // namespace cors - -} // namespace network - -#endif // SERVICES_NETWORK_PUBLIC_CPP_CORS_PREFLIGHT_CACHE_H_ diff --git a/chromium/services/network/public/cpp/cors/preflight_cache_unittest.cc b/chromium/services/network/public/cpp/cors/preflight_cache_unittest.cc deleted file mode 100644 index 5bcbf48538b..00000000000 --- a/chromium/services/network/public/cpp/cors/preflight_cache_unittest.cc +++ /dev/null @@ -1,211 +0,0 @@ -// 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/cors/preflight_cache.h" - -#include "base/test/scoped_feature_list.h" -#include "base/test/simple_test_tick_clock.h" -#include "base/test/task_environment.h" -#include "base/time/time.h" -#include "net/base/features.h" -#include "net/http/http_request_headers.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" - -namespace network { - -namespace cors { - -namespace { - -class PreflightCacheTest : public testing::Test { - public: - PreflightCacheTest() = default; - - protected: - size_t CountEntries() const { return cache_.CountEntriesForTesting(); } - void MayPurge(size_t max_entries, size_t purge_unit) { - cache_.MayPurgeForTesting(max_entries, purge_unit); - } - PreflightCache* cache() { return &cache_; } - - std::unique_ptr<PreflightResult> CreateEntry() { - return PreflightResult::Create(mojom::CredentialsMode::kInclude, - std::string("POST"), base::nullopt, - std::string("5"), nullptr); - } - - void AppendEntry(const url::Origin& origin, - const GURL& url, - const net::NetworkIsolationKey& network_isolation_key) { - cache_.AppendEntry(origin, url, network_isolation_key, CreateEntry()); - } - - bool CheckEntryAndRefreshCache( - const url::Origin& origin, - const GURL& url, - const net::NetworkIsolationKey& network_isolation_key) { - return cache_.CheckIfRequestCanSkipPreflight( - origin, url, network_isolation_key, - network::mojom::CredentialsMode::kInclude, "POST", - net::HttpRequestHeaders(), false); - } - - void Advance(int seconds) { - clock_.Advance(base::TimeDelta::FromSeconds(seconds)); - } - - private: - // testing::Test implementation. - void SetUp() override { PreflightResult::SetTickClockForTesting(&clock_); } - void TearDown() override { PreflightResult::SetTickClockForTesting(nullptr); } - - base::test::TaskEnvironment env_; - PreflightCache cache_; - base::SimpleTestTickClock clock_; -}; - -TEST_F(PreflightCacheTest, CacheSize) { - const url::Origin origin; - const url::Origin other_origin = - url::Origin::Create(GURL("http://www.other.com:80")); - const GURL url("http://www.test.com/A"); - const GURL other_url("http://www.test.com/B"); - - EXPECT_EQ(0u, CountEntries()); - - AppendEntry(origin, url, net::NetworkIsolationKey()); - - EXPECT_EQ(1u, CountEntries()); - - AppendEntry(origin, other_url, net::NetworkIsolationKey()); - - EXPECT_EQ(2u, CountEntries()); - - AppendEntry(other_origin, url, net::NetworkIsolationKey()); - - EXPECT_EQ(3u, CountEntries()); - - // Num of entries is 3, that is not greater than the limit 3u. - // It results in doing nothing. - MayPurge(3u, 2u); - EXPECT_EQ(3u, CountEntries()); - - // Num of entries is 3, that is greater than the limit 2u. - // It results in purging entries by the specified unit 2u, thus only one entry - // remains. - MayPurge(2u, 2u); - EXPECT_EQ(1u, CountEntries()); - - // This will make the cache empty. Note that the cache expects the num of - // remaining entries should be greater than the specified purge unit. - MayPurge(0u, 1u); - EXPECT_EQ(0u, CountEntries()); -} - -TEST_F(PreflightCacheTest, CacheTimeout) { - const url::Origin origin; - const GURL url("http://www.test.com/A"); - const GURL other_url("http://www.test.com/B"); - - EXPECT_EQ(0u, CountEntries()); - - AppendEntry(origin, url, net::NetworkIsolationKey()); - AppendEntry(origin, other_url, net::NetworkIsolationKey()); - - EXPECT_EQ(2u, CountEntries()); - - // Cache entry should still be valid. - EXPECT_TRUE( - CheckEntryAndRefreshCache(origin, url, net::NetworkIsolationKey())); - - // Advance time by ten seconds. - Advance(10); - - // Cache entry should now be expired. - EXPECT_FALSE( - CheckEntryAndRefreshCache(origin, url, net::NetworkIsolationKey())); - - EXPECT_EQ(1u, CountEntries()); - - // Cache entry should be expired. - EXPECT_FALSE( - CheckEntryAndRefreshCache(origin, other_url, net::NetworkIsolationKey())); - - EXPECT_EQ(0u, CountEntries()); -} - -TEST_F(PreflightCacheTest, RespectsNetworkIsolationKeys) { - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeature( - net::features::kAppendFrameOriginToNetworkIsolationKey); - - const std::string kOriginStr1("http://www.test.com/A"); - const url::Origin kOrigin1 = url::Origin::Create(GURL(kOriginStr1)); - const net::NetworkIsolationKey kNik(kOrigin1, kOrigin1); - const GURL kUrl1(kOriginStr1); - - const GURL kUrl2("http://www.other.com:80"); - - // The cache starts empty. - EXPECT_EQ(0u, CountEntries()); - - AppendEntry(kOrigin1, kUrl1, net::NetworkIsolationKey()); - EXPECT_EQ(1u, CountEntries()); - - // This should be indistinguishable from the previous key, so it should not - // increase the size of the cache. - AppendEntry(kOrigin1, kUrl1, net::NetworkIsolationKey()); - EXPECT_EQ(1u, CountEntries()); - EXPECT_TRUE( - CheckEntryAndRefreshCache(kOrigin1, kUrl1, net::NetworkIsolationKey())); - EXPECT_FALSE(CheckEntryAndRefreshCache(kOrigin1, kUrl1, kNik)); - - // This uses a different NIK, so it should result in a new entry in the cache. - AppendEntry(kOrigin1, kUrl1, kNik); - EXPECT_EQ(2u, CountEntries()); - EXPECT_TRUE(CheckEntryAndRefreshCache(kOrigin1, kUrl1, kNik)); - - // Check that an entry we never inserted is not found in the cache. - EXPECT_FALSE(CheckEntryAndRefreshCache(kOrigin1, kUrl2, kNik)); -} - -TEST_F(PreflightCacheTest, HandlesOpaqueOrigins) { - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeature( - net::features::kAppendFrameOriginToNetworkIsolationKey); - - const url::Origin kOrigin1; - const url::Origin kOrigin2; - const net::NetworkIsolationKey kNik1(kOrigin1, kOrigin1); - const net::NetworkIsolationKey kNik2(kOrigin2, kOrigin2); - const GURL kUrl("http://www.test.com/A"); - - // The cache starts empty. - EXPECT_EQ(0u, CountEntries()); - - AppendEntry(kOrigin1, kUrl, kNik1); - EXPECT_EQ(1u, CountEntries()); - EXPECT_TRUE(CheckEntryAndRefreshCache(kOrigin1, kUrl, kNik1)); - - // The cache should report a miss if we use a new opaque origin and the same - // URL and NIK, since the nonces of the origins differ. - EXPECT_FALSE(CheckEntryAndRefreshCache(url::Origin(), kUrl, kNik1)); - - // This should be distinguishable from the previous NIK, so it should - // increase the size of the cache. - AppendEntry(kOrigin1, kUrl, kNik2); - EXPECT_EQ(2u, CountEntries()); - EXPECT_TRUE(CheckEntryAndRefreshCache(kOrigin1, kUrl, kNik2)); - EXPECT_FALSE( - CheckEntryAndRefreshCache(kOrigin1, kUrl, net::NetworkIsolationKey())); - EXPECT_FALSE(CheckEntryAndRefreshCache( - kOrigin1, kUrl, net::NetworkIsolationKey(url::Origin(), url::Origin()))); -} - -} // namespace - -} // namespace cors - -} // namespace network diff --git a/chromium/services/network/public/cpp/cors/preflight_result.cc b/chromium/services/network/public/cpp/cors/preflight_result.cc deleted file mode 100644 index b31b171b89a..00000000000 --- a/chromium/services/network/public/cpp/cors/preflight_result.cc +++ /dev/null @@ -1,212 +0,0 @@ -// 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/cors/preflight_result.h" - -#include "base/memory/ptr_util.h" -#include "base/strings/string_number_conversions.h" -#include "base/strings/string_split.h" -#include "base/strings/string_util.h" -#include "base/time/default_tick_clock.h" -#include "base/time/tick_clock.h" -#include "base/time/time.h" -#include "net/http/http_request_headers.h" -#include "net/http/http_util.h" -#include "services/network/public/cpp/cors/cors.h" - -namespace network { - -namespace cors { - -namespace { - -// Timeout values below are at the discretion of the user agent. - -// Default cache expiry time for an entry that does not have -// Access-Control-Max-Age header in its CORS-preflight response. -constexpr base::TimeDelta kDefaultTimeout = base::TimeDelta::FromSeconds(5); - -// Maximum cache expiry time. Even if a CORS-preflight response contains -// Access-Control-Max-Age header that specifies a longer expiry time, this -// maximum time is applied. -constexpr base::TimeDelta kMaxTimeout = base::TimeDelta::FromHours(2); - -// Holds TickClock instance to overwrite TimeTicks::Now() for testing. -const base::TickClock* tick_clock_for_testing = nullptr; - -base::TimeTicks Now() { - if (tick_clock_for_testing) - return tick_clock_for_testing->NowTicks(); - return base::TimeTicks::Now(); -} - -bool ParseAccessControlMaxAge(const base::Optional<std::string>& max_age, - base::TimeDelta* expiry_delta) { - DCHECK(expiry_delta); - - if (!max_age) - return false; - - uint64_t delta; - if (!base::StringToUint64(*max_age, &delta)) - return false; - - *expiry_delta = base::TimeDelta::FromSeconds(delta); - if (*expiry_delta > kMaxTimeout) - *expiry_delta = kMaxTimeout; - return true; -} - -// Parses |string| as a Access-Control-Allow-* header value, storing the result -// in |set|. This function returns false when |string| does not satisfy the -// syntax here: https://fetch.spec.whatwg.org/#http-new-header-syntax. -bool ParseAccessControlAllowList(const base::Optional<std::string>& string, - base::flat_set<std::string>* set, - bool insert_in_lower_case) { - DCHECK(set); - - if (!string) - return true; - - net::HttpUtil::ValuesIterator it(string->begin(), string->end(), ',', true); - while (it.GetNext()) { - base::StringPiece value = it.value_piece(); - if (!net::HttpUtil::IsToken(value)) { - set->clear(); - return false; - } - set->insert(insert_in_lower_case ? base::ToLowerASCII(value) - : value.as_string()); - } - return true; -} - -} // namespace - -// static -void PreflightResult::SetTickClockForTesting( - const base::TickClock* tick_clock) { - tick_clock_for_testing = tick_clock; -} - -// static -std::unique_ptr<PreflightResult> PreflightResult::Create( - const mojom::CredentialsMode credentials_mode, - 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) { - std::unique_ptr<PreflightResult> result = - base::WrapUnique(new PreflightResult(credentials_mode)); - base::Optional<mojom::CorsError> error = - result->Parse(allow_methods_header, allow_headers_header, max_age_header); - if (error) { - if (detected_error) - *detected_error = error; - return nullptr; - } - return result; -} - -PreflightResult::PreflightResult(const mojom::CredentialsMode credentials_mode) - : credentials_(credentials_mode == mojom::CredentialsMode::kInclude) {} - -PreflightResult::~PreflightResult() = default; - -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)) { - return base::nullopt; - } - - if (!credentials_ && methods_.find("*") != methods_.end()) - return base::nullopt; - - return CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - method); -} - -base::Optional<CorsErrorStatus> -PreflightResult::EnsureAllowedCrossOriginHeaders( - const net::HttpRequestHeaders& headers, - bool is_revalidating) const { - if (!credentials_ && headers_.find("*") != headers_.end()) - return base::nullopt; - - // 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( - 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 base::nullopt; -} - -bool PreflightResult::IsExpired() const { - return absolute_expiry_time_ <= Now(); -} - -bool PreflightResult::EnsureAllowedRequest( - mojom::CredentialsMode credentials_mode, - const std::string& method, - const net::HttpRequestHeaders& headers, - bool is_revalidating) const { - if (!credentials_ && credentials_mode == mojom::CredentialsMode::kInclude) { - return false; - } - - if (EnsureAllowedCrossOriginMethod(method)) - return false; - - if (EnsureAllowedCrossOriginHeaders(headers, is_revalidating)) - return false; - - return true; -} - -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) { - DCHECK(methods_.empty()); - DCHECK(headers_.empty()); - - // Keeps parsed method case for case-sensitive search. - if (!ParseAccessControlAllowList(allow_methods_header, &methods_, false)) - 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; - - base::TimeDelta expiry_delta; - if (max_age_header) { - // Set expiry_delta to 0 on invalid Access-Control-Max-Age headers so to - // invalidate the entry immediately. CORS-preflight response should be still - // usable for the request that initiates the CORS-preflight. - if (!ParseAccessControlMaxAge(max_age_header, &expiry_delta)) - expiry_delta = base::TimeDelta(); - } else { - expiry_delta = kDefaultTimeout; - } - absolute_expiry_time_ = Now() + expiry_delta; - - return base::nullopt; -} - -} // namespace cors - -} // namespace network diff --git a/chromium/services/network/public/cpp/cors/preflight_result.h b/chromium/services/network/public/cpp/cors/preflight_result.h deleted file mode 100644 index 49ef1997066..00000000000 --- a/chromium/services/network/public/cpp/cors/preflight_result.h +++ /dev/null @@ -1,104 +0,0 @@ -// 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_CORS_PREFLIGHT_RESULT_H_ -#define SERVICES_NETWORK_PUBLIC_CPP_CORS_PREFLIGHT_RESULT_H_ - -#include <memory> -#include <string> - -#include "base/component_export.h" -#include "base/containers/flat_set.h" -#include "base/optional.h" -#include "services/network/public/cpp/cors/cors_error_status.h" -#include "services/network/public/mojom/cors.mojom-shared.h" -#include "services/network/public/mojom/fetch_api.mojom-shared.h" - -namespace base { -class TickClock; -} // namespace base - -namespace net { -class HttpRequestHeaders; -} // namespace net - -namespace network { - -namespace cors { - -// Holds CORS-preflight request results, and provides access check methods. -// Each instance can be cached by CORS-preflight cache. -// See https://fetch.spec.whatwg.org/#concept-cache. -class COMPONENT_EXPORT(NETWORK_CPP) PreflightResult final { - public: - static void SetTickClockForTesting(const base::TickClock* tick_clock); - - // Creates a PreflightResult instance from a CORS-preflight result. Returns - // nullptr and |detected_error| is populated with the failed reason if the - // passed parameters contain an invalid entry, and the pointer is valid. - static std::unique_ptr<PreflightResult> Create( - const mojom::CredentialsMode credentials_mode, - 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); - ~PreflightResult(); - - // Checks if the given |method| is allowed by the CORS-preflight response. - base::Optional<CorsErrorStatus> EnsureAllowedCrossOriginMethod( - const std::string& method) const; - - // Checks if the given all |headers| are allowed by the CORS-preflight - // response. - // This does not reject when the headers contain forbidden headers - // (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( - const net::HttpRequestHeaders& headers, - bool is_revalidating) const; - - // Checks if this entry is expired. - bool IsExpired() const; - - // Checks if the given combination of |credentials_mode|, |method|, and - // |headers| is allowed by the CORS-preflight response. - // This also does not reject the forbidden headers as - // EnsureAllowCrossOriginHeaders does not. - bool EnsureAllowedRequest(mojom::CredentialsMode credentials_mode, - const std::string& method, - const net::HttpRequestHeaders& headers, - bool is_revalidating) const; - - // Refers the cache expiry time. - base::TimeTicks absolute_expiry_time() const { return absolute_expiry_time_; } - - protected: - explicit PreflightResult(const mojom::CredentialsMode credentials_mode); - - 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); - - private: - // Holds an absolute time when the result should be expired in the - // CORS-preflight cache. - base::TimeTicks absolute_expiry_time_; - - // Corresponds to the fields of the CORS-preflight cache with the same name in - // the fetch spec. - // |headers_| holds strings in lower case for case-insensitive search. - bool credentials_; - base::flat_set<std::string> methods_; - base::flat_set<std::string> headers_; - - DISALLOW_COPY_AND_ASSIGN(PreflightResult); -}; - -} // namespace cors - -} // namespace network - -#endif // SERVICES_NETWORK_PUBLIC_CPP_CORS_PREFLIGHT_RESULT_H_ diff --git a/chromium/services/network/public/cpp/cors/preflight_result_unittest.cc b/chromium/services/network/public/cpp/cors/preflight_result_unittest.cc deleted file mode 100644 index 6706561b69d..00000000000 --- a/chromium/services/network/public/cpp/cors/preflight_result_unittest.cc +++ /dev/null @@ -1,332 +0,0 @@ -// 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/cors/preflight_result.h" - -#include "base/test/simple_test_tick_clock.h" -#include "base/time/time.h" -#include "net/http/http_request_headers.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace network { - -namespace cors { - -namespace { - -using PreflightResultTest = ::testing::Test; - -constexpr base::Optional<mojom::CorsError> kNoError; - -struct TestCase { - const std::string allow_methods; - const std::string allow_headers; - const mojom::CredentialsMode cache_credentials_mode; - - const std::string request_method; - const std::string request_headers; - const mojom::CredentialsMode request_credentials_mode; - - const base::Optional<CorsErrorStatus> expected_result; -}; - -const TestCase kMethodCases[] = { - // Found in the preflight response. - {"OPTIONS", "", mojom::CredentialsMode::kOmit, "OPTIONS", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"GET", "", mojom::CredentialsMode::kOmit, "GET", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"HEAD", "", mojom::CredentialsMode::kOmit, "HEAD", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"POST", "", mojom::CredentialsMode::kOmit, "POST", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"PUT", "", mojom::CredentialsMode::kOmit, "PUT", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"DELETE", "", mojom::CredentialsMode::kOmit, "DELETE", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - // Access-Control-Allow-Methods = #method, method = token. - // So a non-standard method is accepted as well. - {"FOOBAR", "", mojom::CredentialsMode::kOmit, "FOOBAR", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - - // Found in the safe list. - {"", "", mojom::CredentialsMode::kOmit, "GET", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"", "", mojom::CredentialsMode::kOmit, "HEAD", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"", "", mojom::CredentialsMode::kOmit, "POST", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - - // By '*'. - {"*", "", mojom::CredentialsMode::kOmit, "OPTIONS", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - - // Cache allowing multiple methods. - {"GET, PUT, DELETE", "", mojom::CredentialsMode::kOmit, "GET", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"GET, PUT, DELETE", "", mojom::CredentialsMode::kOmit, "PUT", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"GET, PUT, DELETE", "", mojom::CredentialsMode::kOmit, "DELETE", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - - // Not found in the preflight response or the safe list. - {"", "", mojom::CredentialsMode::kOmit, "OPTIONS", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - "OPTIONS")}, - {"", "", mojom::CredentialsMode::kOmit, "PUT", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - "PUT")}, - {"", "", mojom::CredentialsMode::kOmit, "DELETE", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - "DELETE")}, - {"GET", "", mojom::CredentialsMode::kOmit, "PUT", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - "PUT")}, - {"GET, POST, DELETE", "", mojom::CredentialsMode::kOmit, "PUT", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - "PUT")}, - - // Empty entries in the allow_methods list are ignored. - {"GET,,PUT", "", mojom::CredentialsMode::kOmit, "", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - "")}, - {"GET, ,PUT", "", mojom::CredentialsMode::kOmit, " ", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - " ")}, - // A valid list can contain empty entries so the remaining non-empty - // entries are accepted. - {"GET, ,PUT", "", mojom::CredentialsMode::kOmit, "PUT", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - - // Request method is normalized to upper-case, but allowed methods is not. - // Comparison is in case-sensitive, that means allowed methods should be in - // upper case. - {"put", "", mojom::CredentialsMode::kOmit, "PUT", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - "PUT")}, - {"put", "", mojom::CredentialsMode::kOmit, "put", "", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kMethodDisallowedByPreflightResponse, - "put")}, - {"PUT", "", mojom::CredentialsMode::kOmit, "put", "", - mojom::CredentialsMode::kOmit, base::nullopt}, - // ... But, GET is always allowed by the safe list. - {"get", "", mojom::CredentialsMode::kOmit, "GET", "", - mojom::CredentialsMode::kOmit, base::nullopt}, -}; - -const TestCase kHeaderCases[] = { - // Found in the preflight response. - {"GET", "X-MY-HEADER", mojom::CredentialsMode::kOmit, "GET", - "X-MY-HEADER:t", mojom::CredentialsMode::kOmit, base::nullopt}, - {"GET", "X-MY-HEADER, Y-MY-HEADER", mojom::CredentialsMode::kOmit, "GET", - "X-MY-HEADER:t\r\nY-MY-HEADER:t", mojom::CredentialsMode::kOmit, - base::nullopt}, - {"GET", "x-my-header, Y-MY-HEADER", mojom::CredentialsMode::kOmit, "GET", - "X-MY-HEADER:t\r\ny-my-header:t", mojom::CredentialsMode::kOmit, - base::nullopt}, - - // Found in the safe list. - {"GET", "", mojom::CredentialsMode::kOmit, "GET", "Accept:*/*", - mojom::CredentialsMode::kOmit, base::nullopt}, - - // By '*'. - {"GET", "*", mojom::CredentialsMode::kOmit, "GET", "xyzzy:t", - mojom::CredentialsMode::kOmit, base::nullopt}, - {"GET", "*", mojom::CredentialsMode::kInclude, "GET", "xyzzy:t", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kHeaderDisallowedByPreflightResponse, - "xyzzy")}, - - // Forbidden headers can pass. - {"GET", "", mojom::CredentialsMode::kOmit, "GET", "Host: www.google.com", - mojom::CredentialsMode::kOmit, base::nullopt}, - - // Not found in the preflight response and the safe list. - {"GET", "", mojom::CredentialsMode::kOmit, "GET", "X-MY-HEADER:t", - mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kHeaderDisallowedByPreflightResponse, - "x-my-header")}, - {"GET", "X-SOME-OTHER-HEADER", mojom::CredentialsMode::kOmit, "GET", - "X-MY-HEADER:t", mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kHeaderDisallowedByPreflightResponse, - "x-my-header")}, - {"GET", "X-MY-HEADER", mojom::CredentialsMode::kOmit, "GET", - "X-MY-HEADER:t\r\nY-MY-HEADER:t", mojom::CredentialsMode::kOmit, - CorsErrorStatus(mojom::CorsError::kHeaderDisallowedByPreflightResponse, - "y-my-header")}, -}; - -TEST_F(PreflightResultTest, MaxAge) { - std::unique_ptr<base::SimpleTestTickClock> tick_clock = - std::make_unique<base::SimpleTestTickClock>(); - PreflightResult::SetTickClockForTesting(tick_clock.get()); - - std::unique_ptr<PreflightResult> result1 = - PreflightResult::Create(mojom::CredentialsMode::kOmit, base::nullopt, - base::nullopt, std::string("573"), nullptr); - EXPECT_EQ(base::TimeTicks() + base::TimeDelta::FromSeconds(573), - result1->absolute_expiry_time()); - - // Negative values are invalid. The preflight result itself can be usable, but - // should not cache such results. PreflightResult expresses it as a result - // with 'Access-Control-Max-Age: 0'. - std::unique_ptr<PreflightResult> result2 = - PreflightResult::Create(mojom::CredentialsMode::kOmit, base::nullopt, - base::nullopt, std::string("-765"), nullptr); - EXPECT_EQ(base::TimeTicks(), result2->absolute_expiry_time()); - - PreflightResult::SetTickClockForTesting(nullptr); -} - -TEST_F(PreflightResultTest, EnsureMethods) { - for (const auto& test : kMethodCases) { - std::unique_ptr<PreflightResult> result = - PreflightResult::Create(test.cache_credentials_mode, test.allow_methods, - test.allow_headers, base::nullopt, nullptr); - ASSERT_TRUE(result); - EXPECT_EQ(test.expected_result, - result->EnsureAllowedCrossOriginMethod(test.request_method)); - } -} - -TEST_F(PreflightResultTest, EnsureHeaders) { - for (const auto& test : kHeaderCases) { - std::unique_ptr<PreflightResult> result = - PreflightResult::Create(test.cache_credentials_mode, test.allow_methods, - test.allow_headers, base::nullopt, nullptr); - ASSERT_TRUE(result); - net::HttpRequestHeaders headers; - headers.AddHeadersFromString(test.request_headers); - EXPECT_EQ(test.expected_result, - result->EnsureAllowedCrossOriginHeaders(headers, false)); - } -} - -TEST_F(PreflightResultTest, EnsureRequest) { - for (const auto& test : kMethodCases) { - std::unique_ptr<PreflightResult> result = - PreflightResult::Create(test.cache_credentials_mode, test.allow_methods, - test.allow_headers, base::nullopt, nullptr); - ASSERT_TRUE(result); - net::HttpRequestHeaders headers; - if (!test.request_headers.empty()) - headers.AddHeadersFromString(test.request_headers); - EXPECT_EQ( - test.expected_result == base::nullopt, - result->EnsureAllowedRequest(test.request_credentials_mode, - test.request_method, headers, false)); - } - - for (const auto& test : kHeaderCases) { - std::unique_ptr<PreflightResult> result = - PreflightResult::Create(test.cache_credentials_mode, test.allow_methods, - test.allow_headers, base::nullopt, nullptr); - ASSERT_TRUE(result); - net::HttpRequestHeaders headers; - if (!test.request_headers.empty()) - headers.AddHeadersFromString(test.request_headers); - EXPECT_EQ( - test.expected_result == base::nullopt, - result->EnsureAllowedRequest(test.request_credentials_mode, - test.request_method, headers, false)); - } - - struct { - const mojom::CredentialsMode cache_credentials_mode; - const mojom::CredentialsMode request_credentials_mode; - const bool expected_result; - } credentials_cases[] = { - // Different credential modes. - {mojom::CredentialsMode::kInclude, mojom::CredentialsMode::kOmit, true}, - {mojom::CredentialsMode::kInclude, mojom::CredentialsMode::kInclude, - true}, - - // Credential mode mismatch. - {mojom::CredentialsMode::kOmit, mojom::CredentialsMode::kOmit, true}, - {mojom::CredentialsMode::kOmit, mojom::CredentialsMode::kInclude, false}, - }; - - for (const auto& test : credentials_cases) { - std::unique_ptr<PreflightResult> result = - PreflightResult::Create(test.cache_credentials_mode, std::string("GET"), - base::nullopt, base::nullopt, nullptr); - ASSERT_TRUE(result); - net::HttpRequestHeaders headers; - EXPECT_EQ(test.expected_result, - result->EnsureAllowedRequest(test.request_credentials_mode, "GET", - headers, false)); - } -} - -struct ParseAccessListTestCase { - const std::string input; - const std::vector<std::string> values_to_be_accepted; - const base::Optional<mojom::CorsError> strict_check_result; -}; - -const ParseAccessListTestCase kParseHeadersCases[] = { - {"bad value", {}, mojom::CorsError::kInvalidAllowHeadersPreflightResponse}, - {"X-MY-HEADER, ", {"X-MY-HEADER:t"}, kNoError}, - {"", {}, kNoError}, - {", X-MY-HEADER, Y-MY-HEADER, ,", - {"X-MY-HEADER:t", "Y-MY-HEADER:t"}, - kNoError}}; - -const ParseAccessListTestCase kParseMethodsCases[] = { - {"bad value", {}, mojom::CorsError::kInvalidAllowMethodsPreflightResponse}, - {"GET, ", {"GET"}, kNoError}, - {"", {}, kNoError}, - {", GET, POST, ,", {"GET", "POST"}, kNoError}}; - -TEST_F(PreflightResultTest, ParseAllowControlAllowHeaders) { - for (const auto& test : kParseHeadersCases) { - base::Optional<mojom::CorsError> error; - std::unique_ptr<PreflightResult> result = PreflightResult::Create( - mojom::CredentialsMode::kOmit, /*allow_methods_header=*/base::nullopt, - test.input, /*max_age_header=*/base::nullopt, &error); - EXPECT_EQ(error, test.strict_check_result); - - if (test.strict_check_result == kNoError) { - for (const auto& request_header : test.values_to_be_accepted) { - net::HttpRequestHeaders headers; - headers.AddHeadersFromString(request_header); - EXPECT_EQ(base::nullopt, - result->EnsureAllowedCrossOriginHeaders(headers, false)); - } - } - } -} - -TEST_F(PreflightResultTest, ParseAllowControlAllowMethods) { - for (const auto& test : kParseMethodsCases) { - base::Optional<mojom::CorsError> error; - std::unique_ptr<PreflightResult> result = - PreflightResult::Create(mojom::CredentialsMode::kOmit, test.input, - /*allow_headers_header=*/base::nullopt, - /*max_age_header=*/base::nullopt, &error); - EXPECT_EQ(error, test.strict_check_result); - - if (test.strict_check_result == kNoError) { - for (const auto& request_method : test.values_to_be_accepted) { - EXPECT_EQ(base::nullopt, - result->EnsureAllowedCrossOriginMethod(request_method)); - } - } - } -} - -} // namespace - -} // namespace cors - -} // namespace network diff --git a/chromium/services/network/public/cpp/cross_origin_embedder_policy_parser.cc b/chromium/services/network/public/cpp/cross_origin_embedder_policy_parser.cc index 3f3d9967616..861cbd1f388 100644 --- a/chromium/services/network/public/cpp/cross_origin_embedder_policy_parser.cc +++ b/chromium/services/network/public/cpp/cross_origin_embedder_policy_parser.cc @@ -10,7 +10,6 @@ #include "base/strings/string_piece.h" #include "net/http/http_response_headers.h" #include "net/http/structured_headers.h" -#include "services/network/public/cpp/features.h" namespace network { @@ -45,9 +44,6 @@ Parse(base::StringPiece header_value) { CrossOriginEmbedderPolicy ParseCrossOriginEmbedderPolicy( const net::HttpResponseHeaders& headers) { CrossOriginEmbedderPolicy coep; - if (!base::FeatureList::IsEnabled(features::kCrossOriginEmbedderPolicy)) - return coep; - std::string header_value; if (headers.GetNormalizedHeader(kHeaderName, &header_value)) { std::tie(coep.value, coep.reporting_endpoint) = Parse(header_value); diff --git a/chromium/services/network/public/cpp/cross_origin_embedder_policy_parser_unittest.cc b/chromium/services/network/public/cpp/cross_origin_embedder_policy_parser_unittest.cc index c3fb940ef20..938a542f193 100644 --- a/chromium/services/network/public/cpp/cross_origin_embedder_policy_parser_unittest.cc +++ b/chromium/services/network/public/cpp/cross_origin_embedder_policy_parser_unittest.cc @@ -10,16 +10,11 @@ #include "base/optional.h" #include "base/test/scoped_feature_list.h" #include "net/http/http_response_headers.h" -#include "services/network/public/cpp/features.h" #include "testing/gtest/include/gtest/gtest.h" namespace network { TEST(CrossOriginEmbedderPolicyTest, Parse) { - base::test::ScopedFeatureList scoped_feature_list; - scoped_feature_list.InitAndEnableFeature( - features::kCrossOriginEmbedderPolicy); - constexpr auto kNone = mojom::CrossOriginEmbedderPolicyValue::kNone; constexpr auto kRequireCorp = mojom::CrossOriginEmbedderPolicyValue::kRequireCorp; diff --git a/chromium/services/network/public/cpp/cross_origin_read_blocking.cc b/chromium/services/network/public/cpp/cross_origin_read_blocking.cc index a1a3b54a42e..19956a10208 100644 --- a/chromium/services/network/public/cpp/cross_origin_read_blocking.cc +++ b/chromium/services/network/public/cpp/cross_origin_read_blocking.cc @@ -14,13 +14,13 @@ #include "base/check_op.h" #include "base/command_line.h" -#include "base/containers/flat_set.h" +#include "base/containers/contains.h" +#include "base/containers/fixed_flat_set.h" #include "base/feature_list.h" #include "base/lazy_instance.h" #include "base/metrics/histogram_macros.h" #include "base/no_destructor.h" #include "base/notreached.h" -#include "base/stl_util.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "net/base/mime_sniffer.h" @@ -248,12 +248,14 @@ void BlockResponseHeaders( // disclosure of "application/zip" even though Chrome doesn't have built-in // support for this resource type. And CORB also wants to protect // "application/pdf" even though Chrome happens to support this resource type. -base::flat_set<std::string>& GetNeverSniffedMimeTypes() { - static base::NoDestructor<base::flat_set<std::string>> s_types{{ +const auto& GetNeverSniffedMimeTypes() { + static constexpr auto kNeverSniffedMimeTypes = base::MakeFixedFlatSet< + base::StringPiece>({ + // clang-format off // The types below (zip, protobuf, etc.) are based on most commonly used // content types according to HTTP Archive - see: // https://github.com/whatwg/fetch/issues/860#issuecomment-457330454 - "application/gzip", + "application/gzip", "application/x-gzip", "application/x-protobuf", "application/zip", @@ -308,20 +310,33 @@ base::flat_set<std::string>& GetNeverSniffedMimeTypes() { "multipart/byteranges", // TODO(lukasza): https://crbug.com/802836#c11: Add // application/signed-exchange. - }}; + // clang-format on + }); // All items need to be lower-case, to support case-insensitive comparisons // later. - DCHECK(std::all_of( - s_types->begin(), s_types->end(), - [](const std::string& s) { return s == base::ToLowerASCII(s); })); + DCHECK(std::all_of(kNeverSniffedMimeTypes.begin(), + kNeverSniffedMimeTypes.end(), + [](const auto& s) { return s == base::ToLowerASCII(s); })); - return *s_types; + return kNeverSniffedMimeTypes; } } // namespace // static +bool CrossOriginReadBlocking::IsJavascriptMimeType( + base::StringPiece mime_type) { + constexpr auto kCaseInsensitive = base::CompareCase::INSENSITIVE_ASCII; + for (const std::string& suffix : kJavaScriptSuffixes) { + if (base::EndsWith(mime_type, suffix, kCaseInsensitive)) + return true; + } + + return false; +} + +// static MimeType CrossOriginReadBlocking::GetCanonicalMimeType( base::StringPiece mime_type) { // Checking for image/svg+xml and application/dash+xml early ensures that they @@ -482,10 +497,6 @@ SniffingResult CrossOriginReadBlocking::SniffForJSON(base::StringPiece data) { // Whitespace is ignored (outside of string literals) if (c == ' ' || c == '\t' || c == '\r' || c == '\n') continue; - } else { - // Inside string literals, control characters should result in rejection. - if ((c >= 0 && c < 32) || c == 127) - return kNo; } switch (state) { @@ -789,9 +800,13 @@ CrossOriginReadBlocking::ResponseAnalyzer::ShouldBlockBasedOnHeaders( // Requests from foo.example.com will consult foo.example.com's service worker // first (if one has been registered). The service worker can handle requests // initiated by foo.example.com even if they are cross-origin (e.g. requests - // for bar.example.com). This is okay and should not be blocked by CORB, - // unless the initiator opted out of CORS / opted into receiving an opaque - // response. See also https://crbug.com/803672. + // for bar.example.com). This is okay, because there is no security boundary + // between foo.example.com and the service worker of foo.example.com + because + // the response data is "conjured" within the service worker of + // foo.example.com (rather than being fetched from bar.example.com). + // Therefore such responses should not be blocked by CORB, unless the + // initiator opted out of CORS / opted into receiving an opaque response. See + // also https://crbug.com/803672. if (response.was_fetched_via_service_worker) { switch (response.response_type) { case network::mojom::FetchResponseType::kBasic: @@ -841,7 +856,8 @@ CrossOriginReadBlocking::ResponseAnalyzer::ShouldBlockBasedOnHeaders( request_url, request_url, request_initiator, response, kOverreachingRequestMode, request_initiator_origin_lock, network::mojom::RequestDestination::kEmpty, - CrossOriginEmbedderPolicy())) { + CrossOriginEmbedderPolicy(), + /*reporter=*/nullptr)) { // Ignore mime types and/or sniffing and have CORB block all responses with // COR*P* header. return kBlock; @@ -902,17 +918,6 @@ CrossOriginReadBlocking::ResponseAnalyzer::ShouldBlockBasedOnHeaders( } // static -bool CrossOriginReadBlocking::ResponseAnalyzer::HasNoSniff( - const network::mojom::URLResponseHead& response) { - if (!response.headers) - return false; - std::string nosniff_header; - response.headers->GetNormalizedHeader("x-content-type-options", - &nosniff_header); - return base::LowerCaseEqualsASCII(nosniff_header, "nosniff"); -} - -// static bool CrossOriginReadBlocking::ResponseAnalyzer::SeemsSensitiveFromCORSHeuristic( const network::mojom::URLResponseHead& response) { // Check if the response has an Access-Control-Allow-Origin with a value other @@ -984,15 +989,13 @@ CrossOriginReadBlocking::ResponseAnalyzer::GetMimeTypeBucket( // Javascript is assumed public. See also // https://mimesniff.spec.whatwg.org/#javascript-mime-type. - constexpr auto kCaseInsensitive = base::CompareCase::INSENSITIVE_ASCII; - for (const std::string& suffix : kJavaScriptSuffixes) { - if (base::EndsWith(mime_type, suffix, kCaseInsensitive)) { - return kPublic; - } + if (IsJavascriptMimeType(mime_type)) { + return kPublic; } // Images are assumed public. See also // https://mimesniff.spec.whatwg.org/#image-mime-type. + constexpr auto kCaseInsensitive = base::CompareCase::INSENSITIVE_ASCII; if (base::StartsWith(mime_type, "image", kCaseInsensitive)) { return kPublic; } @@ -1187,6 +1190,17 @@ void CrossOriginReadBlocking::ResponseAnalyzer::LogBlockedResponse() { } // static +bool CrossOriginReadBlocking::ResponseAnalyzer::HasNoSniff( + const network::mojom::URLResponseHead& response) { + if (!response.headers) + return false; + std::string nosniff_header; + response.headers->GetNormalizedHeader("x-content-type-options", + &nosniff_header); + return base::LowerCaseEqualsASCII(nosniff_header, "nosniff"); +} + +// static CrossOriginReadBlocking::ResponseAnalyzer::CrossOriginProtectionDecision CrossOriginReadBlocking::ResponseAnalyzer::BlockingDecisionToProtectionDecision( BlockingDecision blocking_decision) { diff --git a/chromium/services/network/public/cpp/cross_origin_read_blocking.h b/chromium/services/network/public/cpp/cross_origin_read_blocking.h index feeac20861b..00543aceb21 100644 --- a/chromium/services/network/public/cpp/cross_origin_read_blocking.h +++ b/chromium/services/network/public/cpp/cross_origin_read_blocking.h @@ -35,6 +35,9 @@ namespace network { class COMPONENT_EXPORT(NETWORK_CPP) CrossOriginReadBlocking { public: + // Not instantiable - only static methods. + CrossOriginReadBlocking() = delete; + // This enum describes how CORB should decide whether to block a given // no-cors, cross-origin response. // @@ -151,6 +154,9 @@ class COMPONENT_EXPORT(NETWORK_CPP) CrossOriginReadBlocking { void LogAllowedResponse(); void LogBlockedResponse(); + // Returns true if the response has a nosniff header. + static bool HasNoSniff(const network::mojom::URLResponseHead& response); + private: FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest, SeemsSensitiveFromCORSHeuristic); @@ -184,9 +190,6 @@ class COMPONENT_EXPORT(NETWORK_CPP) CrossOriginReadBlocking { const base::Optional<url::Origin>& request_initiator_origin_lock, MimeType canonical_mime_type); - // Returns true if the response has a nosniff header. - static bool HasNoSniff(const network::mojom::URLResponseHead& response); - // Checks if the response seems sensitive for CORB protection logging. // Returns true if the Access-Control-Allow-Origin header has a value other // than *. @@ -307,15 +310,16 @@ class COMPONENT_EXPORT(NETWORK_CPP) CrossOriginReadBlocking { kYes, }; - private: - CrossOriginReadBlocking(); // Not instantiable. + // Returns whether `mime_type` is a Javascript MIME type based on + // https://mimesniff.spec.whatwg.org/#javascript-mime-type + static bool IsJavascriptMimeType(base::StringPiece mime_type); // Returns the representative mime type enum value of the mime type of // response. For example, this returns the same value for all text/xml mime // type families such as application/xml, application/rss+xml. static MimeType GetCanonicalMimeType(base::StringPiece mime_type); - FRIEND_TEST_ALL_PREFIXES(CrossOriginReadBlockingTest, GetCanonicalMimeType); + private: // Returns whether this scheme is a target of the cross-origin read blocking // (CORB) policy. This returns true only for http://* and https://* urls. static bool IsBlockableScheme(const GURL& frame_origin); diff --git a/chromium/services/network/public/cpp/cross_origin_read_blocking_unittest.cc b/chromium/services/network/public/cpp/cross_origin_read_blocking_unittest.cc index 3f9f0b5f43f..c900543c0d8 100644 --- a/chromium/services/network/public/cpp/cross_origin_read_blocking_unittest.cc +++ b/chromium/services/network/public/cpp/cross_origin_read_blocking_unittest.cc @@ -366,11 +366,9 @@ const TestScenario kScenarios[] = { }, { // Blocked, because the unit test doesn't make a call to - // CrossOriginReadBlocking::AddExceptionForFlash (simulating a behavior - // of a compromised renderer that only pretends to be hosting Flash). - // - // The regular scenario is covered by integration tests: - // OutOfProcessPPAPITest.URLLoaderTrusted. + // NetworkService::AddAllowedRequestInitiatorForPlugin (simulating a + // behavior of a compromised renderer that only pretends to be hosting + // PDF). "Blocked: Cross-site fetch HTML from Flash without CORS", __LINE__, "http://www.b.com/plugin.html", // target_url @@ -2567,9 +2565,10 @@ TEST(CrossOriginReadBlockingTest, SniffForJSON) { EXPECT_EQ(SniffingResult::kMaybe, CrossOriginReadBlocking::SniffForJSON("{\"\\\"")) << "Incomplete escape results in maybe"; - EXPECT_EQ(SniffingResult::kNo, + EXPECT_EQ(SniffingResult::kYes, CrossOriginReadBlocking::SniffForJSON("{\"\n\" : true}")) - << "Unescaped control characters are rejected"; + << "Unescaped control characters are accepted (a bit more like " + << "Javascript than strict reading of the JSON spec)"; EXPECT_EQ(SniffingResult::kNo, CrossOriginReadBlocking::SniffForJSON("{}")) << "Empty dictionary is not recognized (since it's valid JS too)"; EXPECT_EQ(SniffingResult::kNo, diff --git a/chromium/services/network/public/cpp/cross_origin_resource_policy.cc b/chromium/services/network/public/cpp/cross_origin_resource_policy.cc index b24addd5a5e..74a96f33f3c 100644 --- a/chromium/services/network/public/cpp/cross_origin_resource_policy.cc +++ b/chromium/services/network/public/cpp/cross_origin_resource_policy.cc @@ -6,12 +6,10 @@ #include <string> -#include "base/feature_list.h" #include "base/metrics/histogram_macros.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/http/http_response_headers.h" #include "services/network/public/cpp/cross_origin_embedder_policy.h" -#include "services/network/public/cpp/features.h" #include "services/network/public/cpp/initiator_lock_compatibility.h" #include "services/network/public/cpp/url_loader_completion_status.h" #include "services/network/public/mojom/cross_origin_embedder_policy.mojom.h" @@ -74,10 +72,8 @@ CrossOriginResourcePolicy::ParsedHeader ParseHeaderByString( if (header_value == "same-site") return CrossOriginResourcePolicy::kSameSite; - if (base::FeatureList::IsEnabled(features::kCrossOriginEmbedderPolicy) && - header_value == "cross-origin") { + if (header_value == "cross-origin") return CrossOriginResourcePolicy::kCrossOrigin; - } // TODO(lukasza): Once https://github.com/whatwg/fetch/issues/760 gets // resolved, add support for parsing specific origins. @@ -162,7 +158,6 @@ base::Optional<mojom::BlockedByResponseReason> IsBlockedInternal( if ((policy == CrossOriginResourcePolicy::kNoHeader || policy == CrossOriginResourcePolicy::kParsingError) && embedder_policy == mojom::CrossOriginEmbedderPolicyValue::kRequireCorp) { - DCHECK(base::FeatureList::IsEnabled(features::kCrossOriginEmbedderPolicy)); policy = CrossOriginResourcePolicy::kSameOrigin; upgrade_to_same_origin = true; } diff --git a/chromium/services/network/public/cpp/cross_origin_resource_policy.h b/chromium/services/network/public/cpp/cross_origin_resource_policy.h index 22c08b380a3..d01e65f25cb 100644 --- a/chromium/services/network/public/cpp/cross_origin_resource_policy.h +++ b/chromium/services/network/public/cpp/cross_origin_resource_policy.h @@ -48,8 +48,7 @@ class COMPONENT_EXPORT(NETWORK_CPP) CrossOriginResourcePolicy { base::Optional<url::Origin> request_initiator_origin_lock, mojom::RequestDestination request_destination, const CrossOriginEmbedderPolicy& embedder_policy, - mojom::CrossOriginEmbedderPolicyReporter* reporter = nullptr) - WARN_UNUSED_RESULT; + mojom::CrossOriginEmbedderPolicyReporter* reporter) WARN_UNUSED_RESULT; // Same as IsBlocked(), but this method can take a raw value of // Cross-Origin-Resource-Policy header instead of using a URLResponseHead. @@ -62,8 +61,7 @@ class COMPONENT_EXPORT(NETWORK_CPP) CrossOriginResourcePolicy { base::Optional<url::Origin> request_initiator_origin_lock, mojom::RequestDestination request_destination, const CrossOriginEmbedderPolicy& embedder_policy, - mojom::CrossOriginEmbedderPolicyReporter* reporter = nullptr) - WARN_UNUSED_RESULT; + mojom::CrossOriginEmbedderPolicyReporter* reporter) WARN_UNUSED_RESULT; // The CORP check for navigation requests. This is expected to be called // from the navigation algorithm. @@ -75,7 +73,7 @@ class COMPONENT_EXPORT(NETWORK_CPP) CrossOriginResourcePolicy { base::Optional<url::Origin> request_initiator_origin_lock, mojom::RequestDestination request_destination, const CrossOriginEmbedderPolicy& embedder_policy, - mojom::CrossOriginEmbedderPolicyReporter* reporter = nullptr); + mojom::CrossOriginEmbedderPolicyReporter* reporter); // Parsing of the Cross-Origin-Resource-Policy http response header. enum ParsedHeader { diff --git a/chromium/services/network/public/cpp/cross_origin_resource_policy_unittest.cc b/chromium/services/network/public/cpp/cross_origin_resource_policy_unittest.cc index 5d6a99818e3..bc7014dcdda 100644 --- a/chromium/services/network/public/cpp/cross_origin_resource_policy_unittest.cc +++ b/chromium/services/network/public/cpp/cross_origin_resource_policy_unittest.cc @@ -6,11 +6,9 @@ #include <vector> #include "base/memory/ref_counted.h" -#include "base/test/scoped_feature_list.h" #include "net/http/http_response_headers.h" #include "net/http/http_util.h" #include "services/network/public/cpp/cross_origin_resource_policy.h" -#include "services/network/public/cpp/features.h" #include "testing/gtest/include/gtest/gtest.h" namespace network { @@ -111,19 +109,10 @@ TEST(CrossOriginResourcePolicyTest, ParseHeader) { } TEST(CrossOriginResourcePolicyTest, CrossSiteHeaderWithCOEP) { - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeature(features::kCrossOriginEmbedderPolicy); EXPECT_EQ(CrossOriginResourcePolicy::kCrossOrigin, ParseHeader("Cross-Origin-Resource-Policy: cross-origin")); } -TEST(CrossOriginResourcePolicyTest, CrossSiteHeaderWithoutCOEP) { - base::test::ScopedFeatureList feature_list; - feature_list.InitAndDisableFeature(features::kCrossOriginEmbedderPolicy); - EXPECT_EQ(CrossOriginResourcePolicy::kParsingError, - ParseHeader("Cross-Origin-Resource-Policy: cross-origin")); -} - bool ShouldAllowSameSite(const std::string& initiator, const std::string& target) { return CrossOriginResourcePolicy::ShouldAllowSameSiteForTesting( @@ -166,9 +155,6 @@ TEST(CrossOriginResourcePolicyTest, ShouldAllowSameSite) { } TEST(CrossOriginResourcePolicyTest, WithCOEP) { - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeature(features::kCrossOriginEmbedderPolicy); - mojom::URLResponseHead corp_none; mojom::URLResponseHead corp_same_origin; mojom::URLResponseHead corp_cross_origin; @@ -309,9 +295,6 @@ TEST(CrossOriginResourcePolicyTest, WithCOEP) { } TEST(CrossOriginResourcePolicyTest, NavigationWithCOEP) { - base::test::ScopedFeatureList feature_list; - feature_list.InitAndEnableFeature(features::kCrossOriginEmbedderPolicy); - mojom::URLResponseHead corp_none; mojom::URLResponseHead corp_same_origin; mojom::URLResponseHead corp_cross_origin; diff --git a/chromium/services/network/public/cpp/data_element.cc b/chromium/services/network/public/cpp/data_element.cc index 32879fefe7a..361eb95c9dd 100644 --- a/chromium/services/network/public/cpp/data_element.cc +++ b/chromium/services/network/public/cpp/data_element.cc @@ -16,63 +16,35 @@ namespace network { -const uint64_t DataElement::kUnknownSize; - -DataElement::DataElement() - : type_(mojom::DataElementType::kUnknown), - offset_(0), - length_(std::numeric_limits<uint64_t>::max()) {} - -DataElement::~DataElement() = default; - -DataElement::DataElement(DataElement&& other) = default; -DataElement& DataElement::operator=(DataElement&& other) = default; - -void DataElement::SetToFilePathRange( - const base::FilePath& path, - uint64_t offset, - uint64_t length, - const base::Time& expected_modification_time) { - type_ = mojom::DataElementType::kFile; - path_ = path; - offset_ = offset; - length_ = length; - expected_modification_time_ = expected_modification_time; -} - -void DataElement::SetToDataPipe( - mojo::PendingRemote<mojom::DataPipeGetter> data_pipe_getter) { - DCHECK(data_pipe_getter); - type_ = mojom::DataElementType::kDataPipe; - data_pipe_getter_ = std::move(data_pipe_getter); -} - -void DataElement::SetToChunkedDataPipe( - mojo::PendingRemote<mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter) { - type_ = mojom::DataElementType::kChunkedDataPipe; - chunked_data_pipe_getter_ = std::move(chunked_data_pipe_getter); -} - -void DataElement::SetToReadOnceStream( - mojo::PendingRemote<mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter) { - type_ = mojom::DataElementType::kReadOnceStream; - chunked_data_pipe_getter_ = std::move(chunked_data_pipe_getter); +DataElementBytes::DataElementBytes() = default; +DataElementBytes::DataElementBytes(std::vector<uint8_t> bytes) + : bytes_(std::move(bytes)) {} +DataElementBytes::DataElementBytes(DataElementBytes&& other) = default; +DataElementBytes& DataElementBytes::operator=(DataElementBytes&& other) = + default; +DataElementBytes::~DataElementBytes() = default; + +DataElementDataPipe::DataElementDataPipe() = default; +DataElementDataPipe::DataElementDataPipe( + mojo::PendingRemote<mojom::DataPipeGetter> data_pipe_getter) + : data_pipe_getter_(std::move(data_pipe_getter)) { + DCHECK(data_pipe_getter_); } +DataElementDataPipe::DataElementDataPipe(DataElementDataPipe&&) = default; +DataElementDataPipe& DataElementDataPipe::operator=( + DataElementDataPipe&& other) = default; +DataElementDataPipe::~DataElementDataPipe() = default; mojo::PendingRemote<mojom::DataPipeGetter> -DataElement::ReleaseDataPipeGetter() { - DCHECK_EQ(mojom::DataElementType::kDataPipe, type_); +DataElementDataPipe::ReleaseDataPipeGetter() { DCHECK(data_pipe_getter_.is_valid()); return std::move(data_pipe_getter_); } -mojo::PendingRemote<mojom::DataPipeGetter> DataElement::CloneDataPipeGetter() - const { - DCHECK_EQ(mojom::DataElementType::kDataPipe, type_); +mojo::PendingRemote<mojom::DataPipeGetter> +DataElementDataPipe::CloneDataPipeGetter() const { DCHECK(data_pipe_getter_.is_valid()); - auto* mutable_this = const_cast<DataElement*>(this); + auto* mutable_this = const_cast<DataElementDataPipe*>(this); mojo::Remote<mojom::DataPipeGetter> owned( std::move(mutable_this->data_pipe_getter_)); mojo::PendingRemote<mojom::DataPipeGetter> clone; @@ -81,80 +53,42 @@ mojo::PendingRemote<mojom::DataPipeGetter> DataElement::CloneDataPipeGetter() return clone; } -const mojo::PendingRemote<mojom::ChunkedDataPipeGetter>& -DataElement::chunked_data_pipe_getter() const { - DCHECK(type_ == mojom::DataElementType::kChunkedDataPipe || - type_ == mojom::DataElementType::kReadOnceStream); - return chunked_data_pipe_getter_; +DataElementChunkedDataPipe::DataElementChunkedDataPipe() = default; +DataElementChunkedDataPipe::DataElementChunkedDataPipe( + mojo::PendingRemote<mojom::ChunkedDataPipeGetter> chunked_data_pipe_getter, + ReadOnlyOnce read_only_once) + : chunked_data_pipe_getter_(std::move(chunked_data_pipe_getter)), + read_only_once_(read_only_once) { + DCHECK(chunked_data_pipe_getter_); } +DataElementChunkedDataPipe::DataElementChunkedDataPipe( + DataElementChunkedDataPipe&& other) = default; +DataElementChunkedDataPipe& DataElementChunkedDataPipe::operator=( + DataElementChunkedDataPipe&& other) = default; +DataElementChunkedDataPipe::~DataElementChunkedDataPipe() = default; mojo::PendingRemote<mojom::ChunkedDataPipeGetter> -DataElement::ReleaseChunkedDataPipeGetter() { - DCHECK(type_ == mojom::DataElementType::kChunkedDataPipe || - type_ == mojom::DataElementType::kReadOnceStream) - << type_; +DataElementChunkedDataPipe::ReleaseChunkedDataPipeGetter() { + DCHECK(chunked_data_pipe_getter_.is_valid()); return std::move(chunked_data_pipe_getter_); } -void PrintTo(const DataElement& x, std::ostream* os) { - const uint64_t kMaxDataPrintLength = 40; - *os << "<DataElement>{type: "; - switch (x.type()) { - case mojom::DataElementType::kBytes: { - uint64_t length = std::min(x.length(), kMaxDataPrintLength); - *os << "TYPE_BYTES, data: [" - << base::HexEncode(x.bytes(), static_cast<size_t>(length)); - if (length < x.length()) { - *os << "<...truncated due to length...>"; - } - *os << "]"; - break; - } - case mojom::DataElementType::kFile: - *os << "TYPE_FILE, path: " << x.path().AsUTF8Unsafe() - << ", expected_modification_time: " << x.expected_modification_time(); - break; - case mojom::DataElementType::kDataPipe: - *os << "TYPE_DATA_PIPE"; - break; - case mojom::DataElementType::kChunkedDataPipe: - *os << "TYPE_CHUNKED_DATA_PIPE"; - break; - case mojom::DataElementType::kReadOnceStream: - *os << "TYPE_READ_ONCE_STREAM"; - break; - case mojom::DataElementType::kUnknown: - *os << "TYPE_UNKNOWN"; - break; - } - *os << ", length: " << x.length() << ", offset: " << x.offset() << "}"; -} - -bool operator==(const DataElement& a, const DataElement& b) { - if (a.type() != b.type() || a.offset() != b.offset() || - a.length() != b.length()) - return false; - switch (a.type()) { - case mojom::DataElementType::kBytes: - return memcmp(a.bytes(), b.bytes(), b.length()) == 0; - case mojom::DataElementType::kFile: - return a.path() == b.path() && - a.expected_modification_time() == b.expected_modification_time(); - case mojom::DataElementType::kDataPipe: - return false; - case mojom::DataElementType::kChunkedDataPipe: - return false; - case mojom::DataElementType::kReadOnceStream: - return false; - case mojom::DataElementType::kUnknown: - NOTREACHED(); - return false; - } - return false; -} - -bool operator!=(const DataElement& a, const DataElement& b) { - return !(a == b); -} +DataElementFile::DataElementFile() = default; +DataElementFile::DataElementFile(const base::FilePath& path, + uint64_t offset, + uint64_t length, + base::Time expected_modification_time) + : path_(path), + offset_(offset), + length_(length), + expected_modification_time_(expected_modification_time) {} +DataElementFile::DataElementFile(DataElementFile&&) = default; +DataElementFile& DataElementFile::operator=(DataElementFile&&) = default; +DataElementFile::~DataElementFile() = default; + +DataElement::DataElement() = default; +DataElement::DataElement(DataElement&& other) = default; +DataElement& DataElement::operator=(DataElement&& other) = default; +DataElement::~DataElement() = default; } // namespace network diff --git a/chromium/services/network/public/cpp/data_element.h b/chromium/services/network/public/cpp/data_element.h index 7eeb277b4ea..84e1859aa61 100644 --- a/chromium/services/network/public/cpp/data_element.h +++ b/chromium/services/network/public/cpp/data_element.h @@ -10,151 +10,188 @@ #include <limits> #include <memory> -#include <ostream> -#include <string> +#include <utility> #include <vector> #include "base/check_op.h" #include "base/component_export.h" #include "base/files/file_path.h" -#include "base/gtest_prod_util.h" +#include "base/strings/string_piece.h" #include "base/time/time.h" -#include "mojo/public/cpp/bindings/enum_traits.h" +#include "base/types/strong_alias.h" #include "mojo/public/cpp/bindings/pending_remote.h" -#include "mojo/public/cpp/system/data_pipe.h" #include "services/network/public/mojom/chunked_data_pipe_getter.mojom-forward.h" #include "services/network/public/mojom/data_pipe_getter.mojom-forward.h" #include "services/network/public/mojom/url_loader.mojom-shared.h" -#include "url/gurl.h" - -namespace blink { -namespace mojom { -class FetchAPIDataElementDataView; -} // namespace mojom -} // namespace blink +#include "third_party/abseil-cpp/absl/types/variant.h" namespace network { -// Represents part of an upload body. This could be one of raw bytes, file data, -// or a mojo pipe that streams data. -class COMPONENT_EXPORT(NETWORK_CPP_BASE) DataElement { +// Represents a part of a request body consisting of bytes. +class COMPONENT_EXPORT(NETWORK_CPP_BASE) DataElementBytes final { public: - static const uint64_t kUnknownSize = std::numeric_limits<uint64_t>::max(); - - DataElement(); - ~DataElement(); - - DataElement(const DataElement&) = delete; - void operator=(const DataElement&) = delete; - DataElement(DataElement&& other); - DataElement& operator=(DataElement&& other); - - mojom::DataElementType type() const { return type_; } - const char* bytes() const { - return reinterpret_cast<const char*>(buf_.data()); - } - const base::FilePath& path() const { return path_; } - uint64_t offset() const { return offset_; } - uint64_t length() const { return length_; } - const base::Time& expected_modification_time() const { - return expected_modification_time_; - } + // Do NOT use this constructor outside of mojo deserialization context. + DataElementBytes(); - // Sets TYPE_BYTES data. This copies the given data into the element. - void SetToBytes(const char* bytes, int bytes_len) { - type_ = mojom::DataElementType::kBytes; - buf_.assign(reinterpret_cast<const uint8_t*>(bytes), - reinterpret_cast<const uint8_t*>(bytes + bytes_len)); - length_ = buf_.size(); - } + explicit DataElementBytes(std::vector<uint8_t> bytes); + DataElementBytes(const DataElementBytes&) = delete; + DataElementBytes(DataElementBytes&& other); + DataElementBytes& operator=(const DataElementBytes&) = delete; + DataElementBytes& operator=(DataElementBytes&& other); + ~DataElementBytes(); - // Sets TYPE_BYTES data. This moves the given data vector into the element. - void SetToBytes(std::vector<uint8_t> bytes) { - type_ = mojom::DataElementType::kBytes; - buf_ = std::move(bytes); - length_ = buf_.size(); - } + const std::vector<uint8_t>& bytes() const { return bytes_; } - // Sets TYPE_BYTES data, and clears the internal bytes buffer. - // For use with AppendBytes. - void SetToEmptyBytes() { - type_ = mojom::DataElementType::kBytes; - buf_.clear(); - length_ = 0; + base::StringPiece AsStringPiece() const { + return base::StringPiece(reinterpret_cast<const char*>(bytes_.data()), + bytes_.size()); } - // Copies and appends the given data into the element. SetToEmptyBytes or - // SetToBytes must be called before this method. - void AppendBytes(const char* bytes, int bytes_len) { - DCHECK_EQ(type_, mojom::DataElementType::kBytes); - DCHECK_NE(length_, std::numeric_limits<uint64_t>::max()); - buf_.insert(buf_.end(), reinterpret_cast<const uint8_t*>(bytes), - reinterpret_cast<const uint8_t*>(bytes + bytes_len)); - length_ = buf_.size(); - } + private: + std::vector<uint8_t> bytes_; +}; - // Sets TYPE_FILE data with range. - void SetToFilePathRange(const base::FilePath& path, - uint64_t offset, - uint64_t length, - const base::Time& expected_modification_time); +// Represents a part of a request body consisting of a data pipe. This is +// typically used for blobs. +class COMPONENT_EXPORT(NETWORK_CPP_BASE) DataElementDataPipe final { + public: + // Do NOT use this constructor outside of mojo deserialization context. + DataElementDataPipe(); - // Sets TYPE_DATA_PIPE data. The data pipe consumer can safely wait for the - // callback passed to Read() to be invoked before reading the request body. - void SetToDataPipe( + explicit DataElementDataPipe( mojo::PendingRemote<mojom::DataPipeGetter> data_pipe_getter); + DataElementDataPipe(const DataElementDataPipe&) = delete; + DataElementDataPipe(DataElementDataPipe&& other); + DataElementDataPipe& operator=(const DataElementDataPipe&) = delete; + DataElementDataPipe& operator=(DataElementDataPipe&& other); + ~DataElementDataPipe(); - // Sets TYPE_CHUNKED_DATA_PIPE data. The data pipe consumer must not wait - // for the callback passed to GetSize() to be invoked before reading the - // request body, as the length may not be known until the entire body has been - // sent. This method triggers a chunked upload, which not all servers may - // support, so SetToDataPipe should be used instead, unless talking with a - // server known to support chunked uploads. - void SetToChunkedDataPipe(mojo::PendingRemote<mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter); - // Almost same as above except |chunked_data_pipe_getter| is read only once - // and you must talk with a server supporting chunked upload. - void SetToReadOnceStream(mojo::PendingRemote<mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter); - - // Takes ownership of the DataPipeGetter, if this is of TYPE_DATA_PIPE. mojo::PendingRemote<mojom::DataPipeGetter> ReleaseDataPipeGetter(); mojo::PendingRemote<mojom::DataPipeGetter> CloneDataPipeGetter() const; - // Can be called only when this is of type kChunkedDataPipe or - // kReadOnceStream. + private: + mojo::PendingRemote<mojom::DataPipeGetter> data_pipe_getter_; +}; + +// Represents a part of a request body consisting of a data pipe without a +// known size. +class COMPONENT_EXPORT(NETWORK_CPP_BASE) DataElementChunkedDataPipe final { + public: + using ReadOnlyOnce = base::StrongAlias<class ReadOnlyOnceTag, bool>; + + // Do NOT use this constructor outside of mojo deserialization context. + DataElementChunkedDataPipe(); + + DataElementChunkedDataPipe( + mojo::PendingRemote<mojom::ChunkedDataPipeGetter> data_pipe_getter, + ReadOnlyOnce read_only_once); + DataElementChunkedDataPipe(const DataElementChunkedDataPipe&) = delete; + DataElementChunkedDataPipe(DataElementChunkedDataPipe&& other); + DataElementChunkedDataPipe& operator=(const DataElementChunkedDataPipe&) = + delete; + DataElementChunkedDataPipe& operator=(DataElementChunkedDataPipe&& other); + ~DataElementChunkedDataPipe(); + const mojo::PendingRemote<mojom::ChunkedDataPipeGetter>& - chunked_data_pipe_getter() const; - // Takes ownership of the DataPipeGetter, if this is of - // kChunkedDataPipe or kReadOnceStream. + chunked_data_pipe_getter() const { + return chunked_data_pipe_getter_; + } mojo::PendingRemote<mojom::ChunkedDataPipeGetter> ReleaseChunkedDataPipeGetter(); + ReadOnlyOnce read_only_once() const { return read_only_once_; } + private: - FRIEND_TEST_ALL_PREFIXES(BlobAsyncTransportStrategyTest, TestInvalidParams); - friend void PrintTo(const DataElement& x, ::std::ostream* os); - friend struct mojo::StructTraits<network::mojom::DataElementDataView, - network::DataElement>; - friend struct mojo::StructTraits<blink::mojom::FetchAPIDataElementDataView, - network::DataElement>; - mojom::DataElementType type_; - // For TYPE_BYTES. - std::vector<uint8_t> buf_; - // For TYPE_FILE. - base::FilePath path_; - // For TYPE_DATA_PIPE. - mojo::PendingRemote<mojom::DataPipeGetter> data_pipe_getter_; - // For TYPE_CHUNKED_DATA_PIPE. mojo::PendingRemote<mojom::ChunkedDataPipeGetter> chunked_data_pipe_getter_; - uint64_t offset_; - uint64_t length_; + ReadOnlyOnce read_only_once_; +}; + +// Represents a part of a request body consisting of (part of) a file. +class COMPONENT_EXPORT(NETWORK_CPP_BASE) DataElementFile final { + public: + // Do NOT use this constructor outside of mojo deserialization context. + DataElementFile(); + + DataElementFile(const base::FilePath& path, + uint64_t offset, + uint64_t length, + base::Time expected_modification_time); + DataElementFile(const DataElementFile&); + DataElementFile& operator=(const DataElementFile&); + DataElementFile(DataElementFile&&); + DataElementFile& operator=(DataElementFile&&); + ~DataElementFile(); + + const base::FilePath& path() const { return path_; } + uint64_t offset() const { return offset_; } + uint64_t length() const { return length_; } + base::Time expected_modification_time() const { + return expected_modification_time_; + } + + private: + base::FilePath path_; + uint64_t offset_ = 0; + uint64_t length_ = 0; base::Time expected_modification_time_; }; -COMPONENT_EXPORT(NETWORK_CPP_BASE) -bool operator==(const DataElement& a, const DataElement& b); -COMPONENT_EXPORT(NETWORK_CPP_BASE) -bool operator!=(const DataElement& a, const DataElement& b); +// Represents part of an upload body. This is a union of various types defined +// above. See them for details. +class COMPONENT_EXPORT(NETWORK_CPP_BASE) DataElement { + public: + using Tag = mojom::DataElementDataView::Tag; + + // Do NOT use this constructor outside of mojo deserialization context. A + // DataElement created by this constructor should be considered as invalid, + // and replaced with a valid value as soon as possible. + DataElement(); + + template <typename T> + explicit DataElement(T&& t) : variant_(std::forward<T>(t)) {} + DataElement(const DataElement&) = delete; + DataElement& operator=(const DataElement&) = delete; + DataElement(DataElement&& other); + DataElement& operator=(DataElement&& other); + ~DataElement(); + + Tag type() const { + switch (variant_.index()) { + case 0: + NOTREACHED(); + return Tag::kBytes; + case 1: + return Tag::kBytes; + case 2: + return Tag::kDataPipe; + case 3: + return Tag::kChunkedDataPipe; + case 4: + return Tag::kFile; + default: + NOTREACHED(); + return Tag::kBytes; + } + } + + template <typename T> + const T& As() const { + return absl::get<T>(variant_); + } + + template <typename T> + T& As() { + return absl::get<T>(variant_); + } + + private: + absl::variant<absl::monostate, + DataElementBytes, + DataElementDataPipe, + DataElementChunkedDataPipe, + DataElementFile> + variant_; +}; } // namespace network diff --git a/chromium/services/network/public/cpp/data_pipe_to_source_stream_unittest.cc b/chromium/services/network/public/cpp/data_pipe_to_source_stream_unittest.cc index 67d30579dda..28ac773f12b 100644 --- a/chromium/services/network/public/cpp/data_pipe_to_source_stream_unittest.cc +++ b/chromium/services/network/public/cpp/data_pipe_to_source_stream_unittest.cc @@ -54,9 +54,8 @@ class DataPipeToSourceStreamTest sizeof(MojoCreateDataPipeOptions), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1, GetParam().pipe_capacity}; mojo::ScopedDataPipeConsumerHandle consumer_end; - CHECK_EQ(MOJO_RESULT_OK, - mojo::CreateDataPipe(&data_pipe_options, &producer_end_, - &consumer_end)); + CHECK_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe(&data_pipe_options, + producer_end_, consumer_end)); adapter_ = std::make_unique<DataPipeToSourceStream>(std::move(consumer_end)); } diff --git a/chromium/services/network/public/cpp/digitally_signed_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/digitally_signed_mojom_traits_unittest.cc index 7272e5e0327..34ca55e5c2a 100644 --- a/chromium/services/network/public/cpp/digitally_signed_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/digitally_signed_mojom_traits_unittest.cc @@ -32,7 +32,7 @@ TEST(DigitallySignedTraitsTest, Roundtrips) { net::ct::DigitallySigned copied; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::DigitallySigned>( - &original, &copied)) + original, copied)) << "with hash " << hash_alg << " and sig " << sig_alg; EXPECT_EQ(original.hash_algorithm, copied.hash_algorithm); EXPECT_EQ(original.signature_algorithm, copied.signature_algorithm); @@ -49,7 +49,7 @@ TEST(DigitallySignedTraitsTest, EmptySignatureRejected) { net::ct::DigitallySigned copied; EXPECT_FALSE(mojo::test::SerializeAndDeserialize<mojom::DigitallySigned>( - &original, &copied)); + original, copied)); } TEST(DigitallySignedTraitsTest, OutOfBoundsEnumsRejected) { @@ -61,8 +61,8 @@ TEST(DigitallySignedTraitsTest, OutOfBoundsEnumsRejected) { net::ct::DigitallySigned copied; EXPECT_DCHECK_DEATH( - mojo::test::SerializeAndDeserialize<mojom::DigitallySigned>(&original, - &copied)); + mojo::test::SerializeAndDeserialize<mojom::DigitallySigned>(original, + copied)); } } // namespace diff --git a/chromium/services/network/public/cpp/features.cc b/chromium/services/network/public/cpp/features.cc index cd4d9239360..3efa7d4392d 100644 --- a/chromium/services/network/public/cpp/features.cc +++ b/chromium/services/network/public/cpp/features.cc @@ -57,6 +57,13 @@ const base::Feature kPauseBrowserInitiatedHeavyTrafficForP2P{ "PauseBrowserInitiatedHeavyTrafficForP2P", base::FEATURE_ENABLED_BY_DEFAULT}; +// When kPauseLowPriorityBrowserRequestsOnWeakSignal is enabled, then a subset +// of the browser initiated requests may be deferred if the device is using +// cellular connection and the signal quality is low. Android only. +const base::Feature kPauseLowPriorityBrowserRequestsOnWeakSignal{ + "PauseLowPriorityBrowserRequestsOnWeakSignal", + base::FEATURE_DISABLED_BY_DEFAULT}; + // When kCORBProtectionSniffing is enabled CORB sniffs additional same-origin // resources if they look sensitive. const base::Feature kCORBProtectionSniffing{"CORBProtectionSniffing", @@ -85,7 +92,7 @@ const base::Feature kCrossOriginOpenerPolicyReportingOriginTrial{ // Enables Cross-Origin Opener Policy (COOP) reporting. // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e const base::Feature kCrossOriginOpenerPolicyReporting{ - "CrossOriginOpenerPolicyReporting", base::FEATURE_DISABLED_BY_DEFAULT}; + "CrossOriginOpenerPolicyReporting", base::FEATURE_ENABLED_BY_DEFAULT}; // Enables Cross-Origin Opener Policy (COOP) access reporting. // https://github.com/camillelamy/explainers/blob/master/coop_reporting.md#report-blocked-accesses-to-other-windows @@ -97,14 +104,6 @@ const base::Feature kCrossOriginOpenerPolicyAccessReporting{ const base::Feature kCrossOriginOpenerPolicyByDefault{ "CrossOriginOpenerPolicyByDefault", base::FEATURE_DISABLED_BY_DEFAULT}; -// Enables Cross-Origin Embedder Policy (COEP). -// https://html.spec.whatwg.org/#coep -// Currently this feature is enabled for all platforms (including webview). -// TODO(https://crbug.com/1140432): Remove this flag after M88 Stable + 1 week = -// 2021-02-01. -const base::Feature kCrossOriginEmbedderPolicy{ - "CrossOriginEmbedderPolicy", base::FEATURE_ENABLED_BY_DEFAULT}; - // Enables the most recent developments on the crossOriginIsolated property. // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/crossOriginIsolated const base::Feature kCrossOriginIsolated{"CrossOriginIsolated", @@ -118,7 +117,7 @@ const base::Feature kSplitAuthCacheByNetworkIsolationKey{ // Enable usage of hardcoded DoH upgrade mapping for use in automatic mode. const base::Feature kDnsOverHttpsUpgrade { "DnsOverHttpsUpgrade", -#if BUILDFLAG(IS_ASH) || defined(OS_MAC) || defined(OS_ANDROID) || \ +#if BUILDFLAG(IS_CHROMEOS_ASH) || defined(OS_MAC) || defined(OS_ANDROID) || \ defined(OS_WIN) base::FEATURE_ENABLED_BY_DEFAULT #else @@ -146,12 +145,6 @@ const base::FeatureParam<std::string> const base::Feature kDisableKeepaliveFetch{"DisableKeepaliveFetch", base::FEATURE_DISABLED_BY_DEFAULT}; -// Attach the origin of the destination URL to the "origin" header -const base::Feature - kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess{ - "DeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess", - base::FEATURE_DISABLED_BY_DEFAULT}; - // Controls whether a |request_initiator| that mismatches // |request_initiator_origin_lock| leads to 1) failing the HTTP request and 2) // calling mojo::ReportBadMessage (on desktop platforms, where NetworkService @@ -161,17 +154,7 @@ const base::Feature // See also https://crbug.com/920634 const base::Feature kRequestInitiatorSiteLockEnfocement = { "RequestInitiatorSiteLockEnfocement", -#if defined(OS_ANDROID) - base::FEATURE_DISABLED_BY_DEFAULT}; -#else base::FEATURE_ENABLED_BY_DEFAULT}; -#endif - -// When the CertVerifierService is enabled, certificate verification will not be -// performed in the network service, but will instead be brokered to a separate -// cert verification service potentially running in a different process. -const base::Feature kCertVerifierService{"CertVerifierService", - base::FEATURE_DISABLED_BY_DEFAULT}; // Enables preprocessing requests with the Trust Tokens API Fetch flags set, // and handling their responses, according to the protocol. @@ -212,19 +195,14 @@ const base::FeatureParam<TrustTokenOriginTrialSpec> // Determines whether Trust Tokens issuance requests should be diverted, at the // corresponding issuers' request, to the operating system instead of sent // to the issuers' servers. +// +// WARNING: If you rename this param, you must update the corresponding flag +// entry in about_flags.cc. const base::FeatureParam<bool> kPlatformProvidedTrustTokenIssuance{ &kTrustTokens, "PlatformProvidedTrustTokenIssuance", false}; -// Enables the Content Security Policy Embedded Enforcement check out of blink -const base::Feature kOutOfBlinkCSPEE{"OutOfBlinkCSPEE", - base::FEATURE_ENABLED_BY_DEFAULT}; - const base::Feature kWebSocketReassembleShortMessages{ "WebSocketReassembleShortMessages", base::FEATURE_ENABLED_BY_DEFAULT}; -// Enables usage of First Party Sets to determine cookie availability. -constexpr base::Feature kFirstPartySets{"FirstPartySets", - base::FEATURE_DISABLED_BY_DEFAULT}; - } // namespace features } // namespace network diff --git a/chromium/services/network/public/cpp/features.h b/chromium/services/network/public/cpp/features.h index a836a51eadc..774efb1dc4b 100644 --- a/chromium/services/network/public/cpp/features.h +++ b/chromium/services/network/public/cpp/features.h @@ -27,6 +27,8 @@ extern const base::Feature kDelayRequestsOnMultiplexedConnections; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kPauseBrowserInitiatedHeavyTrafficForP2P; COMPONENT_EXPORT(NETWORK_CPP) +extern const base::Feature kPauseLowPriorityBrowserRequestsOnWeakSignal; +COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kCORBProtectionSniffing; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kProactivelyThrottleLowPriorityRequests; @@ -41,8 +43,6 @@ extern const base::Feature kCrossOriginOpenerPolicyAccessReporting; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kCrossOriginOpenerPolicyByDefault; COMPONENT_EXPORT(NETWORK_CPP) -extern const base::Feature kCrossOriginEmbedderPolicy; -COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kCrossOriginIsolated; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kSplitAuthCacheByNetworkIsolationKey; @@ -56,12 +56,7 @@ extern const base::FeatureParam<std::string> COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kDisableKeepaliveFetch; COMPONENT_EXPORT(NETWORK_CPP) -extern const base::Feature - kDeriveOriginFromUrlForNeitherGetNorHeadRequestWhenHavingSpecialAccess; -COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kRequestInitiatorSiteLockEnfocement; -COMPONENT_EXPORT(NETWORK_CPP) -extern const base::Feature kCertVerifierService; COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kTrustTokens; @@ -79,14 +74,8 @@ COMPONENT_EXPORT(NETWORK_CPP) extern const base::FeatureParam<bool> kPlatformProvidedTrustTokenIssuance; COMPONENT_EXPORT(NETWORK_CPP) -extern const base::Feature kOutOfBlinkCSPEE; - -COMPONENT_EXPORT(NETWORK_CPP) extern const base::Feature kWebSocketReassembleShortMessages; -COMPONENT_EXPORT(NETWORK_CPP) -extern const base::Feature kFirstPartySets; - } // namespace features } // namespace network 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 index 81792349e33..07abe42a6e8 100644 --- a/chromium/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/host_resolver_mojom_traits_unittest.cc @@ -21,7 +21,7 @@ TEST(HostResolverMojomTraitsTest, DnsConfigOverridesRoundtrip_Empty) { net::DnsConfigOverrides deserialized; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::DnsConfigOverrides>( - &original, &deserialized)); + original, deserialized)); EXPECT_EQ(original, deserialized); } @@ -46,7 +46,7 @@ TEST(HostResolverMojomTraitsTest, DnsConfigOverridesRoundtrip_FullySpecified) { net::DnsConfigOverrides deserialized; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::DnsConfigOverrides>( - &original, &deserialized)); + original, deserialized)); EXPECT_EQ(original, deserialized); } @@ -70,7 +70,7 @@ TEST(HostResolverMojomTraitsTest, DnsConfigOverrides_OnlyDnsOverHttpsServers) { net::DnsConfigOverrides deserialized; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::DnsConfigOverrides>( - &original, &deserialized)); + original, deserialized)); EXPECT_EQ(original, deserialized); } @@ -81,7 +81,7 @@ TEST(HostResolverMojomTraitsTest, ResolveErrorInfo) { net::ResolveErrorInfo deserialized; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::ResolveErrorInfo>( - &original, &deserialized)); + original, deserialized)); EXPECT_EQ(original, deserialized); } diff --git a/chromium/services/network/public/cpp/initiator_lock_compatibility.h b/chromium/services/network/public/cpp/initiator_lock_compatibility.h index 9275c998bf3..4608fe3f89e 100644 --- a/chromium/services/network/public/cpp/initiator_lock_compatibility.h +++ b/chromium/services/network/public/cpp/initiator_lock_compatibility.h @@ -44,9 +44,6 @@ enum class InitiatorLockCompatibility { // - HTML Imports (see https://crbug.com/871827#c9). kIncorrectLock = 4, - // Covered by CrossOriginReadBlockingExceptionForPlugin::ShouldAllowForPlugin. - kExcludedCorbForPlugin = 6, - // Covered by AddAllowedRequestInitiatorForPlugin. kAllowedRequestInitiatorForPlugin = 7, 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 index da85b3b5265..43af5b69106 100644 --- a/chromium/services/network/public/cpp/ip_address_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/ip_address_mojom_traits_unittest.cc @@ -16,7 +16,7 @@ TEST(IPAddressStructTraitsTest, Ipv4) { IPAddress deserialized; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<network::mojom::IPAddress>( - &original, &deserialized)); + original, deserialized)); EXPECT_EQ(original, deserialized); } @@ -26,7 +26,7 @@ TEST(IPAddressStructTraitsTest, Ipv6) { IPAddress deserialized; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<network::mojom::IPAddress>( - &original, &deserialized)); + original, deserialized)); EXPECT_EQ(original, deserialized); } @@ -40,7 +40,7 @@ TEST(IPAddressStructTraitsTest, InvalidAddress) { IPAddress deserialized; EXPECT_FALSE(mojo::test::SerializeAndDeserialize<network::mojom::IPAddress>( - &original, &deserialized)); + original, deserialized)); } } // namespace diff --git a/chromium/services/network/public/cpp/ip_address_space_util.cc b/chromium/services/network/public/cpp/ip_address_space_util.cc index 00eeef595da..a9824b1f17f 100644 --- a/chromium/services/network/public/cpp/ip_address_space_util.cc +++ b/chromium/services/network/public/cpp/ip_address_space_util.cc @@ -5,9 +5,6 @@ #include "services/network/public/cpp/ip_address_space_util.h" #include "net/base/ip_address.h" -#include "services/network/public/cpp/content_security_policy/content_security_policy.h" -#include "services/network/public/mojom/url_response_head.mojom.h" -#include "url/gurl.h" namespace network { @@ -44,46 +41,4 @@ bool IsLessPublicAddressSpace(IPAddressSpace lhs, IPAddressSpace rhs) { return CollapseUnknown(lhs) < CollapseUnknown(rhs); } -// Helper for CalculateClientAddressSpace() with the same arguments. -// -// If the response was fetched via service workers, returns the last URL in the -// list. Otherwise returns |request_url|. -// -// See: https://fetch.spec.whatwg.org/#concept-response-url-list -const GURL& ResponseUrl(const GURL& request_url, - const mojom::URLResponseHead* response_head) { - if (response_head && !response_head->url_list_via_service_worker.empty()) { - return response_head->url_list_via_service_worker.back(); - } - - return request_url; -} - -IPAddressSpace CalculateClientAddressSpace( - const GURL& url, - const mojom::URLResponseHead* response_head) { - if (ResponseUrl(url, response_head).SchemeIsFile()) { - // See: https://wicg.github.io/cors-rfc1918/#file-url. - return IPAddressSpace::kLocal; - } - - if (!response_head) { - return IPAddressSpace::kUnknown; - } - - // First, check whether the response forces itself into a public address space - // as per https://wicg.github.io/cors-rfc1918/#csp. - DCHECK(response_head->parsed_headers) - << "CalculateIPAddressSpace() called for URL " << url - << " with null parsed_headers."; - if (response_head->parsed_headers && - ShouldTreatAsPublicAddress( - response_head->parsed_headers->content_security_policy)) { - return IPAddressSpace::kPublic; - } - - // Otherwise, calculate the address space via the provided IP address. - return IPAddressToIPAddressSpace(response_head->remote_endpoint.address()); -} - } // namespace network diff --git a/chromium/services/network/public/cpp/ip_address_space_util.h b/chromium/services/network/public/cpp/ip_address_space_util.h index 4e8423ca1db..440e9bc8de4 100644 --- a/chromium/services/network/public/cpp/ip_address_space_util.h +++ b/chromium/services/network/public/cpp/ip_address_space_util.h @@ -6,9 +6,6 @@ #define SERVICES_NETWORK_PUBLIC_CPP_IP_ADDRESS_SPACE_UTIL_H_ #include "services/network/public/mojom/ip_address_space.mojom.h" -#include "services/network/public/mojom/url_response_head.mojom-forward.h" - -class GURL; namespace net { @@ -39,30 +36,6 @@ bool COMPONENT_EXPORT(NETWORK_CPP) IsLessPublicAddressSpace(mojom::IPAddressSpace lhs, mojom::IPAddressSpace rhs); -// Given a request URL and response information, this function calculates the -// IPAddressSpace which should be associated with documents or worker global -// scopes (collectively: request clients) instantiated from this resource. -// -// |response_head| may be nullptr. Caller retains ownership. If not nullptr, -// then |response_head->parsed_headers| must be populated with the result of -// parsing |response->headers|. -// -// WARNING: This function is defined here for proximity with related code and -// the data structures involved. However since it deals with higher-level -// concepts too (documents and worker global scopes), it should probably only be -// used at the content/ layer or above. -// -// See: https://wicg.github.io/cors-rfc1918/#address-space -// -// TODO(https://crbug.com/1134601): This implementation treats requests that -// don't use a URL loader (`about:blank`), as well as requests whose IP address -// is invalid (`about:srcdoc`, `blob:`, etc.) as `kUnknown`. This is incorrect. -// We'll eventually want to make sure we inherit from the client's creator -// in some cases), but safe, as `kUnknown` is treated the same as `kPublic`. -mojom::IPAddressSpace COMPONENT_EXPORT(NETWORK_CPP) - CalculateClientAddressSpace(const GURL& url, - const mojom::URLResponseHead* response_head); - } // namespace network #endif // SERVICES_NETWORK_PUBLIC_CPP_IP_ADDRESS_SPACE_UTIL_H_ diff --git a/chromium/services/network/public/cpp/ip_address_space_util_unittest.cc b/chromium/services/network/public/cpp/ip_address_space_util_unittest.cc index de54b30f8a9..6891669f9f0 100644 --- a/chromium/services/network/public/cpp/ip_address_space_util_unittest.cc +++ b/chromium/services/network/public/cpp/ip_address_space_util_unittest.cc @@ -4,26 +4,19 @@ #include "services/network/public/cpp/ip_address_space_util.h" -#include <utility> - #include "net/base/ip_address.h" -#include "net/base/ip_endpoint.h" -#include "services/network/public/mojom/content_security_policy.mojom.h" -#include "services/network/public/mojom/parsed_headers.mojom.h" -#include "services/network/public/mojom/url_response_head.mojom.h" #include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" namespace network { namespace { -using mojom::ContentSecurityPolicy; using mojom::IPAddressSpace; -using mojom::ParsedHeaders; -using mojom::URLResponseHead; using net::IPAddress; using net::IPAddressBytes; -using net::IPEndPoint; + +IPAddress PublicIPv4Address() { + return IPAddress(64, 233, 160, 0); +} IPAddress PrivateIPv4Address() { return IPAddress(192, 168, 1, 1); @@ -32,7 +25,7 @@ IPAddress PrivateIPv4Address() { TEST(IPAddressSpaceTest, IPAddressToIPAddressSpacev4) { EXPECT_EQ(IPAddressToIPAddressSpace(IPAddress()), IPAddressSpace::kUnknown); - EXPECT_EQ(IPAddressToIPAddressSpace(IPAddress(64, 233, 160, 0)), + EXPECT_EQ(IPAddressToIPAddressSpace(PublicIPv4Address()), IPAddressSpace::kPublic); EXPECT_EQ(IPAddressToIPAddressSpace(PrivateIPv4Address()), @@ -108,82 +101,5 @@ TEST(IPAddressSpaceTest, IsLessPublicAddressSpaceThanUnknown) { IPAddressSpace::kUnknown)); } -TEST(IPAddressSpaceTest, CalculateClientAddressSpaceFileURL) { - EXPECT_EQ(IPAddressSpace::kLocal, - CalculateClientAddressSpace(GURL("file:///foo"), nullptr)); -} - -TEST(IPAddressSpaceTest, - CalculateIPAddressSpaceFetchedViaServiceWorkerFromFile) { - URLResponseHead response_head; - response_head.url_list_via_service_worker.emplace_back("http://bar.test"); - response_head.url_list_via_service_worker.emplace_back("file:///foo"); - response_head.parsed_headers = ParsedHeaders::New(); - - EXPECT_EQ( - IPAddressSpace::kLocal, - CalculateClientAddressSpace(GURL("http://foo.test"), &response_head)); -} - -TEST(IPAddressSpaceTest, - CalculateIPAddressSpaceFetchedViaServiceWorkerFromHttp) { - URLResponseHead response_head; - response_head.url_list_via_service_worker.emplace_back("file:///foo"); - response_head.url_list_via_service_worker.emplace_back("http://bar.test"); - response_head.parsed_headers = ParsedHeaders::New(); - - EXPECT_EQ( - IPAddressSpace::kUnknown, - CalculateClientAddressSpace(GURL("http://foo.test"), &response_head)); -} - -TEST(IPAddressSpaceTest, - CalculateIPAddressSpaceFetchedViaServiceWorkerFromHttpInsteadOfFile) { - URLResponseHead response_head; - response_head.url_list_via_service_worker.emplace_back("http://bar.test"); - response_head.parsed_headers = ParsedHeaders::New(); - - EXPECT_EQ(IPAddressSpace::kUnknown, - CalculateClientAddressSpace(GURL("file:///foo"), &response_head)); -} - -TEST(IPAddressSpaceTest, CalculateClientAddressSpaceNullResponseHead) { - EXPECT_EQ(IPAddressSpace::kUnknown, - CalculateClientAddressSpace(GURL("http://foo.test"), nullptr)); -} - -TEST(IPAddressSpaceTest, CalculateClientAddressSpaceEmptyResponseHead) { - URLResponseHead response_head; - response_head.parsed_headers = ParsedHeaders::New(); - EXPECT_EQ( - IPAddressSpace::kUnknown, - CalculateClientAddressSpace(GURL("http://foo.test"), &response_head)); -} - -TEST(IPAddressSpaceTest, CalculateClientAddressSpaceIPAddress) { - URLResponseHead response_head; - response_head.remote_endpoint = IPEndPoint(PrivateIPv4Address(), 1234); - response_head.parsed_headers = ParsedHeaders::New(); - - EXPECT_EQ( - IPAddressSpace::kPrivate, - CalculateClientAddressSpace(GURL("http://foo.test"), &response_head)); -} - -TEST(IPAddressSpaceTest, CalculateClientAddressSpaceTreatAsPublicAddress) { - URLResponseHead response_head; - response_head.remote_endpoint = IPEndPoint(IPAddress::IPv4Localhost(), 1234); - - auto csp = ContentSecurityPolicy::New(); - csp->treat_as_public_address = true; - response_head.parsed_headers = ParsedHeaders::New(); - response_head.parsed_headers->content_security_policy.push_back( - std::move(csp)); - - EXPECT_EQ( - IPAddressSpace::kPublic, - CalculateClientAddressSpace(GURL("http://foo.test"), &response_head)); -} - } // namespace } // namespace network diff --git a/chromium/services/network/public/cpp/is_potentially_trustworthy.cc b/chromium/services/network/public/cpp/is_potentially_trustworthy.cc index 37f6fd35b01..3dacd3c4d47 100644 --- a/chromium/services/network/public/cpp/is_potentially_trustworthy.cc +++ b/chromium/services/network/public/cpp/is_potentially_trustworthy.cc @@ -176,7 +176,7 @@ std::vector<std::string> ParseSecureOriginAllowlistFromCmdline() { std::vector<std::string> origin_patterns = ParseSecureOriginAllowlist(origins_str); -#if BUILDFLAG(IS_ASH) +#if BUILDFLAG(IS_CHROMEOS_ASH) // For Crostini, we allow access to the default VM/container as a secure // origin via the hostname penguin.linux.test. We are required to use a // wildcard for the prefix because we do not know what the port number is. @@ -199,11 +199,32 @@ bool IsAllowlisted(const std::vector<std::string>& allowlist, return false; } +bool IsSchemeConsideredAuthenticated(base::StringPiece scheme) { + // The code below is based on the specification at + // https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin + + // 7. If origin’s scheme component is one which the user agent considers to be + // authenticated, return "Potentially Trustworthy". + // Note: See §7.1 Packaged Applications for detail here. + // + // Note that this ignores some schemes that are considered trustworthy by + // higher layers (e.g. see GetSchemesBypassingSecureContextCheck in //chrome). + // + // See also + // - content::ContentClient::AddAdditionalSchemes and + // content::ContentClient::Schemes::local_schemes and + // content::ContentClient::Schemes::secure_schemes + // - url::AddLocalScheme + // - url::AddSecureScheme + return base::Contains(url::GetSecureSchemes(), scheme) || + base::Contains(url::GetLocalSchemes(), scheme); +} + } // namespace bool IsOriginPotentiallyTrustworthy(const url::Origin& origin) { // The code below is based on the specification at - // https://www.w3.org/TR/powerful-features/#is-origin-trustworthy. + // https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin // 1. If origin is an opaque origin, return "Not Trustworthy". if (origin.opaque()) @@ -214,70 +235,52 @@ bool IsOriginPotentiallyTrustworthy(const url::Origin& origin) { // 3. If origin’s scheme is either "https" or "wss", return "Potentially // Trustworthy". + // This is somewhat redundant with the GetSecureSchemes()-based check below. if (GURL::SchemeIsCryptographic(origin.scheme())) return true; // 4. If origin’s host component matches one of the CIDR notations 127.0.0.0/8 // or ::1/128 [RFC4632], return "Potentially Trustworthy". - // - // Diverging from the spec a bit here - in addition to the hostnames covered - // by https://www.w3.org/TR/powerful-features/#is-origin-trustworthy, the code - // below also considers "localhost" to be potentially secure. - // - // Cannot just pass |origin.host()| to |HostStringIsLocalhost|, because of the - // need to also strip the brackets from things like "[::1]". + // 5. If origin’s host component is "localhost" or falls within ".localhost", + // and the user agent conforms to the name resolution rules in + // [let-localhost-be-localhost], return "Potentially Trustworthy". if (net::IsLocalhost(origin.GetURL())) return true; - // 5. If origin’s scheme component is file, return "Potentially Trustworthy". + // 6. If origin’s scheme component is file, return "Potentially Trustworthy". // - // This is somewhat redundant with the GetLocalSchemes-based check below. + // This is somewhat redundant with the GetLocalSchemes-based + // IsSchemeConsideredAuthenticated check below. if (origin.scheme() == url::kFileScheme) return true; - // 6. If origin’s scheme component is one which the user agent considers to be + // 7. If origin’s scheme component is one which the user agent considers to be // authenticated, return "Potentially Trustworthy". // Note: See §7.1 Packaged Applications for detail here. - // - // Note that this ignores some schemes that are considered trustworthy by - // higher layers (e.g. see GetSchemesBypassingSecureContextCheck in //chrome). - // - // See also - // - content::ContentClient::AddAdditionalSchemes and - // content::ContentClient::Schemes::local_schemes and - // content::ContentClient::Schemes::secure_schemes - // - url::AddLocalScheme - // - url::AddSecureScheme - if (base::Contains(url::GetSecureSchemes(), origin.scheme()) || - base::Contains(url::GetLocalSchemes(), origin.scheme())) { + if (IsSchemeConsideredAuthenticated(origin.scheme())) return true; - } - // 7. If origin has been configured as a trustworthy origin, return + // 8. If origin has been configured as a trustworthy origin, return // "Potentially Trustworthy". // Note: See §7.2 Development Environments for detail here. if (SecureOriginAllowlist::GetInstance().IsOriginAllowlisted(origin)) return true; - // 8. Return "Not Trustworthy". + // 9. Return "Not Trustworthy". return false; } bool IsUrlPotentiallyTrustworthy(const GURL& url) { // The code below is based on the specification at - // https://www.w3.org/TR/powerful-features/#is-url-trustworthy. + // https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-url - // 1. If url’s scheme is "data", return "Not Trustworthy". - // Note: This aligns the definition of a secure context with the de facto - // "data: URL as opaque origin" behavior that a majority of today’s - // browsers have agreed upon, rather than the de jure "data: URL inherits - // origin" behavior defined in HTML. - if (url.SchemeIs(url::kDataScheme)) - return false; - - // 2. If url is "about:blank" or "about:srcdoc", return "Potentially + // 1. If url is "about:blank" or "about:srcdoc", return "Potentially // Trustworthy". - if (url.SchemeIs(url::kAboutScheme)) + if (url.IsAboutBlank() || url.IsAboutSrcdoc()) + return true; + + // 2. If url’s scheme is "data", return "Potentially Trustworthy". + if (url.SchemeIs(url::kDataScheme)) return true; // 3. Return the result of executing §3.2 Is origin potentially trustworthy? @@ -285,7 +288,15 @@ bool IsUrlPotentiallyTrustworthy(const GURL& url) { // Note: The origin of blob: and filesystem: URLs is the origin of the // context in which they were created. Therefore, blobs created in a // trustworthy origin will themselves be potentially trustworthy. - return IsOriginPotentiallyTrustworthy(url::Origin::Create(url)); + url::Origin origin = url::Origin::Create(url); + if (origin.opaque() && IsSchemeConsideredAuthenticated(url.scheme_piece())) { + // Authenticated schemes should be treated as trustworthy, even if they + // translate into an opaque origin (e.g. because some of them might also be + // registered as a no-access, like the //content-layer chrome-error:// or + // the //chrome-layer chrome-native://). + return true; + } + return IsOriginPotentiallyTrustworthy(origin); } // static diff --git a/chromium/services/network/public/cpp/is_potentially_trustworthy_unittest.cc b/chromium/services/network/public/cpp/is_potentially_trustworthy_unittest.cc index 3f9adfdb3fd..947dd4a8e55 100644 --- a/chromium/services/network/public/cpp/is_potentially_trustworthy_unittest.cc +++ b/chromium/services/network/public/cpp/is_potentially_trustworthy_unittest.cc @@ -2,7 +2,7 @@ // 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/is_potentially_trustworthy.h" +#include "services/network/public/cpp/is_potentially_trustworthy_unittest.h" #include "base/test/scoped_command_line.h" #include "services/network/public/cpp/network_switches.h" @@ -10,8 +10,10 @@ #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" #include "url/origin.h" +#include "url/url_util.h" namespace network { +namespace test { bool IsOriginAllowlisted(const url::Origin& origin) { return SecureOriginAllowlist::GetInstance().IsOriginAllowlisted(origin); @@ -21,8 +23,8 @@ bool IsOriginAllowlisted(const char* str) { return IsOriginAllowlisted(url::Origin::Create(GURL(str))); } -bool IsPotentiallyTrustworthy(const char* str) { - return IsUrlPotentiallyTrustworthy(GURL(str)); +bool IsUrlPotentiallyTrustworthy(const char* str) { + return network::IsUrlPotentiallyTrustworthy(GURL(str)); } std::vector<std::string> CanonicalizeAllowlist( @@ -32,83 +34,41 @@ std::vector<std::string> CanonicalizeAllowlist( allowlist, rejected_patterns); } -TEST(IsPotentiallyTrustworthy, MainTest) { - const url::Origin unique_origin; - EXPECT_FALSE(IsOriginPotentiallyTrustworthy(unique_origin)); - const url::Origin opaque_origin = - url::Origin::Create(GURL("https://www.example.com")) - .DeriveNewOpaqueOrigin(); - EXPECT_FALSE(IsOriginPotentiallyTrustworthy(opaque_origin)); - - EXPECT_TRUE(IsPotentiallyTrustworthy("about:blank")); - EXPECT_TRUE(IsPotentiallyTrustworthy("about:blank#ref")); - EXPECT_TRUE(IsPotentiallyTrustworthy("about:srcdoc")); - - EXPECT_FALSE(IsPotentiallyTrustworthy("data:test/plain;blah")); - EXPECT_FALSE(IsPotentiallyTrustworthy("javascript:alert('blah')")); - - EXPECT_TRUE(IsPotentiallyTrustworthy("file:///test/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("file:///test/")); - EXPECT_TRUE(IsPotentiallyTrustworthy("file://localhost/test/")); - EXPECT_TRUE(IsPotentiallyTrustworthy("file://otherhost/test/")); - - EXPECT_TRUE(IsPotentiallyTrustworthy("https://example.com/fun.html")); - EXPECT_FALSE(IsPotentiallyTrustworthy("http://example.com/fun.html")); - - EXPECT_TRUE(IsPotentiallyTrustworthy("wss://example.com/fun.html")); - EXPECT_FALSE(IsPotentiallyTrustworthy("ws://example.com/fun.html")); - - EXPECT_TRUE(IsPotentiallyTrustworthy("http://localhost/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("http://localhost./fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("http://pumpkin.localhost/fun.html")); +// TODO(crbug.com/1153336 and crbug.com/1164416): Fix product behavior, so that +// blink::SecurityOrigin::IsSecure(const KURL&) is compatible with +// network::IsUrlPotentiallyTrustworthy(const GURL&) and then move the tests +// below to the AbstractTrustworthinessTest.UrlFromString test case in +// //services/network/public/cpp/is_potentially_trustworthy_unittest.h +// See also SecurityOriginTest.IsSecure test. +TEST(IsPotentiallyTrustworthy, Url) { + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("file:///test/fun.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("file:///test/")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("file://localhost/test/")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("file://otherhost/test/")); + + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("http://localhost/fun.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("http://localhost./fun.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("http://pumpkin.localhost/fun.html")); EXPECT_TRUE( - IsPotentiallyTrustworthy("http://crumpet.pumpkin.localhost/fun.html")); + IsUrlPotentiallyTrustworthy("http://crumpet.pumpkin.localhost/fun.html")); EXPECT_TRUE( - IsPotentiallyTrustworthy("http://pumpkin.localhost:8080/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy( + IsUrlPotentiallyTrustworthy("http://pumpkin.localhost:8080/fun.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy( "http://crumpet.pumpkin.localhost:3000/fun.html")); - EXPECT_FALSE(IsPotentiallyTrustworthy("http://localhost.com/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("https://localhost.com/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("http://127.0.0.1/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("ftp://127.0.0.1/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("http://127.3.0.1/fun.html")); - EXPECT_FALSE(IsPotentiallyTrustworthy("http://127.example.com/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("https://127.example.com/fun.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("http://127.0.0.1/fun.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("ftp://127.0.0.1/fun.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("http://127.3.0.1/fun.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("http://[::1]/fun.html")); - EXPECT_FALSE(IsPotentiallyTrustworthy("http://[::2]/fun.html")); - EXPECT_FALSE(IsPotentiallyTrustworthy("http://[::1].example.com/fun.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("http://[::1]/fun.html")); - // IPv4 mapped IPv6 literals for loopback. - EXPECT_FALSE(IsPotentiallyTrustworthy("http://[::ffff:127.0.0.1]/")); - EXPECT_FALSE(IsPotentiallyTrustworthy("http://[::ffff:7f00:1]")); - - // IPv4 compatible IPv6 literal for loopback. - EXPECT_FALSE(IsPotentiallyTrustworthy("http://[::127.0.0.1]")); - - EXPECT_FALSE(IsPotentiallyTrustworthy("http://loopback")); - - EXPECT_TRUE(IsPotentiallyTrustworthy("http://localhost6")); - EXPECT_TRUE(IsPotentiallyTrustworthy("ftp://localhost6.localdomain6")); - EXPECT_TRUE(IsPotentiallyTrustworthy("http://localhost.localdomain")); - - EXPECT_FALSE( - IsPotentiallyTrustworthy("filesystem:http://www.example.com/temporary/")); - EXPECT_FALSE( - IsPotentiallyTrustworthy("filesystem:ftp://www.example.com/temporary/")); EXPECT_TRUE( - IsPotentiallyTrustworthy("filesystem:ftp://127.0.0.1/temporary/")); - EXPECT_TRUE(IsPotentiallyTrustworthy( - "filesystem:https://www.example.com/temporary/")); - - EXPECT_FALSE( - IsPotentiallyTrustworthy("blob:http://www.example.com/guid-goes-here")); - EXPECT_FALSE( - IsPotentiallyTrustworthy("blob:ftp://www.example.com/guid-goes-here")); - EXPECT_TRUE(IsPotentiallyTrustworthy("blob:ftp://127.0.0.1/guid-goes-here")); + IsUrlPotentiallyTrustworthy("filesystem:ftp://127.0.0.1/temporary/")); EXPECT_TRUE( - IsPotentiallyTrustworthy("blob:https://www.example.com/guid-goes-here")); + IsUrlPotentiallyTrustworthy("blob:ftp://127.0.0.1/guid-goes-here")); + + EXPECT_FALSE(IsUrlPotentiallyTrustworthy("blob:data:text/html,Hello")); + EXPECT_FALSE(IsUrlPotentiallyTrustworthy("blob:about:blank")); } class SecureOriginAllowlistTest : public testing::Test { @@ -121,8 +81,8 @@ class SecureOriginAllowlistTest : public testing::Test { TEST_F(SecureOriginAllowlistTest, UnsafelyTreatInsecureOriginAsSecure) { EXPECT_FALSE(IsOriginAllowlisted("http://example.com/a.html")); EXPECT_FALSE(IsOriginAllowlisted("http://127.example.com/a.html")); - EXPECT_FALSE(IsPotentiallyTrustworthy("http://example.com/a.html")); - EXPECT_FALSE(IsPotentiallyTrustworthy("http://127.example.com/a.html")); + EXPECT_FALSE(IsUrlPotentiallyTrustworthy("http://example.com/a.html")); + EXPECT_FALSE(IsUrlPotentiallyTrustworthy("http://127.example.com/a.html")); // Add http://example.com and http://127.example.com to allowlist by // command-line and see if they are now considered secure origins. @@ -136,13 +96,13 @@ TEST_F(SecureOriginAllowlistTest, UnsafelyTreatInsecureOriginAsSecure) { // They should be now allow-listed. EXPECT_TRUE(IsOriginAllowlisted("http://example.com/a.html")); EXPECT_TRUE(IsOriginAllowlisted("http://127.example.com/a.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("http://example.com/a.html")); - EXPECT_TRUE(IsPotentiallyTrustworthy("http://127.example.com/a.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("http://example.com/a.html")); + EXPECT_TRUE(IsUrlPotentiallyTrustworthy("http://127.example.com/a.html")); // Check that similarly named sites are not considered secure. - EXPECT_FALSE(IsPotentiallyTrustworthy("http://128.example.com/a.html")); + EXPECT_FALSE(IsUrlPotentiallyTrustworthy("http://128.example.com/a.html")); EXPECT_FALSE( - IsPotentiallyTrustworthy("http://foobar.127.example.com/a.html")); + IsUrlPotentiallyTrustworthy("http://foobar.127.example.com/a.html")); // When port is not specified, default port is assumed. EXPECT_TRUE(IsOriginAllowlisted("http://example.com:80/a.html")); @@ -199,7 +159,8 @@ TEST_F(SecureOriginAllowlistTest, HostnamePatterns) { GURL input_url(test.test_input); url::Origin input_origin = url::Origin::Create(input_url); EXPECT_EQ(test.expected_secure, IsOriginAllowlisted(input_origin)); - EXPECT_EQ(test.expected_secure, IsPotentiallyTrustworthy(test.test_input)); + EXPECT_EQ(test.expected_secure, + IsUrlPotentiallyTrustworthy(test.test_input)); } } @@ -254,4 +215,27 @@ TEST_F(SecureOriginAllowlistTest, Canonicalization) { EXPECT_THAT(canonicalized, ::testing::ElementsAre("*.example.com")); } +class TrustworthinessTestTraits : public url::UrlOriginTestTraits { + public: + using OriginType = url::Origin; + + static bool IsOriginPotentiallyTrustworthy(const OriginType& origin) { + return network::IsOriginPotentiallyTrustworthy(origin); + } + static bool IsUrlPotentiallyTrustworthy(base::StringPiece str) { + return network::IsUrlPotentiallyTrustworthy(GURL(str)); + } + static bool IsOriginOfLocalhost(const OriginType& origin) { + return net::IsLocalhost(origin.GetURL()); + } + + // Only static members = no constructors are needed. + TrustworthinessTestTraits() = delete; +}; + +INSTANTIATE_TYPED_TEST_SUITE_P(UrlOrigin, + AbstractTrustworthinessTest, + TrustworthinessTestTraits); + +} // namespace test } // namespace network diff --git a/chromium/services/network/public/cpp/is_potentially_trustworthy_unittest.h b/chromium/services/network/public/cpp/is_potentially_trustworthy_unittest.h new file mode 100644 index 00000000000..0c2ff61b289 --- /dev/null +++ b/chromium/services/network/public/cpp/is_potentially_trustworthy_unittest.h @@ -0,0 +1,340 @@ +// Copyright 2021 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_IS_POTENTIALLY_TRUSTWORTHY_UNITTEST_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_IS_POTENTIALLY_TRUSTWORTHY_UNITTEST_H_ + +#include "base/strings/string_piece.h" +#include "net/base/url_util.h" +#include "services/network/public/cpp/is_potentially_trustworthy.h" +#include "url/origin_abstract_tests.h" + +namespace network { +namespace test { + +// AbstractTrustworthinessTest below abstracts away differences between +// network::IsOriginPotentiallyTrustworthy and +// blink::SecurityOrigin::IsPotentiallyTrustworthy by parametrizing the tests +// with a class that has to expose the same members as url::UrlOriginTestTraits +// and the following extra members: +// static bool IsOriginPotentiallyTrustworthy(const OriginType& origin); +// static bool IsUrlPotentiallyTrustworthy(base::StringPiece str); +// static bool IsOriginOfLocalhost(const OriginType& origin); +template <typename TTrustworthinessTraits> +class AbstractTrustworthinessTest + : public url::AbstractOriginTest<TTrustworthinessTraits> { + protected: + // Wrappers that help ellide away TTrustworthinessTraits. + // + // Note that calling the wrappers needs to be prefixed with `this->...` to + // avoid hitting: explicit qualification required to use member 'FooBar' + // from dependent base class. + using OriginType = typename TTrustworthinessTraits::OriginType; + bool IsOriginPotentiallyTrustworthy(const OriginType& origin) { + return TTrustworthinessTraits::IsOriginPotentiallyTrustworthy(origin); + } + bool IsOriginPotentiallyTrustworthy(base::StringPiece str) { + auto origin = this->CreateOriginFromString(str); + return TTrustworthinessTraits::IsOriginPotentiallyTrustworthy(origin); + } + bool IsUrlPotentiallyTrustworthy(base::StringPiece str) { + return TTrustworthinessTraits::IsUrlPotentiallyTrustworthy(str); + } + bool IsOriginOfLocalhost(const OriginType& origin) { + return TTrustworthinessTraits::IsOriginOfLocalhost(origin); + } +}; + +TYPED_TEST_SUITE_P(AbstractTrustworthinessTest); + +TYPED_TEST_P(AbstractTrustworthinessTest, OpaqueOrigins) { + auto unique_origin = this->CreateUniqueOpaqueOrigin(); + EXPECT_FALSE(this->IsOriginPotentiallyTrustworthy(unique_origin)); + + auto example_origin = this->CreateOriginFromString("https://www.example.com"); + auto opaque_origin = this->DeriveNewOpaqueOrigin(example_origin); + EXPECT_FALSE(this->IsOriginPotentiallyTrustworthy(opaque_origin)); +} + +TYPED_TEST_P(AbstractTrustworthinessTest, OriginFromString) { + EXPECT_FALSE(this->IsOriginPotentiallyTrustworthy("about:blank")); + EXPECT_FALSE(this->IsOriginPotentiallyTrustworthy("about:blank#ref")); + EXPECT_FALSE(this->IsOriginPotentiallyTrustworthy("about:srcdoc")); + EXPECT_FALSE( + this->IsOriginPotentiallyTrustworthy("javascript:alert('blah')")); + EXPECT_FALSE(this->IsOriginPotentiallyTrustworthy("data:test/plain;blah")); + + EXPECT_TRUE(this->IsOriginPotentiallyTrustworthy( + "quic-transport://example.com/counter")); +} + +TYPED_TEST_P(AbstractTrustworthinessTest, CustomSchemes) { + // Custom testing schemes are registered in url::AbstractOriginTest::SetUp. + // Let's double-check that schemes we test with have the expected properties. + EXPECT_TRUE(base::Contains(url::GetSecureSchemes(), "sec")); + EXPECT_TRUE(base::Contains(url::GetSecureSchemes(), "sec-std-with-host")); + EXPECT_TRUE(base::Contains(url::GetSecureSchemes(), "sec-noaccess")); + EXPECT_TRUE(base::Contains(url::GetNoAccessSchemes(), "sec-noaccess")); + EXPECT_TRUE(base::Contains(url::GetNoAccessSchemes(), "noaccess")); + EXPECT_TRUE(GURL("sec-std-with-host://blah/x.js").IsStandard()); + + // Unrecognized / unknown schemes are not trustworthy. + EXPECT_FALSE( + this->IsOriginPotentiallyTrustworthy("unknown-scheme://example.com")); + EXPECT_FALSE( + this->IsUrlPotentiallyTrustworthy("unknown-scheme://example.com")); + + // Secure URLs are trustworthy, even if their scheme is also marked as + // no-access, or are not marked as standard. See also //chrome-layer + // ChromeContentClientTest.AdditionalSchemes test and + // https://crbug.com/734581. + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("sec://blah/x.js")); + EXPECT_TRUE( + this->IsUrlPotentiallyTrustworthy("sec-std-with-host://blah/x.js")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("sec-noaccess://blah/x.js")); + EXPECT_TRUE( + this->IsOriginPotentiallyTrustworthy("sec-std-with-host://blah/x.js")); + // No-access and non-standard/non-local schemes translate into an + // untrustworthy, opaque origin. + // TODO(lukasza): Maybe if the spec had a notion of an origin *precursor*, + // then it could inspect the scheme of the precursor. After this, it may be + // possible to EXPECT_TRUE below... + EXPECT_FALSE(this->IsOriginPotentiallyTrustworthy("sec://blah/x.js")); + EXPECT_FALSE( + this->IsOriginPotentiallyTrustworthy("sec-noaccess://blah/x.js")); + + // No-access, non-secure schemes are untrustworthy. + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("noaccess:blah")); + EXPECT_FALSE(this->IsOriginPotentiallyTrustworthy("noaccess:blah")); + + // Standard, but non-secure schemes are untrustworthy. + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("std-with-host://blah/x.js")); + EXPECT_FALSE( + this->IsOriginPotentiallyTrustworthy("std-with-host://blah/x.js")); +} + +TYPED_TEST_P(AbstractTrustworthinessTest, UrlFromString) { + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:blank")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:blank?x=2")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:blank#ref")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:blank?x=2#ref")); + + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:srcdoc")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:srcdoc?x=2")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:srcdoc#ref")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:srcdoc?x=2#ref")); + + // The test expectations below document the current behavior, that "emerges" + // from how out implementation of IsUrlPotentiallyTrustworthy treats scenarios + // that are not covered by the spec. The current behavior might or might not + // be the desirable long-term behavior (it just accidentally emerged from the + // current implentattion). In particular, not how + // https://www.w3.org/TR/secure-contexts/#is-url-trustworthy only mentions how + // to deal with "about:srcdoc" and "about:blank", and how + // https://github.com/w3c/webappsec-secure-contexts/issues/85 discusses + // general treatment of "secure" / "authenticated" schemes (see also + // IsSchemeConsideredAuthenticated in product code). + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:about")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("about:mumble")); + + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("data:test/plain;blah")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("data:text/html,Hello")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("javascript:alert('blah')")); + + EXPECT_TRUE( + this->IsUrlPotentiallyTrustworthy("https://example.com/fun.html")); + EXPECT_FALSE( + this->IsUrlPotentiallyTrustworthy("http://example.com/fun.html")); + + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("ftp://example.com/")); + + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy("wss://example.com/fun.html")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("ws://example.com/fun.html")); + + EXPECT_FALSE( + this->IsUrlPotentiallyTrustworthy("http://localhost.com/fun.html")); + EXPECT_TRUE( + this->IsUrlPotentiallyTrustworthy("https://localhost.com/fun.html")); + + EXPECT_FALSE( + this->IsUrlPotentiallyTrustworthy("http://127.example.com/fun.html")); + EXPECT_TRUE( + this->IsUrlPotentiallyTrustworthy("https://127.example.com/fun.html")); + + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("http://[::2]/fun.html")); + EXPECT_FALSE( + this->IsUrlPotentiallyTrustworthy("http://[::1].example.com/fun.html")); + + // IPv4 mapped IPv6 literals for loopback. + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("http://[::ffff:127.0.0.1]/")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("http://[::ffff:7f00:1]")); + + // IPv4 compatible IPv6 literal for loopback. + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("http://[::127.0.0.1]")); + + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("http://loopback")); + + // Legacy localhost names. + EXPECT_FALSE( + this->IsUrlPotentiallyTrustworthy("http://localhost.localdomain")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("http://localhost6")); + EXPECT_FALSE( + this->IsUrlPotentiallyTrustworthy("ftp://localhost6.localdomain6")); + + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy( + "filesystem:http://www.example.com/temporary/")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy( + "filesystem:ftp://www.example.com/temporary/")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy( + "filesystem:https://www.example.com/temporary/")); + + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy( + "blob:http://www.example.com/guid-goes-here")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy( + "blob:ftp://www.example.com/guid-goes-here")); + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy( + "blob:https://www.example.com/guid-goes-here")); + + EXPECT_FALSE( + this->IsUrlPotentiallyTrustworthy("filesystem:data:text/html,Hello")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy("filesystem:about:blank")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy( + "blob:blob:https://example.com/578223a1-8c13-17b3-84d5-eca045ae384a")); + EXPECT_FALSE(this->IsUrlPotentiallyTrustworthy( + "filesystem:blob:https://example.com/" + "578223a1-8c13-17b3-84d5-eca045ae384a")); + + EXPECT_TRUE(this->IsUrlPotentiallyTrustworthy( + "quic-transport://example.com/counter")); +} + +TYPED_TEST_P(AbstractTrustworthinessTest, TestcasesInheritedFromBlink) { + struct TestCase { + bool is_potentially_trustworthy; + bool is_localhost; + const char* url; + }; + + TestCase inputs[] = { + // Access is granted to webservers running on localhost. + {true, true, "http://localhost"}, + {true, true, "http://localhost."}, + {true, true, "http://LOCALHOST"}, + {true, true, "http://localhost:100"}, + {true, true, "http://a.localhost"}, + {true, true, "http://a.b.localhost"}, + {true, true, "http://127.0.0.1"}, + {true, true, "http://127.0.0.2"}, + {true, true, "http://127.1.0.2"}, + {true, true, "http://0177.00.00.01"}, + {true, true, "http://[::1]"}, + {true, true, "http://[0:0::1]"}, + {true, true, "http://[0:0:0:0:0:0:0:1]"}, + {true, true, "http://[::1]:21"}, + {true, true, "http://127.0.0.1:8080"}, + {true, true, "ftp://127.0.0.1"}, + {true, true, "ftp://127.0.0.1:443"}, + {true, true, "ws://127.0.0.1"}, + + // Non-localhost over HTTP + {false, false, "http://[1::]"}, + {false, false, "http://[::2]"}, + {false, false, "http://[1::1]"}, + {false, false, "http://[1:2::3]"}, + {false, false, "http://[::127.0.0.1]"}, + {false, false, "http://a.127.0.0.1"}, + {false, false, "http://127.0.0.1.b"}, + {false, false, "http://localhost.a"}, + + // loopback resolves to localhost on Windows, but not + // recognized generically here. + {false, false, "http://loopback"}, + + // IPv4 mapped IPv6 literals for 127.0.0.1. + {false, false, "http://[::ffff:127.0.0.1]"}, + {false, false, "http://[::ffff:7f00:1]"}, + + // IPv4 compatible IPv6 literal for 127.0.0.1. + {false, false, "http://[::127.0.0.1]"}, + + // Legacy localhost names. + {false, false, "http://localhost.localdomain"}, + {false, false, "http://localhost6"}, + {false, false, "ftp://localhost6.localdomain6"}, + + // Secure transports are considered trustworthy. + {true, false, "https://foobar.com"}, + {true, false, "wss://foobar.com"}, + {true, false, "quic-transport://example.com/counter"}, + + // Insecure transports are not considered trustworthy. + {false, false, "ftp://foobar.com"}, + {false, false, "http://foobar.com"}, + {false, false, "http://foobar.com:443"}, + {false, false, "ws://foobar.com"}, + {false, false, "custom-scheme://example.com"}, + + // Local files are considered trustworthy. + {true, false, "file:///home/foobar/index.html"}, + + // blob: URLs must look to the inner URL's origin, and apply the same + // rules as above. Spot check some of them + {true, true, + "blob:http://localhost:1000/578223a1-8c13-17b3-84d5-eca045ae384a"}, + {true, false, + "blob:https://foopy:99/578223a1-8c13-17b3-84d5-eca045ae384a"}, + {false, false, "blob:http://baz:99/578223a1-8c13-17b3-84d5-eca045ae384a"}, + {false, false, "blob:ftp://evil:99/578223a1-8c13-17b3-84d5-eca045ae384a"}, + {false, false, "blob:data:text/html,Hello"}, + {false, false, "blob:about:blank"}, + {false, false, + "blob:blob:https://example.com/578223a1-8c13-17b3-84d5-eca045ae384a"}, + + // filesystem: URLs work the same as blob: URLs, and look to the inner + // URL for security origin. + {true, true, "filesystem:http://localhost:1000/foo"}, + {true, false, "filesystem:https://foopy:99/foo"}, + {false, false, "filesystem:http://baz:99/foo"}, + {false, false, "filesystem:ftp://evil:99/foo"}, + {false, false, "filesystem:data:text/html,Hello"}, + {false, false, "filesystem:about:blank"}, + {false, false, + "filesystem:blob:https://example.com/" + "578223a1-8c13-17b3-84d5-eca045ae384a"}, + + // about: and data: URLs. + {false, false, "about:blank"}, + {false, false, "about:srcdoc"}, + {false, false, "data:text/html,Hello"}, + }; + + for (size_t i = 0; i < base::size(inputs); ++i) { + SCOPED_TRACE(inputs[i].url); + auto origin = this->CreateOriginFromString(inputs[i].url); + EXPECT_EQ(inputs[i].is_potentially_trustworthy, + this->IsOriginPotentiallyTrustworthy(origin)); + EXPECT_EQ(inputs[i].is_localhost, this->IsOriginOfLocalhost(origin)); + + GURL test_gurl(inputs[i].url); + if (!(test_gurl.SchemeIsBlob() || test_gurl.SchemeIsFileSystem())) { + // Check that the origin's notion of localhost matches //net's notion of + // localhost. This is skipped for blob: and filesystem: URLs since + // blink::SecurityOrigin uses their inner URL's origin. + EXPECT_EQ(net::IsLocalhost(GURL(inputs[i].url)), + this->IsOriginOfLocalhost(origin)); + } + } +} + +REGISTER_TYPED_TEST_SUITE_P(AbstractTrustworthinessTest, + OpaqueOrigins, + OriginFromString, + CustomSchemes, + UrlFromString, + TestcasesInheritedFromBlink); + +} // namespace test +} // namespace network + +#endif // SERVICES_NETWORK_PUBLIC_CPP_IS_POTENTIALLY_TRUSTWORTHY_UNITTEST_H_ diff --git a/chromium/services/network/public/cpp/isolation_info_mojom_traits.cc b/chromium/services/network/public/cpp/isolation_info_mojom_traits.cc index b9434b1a64e..a2e0f3ae2df 100644 --- a/chromium/services/network/public/cpp/isolation_info_mojom_traits.cc +++ b/chromium/services/network/public/cpp/isolation_info_mojom_traits.cc @@ -51,6 +51,7 @@ bool StructTraits<network::mojom::IsolationInfoDataView, net::IsolationInfo>:: base::Optional<url::Origin> frame_origin; net::SiteForCookies site_for_cookies; net::IsolationInfo::RequestType request_type; + base::Optional<std::vector<net::SchemefulSite>> mojo_party_context; if (!data.ReadTopFrameOrigin(&top_frame_origin)) { network::debug::SetDeserializationCrashKeyString("isolation_top_origin"); @@ -61,14 +62,23 @@ bool StructTraits<network::mojom::IsolationInfoDataView, net::IsolationInfo>:: return false; } if (!data.ReadSiteForCookies(&site_for_cookies) || - !data.ReadRequestType(&request_type)) { + !data.ReadRequestType(&request_type) || + !data.ReadPartyContext(&mojo_party_context)) { return false; } + base::Optional<std::set<net::SchemefulSite>> party_context; + if (mojo_party_context.has_value()) { + party_context = std::set<net::SchemefulSite>(mojo_party_context->begin(), + mojo_party_context->end()); + if (party_context->size() != mojo_party_context->size()) + return false; + } + base::Optional<net::IsolationInfo> isolation_info = - net::IsolationInfo::CreateIfConsistent(request_type, top_frame_origin, - frame_origin, site_for_cookies, - data.opaque_and_non_transient()); + net::IsolationInfo::CreateIfConsistent( + request_type, top_frame_origin, frame_origin, site_for_cookies, + data.opaque_and_non_transient(), std::move(party_context)); if (!isolation_info) { network::debug::SetDeserializationCrashKeyString("isolation_inconsistent"); return false; diff --git a/chromium/services/network/public/cpp/isolation_info_mojom_traits.h b/chromium/services/network/public/cpp/isolation_info_mojom_traits.h index 44a123f8e8a..c9a5a323eb5 100644 --- a/chromium/services/network/public/cpp/isolation_info_mojom_traits.h +++ b/chromium/services/network/public/cpp/isolation_info_mojom_traits.h @@ -9,6 +9,7 @@ #include "mojo/public/cpp/bindings/struct_traits.h" #include "net/base/isolation_info.h" #include "net/cookies/site_for_cookies.h" +#include "services/network/public/cpp/schemeful_site_mojom_traits.h" #include "services/network/public/mojom/isolation_info.mojom-shared.h" #include "url/mojom/origin_mojom_traits.h" #include "url/origin.h" @@ -52,6 +53,11 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) return input.site_for_cookies(); } + static const base::Optional<std::set<net::SchemefulSite>> party_context( + const net::IsolationInfo& input) { + return input.party_context_; + } + static bool Read(network::mojom::IsolationInfoDataView data, net::IsolationInfo* out); }; diff --git a/chromium/services/network/public/cpp/isolation_info_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/isolation_info_mojom_traits_unittest.cc index 7e2b034d1ce..f4260564cd5 100644 --- a/chromium/services/network/public/cpp/isolation_info_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/isolation_info_mojom_traits_unittest.cc @@ -20,21 +20,34 @@ namespace mojo { TEST(IsolationInfoMojomTraitsTest, SerializeAndDeserialize) { const url::Origin kOrigin1 = url::Origin::Create(GURL("https://a.test/")); const url::Origin kOrigin2 = url::Origin::Create(GURL("https://b.test/")); + + const base::Optional<std::set<net::SchemefulSite>> kPartyContext1 = + base::nullopt; + const base::Optional<std::set<net::SchemefulSite>> kPartyContext2 = + std::set<net::SchemefulSite>(); + const base::Optional<std::set<net::SchemefulSite>> kPartyContext3 = + std::set<net::SchemefulSite>{ + net::SchemefulSite(url::Origin::Create(GURL("https://c.test/")))}; + std::vector<net::IsolationInfo> keys = { net::IsolationInfo(), net::IsolationInfo::CreateTransient(), net::IsolationInfo::CreateOpaqueAndNonTransient(), - net::IsolationInfo::Create(net::IsolationInfo::RequestType::kMainFrame, - kOrigin1, kOrigin1, - net::SiteForCookies::FromOrigin(kOrigin1)), + net::IsolationInfo::Create( + net::IsolationInfo::RequestType::kMainFrame, kOrigin1, kOrigin1, + net::SiteForCookies::FromOrigin(kOrigin1), kPartyContext2), + net::IsolationInfo::Create( + net::IsolationInfo::RequestType::kSubFrame, kOrigin1, kOrigin2, + net::SiteForCookies::FromOrigin(kOrigin1), kPartyContext2), net::IsolationInfo::Create(net::IsolationInfo::RequestType::kSubFrame, - kOrigin1, kOrigin2, - net::SiteForCookies::FromOrigin(kOrigin1)), - net::IsolationInfo::Create(net::IsolationInfo::RequestType::kSubFrame, - kOrigin1, kOrigin2, net::SiteForCookies()), + kOrigin1, kOrigin2, net::SiteForCookies(), + kPartyContext3), + net::IsolationInfo::Create( + net::IsolationInfo::RequestType::kOther, kOrigin1, kOrigin1, + net::SiteForCookies::FromOrigin(kOrigin1), kPartyContext1), net::IsolationInfo::Create(net::IsolationInfo::RequestType::kOther, - kOrigin1, kOrigin1, - net::SiteForCookies::FromOrigin(kOrigin1)), + url::Origin(), url::Origin(), + net::SiteForCookies(), kPartyContext1), net::IsolationInfo::Create(net::IsolationInfo::RequestType::kOther, url::Origin(), url::Origin(), net::SiteForCookies()), @@ -44,7 +57,7 @@ TEST(IsolationInfoMojomTraitsTest, SerializeAndDeserialize) { net::IsolationInfo copied; EXPECT_TRUE( mojo::test::SerializeAndDeserialize<network::mojom::IsolationInfo>( - &original, &copied)); + original, copied)); EXPECT_TRUE(original.IsEqualForTesting(copied)); } } diff --git a/chromium/services/network/public/cpp/isolation_opt_in_hints.cc b/chromium/services/network/public/cpp/isolation_opt_in_hints.cc deleted file mode 100644 index 8453fcaabe6..00000000000 --- a/chromium/services/network/public/cpp/isolation_opt_in_hints.cc +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2019 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/isolation_opt_in_hints.h" - -#include <type_traits> - -namespace network { - -IsolationOptInHints GetIsolationOptInHintFromString( - const std::string& hint_str) { - if (hint_str == "prefer_isolated_event_loop") - return IsolationOptInHints::PREFER_ISOLATED_EVENT_LOOP; - if (hint_str == "prefer_isolated_memory") - return IsolationOptInHints::PREFER_ISOLATED_MEMORY; - if (hint_str == "for_side_channel_protection") - return IsolationOptInHints::FOR_SIDE_CHANNEL_PROTECTION; - if (hint_str == "for_memory_measurement") - return IsolationOptInHints::FOR_MEMORY_ISOLATION; - return IsolationOptInHints::NO_HINTS; -} - -IsolationOptInHints& operator|=(IsolationOptInHints& lhs, - IsolationOptInHints& rhs) { - lhs = static_cast<IsolationOptInHints>( - static_cast<std::underlying_type<IsolationOptInHints>::type>(lhs) | - static_cast<std::underlying_type<IsolationOptInHints>::type>(rhs)); - return lhs; -} - -IsolationOptInHints& operator&(IsolationOptInHints& lhs, - IsolationOptInHints& rhs) { - lhs = static_cast<IsolationOptInHints>( - static_cast<std::underlying_type<IsolationOptInHints>::type>(lhs) & - static_cast<std::underlying_type<IsolationOptInHints>::type>(rhs)); - return lhs; -} - -} // namespace network diff --git a/chromium/services/network/public/cpp/isolation_opt_in_hints.h b/chromium/services/network/public/cpp/isolation_opt_in_hints.h deleted file mode 100644 index 62ccf974275..00000000000 --- a/chromium/services/network/public/cpp/isolation_opt_in_hints.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2019 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_ISOLATION_OPT_IN_HINTS_H_ -#define SERVICES_NETWORK_PUBLIC_CPP_ISOLATION_OPT_IN_HINTS_H_ - -#include <string> - -#include "base/component_export.h" - -namespace network { - -// The following definitions correspond to the draft spec found here: -// https://github.com/domenic/origin-isolation#proposed-hints. -enum class IsolationOptInHints : unsigned { - NO_HINTS = 0x0, - PREFER_ISOLATED_EVENT_LOOP = 0x1, - PREFER_ISOLATED_MEMORY = 0x2, - FOR_SIDE_CHANNEL_PROTECTION = 0x4, - FOR_MEMORY_ISOLATION = 0x8, - ALL_HINTS_ACTIVE = 0xF -}; - -// Converts hint strings into their corresponding IsolationOptInHints values. -// The hint strings are specified at -// https://github.com/domenic/origin-isolation#proposed-hints. -COMPONENT_EXPORT(NETWORK_CPP_BASE) -IsolationOptInHints GetIsolationOptInHintFromString( - const std::string& hint_str); - -COMPONENT_EXPORT(NETWORK_CPP_BASE) -IsolationOptInHints& operator|=(IsolationOptInHints& lhs, - IsolationOptInHints& rhs); - -COMPONENT_EXPORT(NETWORK_CPP_BASE) -IsolationOptInHints& operator&(IsolationOptInHints& lhs, - IsolationOptInHints& rhs); - -} // namespace network -#endif // SERVICES_NETWORK_PUBLIC_CPP_ISOLATION_OPT_IN_HINTS_H_ diff --git a/chromium/services/network/public/cpp/load_timing_info_mojom_traits.cc b/chromium/services/network/public/cpp/load_timing_info_mojom_traits.cc index 9039014d7f8..67ec1220638 100644 --- a/chromium/services/network/public/cpp/load_timing_info_mojom_traits.cc +++ b/chromium/services/network/public/cpp/load_timing_info_mojom_traits.cc @@ -34,6 +34,8 @@ bool StructTraits<network::mojom::LoadTimingInfoDataView, net::LoadTimingInfo>:: data.ReadSendEnd(&out->send_end) && data.ReadReceiveHeadersStart(&out->receive_headers_start) && data.ReadReceiveHeadersEnd(&out->receive_headers_end) && + data.ReadReceiveNonInformationalHeadersStart( + &out->receive_non_informational_headers_start) && data.ReadFirstEarlyHintsTime(&out->first_early_hints_time) && data.ReadPushStart(&out->push_start) && data.ReadPushEnd(&out->push_end) && diff --git a/chromium/services/network/public/cpp/load_timing_info_mojom_traits.h b/chromium/services/network/public/cpp/load_timing_info_mojom_traits.h index 0bec1dbc1b5..64d69a622ce 100644 --- a/chromium/services/network/public/cpp/load_timing_info_mojom_traits.h +++ b/chromium/services/network/public/cpp/load_timing_info_mojom_traits.h @@ -96,6 +96,11 @@ struct StructTraits<network::mojom::LoadTimingInfoDataView, return obj.receive_headers_end; } + static base::TimeTicks receive_non_informational_headers_start( + const net::LoadTimingInfo& obj) { + return obj.receive_non_informational_headers_start; + } + static base::TimeTicks first_early_hints_time( const net::LoadTimingInfo& obj) { return obj.first_early_hints_time; 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 9567cfd9c8e..f7f7db1f7c2 100644 --- a/chromium/services/network/public/cpp/net_ipc_param_traits.cc +++ b/chromium/services/network/public/cpp/net_ipc_param_traits.cc @@ -11,30 +11,6 @@ namespace IPC { -void ParamTraits<net::AuthChallengeInfo>::Write(base::Pickle* m, - const param_type& p) { - WriteParam(m, p.is_proxy); - WriteParam(m, p.challenger); - WriteParam(m, p.scheme); - WriteParam(m, p.realm); - WriteParam(m, p.challenge); - WriteParam(m, p.path); -} - -bool ParamTraits<net::AuthChallengeInfo>::Read(const base::Pickle* m, - base::PickleIterator* iter, - param_type* r) { - return ReadParam(m, iter, &r->is_proxy) && - ReadParam(m, iter, &r->challenger) && ReadParam(m, iter, &r->scheme) && - ReadParam(m, iter, &r->realm) && ReadParam(m, iter, &r->challenge) && - ReadParam(m, iter, &r->path); -} - -void ParamTraits<net::AuthChallengeInfo>::Log(const param_type& p, - std::string* l) { - l->append("<AuthChallengeInfo>"); -} - void ParamTraits<net::AuthCredentials>::Write(base::Pickle* m, const param_type& p) { WriteParam(m, p.username()); @@ -472,6 +448,7 @@ void ParamTraits<net::LoadTimingInfo>::Write(base::Pickle* m, WriteParam(m, p.send_end); WriteParam(m, p.receive_headers_start); WriteParam(m, p.receive_headers_end); + WriteParam(m, p.receive_non_informational_headers_start); WriteParam(m, p.first_early_hints_time); WriteParam(m, p.push_start); WriteParam(m, p.push_end); @@ -503,6 +480,7 @@ bool ParamTraits<net::LoadTimingInfo>::Read(const base::Pickle* m, ReadParam(m, iter, &r->send_end) && ReadParam(m, iter, &r->receive_headers_start) && ReadParam(m, iter, &r->receive_headers_end) && + ReadParam(m, iter, &r->receive_non_informational_headers_start) && ReadParam(m, iter, &r->first_early_hints_time) && ReadParam(m, iter, &r->push_start) && ReadParam(m, iter, &r->push_end); } @@ -542,6 +520,8 @@ void ParamTraits<net::LoadTimingInfo>::Log(const param_type& p, l->append(", "); LogParam(p.receive_headers_end, l); l->append(", "); + LogParam(p.receive_non_informational_headers_start, l); + l->append(", "); LogParam(p.first_early_hints_time, l); l->append(", "); LogParam(p.push_start, l); @@ -552,23 +532,19 @@ void ParamTraits<net::LoadTimingInfo>::Log(const param_type& p, void ParamTraits<net::SiteForCookies>::Write(base::Pickle* m, const param_type& p) { - WriteParam(m, p.scheme()); - WriteParam(m, p.registrable_domain()); + WriteParam(m, p.site()); WriteParam(m, p.schemefully_same()); } bool ParamTraits<net::SiteForCookies>::Read(const base::Pickle* m, base::PickleIterator* iter, param_type* r) { - std::string scheme, registrable_domain; + net::SchemefulSite site; bool schemefully_same; - if (!ReadParam(m, iter, &scheme) || - !ReadParam(m, iter, ®istrable_domain) || - !ReadParam(m, iter, &schemefully_same)) + if (!ReadParam(m, iter, &site) || !ReadParam(m, iter, &schemefully_same)) return false; - return net::SiteForCookies::FromWire(scheme, registrable_domain, - schemefully_same, r); + return net::SiteForCookies::FromWire(site, schemefully_same, r); } void ParamTraits<net::SiteForCookies>::Log(const param_type& p, @@ -618,6 +594,26 @@ void ParamTraits<url::Origin>::Log(const url::Origin& p, std::string* l) { l->append(p.Serialize()); } +void ParamTraits<net::SchemefulSite>::Write(base::Pickle* m, + const net::SchemefulSite& p) { + WriteParam(m, p.site_as_origin_); +} + +bool ParamTraits<net::SchemefulSite>::Read(const base::Pickle* m, + base::PickleIterator* iter, + net::SchemefulSite* p) { + url::Origin site_as_origin; + if (!ReadParam(m, iter, &site_as_origin)) + return false; + + return net::SchemefulSite::FromWire(site_as_origin, p); +} + +void ParamTraits<net::SchemefulSite>::Log(const net::SchemefulSite& p, + std::string* l) { + l->append(p.Serialize()); +} + } // namespace IPC // Generation of IPC definitions. diff --git a/chromium/services/network/public/cpp/net_ipc_param_traits.h b/chromium/services/network/public/cpp/net_ipc_param_traits.h index 53f744155be..5af4b3dabc0 100644 --- a/chromium/services/network/public/cpp/net_ipc_param_traits.h +++ b/chromium/services/network/public/cpp/net_ipc_param_traits.h @@ -19,6 +19,7 @@ #include "net/base/load_timing_info.h" #include "net/base/proxy_server.h" #include "net/base/request_priority.h" +#include "net/base/schemeful_site.h" #include "net/cert/cert_verify_result.h" #include "net/cert/ct_policy_status.h" #include "net/cert/signed_certificate_timestamp.h" @@ -51,16 +52,6 @@ namespace IPC { template <> -struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ParamTraits<net::AuthChallengeInfo> { - typedef net::AuthChallengeInfo param_type; - static void Write(base::Pickle* m, const param_type& p); - static bool Read(const base::Pickle* m, - base::PickleIterator* iter, - param_type* r); - static void Log(const param_type& p, std::string* l); -}; - -template <> struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ParamTraits<net::AuthCredentials> { typedef net::AuthCredentials param_type; static void Write(base::Pickle* m, const param_type& p); @@ -244,6 +235,16 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ParamTraits<url::Origin> { static void Log(const param_type& p, std::string* l); }; +template <> +struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ParamTraits<net::SchemefulSite> { + typedef net::SchemefulSite param_type; + static void Write(base::Pickle* m, const param_type& p); + static bool Read(const base::Pickle* m, + base::PickleIterator* iter, + param_type* p); + static void Log(const param_type& p, std::string* l); +}; + } // namespace IPC #endif // INTERNAL_SERVICES_NETWORK_PUBLIC_CPP_NET_IPC_PARAM_TRAITS_H_ 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 edab086e7d1..eb3522eebf7 100644 --- a/chromium/services/network/public/cpp/network_ipc_param_traits.cc +++ b/chromium/services/network/public/cpp/network_ipc_param_traits.cc @@ -9,161 +9,6 @@ #include "ipc/ipc_platform_file.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "net/http/http_util.h" -#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h" -#include "services/network/public/mojom/data_pipe_getter.mojom.h" -#include "services/network/public/mojom/url_loader.mojom-shared.h" - -namespace IPC { - -void ParamTraits<network::DataElement>::Write(base::Pickle* m, - const param_type& p) { - WriteParam(m, static_cast<int>(p.type())); - switch (p.type()) { - case network::mojom::DataElementType::kBytes: { - m->WriteData(p.bytes(), static_cast<int>(p.length())); - break; - } - case network::mojom::DataElementType::kFile: { - WriteParam(m, p.path()); - WriteParam(m, p.offset()); - WriteParam(m, p.length()); - WriteParam(m, p.expected_modification_time()); - break; - } - case network::mojom::DataElementType::kDataPipe: { - WriteParam(m, p.CloneDataPipeGetter().PassPipe().release()); - break; - } - case network::mojom::DataElementType::kChunkedDataPipe: { - WriteParam(m, const_cast<network::DataElement&>(p) - .ReleaseChunkedDataPipeGetter() - .PassPipe() - .release()); - break; - } - case network::mojom::DataElementType::kReadOnceStream: - case network::mojom::DataElementType::kUnknown: { - NOTREACHED(); - break; - } - } -} - -bool ParamTraits<network::DataElement>::Read(const base::Pickle* m, - base::PickleIterator* iter, - param_type* r) { - int type; - if (!ReadParam(m, iter, &type)) - return false; - switch (static_cast<network::mojom::DataElementType>(type)) { - case network::mojom::DataElementType::kBytes: { - const char* data; - int len; - if (!iter->ReadData(&data, &len)) - return false; - r->SetToBytes(data, len); - return true; - } - case network::mojom::DataElementType::kFile: { - base::FilePath file_path; - uint64_t offset, length; - base::Time expected_modification_time; - if (!ReadParam(m, iter, &file_path)) - return false; - if (!ReadParam(m, iter, &offset)) - return false; - if (!ReadParam(m, iter, &length)) - return false; - if (!ReadParam(m, iter, &expected_modification_time)) - return false; - r->SetToFilePathRange(file_path, offset, length, - expected_modification_time); - return true; - } - case network::mojom::DataElementType::kDataPipe: { - mojo::MessagePipeHandle message_pipe; - if (!ReadParam(m, iter, &message_pipe)) - return false; - mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter( - mojo::ScopedMessagePipeHandle(message_pipe), 0u); - r->SetToDataPipe(std::move(data_pipe_getter)); - return true; - } - case network::mojom::DataElementType::kChunkedDataPipe: { - mojo::MessagePipeHandle message_pipe; - if (!ReadParam(m, iter, &message_pipe)) - return false; - mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter(mojo::ScopedMessagePipeHandle(message_pipe), - 0u); - - r->SetToChunkedDataPipe(std::move(chunked_data_pipe_getter)); - return true; - } - case network::mojom::DataElementType::kReadOnceStream: - case network::mojom::DataElementType::kUnknown: { - NOTREACHED(); - return false; - } - } - return false; -} - -void ParamTraits<network::DataElement>::Log(const param_type& p, - std::string* l) { - l->append("<network::DataElement>"); -} - -void ParamTraits<scoped_refptr<network::ResourceRequestBody>>::Write( - base::Pickle* m, - const param_type& p) { - WriteParam(m, p.get() != nullptr); - if (p.get()) { - WriteParam(m, *p->elements()); - WriteParam(m, p->identifier()); - WriteParam(m, p->contains_sensitive_info()); - } -} - -bool ParamTraits<scoped_refptr<network::ResourceRequestBody>>::Read( - const base::Pickle* m, - base::PickleIterator* iter, - param_type* r) { - bool has_object; - if (!ReadParam(m, iter, &has_object)) - return false; - if (!has_object) - return true; - std::vector<network::DataElement> elements; - if (!ReadParam(m, iter, &elements)) - return false; - // A chunked element is only allowed if it's the only one element. - if (elements.size() > 1) { - for (const auto& element : elements) { - if (element.type() == network::mojom::DataElementType::kChunkedDataPipe) - return false; - } - } - int64_t identifier; - if (!ReadParam(m, iter, &identifier)) - return false; - bool contains_sensitive_info; - if (!ReadParam(m, iter, &contains_sensitive_info)) - return false; - *r = new network::ResourceRequestBody; - (*r)->swap_elements(&elements); - (*r)->set_identifier(identifier); - (*r)->set_contains_sensitive_info(contains_sensitive_info); - return true; -} - -void ParamTraits<scoped_refptr<network::ResourceRequestBody>>::Log( - const param_type& p, - std::string* l) { - l->append("<ResourceRequestBody>"); -} - -} // namespace IPC // Generation of IPC definitions. 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 a898d3f7ad7..25260fbb5a0 100644 --- a/chromium/services/network/public/cpp/network_ipc_param_traits.h +++ b/chromium/services/network/public/cpp/network_ipc_param_traits.h @@ -25,10 +25,8 @@ #include "net/nqe/effective_connection_type.h" #include "net/ssl/ssl_cert_request_info.h" #include "net/ssl/ssl_info.h" -#include "services/network/public/cpp/isolation_opt_in_hints.h" #include "services/network/public/cpp/net_ipc_param_traits.h" #include "services/network/public/cpp/origin_policy.h" -#include "services/network/public/cpp/resource_request_body.h" #include "services/network/public/cpp/url_loader_completion_status.h" #include "services/network/public/mojom/blocked_by_response_reason.mojom-shared.h" #include "services/network/public/mojom/cors.mojom-shared.h" @@ -41,43 +39,6 @@ // This file defines IPC::ParamTraits for network:: classes / structs. // For IPC::ParamTraits for net:: class / structs, see net_ipc_param_traits.h. -#ifndef INTERNAL_SERVICES_NETWORK_PUBLIC_CPP_NETWORK_IPC_PARAM_TRAITS_H_ -#define INTERNAL_SERVICES_NETWORK_PUBLIC_CPP_NETWORK_IPC_PARAM_TRAITS_H_ - -#undef IPC_MESSAGE_EXPORT -#define IPC_MESSAGE_EXPORT COMPONENT_EXPORT(NETWORK_CPP_BASE) - -namespace IPC { - -// TODO(Richard): Remove this traits after usage of -// content::mojom::OpenURLParams disappears. -template <> -struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ParamTraits<network::DataElement> { - typedef network::DataElement param_type; - static void Write(base::Pickle* m, const param_type& p); - static bool Read(const base::Pickle* m, - base::PickleIterator* iter, - param_type* r); - static void Log(const param_type& p, std::string* l); -}; - -// TODO(Richard): Remove this traits after usage of OpenURLParams struct -// disappears. -template <> -struct COMPONENT_EXPORT(NETWORK_CPP_BASE) - ParamTraits<scoped_refptr<network::ResourceRequestBody>> { - typedef scoped_refptr<network::ResourceRequestBody> param_type; - static void Write(base::Pickle* m, const param_type& p); - static bool Read(const base::Pickle* m, - base::PickleIterator* iter, - param_type* r); - static void Log(const param_type& p, std::string* l); -}; - -} // namespace IPC - -#endif // INTERNAL_SERVICES_NETWORK_PUBLIC_CPP_NETWORK_IPC_PARAM_TRAITS_H_ - IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::CorsError, network::mojom::CorsError::kMaxValue) @@ -132,15 +93,11 @@ IPC_ENUM_TRAITS_MAX_VALUE(network::mojom::TrustTokenOperationStatus, IPC_ENUM_TRAITS_MAX_VALUE(network::OriginPolicyState, network::OriginPolicyState::kMaxValue) -IPC_ENUM_TRAITS_MAX_VALUE(network::IsolationOptInHints, - network::IsolationOptInHints::ALL_HINTS_ACTIVE) - IPC_STRUCT_TRAITS_BEGIN(network::OriginPolicyContents) IPC_STRUCT_TRAITS_MEMBER(ids) IPC_STRUCT_TRAITS_MEMBER(feature_policy) IPC_STRUCT_TRAITS_MEMBER(content_security_policies) IPC_STRUCT_TRAITS_MEMBER(content_security_policies_report_only) - IPC_STRUCT_TRAITS_MEMBER(isolation_optin_hints) IPC_STRUCT_TRAITS_END() IPC_STRUCT_TRAITS_BEGIN(network::OriginPolicy) diff --git a/chromium/services/network/public/cpp/network_isolation_key_mojom_traits.cc b/chromium/services/network/public/cpp/network_isolation_key_mojom_traits.cc index e0e75a8009b..7b139feb984 100644 --- a/chromium/services/network/public/cpp/network_isolation_key_mojom_traits.cc +++ b/chromium/services/network/public/cpp/network_isolation_key_mojom_traits.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "services/network/public/cpp/network_isolation_key_mojom_traits.h" + #include "net/base/features.h" namespace mojo { @@ -11,7 +12,7 @@ bool StructTraits<network::mojom::NetworkIsolationKeyDataView, net::NetworkIsolationKey>:: Read(network::mojom::NetworkIsolationKeyDataView data, net::NetworkIsolationKey* out) { - base::Optional<url::Origin> top_frame_site, frame_site; + base::Optional<net::SchemefulSite> top_frame_site, frame_site; if (!data.ReadTopFrameSite(&top_frame_site)) return false; if (!data.ReadFrameSite(&frame_site)) @@ -25,9 +26,10 @@ bool StructTraits<network::mojom::NetworkIsolationKeyDataView, if (!frame_site.has_value()) { DCHECK(!base::FeatureList::IsEnabled( net::features::kAppendFrameOriginToNetworkIsolationKey)); - frame_site = url::Origin(); + frame_site = net::SchemefulSite(); } - *out = net::NetworkIsolationKey(top_frame_site.value(), frame_site.value()); + *out = net::NetworkIsolationKey(std::move(top_frame_site.value()), + std::move(frame_site.value())); } else { *out = net::NetworkIsolationKey(); } diff --git a/chromium/services/network/public/cpp/network_isolation_key_mojom_traits.h b/chromium/services/network/public/cpp/network_isolation_key_mojom_traits.h index 80043f328e3..c6e554e0674 100644 --- a/chromium/services/network/public/cpp/network_isolation_key_mojom_traits.h +++ b/chromium/services/network/public/cpp/network_isolation_key_mojom_traits.h @@ -7,9 +7,9 @@ #include "mojo/public/cpp/bindings/struct_traits.h" #include "net/base/network_isolation_key.h" +#include "net/base/schemeful_site.h" +#include "services/network/public/cpp/schemeful_site_mojom_traits.h" #include "services/network/public/mojom/network_isolation_key.mojom-shared.h" -#include "url/mojom/origin_mojom_traits.h" -#include "url/origin.h" namespace mojo { @@ -17,12 +17,12 @@ template <> struct COMPONENT_EXPORT(NETWORK_CPP_BASE) StructTraits<network::mojom::NetworkIsolationKeyDataView, net::NetworkIsolationKey> { - static const base::Optional<url::Origin>& top_frame_site( + static const base::Optional<net::SchemefulSite>& top_frame_site( const net::NetworkIsolationKey& input) { return input.GetTopFrameSite(); } - static const base::Optional<url::Origin>& frame_site( + static const base::Optional<net::SchemefulSite>& frame_site( const net::NetworkIsolationKey& input) { return input.GetFrameSite(); } diff --git a/chromium/services/network/public/cpp/network_isolation_key_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/network_isolation_key_mojom_traits_unittest.cc index 4482335ea0b..65c0ff6757c 100644 --- a/chromium/services/network/public/cpp/network_isolation_key_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/network_isolation_key_mojom_traits_unittest.cc @@ -26,7 +26,7 @@ TEST(NetworkIsolationKeyMojomTraitsTest, SerializeAndDeserialize) { SCOPED_TRACE(original.ToDebugString()); net::NetworkIsolationKey copied; EXPECT_TRUE(mojo::test::SerializeAndDeserialize< - network::mojom::NetworkIsolationKey>(&original, &copied)); + network::mojom::NetworkIsolationKey>(original, copied)); EXPECT_EQ(original, copied); } } @@ -57,7 +57,7 @@ TEST_F(NetworkIsolationKeyMojomTraitsWithFrameOriginTest, SCOPED_TRACE(original.ToDebugString()); net::NetworkIsolationKey copied; EXPECT_TRUE(mojo::test::SerializeAndDeserialize< - network::mojom::NetworkIsolationKey>(&original, &copied)); + network::mojom::NetworkIsolationKey>(original, copied)); EXPECT_EQ(original, copied); EXPECT_EQ(original.GetTopFrameSite(), copied.GetTopFrameSite()); EXPECT_EQ(original.GetFrameSite(), copied.GetFrameSite()); 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 3ac8d893279..efc8f609dd0 100644 --- a/chromium/services/network/public/cpp/network_param_mojom_traits.cc +++ b/chromium/services/network/public/cpp/network_param_mojom_traits.cc @@ -4,8 +4,23 @@ #include "services/network/public/cpp/network_param_mojom_traits.h" +#include "mojo/public/cpp/bindings/struct_traits.h" + namespace mojo { +bool StructTraits<network::mojom::AuthChallengeInfoDataView, + net::AuthChallengeInfo>:: + Read(network::mojom::AuthChallengeInfoDataView data, + net::AuthChallengeInfo* out) { + out->is_proxy = data.is_proxy(); + if (!data.ReadChallenger(&out->challenger) || + !data.ReadScheme(&out->scheme) || !data.ReadRealm(&out->realm) || + !data.ReadChallenge(&out->challenge) || !data.ReadPath(&out->path)) { + return false; + } + return true; +} + bool StructTraits<network::mojom::HttpVersionDataView, net::HttpVersion>::Read( network::mojom::HttpVersionDataView data, net::HttpVersion* out) { 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 81e3d8223a5..7e7fcf15d1d 100644 --- a/chromium/services/network/public/cpp/network_param_mojom_traits.h +++ b/chromium/services/network/public/cpp/network_param_mojom_traits.h @@ -5,14 +5,51 @@ #ifndef SERVICES_NETWORK_PUBLIC_CPP_NETWORK_PARAM_MOJOM_TRAITS_H_ #define SERVICES_NETWORK_PUBLIC_CPP_NETWORK_PARAM_MOJOM_TRAITS_H_ +#include "base/component_export.h" #include "mojo/public/cpp/bindings/struct_traits.h" +#include "net/base/auth.h" #include "net/http/http_version.h" -#include "services/network/public/mojom/network_param.mojom.h" +#include "services/network/public/mojom/network_param.mojom-shared.h" +#include "url/mojom/origin_mojom_traits.h" namespace mojo { template <> -class StructTraits<network::mojom::HttpVersionDataView, net::HttpVersion> { +class COMPONENT_EXPORT(NETWORK_CPP_BASE) + StructTraits<network::mojom::AuthChallengeInfoDataView, + net::AuthChallengeInfo> { + public: + static bool is_proxy(const net::AuthChallengeInfo& auth_challenge_info) { + return auth_challenge_info.is_proxy; + } + static const url::Origin& challenger( + const net::AuthChallengeInfo& auth_challenge_info) { + return auth_challenge_info.challenger; + } + static const std::string& scheme( + const net::AuthChallengeInfo& auth_challenge_info) { + return auth_challenge_info.scheme; + } + static const std::string& realm( + const net::AuthChallengeInfo& auth_challenge_info) { + return auth_challenge_info.realm; + } + static const std::string& challenge( + const net::AuthChallengeInfo& auth_challenge_info) { + return auth_challenge_info.challenge; + } + static const std::string& path( + const net::AuthChallengeInfo& auth_challenge_info) { + return auth_challenge_info.path; + } + + static bool Read(network::mojom::AuthChallengeInfoDataView data, + net::AuthChallengeInfo* out); +}; + +template <> +class COMPONENT_EXPORT(NETWORK_CPP_BASE) + StructTraits<network::mojom::HttpVersionDataView, net::HttpVersion> { public: static int16_t major_value(net::HttpVersion version) { return version.major_value(); diff --git a/chromium/services/network/public/cpp/not_implemented_url_loader_factory.cc b/chromium/services/network/public/cpp/not_implemented_url_loader_factory.cc index 149e6734a6e..56516cb21d1 100644 --- a/chromium/services/network/public/cpp/not_implemented_url_loader_factory.cc +++ b/chromium/services/network/public/cpp/not_implemented_url_loader_factory.cc @@ -10,7 +10,9 @@ namespace network { -NotImplementedURLLoaderFactory::NotImplementedURLLoaderFactory() = default; +NotImplementedURLLoaderFactory::NotImplementedURLLoaderFactory( + mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver) + : SelfDeletingURLLoaderFactory(std::move(factory_receiver)) {} NotImplementedURLLoaderFactory::~NotImplementedURLLoaderFactory() = default; @@ -29,9 +31,18 @@ void NotImplementedURLLoaderFactory::CreateLoaderAndStart( ->OnComplete(status); } -void NotImplementedURLLoaderFactory::Clone( - mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) { - receivers_.Add(this, std::move(receiver)); +// static +mojo::PendingRemote<network::mojom::URLLoaderFactory> +NotImplementedURLLoaderFactory::Create() { + mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote; + + // The NotImplementedURLLoaderFactory will delete itself when there are no + // more receivers - see the NotImplementedURLLoaderFactory::OnDisconnect + // method. + new NotImplementedURLLoaderFactory( + pending_remote.InitWithNewPipeAndPassReceiver()); + + return pending_remote; } } // namespace network diff --git a/chromium/services/network/public/cpp/not_implemented_url_loader_factory.h b/chromium/services/network/public/cpp/not_implemented_url_loader_factory.h index baca98d17a3..a630aade0bf 100644 --- a/chromium/services/network/public/cpp/not_implemented_url_loader_factory.h +++ b/chromium/services/network/public/cpp/not_implemented_url_loader_factory.h @@ -9,6 +9,7 @@ #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver_set.h" +#include "services/network/public/cpp/self_deleting_url_loader_factory.h" #include "services/network/public/mojom/url_loader_factory.mojom.h" namespace network { @@ -16,11 +17,18 @@ namespace network { // A URLLoaderFactory which just fails to create a loader with // net::ERR_NOT_IMPLEMENTED. class COMPONENT_EXPORT(NETWORK_CPP) NotImplementedURLLoaderFactory final - : public network::mojom::URLLoaderFactory { + : public SelfDeletingURLLoaderFactory { public: - NotImplementedURLLoaderFactory(); + // Returns mojo::PendingRemote to a newly constructed + // NotImplementedURLLoaderFactory. The factory is self-owned - it will delete + // itself once there are no more receivers (including the receiver associated + // with the returned mojo::PendingRemote and the receivers bound by the Clone + // method). + static mojo::PendingRemote<network::mojom::URLLoaderFactory> Create(); + ~NotImplementedURLLoaderFactory() override; + private: // network::mojom::URLLoaderFactory implementation. void CreateLoaderAndStart( mojo::PendingReceiver<network::mojom::URLLoader> receiver, @@ -32,11 +40,11 @@ class COMPONENT_EXPORT(NETWORK_CPP) NotImplementedURLLoaderFactory final const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) override; - void Clone(mojo::PendingReceiver<network::mojom::URLLoaderFactory> receiver) - override; - - private: - mojo::ReceiverSet<network::mojom::URLLoaderFactory> receivers_; + // Constructs a NotImplementedURLLoaderFactory object that will self-delete + // once all receivers disconnect (including |factory_receiver| below as well + // as receivers that connect via the Clone method). + explicit NotImplementedURLLoaderFactory( + mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver); DISALLOW_COPY_AND_ASSIGN(NotImplementedURLLoaderFactory); }; diff --git a/chromium/services/network/public/cpp/opaque_response_blocking.cc b/chromium/services/network/public/cpp/opaque_response_blocking.cc new file mode 100644 index 00000000000..442304eb8d9 --- /dev/null +++ b/chromium/services/network/public/cpp/opaque_response_blocking.cc @@ -0,0 +1,220 @@ +// Copyright 2021 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/opaque_response_blocking.h" + +#include "base/metrics/histogram_functions.h" +#include "base/strings/string_piece.h" +#include "net/url_request/url_request.h" +#include "services/network/public/cpp/cross_origin_read_blocking.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/mojom/url_response_head.mojom.h" + +namespace network { + +namespace { + +// This corresponds to "opaque-blocklisted-never-sniffed MIME type" in ORB spec. +bool IsOpaqueBlocklistedNeverSniffedMimeType(base::StringPiece mime_type) { + return CrossOriginReadBlocking::GetCanonicalMimeType(mime_type) == + CrossOriginReadBlocking::MimeType::kNeverSniffed; +} + +// ORB spec says that "An opaque-safelisted MIME type" is a JavaScript MIME type +// or a MIME type whose essence is "text/css" or "image/svg+xml". +bool IsOpaqueSafelistedMimeType(base::StringPiece mime_type) { + // Based on the spec: Is it a MIME type whose essence is "text/css" or + // "image/svg+xml"? + if (base::LowerCaseEqualsASCII(mime_type, "image/svg+xml") || + base::LowerCaseEqualsASCII(mime_type, "text/css")) { + return true; + } + + // Based on the spec: Is it a JavaScript MIME type? + if (CrossOriginReadBlocking::IsJavascriptMimeType(mime_type)) + return true; + + // https://github.com/annevk/orb/issues/20 tracks explicitly covering DASH + // mime type in the ORB algorithm. + if (base::LowerCaseEqualsASCII(mime_type, "application/dash+xml")) + return true; + + return false; +} + +// Return true for multimedia MIME types that +// 1) are not explicitly covered by ORB (e.g. that do not begin with "audio/", +// "image/", "video/" and that are not covered by +// IsOpaqueSafelistedMimeType). +// 2) would be recognized by sniffing from steps 6 or 7 of ORB: +// step 6. If the image type pattern matching algorithm ... +// step 7. If the audio or video type pattern matching algorithm ... +bool IsSniffableMultimediaType(base::StringPiece mime_type) { + if (base::LowerCaseEqualsASCII(mime_type, "application/ogg")) + return true; + + return false; +} + +// This corresponds to https://fetch.spec.whatwg.org/#ok-status +bool IsOkayHttpStatus(const mojom::URLResponseHead& response) { + if (!response.headers) + return false; + + int code = response.headers->response_code(); + return (200 <= code) && (code <= 299); +} + +bool IsHttpStatus(const mojom::URLResponseHead& response, + int expected_status_code) { + if (!response.headers) + return false; + + int code = response.headers->response_code(); + return code == expected_status_code; +} + +bool IsOpaqueResponse(const base::Optional<url::Origin>& request_initiator, + mojom::RequestMode request_mode, + const mojom::URLResponseHead& response) { + // ORB only applies to "no-cors" requests. + if (request_mode != mojom::RequestMode::kNoCors) + return false; + + // Browser-initiated requests are never opaque. + if (!request_initiator.has_value()) + return false; + + // Requests from foo.example.com will consult foo.example.com's service worker + // first (if one has been registered). The service worker can handle requests + // initiated by foo.example.com even if they are cross-origin (e.g. requests + // for bar.example.com). This is okay, because there is no security boundary + // between foo.example.com and the service worker of foo.example.com + because + // the response data is "conjured" within the service worker of + // foo.example.com (rather than being fetched from bar.example.com). + // Therefore such responses should not be blocked by CORB, unless the + // initiator opted out of CORS / opted into receiving an opaque response. See + // also https://crbug.com/803672. + if (response.was_fetched_via_service_worker) { + switch (response.response_type) { + case network::mojom::FetchResponseType::kBasic: + case network::mojom::FetchResponseType::kCors: + case network::mojom::FetchResponseType::kDefault: + case network::mojom::FetchResponseType::kError: + // Non-opaque responses shouldn't be blocked. + return false; + case network::mojom::FetchResponseType::kOpaque: + case network::mojom::FetchResponseType::kOpaqueRedirect: + // Opaque responses are eligible for blocking. Continue on... + break; + } + } + + return true; +} + +ResponseHeadersHeuristicForUma CalculateResponseHeadersHeuristicForUma( + const GURL& request_url, + const base::Optional<url::Origin>& request_initiator, + mojom::RequestMode request_mode, + const mojom::URLResponseHead& response) { + // Exclude responses that ORB doesn't apply to. + if (!IsOpaqueResponse(request_initiator, request_mode, response)) + return ResponseHeadersHeuristicForUma::kNonOpaqueResponse; + DCHECK(request_initiator.has_value()); + + // Same-origin requests are allowed (the spec doesn't explicitly deal with + // this). + url::Origin target_origin = url::Origin::Create(request_url); + if (request_initiator->IsSameOriginWith(target_origin)) + return ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders; + + // Presence of an "X-Content-Type-Options: nosniff" header means that ORB will + // reach a final decision in step 8, before reaching Javascript parsing in + // step 12: + // step 8. If nosniff is true, then return false. + // ... + // step 12. If response's body parses as JavaScript ... + if (CrossOriginReadBlocking::ResponseAnalyzer::HasNoSniff(response)) + return ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders; + + // If a mime type is missing then ORB will reach a final decision in step 10, + // before reaching Javascript parsing in step 12: + // step 10. If mimeType is failure, then return true. + // ... + // step 12. If response's body parses as JavaScript ... + std::string mime_type; + if (!response.headers || !response.headers->GetMimeType(&mime_type)) + return ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders; + + // Specific MIME types might make ORB reach a final decision before reaching + // Javascript parsing step: + // step 3.i. If mimeType is an opaque-safelisted MIME type, then return + // true. + // step 3.ii. If mimeType is an opaque-blocklisted-never-sniffed MIME + // type, then return false. + // ... + // step 11. If mimeType's essence starts with "audio/", "image/", or + // "video/", then return false. + // ... + // step 12. If response's body parses as JavaScript ... + if (IsOpaqueBlocklistedNeverSniffedMimeType(mime_type) || + IsOpaqueSafelistedMimeType(mime_type) || + IsSniffableMultimediaType(mime_type)) { + return ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders; + } + constexpr auto kCaseInsensitive = base::CompareCase::INSENSITIVE_ASCII; + if (base::StartsWith(mime_type, "audio/", kCaseInsensitive) || + base::StartsWith(mime_type, "image/", kCaseInsensitive) || + base::StartsWith(mime_type, "video/", kCaseInsensitive)) { + return ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders; + } + + // If the http response indicates an error, or a 206 response, then ORB will + // reach a final decision before reaching Javascript parsing in step 12: + // step 9. If response's status is not an ok status, then return false. + // ... + // step 12. If response's body parses as JavaScript ... + if (!IsOkayHttpStatus(response) || IsHttpStatus(response, 206)) + return ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders; + + // Otherwise we need to parse the response body as Javascript. + return ResponseHeadersHeuristicForUma::kRequiresJavascriptParsing; +} + +} // namespace + +void LogUmaForOpaqueResponseBlocking( + const GURL& request_url, + const base::Optional<url::Origin>& request_initiator, + mojom::RequestMode request_mode, + mojom::RequestDestination request_destination, + const mojom::URLResponseHead& response) { + ResponseHeadersHeuristicForUma response_headers_decision = + CalculateResponseHeadersHeuristicForUma(request_url, request_initiator, + request_mode, response); + base::UmaHistogramEnumeration( + "SiteIsolation.ORB.ResponseHeadersHeuristic.Decision", + response_headers_decision); + + switch (response_headers_decision) { + case ResponseHeadersHeuristicForUma::kNonOpaqueResponse: + break; + + case ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders: + base::UmaHistogramEnumeration( + "SiteIsolation.ORB.ResponseHeadersHeuristic.ProcessedBasedOnHeaders", + request_destination); + break; + + case ResponseHeadersHeuristicForUma::kRequiresJavascriptParsing: + base::UmaHistogramEnumeration( + "SiteIsolation.ORB.ResponseHeadersHeuristic." + "RequiresJavascriptParsing", + request_destination); + break; + } +} + +} // namespace network diff --git a/chromium/services/network/public/cpp/opaque_response_blocking.h b/chromium/services/network/public/cpp/opaque_response_blocking.h new file mode 100644 index 00000000000..5b622729616 --- /dev/null +++ b/chromium/services/network/public/cpp/opaque_response_blocking.h @@ -0,0 +1,58 @@ +// Copyright 2021 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_OPAQUE_RESPONSE_BLOCKING_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_OPAQUE_RESPONSE_BLOCKING_H_ + +#include "base/component_export.h" +#include "base/optional.h" +#include "services/network/public/mojom/fetch_api.mojom.h" +#include "services/network/public/mojom/url_response_head.mojom-forward.h" +#include "url/gurl.h" +#include "url/origin.h" + +namespace network { + +// Result of applying heuristics based on partial ORB algorithm (suitable only +// for UMA). +// +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. +enum class ResponseHeadersHeuristicForUma { + // ORB only applies to opaque respones. The enum value below includes: + // - mode != no-cors (e.g. mode=navigate or mode=cors) + // - browser-initiated requests + kNonOpaqueResponse = 0, + + // ORB algorithm can *surely* make a decision based on response headers. The + // enum value below covers all subresource requests: + // - scripts, images, video, etc. + // - both same-origin and cross-origin requests + kProcessedBasedOnHeaders = 1, + + // ORB algorithm *might* require parsing the response body as Javascript. + // + // This might be a false positive if the response: + // 1) sniffs as an audio/image/video format + // 2) represents a valid range response for a media element + kRequiresJavascriptParsing = 2, + + // Required enum value to support UMA macros. + kMaxValue = kRequiresJavascriptParsing, +}; + +// Logs UMA tracking expected behavior characteristics of the Opaque Response +// Blocking algorithm (a potential successor of CORB aka Cross-Origin Read +// Blocking). See also https://github.com/annevk/orb +COMPONENT_EXPORT(NETWORK_CPP) +void LogUmaForOpaqueResponseBlocking( + const GURL& request_url, + const base::Optional<url::Origin>& request_initiator, + mojom::RequestMode request_mode, + mojom::RequestDestination request_destination, + const mojom::URLResponseHead& response); + +} // namespace network + +#endif // SERVICES_NETWORK_PUBLIC_CPP_OPAQUE_RESPONSE_BLOCKING_H_ diff --git a/chromium/services/network/public/cpp/opaque_response_blocking_unittest.cc b/chromium/services/network/public/cpp/opaque_response_blocking_unittest.cc new file mode 100644 index 00000000000..80d42e0bb94 --- /dev/null +++ b/chromium/services/network/public/cpp/opaque_response_blocking_unittest.cc @@ -0,0 +1,261 @@ +// Copyright 2021 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/opaque_response_blocking.h" +#include "base/strings/string_util.h" +#include "base/test/metrics/histogram_tester.h" +#include "base/test/task_environment.h" +#include "net/base/mime_sniffer.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_util.h" +#include "services/network/public/mojom/url_response_head.mojom.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace network { +namespace { + +// ResourceType::kImage from resource_load_info.mojom +constexpr mojom::RequestDestination kDefaultRequestDestination = + mojom::RequestDestination::kImage; + +const char kDefaultRequestUrl[] = "https://target.example.com/foo"; + +struct TestInput { + GURL request_url; + base::Optional<url::Origin> request_initiator; + mojom::RequestMode request_mode; + mojom::RequestDestination request_destination; + mojom::URLResponseHeadPtr response; +}; + +class TestInputBuilder { + public: + TestInputBuilder() = default; + + // No copy constructor or assignment operator. + TestInputBuilder(const TestInputBuilder&) = delete; + TestInputBuilder& operator=(const TestInputBuilder&) = delete; + + TestInput Build() const { + std::string raw_headers = base::ReplaceStringPlaceholders( + "$1\nContent-Type: $2\n", {http_status_line_, mime_type_}, nullptr); + if (no_sniff_) + raw_headers += "X-Content-Type-Options: nosniff\n"; + + mojom::URLResponseHeadPtr response = mojom::URLResponseHead::New(); + response->headers = base::MakeRefCounted<net::HttpResponseHeaders>( + net::HttpUtil::AssembleRawHeaders(raw_headers)); + response->was_fetched_via_service_worker = false; + response->response_type = mojom::FetchResponseType::kDefault; + + return TestInput{ + .request_url = request_url_, + .request_initiator = request_initiator_, + .request_mode = request_mode_, + .request_destination = kDefaultRequestDestination, + .response = std::move(response), + }; + } + + TestInputBuilder& WithHttpStatus(std::string status_line) { + http_status_line_ = status_line; + return *this; + } + + TestInputBuilder& WithMimeType(std::string mime_type) { + mime_type_ = mime_type; + return *this; + } + + TestInputBuilder& WithInitiator( + const base::Optional<url::Origin>& initiator) { + request_initiator_ = initiator; + return *this; + } + + TestInputBuilder& WithMode(const mojom::RequestMode& mode) { + request_mode_ = mode; + return *this; + } + + TestInputBuilder& WithNoSniff() { + no_sniff_ = true; + return *this; + } + + private: + std::string http_status_line_ = "HTTP/1.1 200 OK"; + std::string mime_type_ = "application/octet-stream"; + GURL request_url_ = GURL(kDefaultRequestUrl); + base::Optional<url::Origin> request_initiator_ = + url::Origin::Create(GURL("https://initiator.example.com")); + mojom::RequestMode request_mode_ = mojom::RequestMode::kNoCors; + bool no_sniff_ = false; +}; + +void LogUmaForOpaqueResponseBlocking(const TestInput& test_input) { + network::LogUmaForOpaqueResponseBlocking( + test_input.request_url, test_input.request_initiator, + test_input.request_mode, test_input.request_destination, + *test_input.response); +} + +void LogUmaForOpaqueResponseBlocking( + const TestInputBuilder& test_input_builder) { + LogUmaForOpaqueResponseBlocking(test_input_builder.Build()); +} + +void TestUma(const TestInput& test_input, + const ResponseHeadersHeuristicForUma& expected_uma_decision) { + base::HistogramTester histograms; + LogUmaForOpaqueResponseBlocking(test_input); + + // Verify that the ...Heuristic.Decision UMA has only a single/unique bucket + // (for `expected_uma_decision`) and that only 1 sample has been logged for + // this bucket. + histograms.ExpectUniqueSample( + "SiteIsolation.ORB.ResponseHeadersHeuristic.Decision", + expected_uma_decision, 1 /* expected_count */); +} + +void TestUma(const TestInputBuilder& test_input_builder, + const ResponseHeadersHeuristicForUma& expected_uma_decision) { + TestUma(test_input_builder.Build(), expected_uma_decision); +} + +TEST(OpaqueResponseBlocking, DefaultTestInput) { + // Verify properties of the default test input: renderer-initiated, + // cross-origin, no-cors request with a successful application/octet-stream + // response. + TestInput default_test_input = TestInputBuilder().Build(); + EXPECT_TRUE(default_test_input.request_initiator.has_value()); + EXPECT_FALSE(default_test_input.request_initiator->IsSameOriginWith( + url::Origin::Create(default_test_input.request_url))); + EXPECT_EQ(mojom::RequestMode::kNoCors, default_test_input.request_mode); + EXPECT_TRUE(default_test_input.response); + EXPECT_TRUE(default_test_input.response->headers); + EXPECT_EQ(200, default_test_input.response->headers->response_code()); + std::string mime_type; + EXPECT_TRUE(default_test_input.response->headers->GetMimeType(&mime_type)); + EXPECT_EQ("application/octet-stream", mime_type); + + // Verify that the right UMA is logged for the default test input. + TestUma(default_test_input, + ResponseHeadersHeuristicForUma::kRequiresJavascriptParsing); +} + +TEST(OpaqueResponseBlocking, ResourceTypeUma) { + // RequiresJavascriptParsing case. + { + base::HistogramTester histograms; + TestUma(TestInputBuilder(), + ResponseHeadersHeuristicForUma::kRequiresJavascriptParsing); + histograms.ExpectTotalCount( + "SiteIsolation.ORB.ResponseHeadersHeuristic.ProcessedBasedOnHeaders", + 0); + histograms.ExpectUniqueSample( + "SiteIsolation.ORB.ResponseHeadersHeuristic.RequiresJavascriptParsing", + kDefaultRequestDestination, + 1 /* `expected_count` of `kDefaultRequestDestination` */); + } + + // ProcessedBasedOnHeaders case. + { + base::HistogramTester histograms; + TestUma(TestInputBuilder().WithNoSniff(), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + histograms.ExpectUniqueSample( + "SiteIsolation.ORB.ResponseHeadersHeuristic.ProcessedBasedOnHeaders", + kDefaultRequestDestination, + 1 /* `expected_count` of `kDefaultRequestDestination` */); + histograms.ExpectTotalCount( + "SiteIsolation.ORB.ResponseHeadersHeuristic.RequiresJavascriptParsing", + 0); + } + + // Non-opaque case.. + { + base::HistogramTester histograms; + LogUmaForOpaqueResponseBlocking( + TestInputBuilder().WithInitiator(base::nullopt)); + histograms.ExpectTotalCount( + "SiteIsolation.ORB.ResponseHeadersHeuristic.ProcessedBasedOnHeaders", + 0); + histograms.ExpectTotalCount( + "SiteIsolation.ORB.ResponseHeadersHeuristic.RequiresJavascriptParsing", + 0); + } + + LogUmaForOpaqueResponseBlocking(TestInputBuilder().WithNoSniff()); +} + +TEST(OpaqueResponseBlocking, NonOpaqueRequests) { + // Browser-initiated request. + TestUma(TestInputBuilder().WithInitiator(base::nullopt), + ResponseHeadersHeuristicForUma::kNonOpaqueResponse); + + // Mode != no-cors. + TestUma(TestInputBuilder().WithMode(mojom::RequestMode::kCors), + ResponseHeadersHeuristicForUma::kNonOpaqueResponse); + TestUma(TestInputBuilder().WithMode(mojom::RequestMode::kNavigate), + ResponseHeadersHeuristicForUma::kNonOpaqueResponse); +} + +TEST(OpaqueResponseBlocking, SameOrigin) { + TestUma(TestInputBuilder().WithInitiator( + url::Origin::Create(GURL(kDefaultRequestUrl))), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); +} + +TEST(OpaqueResponseBlocking, NoSniff) { + TestUma(TestInputBuilder().WithNoSniff(), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); +} + +TEST(OpaqueResponseBlocking, MimeTypes) { + // Unrecognized type: + TestUma(TestInputBuilder().WithMimeType("application/octet-stream"), + ResponseHeadersHeuristicForUma::kRequiresJavascriptParsing); + + // opaque-safelisted MIME types: + TestUma(TestInputBuilder().WithMimeType("application/dash+xml"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + TestUma(TestInputBuilder().WithMimeType("application/javascript"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + TestUma(TestInputBuilder().WithMimeType("image/svg+xml"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + TestUma(TestInputBuilder().WithMimeType("text/css"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + + // Example of a opaque-blocklisted-never-sniffed MIME type: + TestUma(TestInputBuilder().WithMimeType("application/pdf"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + + // Future multimedia types: + TestUma(TestInputBuilder().WithMimeType("audio/some-future-type"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + TestUma(TestInputBuilder().WithMimeType("image/some-future-type"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + TestUma(TestInputBuilder().WithMimeType("video/some-future-type"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); +} + +TEST(OpaqueResponseBlocking, HttpStatusCodes) { + TestUma(TestInputBuilder().WithHttpStatus("HTTP/1.1 200 OK"), + ResponseHeadersHeuristicForUma::kRequiresJavascriptParsing); + + TestUma(TestInputBuilder().WithHttpStatus("HTTP/1.1 206 Range"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + TestUma(TestInputBuilder().WithHttpStatus("HTTP/1.1 300 Redirect"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + TestUma(TestInputBuilder().WithHttpStatus("HTTP/1.1 400 NotFound"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); + TestUma(TestInputBuilder().WithHttpStatus("HTTP/1.1 500 ServerError"), + ResponseHeadersHeuristicForUma::kProcessedBasedOnHeaders); +} + +} // namespace +} // namespace network diff --git a/chromium/services/network/public/cpp/origin_isolation_parser.cc b/chromium/services/network/public/cpp/origin_agent_cluster_parser.cc index b324c3d01e8..af23159a5c2 100644 --- a/chromium/services/network/public/cpp/origin_isolation_parser.cc +++ b/chromium/services/network/public/cpp/origin_agent_cluster_parser.cc @@ -2,12 +2,12 @@ // 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/origin_isolation_parser.h" +#include "services/network/public/cpp/origin_agent_cluster_parser.h" #include "net/http/structured_headers.h" namespace network { -bool ParseOriginIsolation(const std::string& header_value) { +bool ParseOriginAgentCluster(const std::string& header_value) { const auto item = net::structured_headers::ParseItem(header_value); return item && item->item.is_boolean() && item->item.GetBoolean(); } diff --git a/chromium/services/network/public/cpp/origin_isolation_parser.h b/chromium/services/network/public/cpp/origin_agent_cluster_parser.h index dd4f7d7fc81..04610c5fff0 100644 --- a/chromium/services/network/public/cpp/origin_isolation_parser.h +++ b/chromium/services/network/public/cpp/origin_agent_cluster_parser.h @@ -2,22 +2,22 @@ // 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_ORIGIN_ISOLATION_PARSER_H_ -#define SERVICES_NETWORK_PUBLIC_CPP_ORIGIN_ISOLATION_PARSER_H_ +#ifndef SERVICES_NETWORK_PUBLIC_CPP_ORIGIN_AGENT_CLUSTER_PARSER_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_ORIGIN_AGENT_CLUSTER_PARSER_H_ #include <string> #include "base/component_export.h" namespace network { -// Parsing is done following the Origin-Isolation spec draft: -// https://github.com/whatwg/html/pull/5545 +// This part of the spec specifically handles header parsing: +// https://html.spec.whatwg.org/C/#initialise-the-document-object // // See the comment in network::PopulateParsedHeaders for restrictions on this // function. COMPONENT_EXPORT(NETWORK_CPP) -bool ParseOriginIsolation(const std::string&); +bool ParseOriginAgentCluster(const std::string&); } // namespace network -#endif // SERVICES_NETWORK_PUBLIC_CPP_ORIGIN_ISOLATION_PARSER_H_ +#endif // SERVICES_NETWORK_PUBLIC_CPP_ORIGIN_AGENT_CLUSTER_PARSER_H_ diff --git a/chromium/services/network/public/cpp/origin_agent_cluster_parser_unittest.cc b/chromium/services/network/public/cpp/origin_agent_cluster_parser_unittest.cc new file mode 100644 index 00000000000..11b2c3194c4 --- /dev/null +++ b/chromium/services/network/public/cpp/origin_agent_cluster_parser_unittest.cc @@ -0,0 +1,31 @@ +// Copyright 2020 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/origin_agent_cluster_parser.h" + +#include <string> +#include <vector> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace network { + +TEST(OriginAgentClusterHeaderTest, Parse) { + EXPECT_EQ(ParseOriginAgentCluster(""), false); + + EXPECT_EQ(ParseOriginAgentCluster("?1"), true); + EXPECT_EQ(ParseOriginAgentCluster("?0"), false); + + EXPECT_EQ(ParseOriginAgentCluster("?1;param"), true); + EXPECT_EQ(ParseOriginAgentCluster("?1;param=value"), true); + EXPECT_EQ(ParseOriginAgentCluster("?1;param=value;param2=value2"), true); + + EXPECT_EQ(ParseOriginAgentCluster("true"), false); + EXPECT_EQ(ParseOriginAgentCluster("\"?1\""), false); + EXPECT_EQ(ParseOriginAgentCluster("1"), false); + EXPECT_EQ(ParseOriginAgentCluster("?2"), false); + EXPECT_EQ(ParseOriginAgentCluster("(?1)"), false); +} + +} // namespace network diff --git a/chromium/services/network/public/cpp/origin_isolation_parser_unittest.cc b/chromium/services/network/public/cpp/origin_isolation_parser_unittest.cc deleted file mode 100644 index 0b53d3a0901..00000000000 --- a/chromium/services/network/public/cpp/origin_isolation_parser_unittest.cc +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2020 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/origin_isolation_parser.h" - -#include <string> -#include <vector> - -#include "testing/gtest/include/gtest/gtest.h" - -namespace network { - -TEST(OriginIsolationHeaderTest, Parse) { - EXPECT_EQ(ParseOriginIsolation(""), false); - - EXPECT_EQ(ParseOriginIsolation("?1"), true); - EXPECT_EQ(ParseOriginIsolation("?0"), false); - - EXPECT_EQ(ParseOriginIsolation("?1;param"), true); - EXPECT_EQ(ParseOriginIsolation("?1;param=value"), true); - EXPECT_EQ(ParseOriginIsolation("?1;param=value;param2=value2"), true); - - EXPECT_EQ(ParseOriginIsolation("true"), false); - EXPECT_EQ(ParseOriginIsolation("\"?1\""), false); - EXPECT_EQ(ParseOriginIsolation("1"), false); - EXPECT_EQ(ParseOriginIsolation("?2"), false); - EXPECT_EQ(ParseOriginIsolation("(?1)"), false); -} - -} // namespace network diff --git a/chromium/services/network/public/cpp/origin_policy.cc b/chromium/services/network/public/cpp/origin_policy.cc index 4ecc285c174..e1d3d831764 100644 --- a/chromium/services/network/public/cpp/origin_policy.cc +++ b/chromium/services/network/public/cpp/origin_policy.cc @@ -26,14 +26,12 @@ OriginPolicyContents::OriginPolicyContents( const std::vector<std::string>& ids, const base::Optional<std::string>& feature_policy, const std::vector<std::string>& content_security_policies, - const std::vector<std::string>& content_security_policies_report_only, - const base::Optional<IsolationOptInHints>& isolation_optin_hints) + const std::vector<std::string>& content_security_policies_report_only) : ids(ids), feature_policy(feature_policy), content_security_policies(content_security_policies), content_security_policies_report_only( - content_security_policies_report_only), - isolation_optin_hints(isolation_optin_hints) {} + content_security_policies_report_only) {} OriginPolicyContents& OriginPolicyContents::operator=( const OriginPolicyContents& other) = default; @@ -42,8 +40,7 @@ bool OriginPolicyContents::operator==(const OriginPolicyContents& other) const { return ids == other.ids && feature_policy == other.feature_policy && content_security_policies == other.content_security_policies && content_security_policies_report_only == - other.content_security_policies_report_only && - isolation_optin_hints == other.isolation_optin_hints; + other.content_security_policies_report_only; } OriginPolicyContentsPtr OriginPolicyContents::ClonePtr() { diff --git a/chromium/services/network/public/cpp/origin_policy.h b/chromium/services/network/public/cpp/origin_policy.h index bb4cec880b2..b1c2e08174a 100644 --- a/chromium/services/network/public/cpp/origin_policy.h +++ b/chromium/services/network/public/cpp/origin_policy.h @@ -10,7 +10,6 @@ #include <vector> #include "base/optional.h" -#include "services/network/public/cpp/isolation_opt_in_hints.h" #include "url/gurl.h" namespace network { @@ -50,8 +49,7 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) OriginPolicyContents { const std::vector<std::string>& ids, const base::Optional<std::string>& feature_policy, const std::vector<std::string>& content_security_policies, - const std::vector<std::string>& content_security_policies_report_only, - const base::Optional<IsolationOptInHints>& isolation_optin_hints); + const std::vector<std::string>& content_security_policies_report_only); OriginPolicyContents(const OriginPolicyContents& other); OriginPolicyContents& operator=(const OriginPolicyContents& other); @@ -92,11 +90,6 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) OriginPolicyContents { // a "report" disposition. // https://w3c.github.io/webappsec-csp/#policy-disposition std::vector<std::string> content_security_policies_report_only; - - // This field, if present, indicates that the origin is opting in to - // origin-based isolation. The int contains zero or more flag bits indicating - // what the origin is hoping to achieve through isolation. - base::Optional<IsolationOptInHints> isolation_optin_hints; }; // Native implementation of mojom::OriginPolicy. This is done so we can pass diff --git a/chromium/services/network/public/cpp/parsed_headers.cc b/chromium/services/network/public/cpp/parsed_headers.cc index 2fb31bb4a91..548a128ec72 100644 --- a/chromium/services/network/public/cpp/parsed_headers.cc +++ b/chromium/services/network/public/cpp/parsed_headers.cc @@ -10,7 +10,8 @@ #include "services/network/public/cpp/cross_origin_embedder_policy_parser.h" #include "services/network/public/cpp/cross_origin_opener_policy_parser.h" #include "services/network/public/cpp/features.h" -#include "services/network/public/cpp/origin_isolation_parser.h" +#include "services/network/public/cpp/origin_agent_cluster_parser.h" +#include "services/network/public/cpp/x_frame_options_parser.h" namespace network { @@ -31,12 +32,12 @@ mojom::ParsedHeadersPtr PopulateParsedHeaders( parsed_headers->cross_origin_opener_policy = ParseCrossOriginOpenerPolicy( *headers, parsed_headers->cross_origin_embedder_policy); - // TODO(https://crbug.com/1157917): we implement the change at - // https://github.com/whatwg/html/pull/6214 but all the class and function - // names are not yet updated. - std::string origin_isolation; - if (headers->GetNormalizedHeader("Origin-Agent-Cluster", &origin_isolation)) - parsed_headers->origin_isolation = ParseOriginIsolation(origin_isolation); + std::string origin_agent_cluster; + if (headers->GetNormalizedHeader("Origin-Agent-Cluster", + &origin_agent_cluster)) { + parsed_headers->origin_agent_cluster = + ParseOriginAgentCluster(origin_agent_cluster); + } std::string accept_ch; if (headers->GetNormalizedHeader("Accept-CH", &accept_ch)) @@ -52,6 +53,8 @@ mojom::ParsedHeadersPtr PopulateParsedHeaders( if (headers->GetNormalizedHeader("Critical-CH", &critical_ch)) parsed_headers->critical_ch = ParseClientHintsHeader(critical_ch); + parsed_headers->xfo = ParseXFrameOptions(*headers); + return parsed_headers; } 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 b5ec2918e6a..ebb9d810b18 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 @@ -22,7 +22,7 @@ bool TestProxyConfigRoundTrip(net::ProxyConfigWithAnnotation& original_config) { net::ProxyConfigWithAnnotation copied_config; EXPECT_TRUE( mojo::test::SerializeAndDeserialize<mojom::ProxyConfigWithAnnotation>( - &original_config, &copied_config)); + original_config, copied_config)); return original_config.value().Equals(copied_config.value()) && original_config.traffic_annotation() == diff --git a/chromium/services/network/public/cpp/request_destination.cc b/chromium/services/network/public/cpp/request_destination.cc index 38884cc4697..c61db5ed468 100644 --- a/chromium/services/network/public/cpp/request_destination.cc +++ b/chromium/services/network/public/cpp/request_destination.cc @@ -6,6 +6,8 @@ namespace network { +// These strings are used in histograms, so do not change the values without +// updating/deprecating histograms which use RequestDestination. const char* RequestDestinationToString( network::mojom::RequestDestination dest) { switch (dest) { @@ -47,6 +49,8 @@ const char* RequestDestinationToString( return "track"; case network::mojom::RequestDestination::kVideo: return "video"; + case network::mojom::RequestDestination::kWebBundle: + return "webbundle"; case network::mojom::RequestDestination::kWorker: return "worker"; case network::mojom::RequestDestination::kXslt: @@ -56,4 +60,4 @@ const char* RequestDestinationToString( return "empty"; } -} // namespace network
\ No newline at end of file +} // namespace network diff --git a/chromium/services/network/public/cpp/resource_request.cc b/chromium/services/network/public/cpp/resource_request.cc index ee0150fbd6d..61a6f05d5d7 100644 --- a/chromium/services/network/public/cpp/resource_request.cc +++ b/chromium/services/network/public/cpp/resource_request.cc @@ -7,6 +7,7 @@ #include "mojo/public/cpp/bindings/pending_remote.h" #include "net/base/load_flags.h" #include "services/network/public/mojom/cookie_access_observer.mojom.h" +#include "services/network/public/mojom/web_bundle_handle.mojom.h" namespace network { @@ -23,6 +24,19 @@ mojo::PendingRemote<mojom::CookieAccessObserver> Clone( return new_remote; } +mojo::PendingRemote<mojom::AuthenticationAndCertificateObserver> Clone( + mojo::PendingRemote<mojom::AuthenticationAndCertificateObserver>* + observer) { + if (!*observer) + return mojo::NullRemote(); + mojo::Remote<mojom::AuthenticationAndCertificateObserver> remote( + std::move(*observer)); + mojo::PendingRemote<mojom::AuthenticationAndCertificateObserver> new_remote; + remote->Clone(new_remote.InitWithNewPipeAndPassReceiver()); + *observer = remote.Unbind(); + return new_remote; +} + // Returns true iff either holds true: // // - both |lhs| and |rhs| are nullopt, or @@ -34,6 +48,13 @@ bool OptionalTrustedParamsEqualsForTesting( return (!lhs && !rhs) || (lhs && rhs && lhs->EqualsForTesting(*rhs)); } +bool OptionalWebBundleTokenParamsEqualsForTesting( // IN-TEST + const base::Optional<ResourceRequest::WebBundleTokenParams>& lhs, + const base::Optional<ResourceRequest::WebBundleTokenParams>& rhs) { + return (!lhs && !rhs) || + (lhs && rhs && lhs->EqualsForTesting(*rhs)); // IN-TEST +} + } // namespace ResourceRequest::TrustedParams::TrustedParams() = default; @@ -51,6 +72,10 @@ ResourceRequest::TrustedParams& ResourceRequest::TrustedParams::operator=( cookie_observer = Clone(&const_cast<mojo::PendingRemote<mojom::CookieAccessObserver>&>( other.cookie_observer)); + auth_cert_observer = + Clone(&const_cast< + mojo::PendingRemote<mojom::AuthenticationAndCertificateObserver>&>( + other.auth_cert_observer)); client_security_state = other.client_security_state.Clone(); return *this; } @@ -63,6 +88,59 @@ bool ResourceRequest::TrustedParams::EqualsForTesting( client_security_state == trusted_params.client_security_state; } +ResourceRequest::WebBundleTokenParams::WebBundleTokenParams() = default; +ResourceRequest::WebBundleTokenParams::~WebBundleTokenParams() = default; + +ResourceRequest::WebBundleTokenParams::WebBundleTokenParams( + const WebBundleTokenParams& other) { + *this = other; +} + +ResourceRequest::WebBundleTokenParams& +ResourceRequest::WebBundleTokenParams::operator=( + const WebBundleTokenParams& other) { + bundle_url = other.bundle_url; + token = other.token; + handle = other.CloneHandle(); + render_process_id = other.render_process_id; + return *this; +} + +ResourceRequest::WebBundleTokenParams::WebBundleTokenParams( + const GURL& bundle_url, + const base::UnguessableToken& token, + mojo::PendingRemote<mojom::WebBundleHandle> handle) + : bundle_url(bundle_url), token(token), handle(std::move(handle)) {} + +ResourceRequest::WebBundleTokenParams::WebBundleTokenParams( + const GURL& bundle_url, + const base::UnguessableToken& token, + int32_t render_process_id) + : bundle_url(bundle_url), + token(token), + render_process_id(render_process_id) {} + +bool ResourceRequest::WebBundleTokenParams::EqualsForTesting( + const WebBundleTokenParams& other) const { + return bundle_url == other.bundle_url && token == other.token && + ((handle && other.handle) || (!handle && !other.handle)) && + render_process_id == other.render_process_id; +} + +mojo::PendingRemote<mojom::WebBundleHandle> +ResourceRequest::WebBundleTokenParams::CloneHandle() const { + if (!handle) + return mojo::NullRemote(); + mojo::Remote<network::mojom::WebBundleHandle> remote(std::move( + const_cast<mojo::PendingRemote<network::mojom::WebBundleHandle>&>( + handle))); + mojo::PendingRemote<network::mojom::WebBundleHandle> new_remote; + remote->Clone(new_remote.InitWithNewPipeAndPassReceiver()); + const_cast<mojo::PendingRemote<network::mojom::WebBundleHandle>&>(handle) = + remote.Unbind(); + return new_remote; +} + ResourceRequest::ResourceRequest() {} ResourceRequest::ResourceRequest(const ResourceRequest& request) = default; ResourceRequest::~ResourceRequest() {} @@ -70,8 +148,6 @@ ResourceRequest::~ResourceRequest() {} bool ResourceRequest::EqualsForTesting(const ResourceRequest& request) const { return method == request.method && url == request.url && site_for_cookies.IsEquivalent(request.site_for_cookies) && - force_ignore_site_for_cookies == - request.force_ignore_site_for_cookies && update_first_party_url_on_redirect == request.update_first_party_url_on_redirect && request_initiator == request.request_initiator && @@ -91,8 +167,7 @@ bool ResourceRequest::EqualsForTesting(const ResourceRequest& request) const { originated_from_service_worker == request.originated_from_service_worker && skip_service_worker == request.skip_service_worker && - corb_detachable == request.corb_detachable && - corb_excluded == request.corb_excluded && mode == request.mode && + corb_detachable == request.corb_detachable && mode == request.mode && credentials_mode == request.credentials_mode && redirect_mode == request.redirect_mode && fetch_integrity == request.fetch_integrity && @@ -120,11 +195,14 @@ bool ResourceRequest::EqualsForTesting(const ResourceRequest& request) const { is_signed_exchange_prefetch_cache_enabled == request.is_signed_exchange_prefetch_cache_enabled && is_fetch_like_api == request.is_fetch_like_api && + is_favicon == request.is_favicon && obey_origin_policy == request.obey_origin_policy && recursive_prefetch_token == request.recursive_prefetch_token && OptionalTrustedParamsEqualsForTesting(trusted_params, request.trusted_params) && - trust_token_params == request.trust_token_params; + trust_token_params == request.trust_token_params && + OptionalWebBundleTokenParamsEqualsForTesting( // IN-TEST + web_bundle_token_params, request.web_bundle_token_params); } bool ResourceRequest::SendsCookies() const { diff --git a/chromium/services/network/public/cpp/resource_request.h b/chromium/services/network/public/cpp/resource_request.h index fb4d30c234d..6443dd62c00 100644 --- a/chromium/services/network/public/cpp/resource_request.h +++ b/chromium/services/network/public/cpp/resource_request.h @@ -20,12 +20,14 @@ #include "net/url_request/referrer_policy.h" #include "services/network/public/cpp/optional_trust_token_params.h" #include "services/network/public/cpp/resource_request_body.h" +#include "services/network/public/mojom/auth_and_certificate_observer.mojom.h" #include "services/network/public/mojom/client_security_state.mojom.h" #include "services/network/public/mojom/cookie_access_observer.mojom.h" #include "services/network/public/mojom/cors.mojom-shared.h" #include "services/network/public/mojom/fetch_api.mojom-shared.h" #include "services/network/public/mojom/referrer_policy.mojom-shared.h" #include "services/network/public/mojom/trust_tokens.mojom.h" +#include "services/network/public/mojom/web_bundle_handle.mojom.h" #include "url/gurl.h" #include "url/origin.h" @@ -54,9 +56,43 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { bool disable_secure_dns = false; bool has_user_activation = false; mojo::PendingRemote<mojom::CookieAccessObserver> cookie_observer; + mojo::PendingRemote<mojom::AuthenticationAndCertificateObserver> + auth_cert_observer; mojom::ClientSecurityStatePtr client_security_state; }; + // Typemapped to network.mojom.WebBundleTokenParams, see comments there + // for details of each field. + struct COMPONENT_EXPORT(NETWORK_CPP_BASE) WebBundleTokenParams { + WebBundleTokenParams(); + ~WebBundleTokenParams(); + // Define a non-default copy-constructor because: + // 1. network::ResourceRequest has a requirement that all of + // the members be trivially copyable. + // 2. mojo::PendingRemote is non-copyable. + WebBundleTokenParams(const WebBundleTokenParams& params); + WebBundleTokenParams& operator=(const WebBundleTokenParams& other); + + WebBundleTokenParams(const GURL& bundle_url, + const base::UnguessableToken& token, + mojo::PendingRemote<mojom::WebBundleHandle> handle); + WebBundleTokenParams(const GURL& bundle_url, + const base::UnguessableToken& token, + int32_t render_process_id); + + // For testing. Regarding the equality of |handle|, |this| equals |other| if + // both |handle| exists, or neither exists, because we cannot test the + // equality of two mojo handles. + bool EqualsForTesting(const WebBundleTokenParams& other) const; + + mojo::PendingRemote<mojom::WebBundleHandle> CloneHandle() const; + + GURL bundle_url; + base::UnguessableToken token; + mojo::PendingRemote<mojom::WebBundleHandle> handle; + int32_t render_process_id = -1; + }; + ResourceRequest(); ResourceRequest(const ResourceRequest& request); ~ResourceRequest(); @@ -70,7 +106,6 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { std::string method = net::HttpRequestHeaders::kGetMethod; GURL url; net::SiteForCookies site_for_cookies; - bool force_ignore_site_for_cookies = false; bool update_first_party_url_on_redirect = false; // SECURITY NOTE: |request_initiator| is a security-sensitive field. Please @@ -92,7 +127,6 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { bool originated_from_service_worker = false; bool skip_service_worker = false; bool corb_detachable = false; - bool corb_excluded = false; mojom::RequestMode mode = mojom::RequestMode::kNoCors; mojom::CredentialsMode credentials_mode = mojom::CredentialsMode::kInclude; mojom::RedirectMode redirect_mode = mojom::RedirectMode::kFollow; @@ -119,6 +153,7 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { base::Optional<std::string> devtools_stack_id; bool is_signed_exchange_prefetch_cache_enabled = false; bool is_fetch_like_api = false; + bool is_favicon = false; bool obey_origin_policy = false; base::Optional<base::UnguessableToken> recursive_prefetch_token; base::Optional<TrustedParams> trusted_params; @@ -126,6 +161,7 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequest { // field trivially copyable; see OptionalTrustTokenParams's definition for // more context. OptionalTrustTokenParams trust_token_params; + base::Optional<WebBundleTokenParams> web_bundle_token_params; }; // This does not accept |kDefault| referrer policy. diff --git a/chromium/services/network/public/cpp/resource_request_body.cc b/chromium/services/network/public/cpp/resource_request_body.cc index 59c1b3566ff..c8e7b4e1472 100644 --- a/chromium/services/network/public/cpp/resource_request_body.cc +++ b/chromium/services/network/public/cpp/resource_request_body.cc @@ -24,16 +24,14 @@ scoped_refptr<ResourceRequestBody> ResourceRequestBody::CreateFromBytes( bool ResourceRequestBody::EnableToAppendElement() const { return elements_.empty() || (elements_.front().type() != - mojom::DataElementType::kChunkedDataPipe && - elements_.front().type() != mojom::DataElementType::kReadOnceStream); + mojom::DataElementDataView::Tag::kChunkedDataPipe); } void ResourceRequestBody::AppendBytes(std::vector<uint8_t> bytes) { DCHECK(EnableToAppendElement()); if (bytes.size() > 0) { - elements_.push_back(DataElement()); - elements_.back().SetToBytes(std::move(bytes)); + elements_.emplace_back(DataElementBytes(std::move(bytes))); } } @@ -52,42 +50,34 @@ void ResourceRequestBody::AppendFileRange( const base::Time& expected_modification_time) { DCHECK(EnableToAppendElement()); - elements_.push_back(DataElement()); - elements_.back().SetToFilePathRange(file_path, offset, length, - expected_modification_time); + elements_.emplace_back( + DataElementFile(file_path, offset, length, expected_modification_time)); } void ResourceRequestBody::AppendDataPipe( mojo::PendingRemote<mojom::DataPipeGetter> data_pipe_getter) { DCHECK(EnableToAppendElement()); + DCHECK(data_pipe_getter); - elements_.push_back(DataElement()); - elements_.back().SetToDataPipe(std::move(data_pipe_getter)); + elements_.emplace_back(DataElementDataPipe(std::move(data_pipe_getter))); } void ResourceRequestBody::SetToChunkedDataPipe( - mojo::PendingRemote<mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter) { + mojo::PendingRemote<mojom::ChunkedDataPipeGetter> chunked_data_pipe_getter, + ReadOnlyOnce read_only_once) { DCHECK(elements_.empty()); + DCHECK(chunked_data_pipe_getter); - elements_.push_back(DataElement()); - elements_.back().SetToChunkedDataPipe(std::move(chunked_data_pipe_getter)); -} - -void ResourceRequestBody::SetToReadOnceStream( - mojo::PendingRemote<mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter) { - DCHECK(elements_.empty()); - - elements_.push_back(DataElement()); - elements_.back().SetToReadOnceStream(std::move(chunked_data_pipe_getter)); + elements_.emplace_back(DataElementChunkedDataPipe( + std::move(chunked_data_pipe_getter), read_only_once)); } std::vector<base::FilePath> ResourceRequestBody::GetReferencedFiles() const { std::vector<base::FilePath> result; for (const auto& element : *elements()) { - if (element.type() == mojom::DataElementType::kFile) - result.push_back(element.path()); + if (element.type() == mojom::DataElementDataView::Tag::kFile) { + result.push_back(element.As<DataElementFile>().path()); + } } return result; } diff --git a/chromium/services/network/public/cpp/resource_request_body.h b/chromium/services/network/public/cpp/resource_request_body.h index 8c8517369f9..410e83c1fcc 100644 --- a/chromium/services/network/public/cpp/resource_request_body.h +++ b/chromium/services/network/public/cpp/resource_request_body.h @@ -32,6 +32,8 @@ namespace network { class COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequestBody : public base::RefCountedThreadSafe<ResourceRequestBody> { public: + using ReadOnlyOnce = DataElementChunkedDataPipe::ReadOnlyOnce; + ResourceRequestBody(); // Creates ResourceRequestBody that holds a copy of |bytes|. @@ -59,7 +61,8 @@ class COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequestBody // method should only be used when talking to servers that are are known to // support chunked uploads. void SetToChunkedDataPipe(mojo::PendingRemote<mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter); + chunked_data_pipe_getter, + ReadOnlyOnce read_only_once); // Almost same as above except |chunked_data_pipe_getter| is read only once // and you must talk with a server supporting chunked upload. void SetToReadOnceStream(mojo::PendingRemote<mojom::ChunkedDataPipeGetter> @@ -84,7 +87,7 @@ class COMPONENT_EXPORT(NETWORK_CPP_BASE) ResourceRequestBody int64_t identifier() const { return identifier_; } // Returns paths referred to by |elements| of type - // network::mojom::DataElementType::kFile. + // network::mojom::DataElementDataView::Tag::kFile. std::vector<base::FilePath> GetReferencedFiles() const; // Sets the flag which indicates whether the post data contains sensitive diff --git a/chromium/services/network/public/cpp/schemeful_site_mojom_traits.cc b/chromium/services/network/public/cpp/schemeful_site_mojom_traits.cc index fb0a0957876..675f23f2ef3 100644 --- a/chromium/services/network/public/cpp/schemeful_site_mojom_traits.cc +++ b/chromium/services/network/public/cpp/schemeful_site_mojom_traits.cc @@ -14,16 +14,7 @@ bool StructTraits<network::mojom::SchemefulSiteDataView, net::SchemefulSite>:: if (!data.ReadSiteAsOrigin(&site_as_origin)) return false; - // The origin passed into this constructor may not match the - // `site_as_origin_` used as the internal representation of the schemeful - // site. However, a valid SchemefulSite's internal origin should result in a - // match if used to construct another SchemefulSite. Thus, if there is a - // mismatch here, we must indicate a failure. - net::SchemefulSite ss(site_as_origin); - bool success = site_as_origin == ss.site_as_origin_; - if (success) - *out = std::move(ss); - return success; + return net::SchemefulSite::FromWire(site_as_origin, out); } } // namespace mojo diff --git a/chromium/services/network/public/cpp/schemeful_site_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/schemeful_site_mojom_traits_unittest.cc index 135c92a8c2b..3515073c48d 100644 --- a/chromium/services/network/public/cpp/schemeful_site_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/schemeful_site_mojom_traits_unittest.cc @@ -38,7 +38,7 @@ TEST(SchemefulSiteMojomTraitsTest, SerializeAndDeserialize) { for (auto original : keys) { net::SchemefulSite copied; EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::SchemefulSite>( - &original, &copied)); + original, copied)); EXPECT_EQ(original, copied); } } diff --git a/chromium/services/network/public/cpp/self_deleting_url_loader_factory.cc b/chromium/services/network/public/cpp/self_deleting_url_loader_factory.cc new file mode 100644 index 00000000000..c624b751b21 --- /dev/null +++ b/chromium/services/network/public/cpp/self_deleting_url_loader_factory.cc @@ -0,0 +1,52 @@ +// Copyright 2021 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/self_deleting_url_loader_factory.h" + +#include <utility> + +namespace network { + +SelfDeletingURLLoaderFactory::SelfDeletingURLLoaderFactory( + mojo::PendingReceiver<mojom::URLLoaderFactory> factory_receiver) { + receivers_.set_disconnect_handler(base::BindRepeating( + &SelfDeletingURLLoaderFactory::OnDisconnect, base::Unretained(this))); + receivers_.Add(this, std::move(factory_receiver)); +} + +SelfDeletingURLLoaderFactory::~SelfDeletingURLLoaderFactory() = default; + +void SelfDeletingURLLoaderFactory::DisconnectReceiversAndDestroy() { + // Clear |receivers_| to explicitly make sure that no further method + // invocations or disconnection notifications will happen. (per the + // comment of mojo::ReceiverSet::Clear) + receivers_.Clear(); + + // Similarly to OnDisconnect, if there are no more |receivers_|, then no + // instance methods of |this| can be called in the future (mojo methods Clone + // and CreateLoaderAndStart should be the only public entrypoints). + // Therefore, it is safe to delete |this| at this point. + delete this; +} + +void SelfDeletingURLLoaderFactory::Clone( + mojo::PendingReceiver<mojom::URLLoaderFactory> loader) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + receivers_.Add(this, std::move(loader)); +} + +void SelfDeletingURLLoaderFactory::OnDisconnect() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + + if (receivers_.empty()) { + // If there are no more |receivers_|, then no instance methods of |this| can + // be called in the future (mojo methods Clone and CreateLoaderAndStart + // should be the only public entrypoints). Therefore, it is safe to delete + // |this| at this point. + delete this; + } +} + +} // namespace network diff --git a/chromium/services/network/public/cpp/self_deleting_url_loader_factory.h b/chromium/services/network/public/cpp/self_deleting_url_loader_factory.h new file mode 100644 index 00000000000..8e84590e58d --- /dev/null +++ b/chromium/services/network/public/cpp/self_deleting_url_loader_factory.h @@ -0,0 +1,60 @@ +// Copyright 2021 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_SELF_DELETING_URL_LOADER_FACTORY_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_SELF_DELETING_URL_LOADER_FACTORY_H_ + +#include "base/component_export.h" +#include "base/macros.h" +#include "base/threading/thread_checker.h" +#include "mojo/public/cpp/bindings/pending_receiver.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/receiver_set.h" +#include "services/network/public/mojom/url_loader_factory.mojom.h" + +namespace network { + +// A base class for URLLoaderFactory implementations that takes care of the +// managing the lifetime of the URLLoaderFactory implementation +// which should be owned by the set of its receivers. +class COMPONENT_EXPORT(NETWORK_CPP) SelfDeletingURLLoaderFactory + : public mojom::URLLoaderFactory { + protected: + // Constructs SelfDeletingURLLoaderFactory object that will self-delete + // once all receivers disconnect (including |factory_receiver| below as well + // as receivers that connect via the Clone method). + explicit SelfDeletingURLLoaderFactory( + mojo::PendingReceiver<mojom::URLLoaderFactory> factory_receiver); + + ~SelfDeletingURLLoaderFactory() override; + + // Sometimes a derived class can no longer function, even when the set of + // |receivers_| is still non-empty. This should be rare (typically the + // lifetime of users of mojo::Remote<mojom::URLLoaderFactory> should + // be shorter than whatever the factory depends on), but may happen in some + // corner cases (e.g. in a race between 1) BrowserContext destruction and 2) + // CreateLoaderAndStart mojo call). + // + // When a derived class gets notified that its dependencies got destroyed, it + // should call DisconnectReceiversAndDestroy to prevent any future calls to + // CreateLoaderAndStart. + void DisconnectReceiversAndDestroy(); + + THREAD_CHECKER(thread_checker_); + + private: + // The override below is marked as |final| to make sure derived classes do not + // accidentally side-step lifetime management. + void Clone(mojo::PendingReceiver<mojom::URLLoaderFactory> loader) final; + + void OnDisconnect(); + + mojo::ReceiverSet<mojom::URLLoaderFactory> receivers_; + + DISALLOW_COPY_AND_ASSIGN(SelfDeletingURLLoaderFactory); +}; + +} // namespace network + +#endif // SERVICES_NETWORK_PUBLIC_CPP_SELF_DELETING_URL_LOADER_FACTORY_H_ diff --git a/chromium/services/network/public/cpp/simple_url_loader.cc b/chromium/services/network/public/cpp/simple_url_loader.cc index 779be1f9b97..d7380996ff2 100644 --- a/chromium/services/network/public/cpp/simple_url_loader.cc +++ b/chromium/services/network/public/cpp/simple_url_loader.cc @@ -1135,6 +1135,10 @@ class DownloadAsStreamBodyHandler : public BodyHandler, weak_ptr_factory_.GetWeakPtr())); return; } + if (!body_reader_) { + // If Resume was delayed, body_reader_ could have been deleted. + return; + } body_reader_->Resume(); } @@ -1168,8 +1172,8 @@ SimpleURLLoaderImpl::SimpleURLLoaderImpl( // Bytes should be attached with AttachStringForUpload to allow // streaming of large byte buffers to the network process when uploading. - DCHECK(element.type() != mojom::DataElementType::kFile && - element.type() != mojom::DataElementType::kBytes); + DCHECK(element.type() != mojom::DataElementDataView::Tag::kFile && + element.type() != mojom::DataElementDataView::Tag::kBytes); } } #endif // DCHECK_IS_ON() @@ -1357,7 +1361,7 @@ void SimpleURLLoaderImpl::SetRetryOptions(int max_retries, int retry_mode) { // pipe. // TODO(mmenke): Data pipes can be Cloned(), though, so maybe update code // to do that? - DCHECK(element.type() != mojom::DataElementType::kDataPipe); + DCHECK(element.type() != mojom::DataElementDataView::Tag::kDataPipe); } } #endif // DCHECK_IS_ON() 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 2b9a8a21413..2ee17dfa065 100644 --- a/chromium/services/network/public/cpp/simple_url_loader_unittest.cc +++ b/chromium/services/network/public/cpp/simple_url_loader_unittest.cc @@ -251,6 +251,17 @@ class SimpleLoaderTestHelper : public SimpleURLLoaderStreamConsumer { download_to_stream_async_resume_ = download_to_stream_async_resume; } + // Sets whether the resume-reading closure should be captured and later + // available in TakeCapturedStreamResume() + void set_download_to_stream_capture_resume( + bool download_to_stream_capture_resume) { + download_to_stream_capture_resume_ = download_to_stream_capture_resume; + } + + base::OnceClosure TakeCapturedStreamResume() { + return std::move(captured_stream_resume_); + } + // Sets whether the helper should destroy the SimpleURLLoader in // OnDataReceived. void set_download_to_stream_destroy_on_data_received( @@ -417,6 +428,14 @@ class SimpleLoaderTestHelper : public SimpleURLLoaderStreamConsumer { return; } + if (download_to_stream_capture_resume_) { + if (captured_stream_resume_) { + std::move(captured_stream_resume_).Run(); + } + captured_stream_resume_ = std::move(resume); + return; + } + if (download_to_stream_async_resume_) { base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(resume)); @@ -496,6 +515,8 @@ class SimpleLoaderTestHelper : public SimpleURLLoaderStreamConsumer { bool download_to_stream_destroy_on_data_received_ = false; bool download_to_stream_async_retry_ = false; bool download_to_stream_destroy_on_retry_ = false; + bool download_to_stream_capture_resume_ = false; + base::OnceClosure captured_stream_resume_; bool destroy_loader_on_complete_ = false; @@ -1966,9 +1987,10 @@ class MockURLLoader : public network::mojom::URLLoader { test_events_(std::move(test_events)) { if (request_body && request_body->elements()->size() == 1 && (*request_body->elements())[0].type() == - network::mojom::DataElementType::kDataPipe) { - data_pipe_getter_.Bind( - (*request_body->elements())[0].CloneDataPipeGetter()); + network::mojom::DataElementDataView::Tag::kDataPipe) { + const auto& element = + (*request_body->elements())[0].As<network::DataElementDataPipe>(); + data_pipe_getter_.Bind(element.CloneDataPipeGetter()); DCHECK(data_pipe_getter_); } } @@ -1981,13 +2003,15 @@ class MockURLLoader : public network::mojom::URLLoader { upload_data_pipe_.reset(); weak_factory_for_data_pipe_callbacks_.InvalidateWeakPtrs(); read_run_loop_ = std::make_unique<base::RunLoop>(); - mojo::DataPipe data_pipe; + mojo::ScopedDataPipeProducerHandle producer_handle; + ASSERT_EQ( + mojo::CreateDataPipe(nullptr, producer_handle, upload_data_pipe_), + MOJO_RESULT_OK); data_pipe_getter_->Read( - std::move(data_pipe.producer_handle), + std::move(producer_handle), base::BindOnce( &MockURLLoader::OnReadComplete, weak_factory_for_data_pipe_callbacks_.GetWeakPtr())); - upload_data_pipe_ = std::move(data_pipe.consumer_handle); // Continue instead of break, to avoid spinning the message loop - // only wait for the response if next step indicates to do so. continue; @@ -2091,10 +2115,10 @@ class MockURLLoader : public network::mojom::URLLoader { break; } case TestLoaderEvent::kBodyBufferReceived: { - mojo::DataPipe data_pipe(1024); - body_stream_ = std::move(data_pipe.producer_handle); - client_->OnStartLoadingResponseBody( - std::move(data_pipe.consumer_handle)); + mojo::ScopedDataPipeConsumerHandle consumer_handle; + ASSERT_EQ(mojo::CreateDataPipe(1024, body_stream_, consumer_handle), + MOJO_RESULT_OK); + client_->OnStartLoadingResponseBody(std::move(consumer_handle)); break; } case TestLoaderEvent::kBodyDataRead: { @@ -3420,6 +3444,17 @@ class SimpleURLLoaderMockTimeTest : public testing::Test { SimpleLoaderTestHelper::DownloadType::TO_STRING); } + std::unique_ptr<SimpleLoaderTestHelper> CreateStreamHelper() { + 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::AS_STREAM); + } + protected: base::test::TaskEnvironment task_environment_; std::unique_ptr<base::ScopedDisallowBlocking> disallow_blocking_; @@ -3443,6 +3478,32 @@ TEST_F(SimpleURLLoaderMockTimeTest, TimeoutTriggered) { EXPECT_FALSE(test_helper->simple_url_loader()->CompletionStatus()); } +// Request fails with a timeout like in TimeoutTriggered, and the stream resume +// closure is called after the timeout. The loader is alive throughout. +TEST_F(SimpleURLLoaderMockTimeTest, StreamResumeAfterTimeout) { + MockURLLoaderFactory loader_factory(&task_environment_); + loader_factory.AddEvents( + {TestLoaderEvent::kReceivedResponse, TestLoaderEvent::kBodyBufferReceived, + TestLoaderEvent::kBodyDataRead, TestLoaderEvent::kAdvanceOneSecond, + TestLoaderEvent::kResponseComplete, TestLoaderEvent::kBodyBufferClosed}); + std::unique_ptr<SimpleLoaderTestHelper> test_helper = CreateStreamHelper(); + test_helper->simple_url_loader()->SetTimeoutDuration( + base::TimeDelta::FromSeconds(1)); + test_helper->set_download_to_stream_capture_resume(true); + + loader_factory.RunTest(test_helper.get()); + + EXPECT_EQ(net::ERR_TIMED_OUT, test_helper->simple_url_loader()->NetError()); + EXPECT_FALSE(test_helper->simple_url_loader()->CompletionStatus()); + + base::OnceClosure captured_resume = test_helper->TakeCapturedStreamResume(); + ASSERT_TRUE(captured_resume); + base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, + std::move(captured_resume)); + // Make sure no pending task results in a crash. + base::RunLoop().RunUntilIdle(); +} + // Less time is simulated passing than the timeout value, so this request should // succeed normally. TEST_F(SimpleURLLoaderMockTimeTest, TimeoutNotTriggered) { diff --git a/chromium/services/network/public/cpp/site_for_cookies_mojom_traits.cc b/chromium/services/network/public/cpp/site_for_cookies_mojom_traits.cc index 282d0ef1e08..48968f3e5ea 100644 --- a/chromium/services/network/public/cpp/site_for_cookies_mojom_traits.cc +++ b/chromium/services/network/public/cpp/site_for_cookies_mojom_traits.cc @@ -3,7 +3,7 @@ // found in the LICENSE file. #include "services/network/public/cpp/site_for_cookies_mojom_traits.h" -#include "net/base/features.h" + #include "services/network/public/cpp/crash_keys.h" namespace mojo { @@ -11,16 +11,13 @@ namespace mojo { bool StructTraits<network::mojom::SiteForCookiesDataView, net::SiteForCookies>:: Read(network::mojom::SiteForCookiesDataView data, net::SiteForCookies* out) { - std::string scheme, registrable_domain; - if (!data.ReadScheme(&scheme)) { - return false; - } - if (!data.ReadRegistrableDomain(®istrable_domain)) { + net::SchemefulSite site; + if (!data.ReadSite(&site)) { return false; } - bool result = net::SiteForCookies::FromWire(scheme, registrable_domain, - data.schemefully_same(), out); + bool result = + net::SiteForCookies::FromWire(site, data.schemefully_same(), out); if (!result) { network::debug::SetDeserializationCrashKeyString("site_for_cookie"); } diff --git a/chromium/services/network/public/cpp/site_for_cookies_mojom_traits.h b/chromium/services/network/public/cpp/site_for_cookies_mojom_traits.h index 2663f74a784..de504f69ee2 100644 --- a/chromium/services/network/public/cpp/site_for_cookies_mojom_traits.h +++ b/chromium/services/network/public/cpp/site_for_cookies_mojom_traits.h @@ -5,10 +5,10 @@ #ifndef SERVICES_NETWORK_PUBLIC_CPP_SITE_FOR_COOKIES_MOJOM_TRAITS_H_ #define SERVICES_NETWORK_PUBLIC_CPP_SITE_FOR_COOKIES_MOJOM_TRAITS_H_ -#include <string> - #include "mojo/public/cpp/bindings/struct_traits.h" +#include "net/base/schemeful_site.h" #include "net/cookies/site_for_cookies.h" +#include "services/network/public/cpp/schemeful_site_mojom_traits.h" #include "services/network/public/mojom/site_for_cookies.mojom-shared.h" namespace mojo { @@ -16,13 +16,8 @@ namespace mojo { template <> struct COMPONENT_EXPORT(NETWORK_CPP_BASE) StructTraits<network::mojom::SiteForCookiesDataView, net::SiteForCookies> { - static const std::string& scheme(const net::SiteForCookies& input) { - return input.scheme(); - } - - static const std::string& registrable_domain( - const net::SiteForCookies& input) { - return input.registrable_domain(); + static const net::SchemefulSite& site(const net::SiteForCookies& input) { + return input.site(); } static bool schemefully_same(const net::SiteForCookies& input) { diff --git a/chromium/services/network/public/cpp/site_for_cookies_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/site_for_cookies_mojom_traits_unittest.cc index 65603ccea97..61111db742b 100644 --- a/chromium/services/network/public/cpp/site_for_cookies_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/site_for_cookies_mojom_traits_unittest.cc @@ -22,7 +22,7 @@ TEST(SiteForCookiesMojomTraitsTest, SerializeAndDeserialize) { net::SiteForCookies copied; EXPECT_TRUE( mojo::test::SerializeAndDeserialize<network::mojom::SiteForCookies>( - &original, &copied)); + original, copied)); EXPECT_TRUE(original.IsEquivalent(copied)); EXPECT_EQ(original.schemefully_same(), copied.schemefully_same()); } diff --git a/chromium/services/network/public/cpp/source_stream_to_data_pipe_unittest.cc b/chromium/services/network/public/cpp/source_stream_to_data_pipe_unittest.cc index 6fcac5fc307..60d154e9cea 100644 --- a/chromium/services/network/public/cpp/source_stream_to_data_pipe_unittest.cc +++ b/chromium/services/network/public/cpp/source_stream_to_data_pipe_unittest.cc @@ -58,9 +58,8 @@ class SourceStreamToDataPipeTest sizeof(MojoCreateDataPipeOptions), MOJO_CREATE_DATA_PIPE_FLAG_NONE, 1, GetParam().pipe_capacity}; mojo::ScopedDataPipeProducerHandle producer_end; - CHECK_EQ(MOJO_RESULT_OK, - mojo::CreateDataPipe(&data_pipe_options, &producer_end, - &consumer_end_)); + CHECK_EQ(MOJO_RESULT_OK, mojo::CreateDataPipe(&data_pipe_options, + producer_end, consumer_end_)); adapter_ = std::make_unique<SourceStreamToDataPipe>( std::move(source), std::move(producer_end)); diff --git a/chromium/services/network/public/cpp/trust_token_http_headers.h b/chromium/services/network/public/cpp/trust_token_http_headers.h index ed6fcfb78f5..6906478b311 100644 --- a/chromium/services/network/public/cpp/trust_token_http_headers.h +++ b/chromium/services/network/public/cpp/trust_token_http_headers.h @@ -40,7 +40,7 @@ constexpr char kTrustTokensRequestHeaderSecTime[] = "Sec-Time"; // collection of headers; and, optionally, the request's body). constexpr char kTrustTokensRequestHeaderSecSignature[] = "Sec-Signature"; -// As a request header, provides aRedemption Record obtained from a prior +// As a request header, provides a Redemption Record obtained from a prior // issuance-and-redemption flow. constexpr char kTrustTokensRequestHeaderSecRedemptionRecord[] = "Sec-Redemption-Record"; 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 8d7d40c4fc3..4c3b678b6b0 100644 --- a/chromium/services/network/public/cpp/url_loader_completion_status.h +++ b/chromium/services/network/public/cpp/url_loader_completion_status.h @@ -87,7 +87,7 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) URLLoaderCompletionStatus { base::Optional<net::SSLInfo> ssl_info; // More detailed reason for failing the response with - // ERR_net::ERR_BLOCKED_BY_RESPONSE |error_code|. + // net::ERR_BLOCKED_BY_RESPONSE |error_code|. base::Optional<mojom::BlockedByResponseReason> blocked_by_response_reason; // Set when response blocked by CORB needs to be reported to the DevTools diff --git a/chromium/services/network/public/cpp/url_request_mojom_traits.cc b/chromium/services/network/public/cpp/url_request_mojom_traits.cc index 8626ff6086f..402daf03291 100644 --- a/chromium/services/network/public/cpp/url_request_mojom_traits.cc +++ b/chromium/services/network/public/cpp/url_request_mojom_traits.cc @@ -7,6 +7,7 @@ #include <vector> #include "base/debug/dump_without_crashing.h" +#include "base/metrics/histogram_macros.h" #include "base/notreached.h" #include "mojo/public/cpp/base/file_mojom_traits.h" #include "mojo/public/cpp/base/file_path_mojom_traits.h" @@ -20,6 +21,7 @@ #include "services/network/public/mojom/cookie_access_observer.mojom.h" #include "services/network/public/mojom/trust_tokens.mojom.h" #include "services/network/public/mojom/url_loader.mojom-shared.h" +#include "services/network/public/mojom/web_bundle_handle.mojom.h" #include "url/mojom/origin_mojom_traits.h" #include "url/mojom/url_gurl_mojom_traits.h" @@ -156,12 +158,30 @@ bool StructTraits<network::mojom::TrustedUrlRequestParamsDataView, out->has_user_activation = data.has_user_activation(); out->cookie_observer = data.TakeCookieObserver< mojo::PendingRemote<network::mojom::CookieAccessObserver>>(); + out->auth_cert_observer = data.TakeAuthCertObserver<mojo::PendingRemote< + network::mojom::AuthenticationAndCertificateObserver>>(); if (!data.ReadClientSecurityState(&out->client_security_state)) { return false; } return true; } +bool StructTraits<network::mojom::WebBundleTokenParamsDataView, + network::ResourceRequest::WebBundleTokenParams>:: + Read(network::mojom::WebBundleTokenParamsDataView data, + network::ResourceRequest::WebBundleTokenParams* out) { + if (!data.ReadBundleUrl(&out->bundle_url)) { + return false; + } + if (!data.ReadToken(&out->token)) { + return false; + } + out->handle = data.TakeWebBundleHandle< + mojo::PendingRemote<network::mojom::WebBundleHandle>>(); + out->render_process_id = data.render_process_id(); + return true; +} + bool StructTraits< network::mojom::URLRequestDataView, network::ResourceRequest>::Read(network::mojom::URLRequestDataView data, @@ -203,7 +223,8 @@ bool StructTraits< !data.ReadFetchWindowId(&out->fetch_window_id) || !data.ReadDevtoolsRequestId(&out->devtools_request_id) || !data.ReadDevtoolsStackId(&out->devtools_stack_id) || - !data.ReadRecursivePrefetchToken(&out->recursive_prefetch_token)) { + !data.ReadRecursivePrefetchToken(&out->recursive_prefetch_token) || + !data.ReadWebBundleTokenParams(&out->web_bundle_token_params)) { // Note that data.ReadTrustTokenParams is temporarily handled below. return false; } @@ -216,7 +237,6 @@ bool StructTraits< base::debug::DumpWithoutCrashing(); } - out->force_ignore_site_for_cookies = data.force_ignore_site_for_cookies(); out->update_first_party_url_on_redirect = data.update_first_party_url_on_redirect(); out->load_flags = data.load_flags(); @@ -226,7 +246,6 @@ bool StructTraits< out->originated_from_service_worker = data.originated_from_service_worker(); out->skip_service_worker = data.skip_service_worker(); out->corb_detachable = data.corb_detachable(); - out->corb_excluded = data.corb_excluded(); out->destination = data.destination(); out->keepalive = data.keepalive(); out->has_user_gesture = data.has_user_gesture(); @@ -243,6 +262,7 @@ bool StructTraits< out->is_signed_exchange_prefetch_cache_enabled = data.is_signed_exchange_prefetch_cache_enabled(); out->is_fetch_like_api = data.is_fetch_like_api(); + out->is_favicon = data.is_favicon(); out->obey_origin_policy = data.obey_origin_policy(); return true; } @@ -262,35 +282,100 @@ bool StructTraits<network::mojom::URLRequestBodyDataView, return true; } -bool StructTraits<network::mojom::DataElementDataView, network::DataElement>:: - Read(network::mojom::DataElementDataView data, network::DataElement* out) { - if (!data.ReadPath(&out->path_)) { - network::debug::SetDeserializationCrashKeyString("data_element_path"); +bool StructTraits<network::mojom::DataElementBytesDataView, + network::DataElementBytes>:: + Read(network::mojom::DataElementBytesDataView data, + network::DataElementBytes* out) { + mojo_base::BigBufferView big_buffer_view; + if (!data.ReadData(&big_buffer_view)) { return false; } - if (!data.ReadExpectedModificationTime(&out->expected_modification_time_)) { + *out = network::DataElementBytes(std::vector<uint8_t>( + big_buffer_view.data().begin(), big_buffer_view.data().end())); + return true; +} + +bool StructTraits<network::mojom::DataElementDataPipeDataView, + network::DataElementDataPipe>:: + Read(network::mojom::DataElementDataPipeDataView data, + network::DataElementDataPipe* out) { + auto data_pipe_getter = data.TakeDataPipeGetter< + mojo::PendingRemote<network::mojom::DataPipeGetter>>(); + *out = network::DataElementDataPipe(std::move(data_pipe_getter)); + return true; +} + +bool StructTraits<network::mojom::DataElementChunkedDataPipeDataView, + network::DataElementChunkedDataPipe>:: + Read(network::mojom::DataElementChunkedDataPipeDataView data, + network::DataElementChunkedDataPipe* out) { + auto data_pipe_getter = data.TakeDataPipeGetter< + mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>>(); + UMA_HISTOGRAM_BOOLEAN("NetworkService.StreamingUploadDataPipeGetterValidity", + data_pipe_getter.is_valid()); + *out = network::DataElementChunkedDataPipe( + std::move(data_pipe_getter), + network::DataElementChunkedDataPipe::ReadOnlyOnce(data.read_only_once())); + return true; +} + +bool StructTraits<network::mojom::DataElementFileDataView, + network::DataElementFile>:: + Read(network::mojom::DataElementFileDataView data, + network::DataElementFile* out) { + base::FilePath path; + if (!data.ReadPath(&path)) { return false; } - if (data.type() == network::mojom::DataElementType::kBytes) { - mojo_base::BigBufferView big_buffer; - if (!data.ReadBuf(&big_buffer)) - return false; - // TODO(yoichio): Fix DataElementDataView::ReadBuf issue - // (crbug.com/1152664). - if (data.length() != big_buffer.data().size()) - return false; - out->buf_.clear(); - out->buf_.insert(out->buf_.end(), big_buffer.data().begin(), - big_buffer.data().end()); + base::Time expected_modification_time; + if (!data.ReadExpectedModificationTime(&expected_modification_time)) { + return false; } - out->type_ = data.type(); - out->data_pipe_getter_ = data.TakeDataPipeGetter< - mojo::PendingRemote<network::mojom::DataPipeGetter>>(); - out->chunked_data_pipe_getter_ = data.TakeChunkedDataPipeGetter< - mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>>(); - out->offset_ = data.offset(); - out->length_ = data.length(); + *out = network::DataElementFile(path, data.offset(), data.length(), + expected_modification_time); return true; } +bool UnionTraits<network::mojom::DataElementDataView, network::DataElement>:: + Read(network::mojom::DataElementDataView data, network::DataElement* out) { + using Tag = network::mojom::DataElementDataView::Tag; + DCHECK(!data.is_null()); + + switch (data.tag()) { + case Tag::kBytes: { + network::DataElementBytes bytes; + if (!data.ReadBytes(&bytes)) { + return false; + } + *out = network::DataElement(std::move(bytes)); + return true; + } + case Tag::kDataPipe: { + network::DataElementDataPipe data_pipe; + if (!data.ReadDataPipe(&data_pipe)) { + return false; + } + *out = network::DataElement(std::move(data_pipe)); + return true; + } + case Tag::kChunkedDataPipe: { + network::DataElementChunkedDataPipe chunked_data_pipe; + if (!data.ReadChunkedDataPipe(&chunked_data_pipe)) { + return false; + } + *out = network::DataElement(std::move(chunked_data_pipe)); + return true; + } + case Tag::kFile: { + network::DataElementFile file; + if (!data.ReadFile(&file)) { + return false; + } + *out = network::DataElement(std::move(file)); + return true; + } + } + return false; +} + } // namespace mojo diff --git a/chromium/services/network/public/cpp/url_request_mojom_traits.h b/chromium/services/network/public/cpp/url_request_mojom_traits.h index 2568ed5e70e..e8266469f6f 100644 --- a/chromium/services/network/public/cpp/url_request_mojom_traits.h +++ b/chromium/services/network/public/cpp/url_request_mojom_traits.h @@ -17,6 +17,7 @@ #include "mojo/public/cpp/bindings/enum_traits.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/struct_traits.h" +#include "mojo/public/cpp/bindings/union_traits.h" #include "net/base/request_priority.h" #include "net/url_request/referrer_policy.h" #include "services/network/public/cpp/data_element.h" @@ -24,12 +25,14 @@ #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/resource_request_body.h" #include "services/network/public/cpp/site_for_cookies_mojom_traits.h" +#include "services/network/public/mojom/auth_and_certificate_observer.mojom.h" #include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h" #include "services/network/public/mojom/client_security_state.mojom-forward.h" #include "services/network/public/mojom/cookie_access_observer.mojom.h" #include "services/network/public/mojom/data_pipe_getter.mojom.h" #include "services/network/public/mojom/trust_tokens.mojom.h" #include "services/network/public/mojom/url_loader.mojom-shared.h" +#include "services/network/public/mojom/web_bundle_handle.mojom-shared.h" #include "url/mojom/url_gurl_mojom_traits.h" namespace mojo { @@ -76,6 +79,16 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) const_cast<network::ResourceRequest::TrustedParams&>(trusted_params) .cookie_observer); } + static mojo::PendingRemote< + network::mojom::AuthenticationAndCertificateObserver> + auth_cert_observer( + const network::ResourceRequest::TrustedParams& trusted_params) { + if (!trusted_params.auth_cert_observer) + return mojo::NullRemote(); + return std::move( + const_cast<network::ResourceRequest::TrustedParams&>(trusted_params) + .auth_cert_observer); + } static const network::mojom::ClientSecurityStatePtr& client_security_state( const network::ResourceRequest::TrustedParams& trusted_params) { return trusted_params.client_security_state; @@ -87,6 +100,35 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) template <> struct COMPONENT_EXPORT(NETWORK_CPP_BASE) + StructTraits<network::mojom::WebBundleTokenParamsDataView, + network::ResourceRequest::WebBundleTokenParams> { + static const GURL& bundle_url( + const network::ResourceRequest::WebBundleTokenParams& params) { + return params.bundle_url; + } + static const base::UnguessableToken& token( + const network::ResourceRequest::WebBundleTokenParams& params) { + return params.token; + } + static mojo::PendingRemote<network::mojom::WebBundleHandle> web_bundle_handle( + const network::ResourceRequest::WebBundleTokenParams& params) { + if (!params.handle) + return mojo::NullRemote(); + return std::move( + const_cast<network::ResourceRequest::WebBundleTokenParams&>(params) + .handle); + } + static int32_t render_process_id( + const network::ResourceRequest::WebBundleTokenParams& params) { + return params.render_process_id; + } + + static bool Read(network::mojom::WebBundleTokenParamsDataView data, + network::ResourceRequest::WebBundleTokenParams* out); +}; + +template <> +struct COMPONENT_EXPORT(NETWORK_CPP_BASE) StructTraits<network::mojom::URLRequestDataView, network::ResourceRequest> { static const std::string& method(const network::ResourceRequest& request) { return request.method; @@ -98,10 +140,6 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) const network::ResourceRequest& request) { return request.site_for_cookies; } - static bool force_ignore_site_for_cookies( - const network::ResourceRequest& request) { - return request.force_ignore_site_for_cookies; - } static bool update_first_party_url_on_redirect( const network::ResourceRequest& request) { return request.update_first_party_url_on_redirect; @@ -159,9 +197,6 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) static bool corb_detachable(const network::ResourceRequest& request) { return request.corb_detachable; } - static bool corb_excluded(const network::ResourceRequest& request) { - return request.corb_excluded; - } static network::mojom::RequestMode mode( const network::ResourceRequest& request) { return request.mode; @@ -253,6 +288,9 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) static bool is_fetch_like_api(const network::ResourceRequest& request) { return request.is_fetch_like_api; } + static bool is_favicon(const network::ResourceRequest& request) { + return request.is_favicon; + } static bool obey_origin_policy(const network::ResourceRequest& request) { return request.obey_origin_policy; } @@ -268,6 +306,10 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) const network::ResourceRequest& request) { return request.trust_token_params.as_ptr(); } + static const base::Optional<network::ResourceRequest::WebBundleTokenParams>& + web_bundle_token_params(const network::ResourceRequest& request) { + return request.web_bundle_token_params; + } static bool Read(network::mojom::URLRequestDataView data, network::ResourceRequest* out); @@ -311,40 +353,91 @@ struct COMPONENT_EXPORT(NETWORK_CPP_BASE) template <> struct COMPONENT_EXPORT(NETWORK_CPP_BASE) - StructTraits<network::mojom::DataElementDataView, network::DataElement> { - static const network::mojom::DataElementType& type( - const network::DataElement& element) { - return element.type_; - } - static mojo_base::BigBufferView buf(const network::DataElement& element) { - return mojo_base::BigBufferView(element.buf_); - } - static const base::FilePath& path(const network::DataElement& element) { - return element.path_; + StructTraits<network::mojom::DataElementBytesDataView, + network::DataElementBytes> { + static mojo_base::BigBufferView data(const network::DataElementBytes& data) { + return mojo_base::BigBufferView(data.bytes()); } + + static bool Read(network::mojom::DataElementBytesDataView data, + network::DataElementBytes* out); +}; + +template <> +struct COMPONENT_EXPORT(NETWORK_CPP_BASE) + StructTraits<network::mojom::DataElementDataPipeDataView, + network::DataElementDataPipe> { static mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter( - const network::DataElement& element) { - if (element.type_ != network::mojom::DataElementType::kDataPipe) - return mojo::NullRemote(); - return element.CloneDataPipeGetter(); + const network::DataElementDataPipe& data) { + return data.CloneDataPipeGetter(); } + + static bool Read(network::mojom::DataElementDataPipeDataView data, + network::DataElementDataPipe* out); +}; + +template <> +struct COMPONENT_EXPORT(NETWORK_CPP_BASE) + StructTraits<network::mojom::DataElementChunkedDataPipeDataView, + network::DataElementChunkedDataPipe> { static mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter> - chunked_data_pipe_getter(const network::DataElement& element) { - if (element.type_ != network::mojom::DataElementType::kChunkedDataPipe && - element.type_ != network::mojom::DataElementType::kReadOnceStream) - return mojo::NullRemote(); - return const_cast<network::DataElement&>(element) + data_pipe_getter(const network::DataElementChunkedDataPipe& data) { + return const_cast<network::DataElementChunkedDataPipe&>(data) .ReleaseChunkedDataPipeGetter(); } - static uint64_t offset(const network::DataElement& element) { - return element.offset_; + static bool read_only_once(const network::DataElementChunkedDataPipe& data) { + return static_cast<bool>(data.read_only_once()); + } + + static bool Read(network::mojom::DataElementChunkedDataPipeDataView data, + network::DataElementChunkedDataPipe* out); +}; + +template <> +struct COMPONENT_EXPORT(NETWORK_CPP_BASE) + StructTraits<network::mojom::DataElementFileDataView, + network::DataElementFile> { + static base::FilePath path(const network::DataElementFile& data) { + return data.path(); + } + static uint64_t offset(const network::DataElementFile& data) { + return data.offset(); + } + static uint64_t length(const network::DataElementFile& data) { + return data.length(); + } + static base::Time expected_modification_time( + const network::DataElementFile& data) { + return data.expected_modification_time(); + } + + static bool Read(network::mojom::DataElementFileDataView data, + network::DataElementFile* out); +}; + +template <> +struct COMPONENT_EXPORT(NETWORK_CPP_BASE) + UnionTraits<network::mojom::DataElementDataView, network::DataElement> { + static network::mojom::DataElementDataView::Tag GetTag( + const network::DataElement& data) { + return data.type(); + } + + static const network::DataElementBytes& bytes( + const network::DataElement& data) { + return data.As<network::DataElementBytes>(); + } + static const network::DataElementDataPipe& data_pipe( + const network::DataElement& data) { + return data.As<network::DataElementDataPipe>(); } - static uint64_t length(const network::DataElement& element) { - return element.length_; + static const network::DataElementChunkedDataPipe& chunked_data_pipe( + const network::DataElement& data) { + return data.As<network::DataElementChunkedDataPipe>(); } - static const base::Time& expected_modification_time( - const network::DataElement& element) { - return element.expected_modification_time_; + static const network::DataElementFile& file( + const network::DataElement& data) { + return data.As<network::DataElementFile>(); } static bool Read(network::mojom::DataElementDataView data, diff --git a/chromium/services/network/public/cpp/url_request_mojom_traits_unittest.cc b/chromium/services/network/public/cpp/url_request_mojom_traits_unittest.cc index abbd67bdd87..cbf992e29d5 100644 --- a/chromium/services/network/public/cpp/url_request_mojom_traits_unittest.cc +++ b/chromium/services/network/public/cpp/url_request_mojom_traits_unittest.cc @@ -4,7 +4,9 @@ #include "services/network/public/cpp/url_request_mojom_traits.h" +#include "base/optional.h" #include "base/test/gtest_util.h" +#include "base/test/task_environment.h" #include "mojo/public/cpp/base/unguessable_token_mojom_traits.h" #include "mojo/public/cpp/test_support/test_utils.h" #include "net/base/isolation_info.h" @@ -47,7 +49,6 @@ TEST(URLRequestMojomTraitsTest, Roundtrips_ResourceRequest) { original.url = GURL("https://example.com/resources/dummy.xml"); original.site_for_cookies = net::SiteForCookies::FromUrl(GURL("https://example.com/index.html")); - original.force_ignore_site_for_cookies = true; original.update_first_party_url_on_redirect = false; original.request_initiator = url::Origin::Create(original.url); original.isolated_world_origin = @@ -102,12 +103,104 @@ TEST(URLRequestMojomTraitsTest, Roundtrips_ResourceRequest) { mojom::TrustTokenSignRequestData::kInclude; original.trust_token_params->additional_signed_headers.push_back( "some_header"); + original.web_bundle_token_params = + base::make_optional(ResourceRequest::WebBundleTokenParams( + GURL("https://bundle.test/"), base::UnguessableToken::Create(), + mojo::PendingRemote<network::mojom::WebBundleHandle>())); network::ResourceRequest copied; - EXPECT_TRUE(mojo::test::SerializeAndDeserialize<mojom::URLRequest>(&original, - &copied)); + EXPECT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::URLRequest>(original, copied)); EXPECT_TRUE(original.EqualsForTesting(copied)); } +class DataElementDeserializationTest : public testing::Test { + protected: + base::test::TaskEnvironment task_environment_; +}; + +TEST_F(DataElementDeserializationTest, DataPipe) { + mojo::PendingRemote<mojom::DataPipeGetter> pending_remote; + auto pending_receiver = pending_remote.InitWithNewPipeAndPassReceiver(); + DataElement src{DataElementDataPipe(std::move(pending_remote))}; + + DataElement dest; + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::DataElement>(src, dest)); + ASSERT_EQ(dest.type(), network::DataElement::Tag::kDataPipe); + + mojo::Remote<mojom::DataPipeGetter> remote( + dest.As<DataElementDataPipe>().ReleaseDataPipeGetter()); + + // Make sure that `remote` and `pending_receiver` is connected to each other. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(remote.is_bound()); + EXPECT_TRUE(remote.is_connected()); + + pending_receiver.reset(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(remote.is_bound()); + EXPECT_FALSE(remote.is_connected()); +} + +TEST_F(DataElementDeserializationTest, ChunkedDataPipe) { + mojo::PendingRemote<mojom::ChunkedDataPipeGetter> pending_remote; + auto pending_receiver = pending_remote.InitWithNewPipeAndPassReceiver(); + DataElement src(DataElementChunkedDataPipe( + std::move(pending_remote), + DataElementChunkedDataPipe::ReadOnlyOnce(true))); + DataElement dest; + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::DataElement>(src, dest)); + ASSERT_EQ(dest.type(), network::DataElement::Tag::kChunkedDataPipe); + EXPECT_TRUE(dest.As<DataElementChunkedDataPipe>().read_only_once()); + mojo::Remote<mojom::ChunkedDataPipeGetter> remote( + dest.As<DataElementChunkedDataPipe>().ReleaseChunkedDataPipeGetter()); + + // Make sure that `remote` and `pending_receiver` is connected to each other. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(remote.is_bound()); + EXPECT_TRUE(remote.is_connected()); + + pending_receiver.reset(); + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(remote.is_bound()); + EXPECT_FALSE(remote.is_connected()); +} + +TEST_F(DataElementDeserializationTest, Bytes) { + const std::vector<uint8_t> kData = {8, 1, 9}; + DataElement src{DataElementBytes(kData)}; + DataElement dest; + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::DataElement>(src, dest)); + ASSERT_EQ(mojom::DataElementDataView::Tag::kBytes, dest.type()); + EXPECT_EQ(kData, dest.As<DataElementBytes>().bytes()); +} + +TEST_F(DataElementDeserializationTest, File) { + const base::FilePath kPath = base::FilePath::FromUTF8Unsafe("foobar"); + DataElement src(DataElementFile( + kPath, /*offset=*/3, /*length=*/8, + base::Time::UnixEpoch() + base::TimeDelta::FromMinutes(2))); + DataElement dest; + ASSERT_TRUE( + mojo::test::SerializeAndDeserialize<mojom::DataElement>(src, dest)); + ASSERT_EQ(mojom::DataElementDataView::Tag::kFile, dest.type()); + + const auto& src_file = src.As<DataElementFile>(); + const auto& dest_file = dest.As<DataElementFile>(); + + EXPECT_EQ(src_file.path(), dest_file.path()); + EXPECT_EQ(src_file.offset(), dest_file.offset()); + EXPECT_EQ(src_file.length(), dest_file.length()); + EXPECT_EQ(src_file.expected_modification_time(), + dest_file.expected_modification_time()); +} + } // namespace } // namespace network diff --git a/chromium/services/network/public/cpp/x_frame_options.dict b/chromium/services/network/public/cpp/x_frame_options.dict new file mode 100644 index 00000000000..444b1778646 --- /dev/null +++ b/chromium/services/network/public/cpp/x_frame_options.dict @@ -0,0 +1,11 @@ +# Copyright 2020 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. + +" " +"," +"DENY" +"SAMEORIGIN" +"ALLOWALL" +"ALLOW-FROM" +"https://www.example.com/" diff --git a/chromium/services/network/public/cpp/x_frame_options_parser.cc b/chromium/services/network/public/cpp/x_frame_options_parser.cc new file mode 100644 index 00000000000..e288d200989 --- /dev/null +++ b/chromium/services/network/public/cpp/x_frame_options_parser.cc @@ -0,0 +1,46 @@ +// Copyright 2020 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/x_frame_options_parser.h" + +#include <string> +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "net/http/http_response_headers.h" +#include "services/network/public/mojom/x_frame_options.mojom-shared.h" + +namespace network { + +mojom::XFrameOptionsValue ParseXFrameOptions( + const net::HttpResponseHeaders& headers) { + // Process the 'X-Frame-Options' header: + // https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-x-frame-options-header + // + // Note that we do not support the 'ALLOW-FROM' value defined in RFC7034. + mojom::XFrameOptionsValue result = mojom::XFrameOptionsValue::kNone; + size_t iter = 0; + std::string value; + while (headers.EnumerateHeader(&iter, "x-frame-options", &value)) { + mojom::XFrameOptionsValue current = mojom::XFrameOptionsValue::kInvalid; + + base::StringPiece trimmed = + base::TrimWhitespaceASCII(value, base::TRIM_ALL); + + if (base::LowerCaseEqualsASCII(trimmed, "deny")) + current = mojom::XFrameOptionsValue::kDeny; + else if (base::LowerCaseEqualsASCII(trimmed, "allowall")) + current = mojom::XFrameOptionsValue::kAllowAll; + else if (base::LowerCaseEqualsASCII(trimmed, "sameorigin")) + current = mojom::XFrameOptionsValue::kSameOrigin; + + if (result == mojom::XFrameOptionsValue::kNone) + result = current; + else if (result != current) + result = mojom::XFrameOptionsValue::kConflict; + } + + return result; +} + +} // namespace network diff --git a/chromium/services/network/public/cpp/x_frame_options_parser.h b/chromium/services/network/public/cpp/x_frame_options_parser.h new file mode 100644 index 00000000000..793c0651584 --- /dev/null +++ b/chromium/services/network/public/cpp/x_frame_options_parser.h @@ -0,0 +1,22 @@ +// Copyright 2020 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_X_FRAME_OPTIONS_PARSER_H_ +#define SERVICES_NETWORK_PUBLIC_CPP_X_FRAME_OPTIONS_PARSER_H_ + +#include "base/component_export.h" +#include "services/network/public/mojom/x_frame_options.mojom-forward.h" + +namespace net { +class HttpResponseHeaders; +} + +namespace network { + +COMPONENT_EXPORT(NETWORK_CPP) +mojom::XFrameOptionsValue ParseXFrameOptions(const net::HttpResponseHeaders&); + +} // namespace network + +#endif // SERVICES_NETWORK_PUBLIC_CPP_X_FRAME_OPTIONS_PARSER_H_ diff --git a/chromium/services/network/public/cpp/x_frame_options_parser_fuzzer.cc b/chromium/services/network/public/cpp/x_frame_options_parser_fuzzer.cc new file mode 100644 index 00000000000..234f673aa43 --- /dev/null +++ b/chromium/services/network/public/cpp/x_frame_options_parser_fuzzer.cc @@ -0,0 +1,22 @@ +// Copyright 2020 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/x_frame_options_parser.h" + +#include <string> +#include "net/http/http_response_headers.h" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const std::string test_data(reinterpret_cast<const char*>(data), size); + std::string header_string("HTTP/1.1 200 OK\nX-Frame-Options: "); + header_string += test_data; + header_string += "\n\n"; + + std::replace(header_string.begin(), header_string.end(), '\n', '\0'); + scoped_refptr<net::HttpResponseHeaders> headers = + new net::HttpResponseHeaders(header_string); + + network::ParseXFrameOptions(*headers); + return 0; +} diff --git a/chromium/services/network/public/cpp/x_frame_options_parser_unittest.cc b/chromium/services/network/public/cpp/x_frame_options_parser_unittest.cc new file mode 100644 index 00000000000..bad0364383a --- /dev/null +++ b/chromium/services/network/public/cpp/x_frame_options_parser_unittest.cc @@ -0,0 +1,81 @@ +// Copyright 2020 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/x_frame_options_parser.h" + +#include <string> +#include "net/http/http_response_headers.h" +#include "services/network/public/mojom/x_frame_options.mojom-shared.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +net::HttpResponseHeaders* ConstructHeader(const char* value) { + std::string header_string("HTTP/1.1 200 OK"); + if (value) { + header_string += "\nX-Frame-Options: "; + header_string += value; + } + header_string += "\n\n"; + + std::replace(header_string.begin(), header_string.end(), '\n', '\0'); + net::HttpResponseHeaders* headers = + new net::HttpResponseHeaders(header_string); + + return headers; +} + +} // namespace + +namespace network { + +TEST(XFrameOptionsTest, Parse) { + struct TestCase { + const char* header; + mojom::XFrameOptionsValue expected; + } cases[] = { + // Single values: + {nullptr, mojom::XFrameOptionsValue::kNone}, + {"DENY", mojom::XFrameOptionsValue::kDeny}, + {"SAMEORIGIN", mojom::XFrameOptionsValue::kSameOrigin}, + {"ALLOWALL", mojom::XFrameOptionsValue::kAllowAll}, + {"NOT-A-VALUE", mojom::XFrameOptionsValue::kInvalid}, + {"DeNy", mojom::XFrameOptionsValue::kDeny}, + {"SaMeOrIgIn", mojom::XFrameOptionsValue::kSameOrigin}, + {"AllOWaLL", mojom::XFrameOptionsValue::kAllowAll}, + + // Repeated values: + {"DENY,DENY", mojom::XFrameOptionsValue::kDeny}, + {"SAMEORIGIN,SAMEORIGIN", mojom::XFrameOptionsValue::kSameOrigin}, + {"ALLOWALL,ALLOWALL", mojom::XFrameOptionsValue::kAllowAll}, + {"DENY,DeNy", mojom::XFrameOptionsValue::kDeny}, + {"SAMEORIGIN,SaMeOrIgIn", mojom::XFrameOptionsValue::kSameOrigin}, + {"ALLOWALL,AllOWaLL", mojom::XFrameOptionsValue::kAllowAll}, + {"INVALID,INVALID", mojom::XFrameOptionsValue::kInvalid}, + {"INVALID,DIFFERENTLY-INVALID", mojom::XFrameOptionsValue::kInvalid}, + + // Conflicting values: + {"ALLOWALL,DENY", mojom::XFrameOptionsValue::kConflict}, + {"ALLOWALL,SAMEORIGIN", mojom::XFrameOptionsValue::kConflict}, + {"ALLOWALL,INVALID", mojom::XFrameOptionsValue::kConflict}, + {"DENY,ALLOWALL", mojom::XFrameOptionsValue::kConflict}, + {"DENY,SAMEORIGIN", mojom::XFrameOptionsValue::kConflict}, + {"DENY,INVALID", mojom::XFrameOptionsValue::kConflict}, + {"SAMEORIGIN,ALLOWALL", mojom::XFrameOptionsValue::kConflict}, + {"SAMEORIGIN,DENY", mojom::XFrameOptionsValue::kConflict}, + {"SAMEORIGIN,INVALID", mojom::XFrameOptionsValue::kConflict}, + {"INVALID,DENY", mojom::XFrameOptionsValue::kConflict}, + {"INVALID,SAMEORIGIN", mojom::XFrameOptionsValue::kConflict}, + {"INVALID,ALLOWALL", mojom::XFrameOptionsValue::kConflict}, + }; + + for (const auto& test : cases) { + SCOPED_TRACE(test.header); + scoped_refptr<net::HttpResponseHeaders> headers = + ConstructHeader(test.header); + EXPECT_EQ(test.expected, ParseXFrameOptions(*headers)); + } +} + +} // namespace network diff --git a/chromium/services/network/public/mojom/BUILD.gn b/chromium/services/network/public/mojom/BUILD.gn index ada2742a9a1..e36a90c3cc0 100644 --- a/chromium/services/network/public/mojom/BUILD.gn +++ b/chromium/services/network/public/mojom/BUILD.gn @@ -123,7 +123,10 @@ mojom("mojom_network_isolation_key") { generate_java = true sources = [ "network_isolation_key.mojom" ] - public_deps = [ "//url/mojom:url_mojom_origin" ] + public_deps = [ + ":mojom_schemeful_site", + "//url/mojom:url_mojom_origin", + ] if (!is_ios) { export_class_attribute_blink = "BLINK_PLATFORM_EXPORT" @@ -153,6 +156,7 @@ mojom("mojom_network_isolation_key") { mojom("url_loader_base") { generate_java = true sources = [ + "auth_and_certificate_observer.mojom", "chunked_data_pipe_getter.mojom", "client_security_state.mojom", "cross_origin_embedder_policy.mojom", @@ -162,9 +166,11 @@ mojom("url_loader_base") { "mutable_network_traffic_annotation_tag.mojom", "mutable_partial_network_traffic_annotation_tag.mojom", "trust_tokens.mojom", + "web_bundle_handle.mojom", ] public_deps = [ + ":mojom_network_param", "//mojo/public/mojom/base", "//url/mojom:url_mojom_gurl", "//url/mojom:url_mojom_origin", @@ -247,19 +253,12 @@ mojom("mojom_schemeful_site") { ] } -# This target is split from "mojom" target as the lazy serialization may -# cause problems. See https://crbug.com/822732. -mojom("websocket_mojom") { +mojom("mojom_network_param") { generate_java = true - sources = [ - "network_param.mojom", - "websocket.mojom", - ] + sources = [ "network_param.mojom" ] public_deps = [ - ":mojom_ip_address", "//mojo/public/mojom/base", - "//url/mojom:url_mojom_gurl", "//url/mojom:url_mojom_origin", ] @@ -269,13 +268,25 @@ mojom("websocket_mojom") { export_header_blink = "third_party/blink/public/platform/web_common.h" } - cpp_typemaps = [ + # Typemaps which apply to both Blink and non-Blink bindings. + shared_cpp_typemaps = [ { types = [ { mojom = "network.mojom.AuthChallengeInfo" cpp = "::net::AuthChallengeInfo" }, + ] + traits_headers = + [ "//services/network/public/cpp/network_param_mojom_traits.h" ] + traits_public_deps = + [ "//services/network/public/cpp:network_param_mojom_support" ] + }, + ] + + cpp_typemaps = [ + { + types = [ { mojom = "network.mojom.AuthCredentials" cpp = "::net::AuthCredentials" @@ -320,15 +331,34 @@ mojom("websocket_mojom") { "//services/network/public/cpp/net_ipc_param_traits.h", "//services/network/public/cpp/network_param_mojom_traits.h", ] - traits_sources = - [ "//services/network/public/cpp/network_param_mojom_traits.cc" ] - traits_public_deps = [ - "//ipc", - "//net", - "//services/network/public/cpp:cpp_base", - ] + traits_public_deps = + [ "//services/network/public/cpp:network_param_mojom_support" ] }, ] + cpp_typemaps += shared_cpp_typemaps + blink_cpp_typemaps = [] + blink_cpp_typemaps += shared_cpp_typemaps +} + +# This target is split from "mojom" target as the lazy serialization may +# cause problems. See https://crbug.com/822732. +mojom("websocket_mojom") { + generate_java = true + sources = [ "websocket.mojom" ] + + if (!is_ios) { + export_class_attribute_blink = "BLINK_PLATFORM_EXPORT" + export_define_blink = "BLINK_PLATFORM_IMPLEMENTATION=1" + export_header_blink = "third_party/blink/public/platform/web_common.h" + } + + public_deps = [ + ":mojom_ip_address", + ":mojom_network_param", + "//mojo/public/mojom/base", + "//url/mojom:url_mojom_gurl", + "//url/mojom:url_mojom_origin", + ] } mojom("cookies_mojom") { @@ -511,12 +541,14 @@ mojom("mojom") { "url_response_head.mojom", "web_client_hints_types.mojom", "web_sandbox_flags.mojom", + "x_frame_options.mojom", ] public_deps = [ ":cookies_mojom", ":mojom_ip_address", ":mojom_network_isolation_key", + ":mojom_network_param", ":mojom_schemeful_site", ":url_loader_base", ":websocket_mojom", @@ -545,11 +577,11 @@ 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_lacros) { + if (is_linux || is_chromeos_lacros) { enabled_features += [ "needs_crypt_config" ] } - if (is_android || is_ash) { + if (is_android || is_chromeos_ash) { enabled_features += [ "network_change_notifier_in_browser" ] } @@ -580,6 +612,10 @@ mojom("mojom") { cpp = "::network::ResourceRequest::TrustedParams" }, { + mojom = "network.mojom.WebBundleTokenParams" + cpp = "::network::ResourceRequest::WebBundleTokenParams" + }, + { mojom = "network.mojom.URLRequest" cpp = "::network::ResourceRequest" }, @@ -598,6 +634,26 @@ mojom("mojom") { cpp = "::net::RequestPriority" }, { + mojom = "network.mojom.DataElementBytes" + cpp = "::network::DataElementBytes" + move_only = true + }, + { + mojom = "network.mojom.DataElementDataPipe" + cpp = "::network::DataElementDataPipe" + move_only = true + }, + { + mojom = "network.mojom.DataElementChunkedDataPipe" + cpp = "::network::DataElementChunkedDataPipe" + move_only = true + }, + { + mojom = "network.mojom.DataElementFile" + cpp = "::network::DataElementFile" + move_only = true + }, + { mojom = "network.mojom.DataElement" cpp = "::network::DataElement" move_only = true diff --git a/chromium/services/network/public/mojom/address_list.mojom b/chromium/services/network/public/mojom/address_list.mojom index 9af17c47bd2..ca4b8394437 100644 --- a/chromium/services/network/public/mojom/address_list.mojom +++ b/chromium/services/network/public/mojom/address_list.mojom @@ -9,5 +9,8 @@ import "services/network/public/mojom/ip_endpoint.mojom"; // Mirror of net::AddressList. struct AddressList { array<IPEndPoint> addresses; - string canonical_name; + // The first entry of `dns_aliases`, if it exists, is the canonical name. + // The alias chain is preserved in reverse order, from canonical name (i.e. + // address record name) through to query name. + array<string> dns_aliases; }; diff --git a/chromium/services/network/public/mojom/auth_and_certificate_observer.mojom b/chromium/services/network/public/mojom/auth_and_certificate_observer.mojom new file mode 100644 index 00000000000..0192f6b3cf6 --- /dev/null +++ b/chromium/services/network/public/mojom/auth_and_certificate_observer.mojom @@ -0,0 +1,120 @@ +// Copyright 2021 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 "mojo/public/mojom/base/unguessable_token.mojom"; +import "services/network/public/mojom/network_param.mojom"; +import "url/mojom/url.mojom"; + +// This interface enables the UI to send client certificate selections back to +// the network service. +// +// Defining an interface for this purpose, rather than using a union in the +// response of OnCertificateRequested, enables the NetworkServiceClient to learn +// of the URLLoader destruction via the connection error handler. +interface ClientCertificateResponder { + // Use the selected certificate and continue the URLRequest. + // + // - |provider_name| corresponds to the return value of + // net::SSLPrivateKey::GetProviderName(). + // - |algorithm_preferences| corresponds to the return value of + // net::SSLPrivateKey::GetAlgorithmPreferences(). + ContinueWithCertificate(network.mojom.X509Certificate x509_certificate, + string provider_name, + array<uint16> algorithm_preferences, + pending_remote<SSLPrivateKey> ssl_private_key); + + // Affirmatively select no certificate (this is cached and can affect future + // URLRequests). Does not cancel the URLRequest. + // + // The connection is continued with no client cert. + // net::URLRequest::ContinueWithCertificate(nullptr, nullptr) needs to be + // called. + ContinueWithoutCertificate(); + + // Cancel the URLRequest. The request is aborted. + // net::URLRequest::CancelWithError() needs to be called. + CancelRequest(); +}; + +// 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 +// sent from content/browser to services/network so that services/network can +// have its own net::SSLPrivateKey implementation that internally uses this mojo +// interface. +// The |algorithm| and |input| parameters correspond to the |algorithm| and +// |input| parameters in net::SSLPrivateKey::Sign(). +// The |net_error| and |signature| parameters correspond to the parameters in +// net::SSLPrivateKey::SignCallback. +interface SSLPrivateKey { + // Sign |input| using the private key and |algorithm|. + Sign(uint16 algorithm, + array<uint8> input) => (int32 net_error, array<uint8> signature); +}; + +// The |credentials| output parameter is given to URLRequest::SetAuth() +// through this mojo interface. It is not set when URLRequest::CancelAuth() +// needs to be called. +interface AuthChallengeResponder { + // Called in response to an authentication request. See + // AuthenticationAndCertificateObserver::OnAuthRequired. + OnAuthCredentials(AuthCredentials? credentials); +}; + +// This interface is used by an URLLoaderFactory to provide notification +// of authentication and certificate events. This is typically implemented +// by a trusted process such as the browser process. +interface AuthenticationAndCertificateObserver { + // Called when an SSL certificate is encountered. + // The callback argument is a net::ERROR value. If it's net::OK, then the + // request is resumed. Otherwise it's cancelled with the given error. + OnSSLCertificateError(url.mojom.Url url, + int32 net_error, + SSLInfo ssl_info, + bool fatal) => (int32 net_error); + + // Called when an SSL certificate requested message is received for client + // authentication. + // + // Rather than using one response for multiple purposes, the caller expects + // exactly one response (or disconnect) to be sent back via |cert_responder|. + // + // |window_id| indicates the frame making the request, see + // network::ResourceRequest::fetch_window_id. This parameter is only set + // when the request is from a service worker to identify the origin window. + // The is not needed for frame based requests because they have their own + // URLLoaderFactory and a unique AuthenticationAndCertificateObserver for each + // frame. + OnCertificateRequested( + mojo_base.mojom.UnguessableToken? window_id, + network.mojom.SSLCertRequestInfo cert_info, + pending_remote<ClientCertificateResponder> cert_responder); + + // Called when we receive an authentication failure. + // The |auth_challenge_responder| will respond to auth challenge with + // credentials. |head_headers| can provide response headers for the response + // which has elicited this auth request, if applicable. + // + // |window_id| indicates the frame making the request, see + // network::ResourceRequest::fetch_window_id. This parameter is only set + // when the request is from a service worker to identify the origin window. + // The is not needed for frame based requests because they have their own + // URLLoaderFactory and a unique AuthenticationAndCertificateObserver for each + // frame. + OnAuthRequired( + mojo_base.mojom.UnguessableToken? window_id, + uint32 request_id, + url.mojom.Url url, + bool first_auth_attempt, + AuthChallengeInfo auth_info, + HttpResponseHeaders? head_headers, + pending_remote<AuthChallengeResponder> auth_challenge_responder); + + // Used by the NetworkService to create a copy of this observer. + // (e.g. when creating an observer for URLLoader from URLLoaderFactory's + // observer). + Clone(pending_receiver<AuthenticationAndCertificateObserver> listener); +};
\ No newline at end of file diff --git a/chromium/services/network/public/mojom/client_security_state.mojom b/chromium/services/network/public/mojom/client_security_state.mojom index 283446a3fcb..38c70befdfc 100644 --- a/chromium/services/network/public/mojom/client_security_state.mojom +++ b/chromium/services/network/public/mojom/client_security_state.mojom @@ -20,6 +20,10 @@ enum PrivateNetworkRequestPolicy { // Forbid requests to more-private address spaces than that of the initiator, // when the initiator is not in a secure context. kBlockFromInsecureToMorePrivate, + + // Warn about requests to more-private address spaces than that of the + // initiator when the initiator is not in a secure context. + kWarnFromInsecureToMorePrivate, }; struct ClientSecurityState { diff --git a/chromium/services/network/public/mojom/content_security_policy.mojom b/chromium/services/network/public/mojom/content_security_policy.mojom index 1651aaecc6e..4f41490b5c9 100644 --- a/chromium/services/network/public/mojom/content_security_policy.mojom +++ b/chromium/services/network/public/mojom/content_security_policy.mojom @@ -64,14 +64,15 @@ struct CSPSource { }; enum CSPHashAlgorithm { - SHA256, - SHA384, - SHA512, + None = 0, + SHA256 = 1, + SHA384 = 2, + SHA512 = 4, }; struct CSPHashSource { CSPHashAlgorithm algorithm; - string value; + array<uint8> value; }; struct CSPSourceList { @@ -109,7 +110,6 @@ enum CSPDirectiveName { MediaSrc, NavigateTo, ObjectSrc, - PluginTypes, PrefetchSrc, ReportTo, ReportURI, @@ -127,7 +127,35 @@ enum CSPDirectiveName { WorkerSrc, }; +enum CSPRequireTrustedTypesFor { + None = 0, + Script = 1, +}; + +// The parsed value of the CSP directive 'trusted-types'. +// https://w3c.github.io/webappsec-trusted-types/dist/spec/#trusted-types-csp-directive +struct CSPTrustedTypes { + // The list of policies allowed by the 'trusted-types' directive. + array<string> list; + + // This is true if the directive value contains the wildcard * (meaning all + // policy names are allowed). + bool allow_any = false; + + // This is true if the directive value contains the keyword 'allow-duplicates' + // (which allows creating policies with a name that was already used). + bool allow_duplicates = false; +}; + struct ContentSecurityPolicy { + // The origin used for matching the 'self' keyword. + // https://w3c.github.io/webappsec-csp/#framework-policy + CSPSource self_origin; + + // The raw, unparsed values of the specified CSP directives. Needed for + // reporting. + map<CSPDirectiveName, string> raw_directives; + map<CSPDirectiveName, CSPSourceList> directives; // Spec: https://www.w3.org/TR/upgrade-insecure-requests/ @@ -141,6 +169,9 @@ struct ContentSecurityPolicy { // https://wicg.github.io/cors-rfc1918/#csp bool treat_as_public_address = false; + // https://www.w3.org/TR/mixed-content/#strict-opt-in + bool block_all_mixed_content = false; + // https://www.w3.org/TR/CSP3/#directive-sandbox // This uses the convention: kNone means "nothing is disallowed". WebSandboxFlags sandbox = WebSandboxFlags.kNone; @@ -154,11 +185,18 @@ struct ContentSecurityPolicy { // Set of reporting endpoints to which violation reports are sent. array<string> report_endpoints; - // Plugin types specified by the CSP. If `null`, no plugin-types directive is - // specified and hence all types are allowed. If an empty array, then the - // plugin-types directive is specified with no types, hence no types are - // allowed. - array<string>? plugin_types; + // The parsed value of the directive 'require-trusted-types-for'. + // https://w3c.github.io/webappsec-trusted-types/dist/spec/#require-trusted-types-for-csp-directive + CSPRequireTrustedTypesFor require_trusted_types_for = + CSPRequireTrustedTypesFor.None; + + // The parsed value of the directive 'trusted-types'. + // https://w3c.github.io/webappsec-trusted-types/dist/spec/#trusted-types-csp-directive + // Note: If this is null, the directive was not present. On the other side, if + // this is a default CSPTrustedTypes struct with empty list, it means that the + // directive was present with empty value, so policies may not be created and + // no DOM XSS injection sinks can be used at all. + CSPTrustedTypes? trusted_types; // An array containing a set of errors occurred while parsing the CSP header. array<string> parsing_errors; diff --git a/chromium/services/network/public/mojom/cookie_access_observer.mojom b/chromium/services/network/public/mojom/cookie_access_observer.mojom index 04dc1ae40b2..b151023fae6 100644 --- a/chromium/services/network/public/mojom/cookie_access_observer.mojom +++ b/chromium/services/network/public/mojom/cookie_access_observer.mojom @@ -23,7 +23,12 @@ struct CookieAccessDetails { url.mojom.Url url; SiteForCookies site_for_cookies; - array<CookieWithAccessResult> cookie_list; + // Each element of the `cookie_list` array includes a CookieInclusionStatus + // and a CanonicalCookie if one was successfully constructed; if we were + // unable to create the CanonicalCookie, then we use the cookie string + // instead. Note that this means we always get a CanonicalCookie for reads, + // but may get a cookie string on writes. + array<CookieOrLineWithAccessResult> cookie_list; // |devtools_request_id| contains the DevTools request id of the request // that triggered the cookie change, if the read was triggered by a request. diff --git a/chromium/services/network/public/mojom/cookie_manager.mojom b/chromium/services/network/public/mojom/cookie_manager.mojom index c95b843fc7f..3efaabbf59b 100644 --- a/chromium/services/network/public/mojom/cookie_manager.mojom +++ b/chromium/services/network/public/mojom/cookie_manager.mojom @@ -6,7 +6,6 @@ module network.mojom; import "components/content_settings/core/common/content_settings.mojom"; import "mojo/public/mojom/base/time.mojom"; -import "services/network/public/mojom/schemeful_site.mojom"; import "url/mojom/url.mojom"; // Parameters for constructing a cookie manager. @@ -52,6 +51,8 @@ enum CookieAccessDelegateType { USE_CONTENT_SETTINGS, // Always returns Legacy access semantics. ALWAYS_LEGACY, + // Always returns Non-Legacy access semantics. + ALWAYS_NONLEGACY, }; enum CookiePriority { @@ -100,6 +101,16 @@ struct CookieSameSiteContext { ContextType schemeful_context = CROSS_SITE; }; +// Computed for every cookie access attempt but is only relevant for SameParty +// cookies. +enum SamePartyCookieContextType { + // The opposite to kSameParty. Should be the default value. + kCrossParty, + // If the request URL is in the same First-Party Sets as the top-frame site + // and each member of the isolation_info.party_context. + kSameParty, +}; + // What rules to apply when determining whether access to a particular cookie is // allowed. // Keep in sync with net/cookies/cookie_constants.h. @@ -115,7 +126,12 @@ struct CookieOptions { CookieSameSiteContext same_site_cookie_context; bool update_access_time = true; bool return_excluded_cookies = false; - array<SchemefulSite>? full_party_context; + SamePartyCookieContextType same_party_cookie_context_type = kCrossParty; + // The size of the isolation_info.party_context plus the top-frame site for + // logging purposes. + uint32 full_party_context_size = 0; + // Whether the site is a member of a nontrivial First-Party Set. + bool is_in_nontrivial_first_party_set = false; }; // See net/cookies/canonical_cookie.{h,cc} for documentation. @@ -154,10 +170,22 @@ struct CookieAndLineWithAccessResult { CookieAccessResult access_result; }; +union CookieOrLine { + CanonicalCookie cookie; + string cookie_string; +}; + +struct CookieOrLineWithAccessResult { + CookieOrLine cookie_or_line; + CookieAccessResult access_result; +}; + +// See net/cookies/cookie_access_result.{cc,h} for documentation. struct CookieAccessResult { CookieEffectiveSameSite effective_same_site; CookieAccessSemantics access_semantics; CookieInclusionStatus status; + bool is_allowed_to_access_secure_cookies; }; struct CookieWithAccessResult { diff --git a/chromium/services/network/public/mojom/fetch_api.mojom b/chromium/services/network/public/mojom/fetch_api.mojom index 8b3f2f16df1..c65304ac830 100644 --- a/chromium/services/network/public/mojom/fetch_api.mojom +++ b/chromium/services/network/public/mojom/fetch_api.mojom @@ -27,28 +27,39 @@ enum RequestMode { // // Note: if destination is kDocument it should be a main // resource request for main frames, except for Portals cases. +// +// These values are persisted to logs. Entries should not be renumbered and +// numeric values should never be reused. enum RequestDestination { - kEmpty, - kAudio, - kAudioWorklet, - kDocument, - kEmbed, - kFont, - kFrame, - kIframe, - kImage, - kManifest, - kObject, - kPaintWorklet, - kReport, - kScript, - kServiceWorker, - kSharedWorker, - kStyle, - kTrack, - kVideo, - kWorker, - kXslt, + kEmpty = 0, + kAudio = 1, + kAudioWorklet = 2, + kDocument = 3, + kEmbed = 4, + kFont = 5, + kFrame = 6, + kIframe = 7, + kImage = 8, + kManifest = 9, + kObject = 10, + kPaintWorklet = 11, + kReport = 12, + kScript = 13, + kServiceWorker = 14, + kSharedWorker = 15, + kStyle = 16, + kTrack = 17, + kVideo = 18, + // kWebBundle represents a request for a WebBundle. A <link> element whose + // rel is "webbundle" uses this destination. + // + // e.g. <link rel=webbundle href="example.com/foo.wbn" resources="..."> + // + // Fetch specifiction does not define this destination yet. + // Tracking issue: https://github.com/whatwg/fetch/issues/1120 + kWebBundle = 19, + kWorker = 20, + kXslt = 21, }; // Corresponds to Fetch request's "redirect mode": diff --git a/chromium/services/network/public/mojom/isolation_info.mojom b/chromium/services/network/public/mojom/isolation_info.mojom index cc2571066b6..575d016c90c 100644 --- a/chromium/services/network/public/mojom/isolation_info.mojom +++ b/chromium/services/network/public/mojom/isolation_info.mojom @@ -5,6 +5,7 @@ module network.mojom; import "services/network/public/mojom/site_for_cookies.mojom"; +import "services/network/public/mojom/schemeful_site.mojom"; import "url/mojom/origin.mojom"; // Mapped to net::IsolationInfo::RequestType, which is what consumers should @@ -24,4 +25,5 @@ struct IsolationInfo { url.mojom.Origin? frame_origin; bool opaque_and_non_transient; SiteForCookies site_for_cookies; + array<SchemefulSite>? party_context; }; diff --git a/chromium/services/network/public/mojom/load_timing_info.mojom b/chromium/services/network/public/mojom/load_timing_info.mojom index c8a6db8697e..a9a1b50be63 100644 --- a/chromium/services/network/public/mojom/load_timing_info.mojom +++ b/chromium/services/network/public/mojom/load_timing_info.mojom @@ -29,6 +29,7 @@ struct LoadTimingInfo { mojo_base.mojom.TimeTicks send_end; mojo_base.mojom.TimeTicks receive_headers_start; mojo_base.mojom.TimeTicks receive_headers_end; + mojo_base.mojom.TimeTicks receive_non_informational_headers_start; mojo_base.mojom.TimeTicks first_early_hints_time; mojo_base.mojom.TimeTicks push_start; mojo_base.mojom.TimeTicks push_end; diff --git a/chromium/services/network/public/mojom/network_context.mojom b/chromium/services/network/public/mojom/network_context.mojom index 0a09398cf50..5aca8515f5c 100644 --- a/chromium/services/network/public/mojom/network_context.mojom +++ b/chromium/services/network/public/mojom/network_context.mojom @@ -11,6 +11,7 @@ import "mojo/public/mojom/base/time.mojom"; import "mojo/public/mojom/base/unguessable_token.mojom"; import "mojo/public/mojom/base/values.mojom"; import "services/network/public/mojom/address_list.mojom"; +import "services/network/public/mojom/auth_and_certificate_observer.mojom"; import "services/network/public/mojom/cert_verifier_service.mojom"; import "services/network/public/mojom/client_security_state.mojom"; import "services/network/public/mojom/cookie_access_observer.mojom"; @@ -58,9 +59,6 @@ import "services/network/public/mojom/dhcp_wpad_url_client.mojom"; [EnableIf=is_ct_supported] import "services/network/public/mojom/ct_log_info.mojom"; -[EnableIf=is_trial_comparison_cert_verifier_supported] -import "services/network/public/mojom/trial_comparison_cert_verifier.mojom"; - const uint32 kWebSocketOptionNone = 0; // Disallow the request from sending cookies. Disallow the response from writing // cookies. @@ -73,9 +71,7 @@ const uint32 kWebSocketOptionBlockThirdPartyCookies = 2; // The browser can provide a CustomProxyConfig to a CustomProxyConfigClient // running in the network service. The network service will use the given proxy // configuration if a request matches the proxy rules and all the other -// criteria contained within it. This configuration allows the browser to -// direct the network service to set headers on requests to the given proxies -// before and/or after the caching layer. +// criteria contained within it. struct CustomProxyConfig { // The custom proxy rules to use. Note that ftp:// requests are not // supported. @@ -97,32 +93,19 @@ struct CustomProxyConfig { HttpRequestHeaders connect_tunnel_headers; }; -// Parameters to specify how the net::CertVerifier and net::CertVerifyProc -// objects should be instantiated. -struct CertVerifierCreationParams { - // 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; - - // Specifies which cert verifier implementation to use. - // kDefault - Decided by base::Feature - // kBuiltin - Use CertVerifyProcBuiltin - // kSystem - Use the system CertVerifyProc implementation - [EnableIf=is_builtin_cert_verifier_feature_supported] - enum CertVerifierImpl {kDefault, kBuiltin, kSystem}; - [EnableIf=is_builtin_cert_verifier_feature_supported] - CertVerifierImpl use_builtin_cert_verifier = kDefault; - - // Parameters for the cert verifier comparison trial. This is a temporary - // interface and embedders should not use it. - // See https://crbug.com/649026 - [EnableIf=is_trial_comparison_cert_verifier_supported] - TrialComparisonCertVerifierParams? trial_comparison_cert_verifier_params; +// Observer of custom proxy connections, especially for when errors occur. +interface CustomProxyConnectionObserver { + // Called when use of |bad_proxy| fails due to |net_error|. |net_error| is the + // network error encountered, if any, and OK if the fallback was for a reason + // other than a network error (e.g. the proxy service was explicitly directed + // to skip a proxy). + OnFallback(proxy_resolver.mojom.ProxyServer bad_proxy, int32 net_error); + + // Called when the response headers for the proxy tunnel request have been + // received. + OnTunnelHeadersReceived( + proxy_resolver.mojom.ProxyServer proxy_server, + HttpResponseHeaders response_headers); }; // Includes a pipe to a CertVerifierService for usage by the @@ -133,13 +116,6 @@ struct CertVerifierServiceRemoteParams { cert_verifier_service; }; -// Contains the parameters necessary to either connect to a CertVerifierService -// or create a net::CertVerifier in the network service itself. -union CertVerifierParams { - CertVerifierServiceRemoteParams remote_params; - CertVerifierCreationParams creation_params; -}; - // Client to update the custom proxy config. interface CustomProxyConfigClient { OnCustomProxyConfigUpdated(CustomProxyConfig proxy_config); @@ -335,6 +311,12 @@ struct NetworkContextParams { pending_receiver<CustomProxyConfigClient>? custom_proxy_config_client_receiver; + // If |initial_custom_proxy_config| or |custom_proxy_config_client_receiver| + // is set, information about custom proxy connections will be reported to this + // observer. + pending_remote<CustomProxyConnectionObserver>? + custom_proxy_connection_observer_remote; + // If |proxy_config_client_request| is non-null, this is called during // periods of network activity, and can be used as a signal for polling-based // logic to determine the proxy config. @@ -398,9 +380,8 @@ struct NetworkContextParams { [EnableIf=is_ct_supported] mojo_base.mojom.Time ct_log_update_time; - // Contains either a pipe to a CertVerifierService, or parameters used to - // instantiate a net::CertVerifier in-process. - CertVerifierParams? cert_verifier_params; + // Contains a pipe to a CertVerifierService. + CertVerifierServiceRemoteParams cert_verifier_params; // Initial additional certificates that will be used for certificate // validation. @@ -630,11 +611,6 @@ struct URLLoaderFactoryParams { // impact because of the extra process hops, so use should be minimized. pending_remote<TrustedURLLoaderHeaderClient>? header_client; - // |factory_bound_access_patterns| are used for CORS checks in addition to - // the per-context allow patterns that is managed via NetworkContext - // interface. This still respects the per-context block lists. - CorsOriginAccessPatterns? factory_bound_access_patterns; - // Information used restrict access to identity information (like SameSite // cookies) and to shard network resources, like the cache. If set, takes // precedence over ResourceRequest::TrustedParams::IsolationInfo field @@ -675,6 +651,9 @@ struct URLLoaderFactoryParams { // Used to notify clients about cookie reads or writes. pending_remote<CookieAccessObserver>? cookie_observer; + // Used to notify clients about authentication and certificate events. + pending_remote<AuthenticationAndCertificateObserver>? auth_cert_observer; + // If this equals kForbid, the context to which this loader is bound does not // allow any Trust Tokens (https://github.com/wicg/trust-token-api) // redemption or signing operations. @@ -697,59 +676,10 @@ struct URLLoaderFactoryParams { // trusted source, it would be good to set this depending on the headers' // values, too. TrustTokenRedemptionPolicy trust_token_redemption_policy = kPotentiallyPermit; -}; - -// The |credentials| output parameter is given to URLRequest::SetAuth() -// through this mojo interface. It is not set when URLRequest::CancelAuth() -// needs to be called. -interface AuthChallengeResponder { - OnAuthCredentials(AuthCredentials? credentials); -}; - -// This interface enables the UI to send client certificate selections back to -// the network service. -// -// Defining an interface for this purpose, rather than using a union in the -// response of OnCertificateRequested, enables the NetworkServiceClient to learn -// of the URLLoader destruction via the connection error handler. -interface ClientCertificateResponder { - // Use the selected certificate and continue the URLRequest. - // - // - |provider_name| corresponds to the return value of - // net::SSLPrivateKey::GetProviderName(). - // - |algorithm_preferences| corresponds to the return value of - // net::SSLPrivateKey::GetAlgorithmPreferences(). - ContinueWithCertificate(network.mojom.X509Certificate x509_certificate, - string provider_name, - array<uint16> algorithm_preferences, - pending_remote<SSLPrivateKey> ssl_private_key); - - // Affirmatively select no certificate (this is cached and can affect future - // URLRequests). Does not cancel the URLRequest. - // - // The connection is continued with no client cert. - // net::URLRequest::ContinueWithCertificate(nullptr, nullptr) needs to be - // called. - ContinueWithoutCertificate(); - - // Cancel the URLRequest. The request is aborted. - // net::URLRequest::CancelWithError() needs to be called. - CancelRequest(); -}; -// 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 -// sent from content/browser to services/network so that services/network can -// have its own net::SSLPrivateKey implementation that internally uses this mojo -// interface. -// The |algorithm| and |input| parameters correspond to the |algorithm| and -// |input| parameters in net::SSLPrivateKey::Sign(). -// The |net_error| and |signature| parameters correspond to the parameters in -// net::SSLPrivateKey::SignCallback. -interface SSLPrivateKey { - Sign(uint16 algorithm, - array<uint8> input) => (int32 net_error, array<uint8> signature); + // TODO(lukasza): https://crbug.com/1151008: Consider removing this + // diagnostic aid once the bug is understood. + string debug_tag = ""; }; // Callback interface for NetworkContext when routing identifiers aren't @@ -758,51 +688,6 @@ interface SSLPrivateKey { // // Implemented by the browser process. interface NetworkContextClient { - // Called when we receive an authentication failure. - // The |auth_challenge_responder| will respond to auth challenge with - // credentials. |head| can provide response headers for the response - // which has elicited this auth request, if applicable. - // - // |window_id| or else |process_id| and |routing_id| indicates - // the frame making the request, see - // network::ResourceRequest::fetch_window_id. - OnAuthRequired( - mojo_base.mojom.UnguessableToken? window_id, - int32 process_id, - int32 routing_id, - uint32 request_id, - url.mojom.Url url, - bool first_auth_attempt, - AuthChallengeInfo auth_info, - URLResponseHead? head, - pending_remote<AuthChallengeResponder> auth_challenge_responder); - - // Called when an SSL certificate requested message is received for client - // authentication. - // - // Rather than using one response for multiple purposes, the caller expects - // exactly one response (or disconnect) to be sent back via |cert_responder|. - // - // |window_id| or else |process_id| and |routing_id| indicates the frame - // making the request, see network::ResourceRequest::fetch_window_id. - OnCertificateRequested( - mojo_base.mojom.UnguessableToken? window_id, - int32 process_id, - int32 routing_id, - uint32 request_id, - network.mojom.SSLCertRequestInfo cert_info, - pending_remote<ClientCertificateResponder> cert_responder); - - // Called when an SSL certificate is encountered. - // The callback argument is a net::ERROR value. If it's net::OK, then the - // request is resumed. Otherwise it's cancelled with the given error. - OnSSLCertificateError(int32 process_id, - int32 routing_id, - url.mojom.Url url, - int32 net_error, - SSLInfo ssl_info, - bool fatal) => (int32 net_error); - // 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 @@ -884,12 +769,14 @@ interface NetworkContext { // Gets a RestrictedCookieManager scoped to a given origin, and applying // settings configured on the CookieManager associated with this domain. // - // |site_for_cookies| represents which domains (perhaps none) the cookie - // manager should consider to be first-party, for purposes of SameSite cookies - // and any third-party cookie blocking the embedder may implement. + // |origin| represents the domain for which the RestrictedCookieManager can + // access cookies. It could either be a frame origin when |role| is + // RestrictedCookieManagerRole::SCRIPT (a script scoped to a particular + // document's frame)), or a request origin when |role| is + // RestrictedCookieManagerRole::NETWORK (a network request). // - // |top_frame_origin| represents the domain for top-level frame, and can be - // used to look up preferences that are dependent on that. + // |isolation_info| contains info for SameSite and SameParty cookie queries. + // Must be fully populated. // // If |role| == SCRIPT, this interface can be safely handed out to a process // that is known to represent the given origin, such as a renderer process. @@ -900,8 +787,7 @@ interface NetworkContext { pending_receiver<RestrictedCookieManager> restricted_cookie_manager, RestrictedCookieManagerRole role, url.mojom.Origin origin, - SiteForCookies site_for_cookies, - url.mojom.Origin top_frame_origin, + IsolationInfo isolation_info, pending_remote<CookieAccessObserver>? cookie_observer); // Provides a HasTrustTokensAnswerer scoped to the given top-frame origin @@ -928,6 +814,10 @@ interface NetworkContext { // A null |filter| indicates that all Trust Tokens data should be cleared. ClearTrustTokenData(ClearDataFilter? filter) => (); + // Returns the number of signed-but-not-spent Trust Tokens. + GetStoredTrustTokenCounts() + => (array<StoredTrustTokensForIssuer> tokens); + // Clears network objects with implicit URL history information. Data related // to events that happened prior to |start_time| and after |end_time| may be // retained. Only applies to network objects without more specific methods @@ -1219,19 +1109,19 @@ interface NetworkContext { // the connection is established, and due to message ordering uncertainty we // cannot know what happened. CreateWebSocket( - url.mojom.Url url, - array<string> requested_protocols, - SiteForCookies site_for_cookies, - IsolationInfo isolation_info, - array<HttpHeader> additional_headers, - int32 process_id, - int32 render_frame_id, - url.mojom.Origin origin, - uint32 options, - MutableNetworkTrafficAnnotationTag traffic_annotation, - pending_remote<WebSocketHandshakeClient> handshake_client, - pending_remote<AuthenticationHandler>? auth_handler, - pending_remote<TrustedHeaderClient>? header_client); + url.mojom.Url url, + array<string> requested_protocols, + SiteForCookies site_for_cookies, + IsolationInfo isolation_info, + array<HttpHeader> additional_headers, + int32 process_id, + url.mojom.Origin origin, + uint32 options, + MutableNetworkTrafficAnnotationTag traffic_annotation, + pending_remote<WebSocketHandshakeClient> handshake_client, + pending_remote<AuthenticationAndCertificateObserver>? auth_cert_observer, + pending_remote<WebSocketAuthenticationHandler>? auth_handler, + pending_remote<TrustedHeaderClient>? header_client); // Creates a QuicTransport connection to |url|. |origin| is used for the // client indication - see diff --git a/chromium/services/network/public/mojom/network_isolation_key.mojom b/chromium/services/network/public/mojom/network_isolation_key.mojom index 9b85e28e681..0a26d119a7c 100644 --- a/chromium/services/network/public/mojom/network_isolation_key.mojom +++ b/chromium/services/network/public/mojom/network_isolation_key.mojom @@ -4,17 +4,14 @@ module network.mojom; -import "url/mojom/origin.mojom"; +import "services/network/public/mojom/schemeful_site.mojom"; // Mapped to net::NetworkIsolationKey. struct NetworkIsolationKey { - // These are not true origins, but rather schemeful sites | origins, depending - // on scheme. - // // Keeping optional to allow clients that do not populate top frame origin. // TODO(crbug.com/910721): This will eventually always be populated. - url.mojom.Origin? top_frame_site; - url.mojom.Origin? frame_site; + SchemefulSite? top_frame_site; + SchemefulSite? frame_site; bool opaque_and_non_transient; }; diff --git a/chromium/services/network/public/mojom/network_param.mojom b/chromium/services/network/public/mojom/network_param.mojom index 8f9f5d0be6f..8808e4cc963 100644 --- a/chromium/services/network/public/mojom/network_param.mojom +++ b/chromium/services/network/public/mojom/network_param.mojom @@ -4,8 +4,24 @@ module network.mojom; -[Native] -struct AuthChallengeInfo; +import "url/mojom/origin.mojom"; + +// Information related to an authentication challenge in an HTTP response. +// Typemapped to net::AuthChallengeInfo. +struct AuthChallengeInfo { + // True if the challenge was for proxy authentication. + bool is_proxy; + // The service issuing the challenge. + url.mojom.Origin challenger; + // The authentication scheme used, such as "basic" or "digest". + string scheme; + // The realm of the authentication challenge. May be empty. + string realm; + // The authentication challenge. + string challenge; + // The path on which authentication was requested. + string path; +}; [Native] struct AuthCredentials; diff --git a/chromium/services/network/public/mojom/network_service.mojom b/chromium/services/network/public/mojom/network_service.mojom index 0c7e1a852d1..2c1eddc5fc4 100644 --- a/chromium/services/network/public/mojom/network_service.mojom +++ b/chromium/services/network/public/mojom/network_service.mojom @@ -19,6 +19,7 @@ import "services/network/public/mojom/mutable_network_traffic_annotation_tag.moj 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/ip_address_space.mojom"; import "services/network/public/mojom/network_interface.mojom"; import "services/network/public/mojom/network_param.mojom"; import "services/network/public/mojom/network_quality_estimator_manager.mojom"; @@ -27,6 +28,7 @@ import "services/network/public/mojom/trust_tokens.mojom"; import "services/network/public/mojom/url_loader.mojom"; import "services/network/public/mojom/url_loader_factory.mojom"; import "services/network/public/mojom/url_response_head.mojom"; +import "services/network/public/mojom/client_security_state.mojom"; import "url/mojom/origin.mojom"; import "url/mojom/url.mojom"; @@ -66,7 +68,8 @@ interface NetworkServiceClient { int32 routing_id, string devtool_request_id, array<CookieWithAccessResult> cookies_with_access_result, - array<HttpRawHeaderPair> headers); + array<HttpRawHeaderPair> headers, + ClientSecurityState? client_security_state); // Called to send information about the cookies blocked from storage from a // received response. Only called when |devtool_request_id| is available to @@ -77,7 +80,23 @@ interface NetworkServiceClient { string devtool_request_id, array<CookieAndLineWithAccessResult> cookies_with_access_result, array<HttpRawHeaderPair> headers, - string? raw_response_headers); + string? raw_response_headers, + IPAddressSpace resource_address_space); + + // Called to send information about a private network request that was blocked + // (then |is_warning| is false), or will be blocked in the future (then + // |is_warning| is true). It is possible to share sensitive information with + // DevTools as this is handled in the browser process. Called even when + // |devtool_request_id| is not available to the URLLoader to ensure the + // information can be recorded even when DevTools is closed. + OnPrivateNetworkRequest( + int32 process_id, + int32 routing_id, + string? devtool_request_id, + url.mojom.Url url, + bool is_warning, + IPAddressSpace resource_address_space, + ClientSecurityState client_security_state); // Called to send the CORS preflight request information. Only called when // |devtool_request_id| is available on the original request. @@ -86,7 +105,8 @@ interface NetworkServiceClient { int32 render_frame_id, mojo_base.mojom.UnguessableToken devtool_request_id, URLRequest request, - url.mojom.Url initiator_url); + url.mojom.Url initiator_url, + string initiator_devtool_request_id); // Called to send the CORS preflight response information. Only called when // |devtool_request_id| is available on the original request. @@ -104,6 +124,15 @@ interface NetworkServiceClient { int32 render_frame_id, mojo_base.mojom.UnguessableToken devtool_request_id, URLLoaderCompletionStatus status); + + // Called to send the result of a successful or failed Trust Token + // operation. Only called when |devtools_request_id| is available on the + // original request. + OnTrustTokenOperationDone( + int32 process_id, + int32 routing_id, + string devtool_request_id, + TrustTokenOperationResult result); }; // Values for configuring HTTP authentication that can only be set once. @@ -327,11 +356,6 @@ interface NetworkService { // this call, will use the same CRLSet. UpdateCRLSet(mojo_base.mojom.ReadOnlyBuffer crl_set) => (); - // Updates the configuration used for determining if a site should have legacy - // TLS warnings suppressed. Configs that cannot be parsed as a - // LegacyTLSExperimentConfig (protobuf) will be ignored. - UpdateLegacyTLSConfig(mojo_base.mojom.ReadOnlyBuffer config) => (); - // Notification that the certificate database has been modified. OnCertDBChanged(); @@ -348,15 +372,6 @@ interface NetworkService { [EnableIf=is_win] SetEncryptionKey(string encryption_key); - // Notifies CORB (Cross-Origin Read Blocking) that |process_id| is proxying - // requests on behalf of a universal-access plugin and therefore CORB should - // stop blocking requests marked as ResourceType::kPluginResource. - // - // TODO(lukasza, laforge): https://crbug.com/702995: Remove the ...ForPlugin - // methods once Flash support is removed from Chromium (probably around 2020 - // - see https://www.chromium.org/flash-roadmap). - AddCorbExceptionForPlugin(int32 process_id); - // Notifies |request_initiator_origin_lock| enforcement code that |process_id| // is proxying requests on behalf of a plugin from // |allowed_request_initiator| origin. @@ -367,7 +382,7 @@ interface NetworkService { int32 process_id, url.mojom.Origin allowed_request_initiator); - // Reverts AddCorbExceptionForPlugin and AddAllowedRequestInitiatorForPlugin. + // Reverts AddAllowedRequestInitiatorForPlugin. RemoveSecurityExceptionsForPlugin(int32 process_id); // Called when the system is low on memory. diff --git a/chromium/services/network/public/mojom/network_service_test.mojom b/chromium/services/network/public/mojom/network_service_test.mojom index e7a99de8784..f1dff9d8ae8 100644 --- a/chromium/services/network/public/mojom/network_service_test.mojom +++ b/chromium/services/network/public/mojom/network_service_test.mojom @@ -18,12 +18,15 @@ enum ResolverType { kResolverTypeDirectLookup, }; +// `dns_aliases` is a list of aliases read from DNS records, e.g. CNAME +// aliases, and is intended to preserve the alias chain in reverse, from +// canonical name (i.e. address record name) through to query name. struct Rule { ResolverType resolver_type; string host_pattern; string replacement; int32 host_resolver_flags; - string canonical_name; + array<string> dns_aliases; }; // Testing interface to the network service. @@ -118,8 +121,8 @@ interface NetworkServiceTest { [Sync] SetEVPolicy(array<uint8, 32> fingerprint_sha256, string policy_oid) => (); - // Gets the current count of entries in the preloaded First-Party sets - // mapping. Note that each entry is a registered domain mapped to its owner. + // Gets the current count of entries in the First-Party sets mapping. Note + // that each entry is a registered domain mapped to its owner. [Sync] - GetPreloadedFirstPartySetEntriesCount() => (int64 entries); + GetFirstPartySetEntriesCount() => (int64 entries); }; diff --git a/chromium/services/network/public/mojom/parsed_headers.mojom b/chromium/services/network/public/mojom/parsed_headers.mojom index 6bba67b26b7..5eb06997257 100644 --- a/chromium/services/network/public/mojom/parsed_headers.mojom +++ b/chromium/services/network/public/mojom/parsed_headers.mojom @@ -9,6 +9,7 @@ import "services/network/public/mojom/content_security_policy.mojom"; import "services/network/public/mojom/cross_origin_embedder_policy.mojom"; import "services/network/public/mojom/cross_origin_opener_policy.mojom"; import "services/network/public/mojom/web_client_hints_types.mojom"; +import "services/network/public/mojom/x_frame_options.mojom"; // Holds the parsed representation of several security related HTTP headers. // This struct should only be populated by network::PopulateParsedHeaders() @@ -27,8 +28,8 @@ struct ParsedHeaders { // Cross-Origin-opener-Policy-Report-Only headers. CrossOriginOpenerPolicy cross_origin_opener_policy; - // The parsed value of the Origin-Isolation header. - bool origin_isolation = false; + // The parsed value of the Origin-Agent-Cluster header. + bool origin_agent_cluster = false; // The parsed Accept-CH from response headers. // @@ -62,4 +63,7 @@ struct ParsedHeaders { // For more information, see: // https://tools.ietf.org/html/draft-davidben-http-client-hint-reliability#section-3 array<WebClientHintsType>? critical_ch; + + // The parsed value of the X-Frame-Options header. + XFrameOptionsValue xfo = XFrameOptionsValue.kNone; }; diff --git a/chromium/services/network/public/mojom/quic_transport.mojom b/chromium/services/network/public/mojom/quic_transport.mojom index e8b4de15421..6a629537ba2 100644 --- a/chromium/services/network/public/mojom/quic_transport.mojom +++ b/chromium/services/network/public/mojom/quic_transport.mojom @@ -5,6 +5,7 @@ module network.mojom; import "mojo/public/mojom/base/read_only_buffer.mojom"; +import "mojo/public/mojom/base/time.mojom"; import "url/mojom/url.mojom"; // Represents a QuicTransport error. @@ -65,6 +66,10 @@ interface QuicTransport { // Aborts the stream for |stream_id|. AbortStream(uint32 stream_id, uint64 code); + + // Sets the duration which determines whether an outgoing datagram should be + // discarded due to being in the queue for too long. + SetOutgoingDatagramExpirationDuration(mojo_base.mojom.TimeDelta duration); }; // A mojo interface for the client of QuicTransport. diff --git a/chromium/services/network/public/mojom/site_for_cookies.mojom b/chromium/services/network/public/mojom/site_for_cookies.mojom index 2de2d571e57..1cee8c51b7c 100644 --- a/chromium/services/network/public/mojom/site_for_cookies.mojom +++ b/chromium/services/network/public/mojom/site_for_cookies.mojom @@ -4,11 +4,12 @@ module network.mojom; +import "services/network/public/mojom/schemeful_site.mojom"; + // Mapped to net::SiteForCookies. struct SiteForCookies { // These fields should not be used directly, but rather through the mapped // net::SiteForCookies. - string scheme; - string registrable_domain; + SchemefulSite site; bool schemefully_same; }; diff --git a/chromium/services/network/public/mojom/trust_tokens.mojom b/chromium/services/network/public/mojom/trust_tokens.mojom index c9df1b062f0..9e98ddfd0ad 100644 --- a/chromium/services/network/public/mojom/trust_tokens.mojom +++ b/chromium/services/network/public/mojom/trust_tokens.mojom @@ -205,19 +205,19 @@ struct TrustTokenKeyCommitmentResult { // one operating system, issuers could benefit from a couple different // fallback behaviors depending on their particular requirements. // - // |unavailable_local_issuance_fallback|'s value specifies what action to take - // when both of the following hold simultaneously: + // |unavailable_local_operation_fallback|'s value specifies what action to + // take when both of the following hold simultaneously: // (1) local issuance is specified on at least one OS (i.e. // |request_issuance_locally_on| is nonempty) and // (2) we're not on any of the specified OSes. - enum UnavailableLocalIssuanceFallback { + enum UnavailableLocalOperationFallback { // If we're not on a matching OS, instead attempt a standard web // issuance request against the issuance request's destination URL. kWebIssuance, // If we're not on a matching OS, just fail the issuance request. kReturnWithError, }; - UnavailableLocalIssuanceFallback unavailable_local_issuance_fallback; + UnavailableLocalOperationFallback unavailable_local_operation_fallback; }; // Struct FulfillTrustTokenIssuanceRequest represents a Trust Tokens issuance @@ -247,6 +247,8 @@ struct FulfillTrustTokenIssuanceRequest { // because the Java bindings append the suffix "Response" when generating // callback names. struct FulfillTrustTokenIssuanceAnswer { + // WARNING: Since these values are committed to histograms, please do not + // remove or reorder entries. enum Status { kOk, // It wasn't possible to route the issuance operation to the specified @@ -262,3 +264,25 @@ struct FulfillTrustTokenIssuanceAnswer { // response header. Otherwise, its value is indeterminate. string response; }; + +// TrustTokenOperationResult contains all the information required by +// DevTools. Which fields are set depend on |type| and |status|. +struct TrustTokenOperationResult { + // Required. + TrustTokenOperationType type; + TrustTokenOperationStatus status; + + // Shared among the different operation types. + url.mojom.Origin? issuer; + url.mojom.Origin? top_level_origin; + + // In case of TrustTokenOperationType::kIssuance. + int32 issued_token_count = 0; +}; + +// Struct StoredTrustTokensForIssuer is used by DevTools to inspect +// the current state of the Trust Token store. +struct StoredTrustTokensForIssuer { + url.mojom.Origin issuer; + int32 count; +}; diff --git a/chromium/services/network/public/mojom/url_loader.mojom b/chromium/services/network/public/mojom/url_loader.mojom index 8f1834ec677..9fbe2d0aa94 100644 --- a/chromium/services/network/public/mojom/url_loader.mojom +++ b/chromium/services/network/public/mojom/url_loader.mojom @@ -8,6 +8,7 @@ import "mojo/public/mojom/base/big_buffer.mojom"; import "mojo/public/mojom/base/file_path.mojom"; import "mojo/public/mojom/base/time.mojom"; import "mojo/public/mojom/base/unguessable_token.mojom"; +import "services/network/public/mojom/auth_and_certificate_observer.mojom"; import "services/network/public/mojom/client_security_state.mojom"; import "services/network/public/mojom/cors.mojom"; import "services/network/public/mojom/cookie_access_observer.mojom"; @@ -20,6 +21,7 @@ import "services/network/public/mojom/network_param.mojom"; import "services/network/public/mojom/site_for_cookies.mojom"; import "services/network/public/mojom/trust_tokens.mojom"; import "services/network/public/mojom/url_response_head.mojom"; +import "services/network/public/mojom/web_bundle_handle.mojom"; import "url/mojom/origin.mojom"; import "url/mojom/url.mojom"; @@ -54,20 +56,6 @@ enum URLRequestReferrerPolicy { kNoReferrer }; -// Used for represents the type of the internal contents of -// network::DataElement. -enum DataElementType { - kUnknown = -1, - - kDataPipe, - kChunkedDataPipe, - kReadOnceStream, - kBytes, - - // TODO(https://crbug.com/1132362): Remove this. - kFile, -}; - // Options that may only be set on URLRequests passed to a URLLoaderFactory // created with |is_trusted| set to true. struct TrustedUrlRequestParams { @@ -88,6 +76,11 @@ struct TrustedUrlRequestParams { // URLLoaderFactory will be ignored. pending_remote<CookieAccessObserver>? cookie_observer; + // Observer which should be notified when this URLRequest has authentication + // and certificate events. If this is set to non-null, the observer passed to + // URLLoaderFactory will be ignored. + pending_remote<AuthenticationAndCertificateObserver>? auth_cert_observer; + // Specifies the security state of the client, for cases when the // URLLoaderFactory is shared among multiple clients. // @@ -97,6 +90,23 @@ struct TrustedUrlRequestParams { ClientSecurityState? client_security_state; }; +// Options that may only be set on URLRequests which are related to WebBundle. +struct WebBundleTokenParams { + // The URL of the WebBundle. + url.mojom.Url bundle_url; + // Unique token to identify a WebBundle. + mojo_base.mojom.UnguessableToken token; + // Handle for the WebBundle-related communication between the network process + // and the renderer. This is also used as a 'keep-alive' handle. We clean up + // the WebBundle data in the network process when the renderer-side endpoint + // is deleted. + pending_remote<WebBundleHandle>? web_bundle_handle; + // Renderer process ID of the request initiator frame. Set by the browser + // process, for subframe navigation requests to bundled resources. Not used + // for subresource requests sent by renderer processes. + int32 render_process_id; +}; + // Typemapped to network::ResourceRequest. struct URLRequest { // The request method: GET, POST, etc. @@ -121,12 +131,6 @@ struct URLRequest { // landed. SiteForCookies site_for_cookies; - // Boolean indicating whether SameSite cookies should be indiscriminately - // attached to the request, bypassing the usual site_for_cookies checks. - // Setting this param to true causes SameSite cookies to be included on - // cross-site requests (or requests that "look" cross-site). - bool force_ignore_site_for_cookies; - // First-party URL redirect policy: During server redirects, the first-party // URL for cookies normally doesn't change. However, if this is true, the // the first-party URL should be updated to the URL on every redirect. @@ -255,11 +259,6 @@ struct URLRequest { // If true then the request continues even if it's blocked by CORB. bool corb_detachable = false; - // 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 mode is kNoCors. - bool corb_excluded = false; - // 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). @@ -390,6 +389,9 @@ struct URLRequest { // True for XHR, Fetch, and EventSource. bool is_fetch_like_api; + // True for favicon. + bool is_favicon; + // If set, the network service will attempt to retrieve the appropriate origin // policy, if necessary, and attach it to the ResourceResponseHead. // Spec: https://wicg.github.io/origin-policy/ @@ -408,12 +410,17 @@ struct URLRequest { // and the request has set the trustToken Fetch parameter, denoting that it // wishes to execute a Trust Tokens protocol operation. TrustTokenParams? trust_token_params; + + // Set for WebBundle related requests. See the comment of WebBundleTokenParams + // for details. + WebBundleTokenParams? web_bundle_token_params; }; // URLRequestBody represents body (i.e. upload data) of a HTTP request. // Typemapped to network::ResourceRequestBody struct URLRequestBody { - // Store upload bodies + // The body contents. DataElementChunkedDataPipe can be used in `elements` + // only if `elements` consists of one element. array<DataElement> elements; // Identifies a particular upload instance, which is used by the cache to @@ -429,26 +436,42 @@ struct URLRequestBody { bool allow_http1_for_streaming_upload; }; -// Represents part of an upload body. This could be either one of bytes, file or -// a data pipe. -// Typemapped to network::DataElement -struct DataElement { - DataElementType type; +// Represents part of an upload body consisting of bytes. +struct DataElementBytes { + mojo_base.mojom.BigBuffer data; +}; - // For kBytes. - mojo_base.mojom.BigBuffer buf; - // For kFile +// Represents part of an upload body consisting of (part of) a file. +struct DataElementFile { mojo_base.mojom.FilePath path; - // For kDataPipe - pending_remote<network.mojom.DataPipeGetter>? data_pipe_getter; - // For kChunkedDataPipe - pending_remote<network.mojom.ChunkedDataPipeGetter>? chunked_data_pipe_getter; - uint64 offset; uint64 length; mojo_base.mojom.Time expected_modification_time; }; +// Represents part of an upload body consisting of a data pipe with a known +// size. +struct DataElementDataPipe { + pending_remote<network.mojom.DataPipeGetter> data_pipe_getter; +}; + +// Represents part of an upload body consisting of a data pipe without a known +// size. +struct DataElementChunkedDataPipe { + pending_remote<network.mojom.ChunkedDataPipeGetter> data_pipe_getter; + // When true, a data pipe can be gotten from `chunked_data_pipe_getter` only + // once. + bool read_only_once; +}; + +// Represents part of an upload body. +union DataElement { + DataElementBytes bytes; + DataElementFile file; + DataElementDataPipe data_pipe; + DataElementChunkedDataPipe chunked_data_pipe; +}; + // URLLoader is an interface for performing a single request to a URL. // // Destroying a URLLoader will cancel the associated request. diff --git a/chromium/services/network/public/mojom/url_loader_factory.mojom b/chromium/services/network/public/mojom/url_loader_factory.mojom index b141eff3d63..e8bb8cfea7b 100644 --- a/chromium/services/network/public/mojom/url_loader_factory.mojom +++ b/chromium/services/network/public/mojom/url_loader_factory.mojom @@ -18,8 +18,11 @@ const uint32 kURLLoadOptionSniffMimeType = 2; // Indicates that execution is blocking on the completion of the request. const uint32 kURLLoadOptionSynchronous = 4; -// Sends the net::SSLInfo struct in OnComplete when the connection had a major -// certificate error. +// Sends the net::SSLInfo on request completion when the connection had a major +// certificate error. The SSLInfo can be retrieved from the OnComplete struct +// when the connection failed due to the certificate error, or from the +// OnReceiveResponse struct if the connection proceeded despite the certificate +// error. const uint32 kURLLoadOptionSendSSLInfoForCertificateError = 8; // Uses the header client set in URLLoaderFactoryParams for this request. diff --git a/chromium/services/network/public/mojom/url_response_head.mojom b/chromium/services/network/public/mojom/url_response_head.mojom index fa737edd89d..c37bbd02370 100644 --- a/chromium/services/network/public/mojom/url_response_head.mojom +++ b/chromium/services/network/public/mojom/url_response_head.mojom @@ -134,6 +134,12 @@ struct URLResponseHead { // https://fetch.spec.whatwg.org/#concept-response-type FetchResponseType response_type = FetchResponseType.kDefault; + // Pre-computed padding. This should only be non-zero when |response_type| + // is set to kOpaque. Note, this is not set by network service, but will be + // populated if the response was provided by a service worker FetchEvent + // handler. + int64 padding = 0; + // The cache name of the CacheStorage from where the response is served via // the ServiceWorker. Empty if the response isn't from the CacheStorage. string cache_storage_cache_name; @@ -219,4 +225,14 @@ struct URLResponseHead { // is propagated to the renderer and set on recursive prefetch requests // (see corresponding documentation in url_loader.mojom). mojo_base.mojom.UnguessableToken? recursive_prefetch_token; + + // Aliases, if any, for the destination URL, as read from DNS CNAME records. + // The first entry of `dns_aliases`, if it exists, is the canonical name. + // The alias chain is preserved in reverse order, from canonical name (i.e. + // address record name) through to query name. + array<string> dns_aliases; + + // The URL of WebBundle this response was loaded from. This value is only + // populated for resources loaded from a WebBundle. + url.mojom.Url web_bundle_url; }; diff --git a/chromium/services/network/public/mojom/web_bundle_handle.mojom b/chromium/services/network/public/mojom/web_bundle_handle.mojom new file mode 100644 index 00000000000..d142d37d1fa --- /dev/null +++ b/chromium/services/network/public/mojom/web_bundle_handle.mojom @@ -0,0 +1,28 @@ +// Copyright 2020 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; + +enum WebBundleErrorType { + kMetadataParseError, + kResponseParseError, + kResourceNotFound, + kMemoryQuotaExceeded, + kServingConstraintsNotMet, +}; + +// A handle to a WebBundle instance in the Network Service. Created by a +// renderer, and its remote endpoint is passed to the Network Service. The +// receiving endpoint is held in the renderer and closed to tell the Network +// Service that the Bundle is no longer needed. +interface WebBundleHandle { + // Used to create a copy of this handle. + Clone(pending_receiver<WebBundleHandle> receiver); + + // Report errors to the renderer. + OnWebBundleError(WebBundleErrorType type, string message); + + // Report to the renderer whether successfully loaded the data or failed. + OnWebBundleLoadFinished(bool success); +}; diff --git a/chromium/services/network/public/mojom/websocket.mojom b/chromium/services/network/public/mojom/websocket.mojom index 3b38ab47b1a..004228d90b9 100644 --- a/chromium/services/network/public/mojom/websocket.mojom +++ b/chromium/services/network/public/mojom/websocket.mojom @@ -45,7 +45,7 @@ struct WebSocketHandshakeResponse { }; // This interface is for HTTP Authentication. -interface AuthenticationHandler { +interface WebSocketAuthenticationHandler { // Returns null credentials when it wants to cancel authentication, and // returns a non-null credentials when it wants to use the credentials for // authentication. @@ -127,11 +127,6 @@ interface WebSocketClient { // The interface for the server side of WebSocket. Implemented by the network // service. Used to send out data to the network service. interface WebSocket { - // The client side may observe the following disconnection reason from the - // service side: - const uint32 kInsufficientResources = 1; - const uint32 kInternalFailure = 2; - // Sends a message via mojo datapipe to the remote server. // - |type| is the type of the message. It must be set to either // WebSocketMessageType.TEXT or WebSocketMessageType.BINARY. diff --git a/chromium/services/network/public/mojom/x_frame_options.mojom b/chromium/services/network/public/mojom/x_frame_options.mojom new file mode 100644 index 00000000000..9eaa02ef32b --- /dev/null +++ b/chromium/services/network/public/mojom/x_frame_options.mojom @@ -0,0 +1,16 @@ +// Copyright 2020 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; + +// This enum represents the possible values for the X-Frame-Options header: +// https://html.spec.whatwg.org/multipage/browsing-the-web.html#the-x-frame-options-header. +enum XFrameOptionsValue { + kNone, // No XFO header is present. + kDeny, // XFO: DENY + kSameOrigin, // XFO: SAMEORIGIN + kAllowAll, // XFO: ALLOWALL + kInvalid, // XFO: [anything other than DENY, SAMEORIGIN, or ALLOWALL] + kConflict // Multiple XFO headers are present, with distinct values. +}; diff --git a/chromium/services/network/public/proto/BUILD.gn b/chromium/services/network/public/proto/BUILD.gn index 0ab24319138..7de51ed6bf9 100644 --- a/chromium/services/network/public/proto/BUILD.gn +++ b/chromium/services/network/public/proto/BUILD.gn @@ -6,17 +6,12 @@ import("//services/network/public/cpp/features.gni") import("//third_party/protobuf/proto_library.gni") group("proto") { - public_deps = [ ":tls_deprecation_config_proto" ] - + public_deps = [] if (is_ct_supported) { public_deps += [ ":sct_audit_report_proto" ] } } -proto_library("tls_deprecation_config_proto") { - sources = [ "tls_deprecation_config.proto" ] -} - if (is_ct_supported) { proto_library("sct_audit_report_proto") { sources = [ "sct_audit_report.proto" ] diff --git a/chromium/services/network/public/proto/sct_audit_report.proto b/chromium/services/network/public/proto/sct_audit_report.proto index d292c5c7564..9ae1b8b4528 100644 --- a/chromium/services/network/public/proto/sct_audit_report.proto +++ b/chromium/services/network/public/proto/sct_audit_report.proto @@ -13,16 +13,26 @@ option optimize_for = LITE_RUNTIME; package sct_auditing; +// SCTClientReport represents a single report from a client, containing reports +// from multiple independent connections, each of which contain multiple SCTs. +message SCTClientReport { + // The simplified user agent of the submitter (e.g. Chrome/xy.0.abcd.e). + // Supplements API keys to provide per-version granularity. + string user_agent = 1; + + repeated TLSConnectionReport certificate_report = 2; +} + // TLSConnectionReport is the primary per-handshake report for SCT auditing. message TLSConnectionReport { TLSConnectionContext context = 1; - // SCTs may appear in any order. See SCTWithSourceAndVerifyStatus for details. - repeated SCTWithSourceAndVerifyStatus included_scts = 2; + // SCTs may appear in any order. See SCTWithVerifyStatus for details. + repeated SCTWithVerifyStatus included_sct = 2; } -// TLSConnectionContext contains details about the connection that are common -// to all SCTs observed in that connection. +// TLSConnectionContext contains details about the connection that are common to +// all SCTs observed in that connection. message TLSConnectionContext { // Time when the UA observed the certificate in seconds since the Unix epoch. int64 time_seen = 1; @@ -41,9 +51,9 @@ message TLSConnectionContext { repeated bytes certificate_chain = 3; } -// SCTWithSourceAndVerifyStatus contains the raw SCT, where it was found in the -// certificate, and its validation status according to the UA. -message SCTWithSourceAndVerifyStatus { +// SCTWithVerifyStatus contains the serialized SCT along with the validation +// status according to the UA. +message SCTWithVerifyStatus { // Keep sync'd with SctVerifyStatus in Chrome's net/cert/sct_status_flags.h. enum SctVerifyStatus { // Default to unspecified status. See go/unspecified-enum. @@ -67,15 +77,7 @@ message SCTWithSourceAndVerifyStatus { } SctVerifyStatus status = 1; - // The source is the manner in which the client received the SCT (embedded in - // the certificate, delivered via the TLS handshake, or delivered via OCSP). - enum Source { - SOURCE_UNSPECIFIED = 0; - EMBEDDED = 1; - TLS_EXTENSION = 2; - OCSP_RESPONSE = 3; - } - Source source = 2; - - bytes sct = 3; + // SignedCertificateTimestamp struct serialized via + // net::ct::EncodeSignedCertificateTimestamp(). + bytes serialized_sct = 2; } diff --git a/chromium/services/network/public/proto/tls_deprecation_config.proto b/chromium/services/network/public/proto/tls_deprecation_config.proto deleted file mode 100644 index a54d5d36d3d..00000000000 --- a/chromium/services/network/public/proto/tls_deprecation_config.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2019 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. - -syntax = "proto2"; - -package chrome_browser_ssl; - -option optimize_for = LITE_RUNTIME; - -// The set of sites to be used as a control group for Legacy TLS experiments. -// Warning UI will not be shown on these sites. -message LegacyTLSExperimentConfig { - optional uint32 version_id = 1; - // SHA-256 hash of the hostname of sites in the control group (e.g., for - // "https://test.example.com/foo" the hostname is "test.example.com"). This - // list must be in sorted order (alphanumeric by hash value). - repeated string control_site_hashes = 2; -} |