| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/frame/csp/csp_source.h" |
| |
| #include "services/network/public/mojom/content_security_policy.mojom-blink.h" |
| #include "third_party/blink/public/platform/web_content_security_policy_struct.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/weborigin/known_ports.h" |
| #include "third_party/blink/renderer/platform/weborigin/kurl.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/wtf/assertions.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| enum class SchemeMatchingResult { |
| kNotMatching, |
| kMatchingUpgrade, |
| kMatchingExact |
| }; |
| |
| enum class PortMatchingResult { |
| kNotMatching, |
| kMatchingWildcard, |
| kMatchingUpgrade, |
| kMatchingExact |
| }; |
| |
| SchemeMatchingResult SchemeMatches( |
| const network::mojom::blink::CSPSource& source, |
| const String& protocol, |
| const String& self_protocol) { |
| DCHECK_EQ(protocol, protocol.DeprecatedLower()); |
| const String& scheme = |
| (source.scheme.IsEmpty() ? self_protocol : source.scheme); |
| |
| if (scheme == protocol) |
| return SchemeMatchingResult::kMatchingExact; |
| |
| if ((scheme == "http" && protocol == "https") || |
| (scheme == "ws" && protocol == "wss")) { |
| return SchemeMatchingResult::kMatchingUpgrade; |
| } |
| |
| return SchemeMatchingResult::kNotMatching; |
| } |
| |
| bool HostMatches(const network::mojom::blink::CSPSource& source, |
| const String& host) { |
| if (source.is_host_wildcard) { |
| if (source.host.IsEmpty()) { |
| // host-part = "*" |
| return true; |
| } |
| if (host.EndsWithIgnoringCase(String("." + source.host))) { |
| // host-part = "*." 1*host-char *( "." 1*host-char ) |
| return true; |
| } |
| return false; |
| } |
| return EqualIgnoringASCIICase(source.host, host); |
| } |
| |
| bool PathMatches(const network::mojom::blink::CSPSource& source, |
| const String& url_path) { |
| if (source.path.IsEmpty() || (source.path == "/" && url_path.IsEmpty())) |
| return true; |
| |
| String path = |
| DecodeURLEscapeSequences(url_path, DecodeURLMode::kUTF8OrIsomorphic); |
| |
| if (source.path.EndsWith("/")) |
| return path.StartsWith(source.path); |
| |
| return path == source.path; |
| } |
| |
| PortMatchingResult PortMatches(const network::mojom::blink::CSPSource& source, |
| const String& self_protocol, |
| int port, |
| const String& protocol) { |
| if (source.is_port_wildcard) |
| return PortMatchingResult::kMatchingWildcard; |
| |
| if (port == source.port) { |
| if (port == url::PORT_UNSPECIFIED) |
| return PortMatchingResult::kMatchingWildcard; |
| return PortMatchingResult::kMatchingExact; |
| } |
| |
| bool is_scheme_http; // needed for detecting an upgrade when the port is 0 |
| is_scheme_http = source.scheme.IsEmpty() |
| ? EqualIgnoringASCIICase("http", self_protocol) |
| : EqualIgnoringASCIICase("http", source.scheme); |
| |
| if ((source.port == 80 || |
| ((source.port == url::PORT_UNSPECIFIED || source.port == 443) && |
| is_scheme_http)) && |
| (port == 443 || (port == url::PORT_UNSPECIFIED && |
| DefaultPortForProtocol(protocol) == 443))) { |
| return PortMatchingResult::kMatchingUpgrade; |
| } |
| |
| if (port == url::PORT_UNSPECIFIED) { |
| if (IsDefaultPortForProtocol(source.port, protocol)) |
| return PortMatchingResult::kMatchingExact; |
| return PortMatchingResult::kNotMatching; |
| } |
| |
| if (source.port == url::PORT_UNSPECIFIED) { |
| if (IsDefaultPortForProtocol(port, protocol)) |
| return PortMatchingResult::kMatchingExact; |
| return PortMatchingResult::kNotMatching; |
| } |
| |
| return PortMatchingResult::kNotMatching; |
| } |
| |
| // Helper inline functions for Port and Scheme MatchingResult enums |
| bool inline RequiresUpgrade(const PortMatchingResult result) { |
| return result == PortMatchingResult::kMatchingUpgrade; |
| } |
| bool inline RequiresUpgrade(const SchemeMatchingResult result) { |
| return result == SchemeMatchingResult::kMatchingUpgrade; |
| } |
| |
| bool inline CanUpgrade(const PortMatchingResult result) { |
| return result == PortMatchingResult::kMatchingUpgrade || |
| result == PortMatchingResult::kMatchingWildcard; |
| } |
| |
| bool inline CanUpgrade(const SchemeMatchingResult result) { |
| return result == SchemeMatchingResult::kMatchingUpgrade; |
| } |
| |
| } // namespace |
| |
| bool CSPSourceMatches(const network::mojom::blink::CSPSource& source, |
| const String& self_protocol, |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status) { |
| SchemeMatchingResult schemes_match = |
| SchemeMatches(source, url.Protocol(), self_protocol); |
| if (schemes_match == SchemeMatchingResult::kNotMatching) |
| return false; |
| if (CSPSourceIsSchemeOnly(source)) |
| return true; |
| bool paths_match = (redirect_status == RedirectStatus::kFollowedRedirect) || |
| PathMatches(source, url.GetPath()); |
| PortMatchingResult ports_match = PortMatches( |
| source, self_protocol, url.HasPort() ? url.Port() : url::PORT_UNSPECIFIED, |
| url.Protocol()); |
| |
| // if either the scheme or the port would require an upgrade (e.g. from http |
| // to https) then check that both of them can upgrade to ensure that we don't |
| // run into situations where we only upgrade the port but not the scheme or |
| // viceversa |
| if ((RequiresUpgrade(schemes_match) || (RequiresUpgrade(ports_match))) && |
| (!CanUpgrade(schemes_match) || !CanUpgrade(ports_match))) { |
| return false; |
| } |
| |
| return HostMatches(source, url.Host()) && |
| ports_match != PortMatchingResult::kNotMatching && paths_match; |
| } |
| |
| bool CSPSourceMatchesAsSelf(const network::mojom::blink::CSPSource& source, |
| const KURL& url) { |
| // https://w3c.github.io/webappsec-csp/#match-url-to-source-expression |
| // Step 4. |
| SchemeMatchingResult schemes_match = |
| SchemeMatches(source, url.Protocol(), source.scheme); |
| bool hosts_match = HostMatches(source, url.Host()); |
| PortMatchingResult ports_match = PortMatches( |
| source, source.scheme, url.HasPort() ? url.Port() : url::PORT_UNSPECIFIED, |
| url.Protocol()); |
| |
| // check if the origin is exactly matching |
| if (schemes_match == SchemeMatchingResult::kMatchingExact && hosts_match && |
| (ports_match == PortMatchingResult::kMatchingExact || |
| ports_match == PortMatchingResult::kMatchingWildcard)) { |
| return true; |
| } |
| |
| bool ports_match_or_defaults = |
| (ports_match == PortMatchingResult::kMatchingExact || |
| ((IsDefaultPortForProtocol(source.port, source.scheme) || |
| source.port == url::PORT_UNSPECIFIED) && |
| (!url.HasPort() || |
| IsDefaultPortForProtocol(url.Port(), url.Protocol())))); |
| |
| return hosts_match && ports_match_or_defaults && |
| (url.Protocol() == "https" || url.Protocol() == "wss" || |
| source.scheme == "http"); |
| } |
| |
| bool CSPSourceIsSchemeOnly(const network::mojom::blink::CSPSource& source) { |
| return source.host.IsEmpty() && (!source.is_host_wildcard); |
| } |
| |
| } // namespace blink |