Index: content/common/content_security_policy/csp_source.cc |
diff --git a/content/common/content_security_policy/csp_source.cc b/content/common/content_security_policy/csp_source.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..972d41bcbb7a1ccb65937ef18f592bb6bc13f2b4 |
--- /dev/null |
+++ b/content/common/content_security_policy/csp_source.cc |
@@ -0,0 +1,368 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <algorithm> |
+#include <sstream> |
+ |
+#include "base/strings/string_split.h" |
+#include "base/strings/string_util.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "content/common/content_security_policy/csp_context.h" |
+#include "url/url_canon.h" |
+#include "url/url_util.h" |
+ |
+namespace content { |
+ |
+namespace { |
+bool IsHostCharacter(char c) { |
+ return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || c == '-'; |
+} |
+ |
+bool IsSchemeContinuationCharacter(char c) { |
+ return base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || c == '-' || |
+ c == '+' || c == '.'; |
+} |
+ |
+bool DecodePath(const base::StringPiece& path, std::string* output) { |
+ url::RawCanonOutputT<base::char16> unescaped; |
+ url::DecodeURLEscapeSequences(path.data(), path.size(), &unescaped); |
+ return base::UTF16ToUTF8(unescaped.data(), unescaped.length(), output); |
+} |
+ |
+int DefaultPortForScheme(const std::string& scheme) { |
+ return url::DefaultPortForScheme(scheme.data(), scheme.size()); |
+} |
+ |
+// ; <scheme> production from RFC 3986 |
+// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) |
+bool ParseScheme(CSPSource* csp_source, const base::StringPiece& scheme) { |
+ std::string scheme_lower = base::ToLowerASCII(scheme); |
+ |
+ if (scheme.empty()) |
+ return false; |
+ |
+ if (!base::IsAsciiAlpha(scheme[0]) || |
+ !std::all_of(scheme.begin() + 1, scheme.end(), |
+ IsSchemeContinuationCharacter)) |
+ return false; |
+ |
+ csp_source->scheme = scheme.as_string(); |
+ return true; |
+} |
+ |
+// host = [ "*." ] 1*host-char *( "." 1*host-char ) |
+// / "*" |
+// host-char = ALPHA / DIGIT / "-" |
+// |
+bool ParseHost(CSPSource* csp_source, const base::StringPiece& host) { |
+ if (host.empty()) |
+ return false; |
+ |
+ // Parse and skip leading "*." |
+ if (host[0] == '*') { |
+ csp_source->is_host_wildcard = true; |
+ if (host.size() == 1) |
+ return true; |
+ |
+ if (host[1] != '.') |
+ return false; |
+ |
+ csp_source->host = host.substr(2, std::string::npos).as_string(); |
+ |
+ // Check the '*.' case. |
+ if (csp_source->host.empty()) |
+ return false; |
+ |
+ } else { |
+ csp_source->host = host.as_string(); |
+ } |
+ |
+ for (const base::StringPiece& piece : |
+ base::SplitStringPiece(csp_source->host, ".", base::KEEP_WHITESPACE, |
+ base::SPLIT_WANT_ALL)) { |
+ if (piece.empty() || |
+ !std::all_of(piece.begin(), piece.end(), IsHostCharacter)) |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+// port = 1*DIGIT |
+// / "*" |
+bool ParsePort(CSPSource* csp_source, const base::StringPiece& port) { |
+ if (port.empty()) |
+ return false; |
+ |
+ if (port == "*") { |
+ csp_source->is_port_wildcard = true; |
+ return true; |
+ } |
+ |
+ std::string port_as_string = port.as_string(); |
+ if (!std::all_of(port_as_string.begin(), port_as_string.end(), |
+ base::IsAsciiDigit<char>)) |
+ return false; |
+ |
+ csp_source->port = std::stoi(port.as_string()); |
+ return true; |
+} |
+ |
+bool ParsePath(CSPSource* source, const base::StringPiece& path) { |
+ DCHECK(base::StartsWith(path, "/", base::CompareCase::INSENSITIVE_ASCII)); |
+ |
+ size_t position = path.find_first_of("?#"); |
+ if (position != std::string::npos) { |
+ // path/to/file.js?query=string || path/to/file.js#anchor |
+ // ^ ^ |
+ // TODO(arthursonzogni): Report that the part after {%,?} will be ignored. |
+ } |
+ |
+ return DecodePath(path.substr(0, position), &(source->path)); |
+} |
+ |
+bool ParseSource(CSPSource* csp_source, const std::string& source) { |
+ size_t begin = 0; |
+ size_t position = 0; |
+ position = source.find_first_of(":/", begin); |
+ |
+ if (position == std::string::npos) { |
+ // host |
+ // ^ |
+ return ParseHost(csp_source, source); |
+ } |
+ |
+ if (source[position] == '/') { |
+ // host/path |
+ // ^ |
+ return ParseHost(csp_source, source.substr(begin, position - begin)) && |
+ ParsePath(csp_source, source.substr(position, std::string::npos)); |
+ } |
+ |
+ if (source[position] == ':') { |
+ if (position == source.size() - 1) { |
+ // scheme: |
+ // ^ |
+ return ParseScheme(csp_source, source.substr(begin, position - begin)); |
+ } |
+ |
+ if (source[position + 1] == '/') { |
+ // scheme://(.*) |
+ // ^ ^ |
+ if (!ParseScheme(csp_source, source.substr(begin, position - begin))) |
+ return false; |
+ |
+ // check the presence of "://" |
+ if (position + 3 >= source.size() || source.substr(position, 3) != "://") |
+ return false; |
+ |
+ if (position + 3 == source.size()) { |
+ // scheme:// |
+ // ^ |
+ return ParseScheme(csp_source, source.substr(begin, position - begin)); |
+ } |
+ |
+ // Skip scheme:// |
+ begin = position + 3; |
+ position = source.find_first_of(":/", begin); |
+ if (position == std::string::npos) { |
+ // host |
+ return ParseHost(csp_source, source.substr(begin, std::string::npos)); |
+ } |
+ } |
+ } |
+ |
+ // At this point, the scheme part (if any) has been Skipped. |
+ // host/path || host:port[/path] |
+ // ^ ^ |
+ if (!ParseHost(csp_source, source.substr(begin, position - begin))) |
+ return false; |
+ |
+ if (source[position] == '/') { |
+ // host/path |
+ // ^ |
+ return ParsePath(csp_source, source.substr(position, std::string::npos)); |
+ } else { |
+ // host:port[/path] |
+ // ^ |
+ DCHECK(source[position] == ':'); |
+ |
+ // Skip host: |
+ begin = position + 1; |
+ position = source.find_first_of("/", begin); |
+ |
+ if (!ParsePort(csp_source, source.substr(begin, position - begin))) |
+ return false; |
+ |
+ if (position == std::string::npos) |
+ return true; |
+ |
+ // port/path |
+ // ^ |
+ return ParsePath(csp_source, source.substr(position, std::string::npos)); |
+ } |
+} |
+ |
+} // namespace |
+ |
+CSPSource::CSPSource() |
+ : scheme(), |
+ host(), |
+ is_host_wildcard(false), |
+ port(url::PORT_UNSPECIFIED), |
+ is_port_wildcard(false), |
+ path() {} |
+ |
+CSPSource::CSPSource(const std::string& scheme, |
+ const std::string& host, |
+ bool is_host_wildcard, |
+ int port, |
+ int is_port_wildcard, |
+ const std::string& path) |
+ : scheme(scheme), |
+ host(host), |
+ is_host_wildcard(is_host_wildcard), |
+ port(port), |
+ is_port_wildcard(is_port_wildcard), |
+ path(path) { |
+ DCHECK(!has_port() || has_host()); // port => host |
+ DCHECK(!has_path() || has_host()); // path => host |
+ DCHECK(!is_port_wildcard || port == url::PORT_UNSPECIFIED); |
+} |
+ |
+CSPSource::CSPSource(const CSPSource& source) = default; |
+CSPSource::~CSPSource() = default; |
+ |
+// source = scheme ":" |
+// / ( [ scheme "://" ] host [ port ] [ path ] ) |
+// / "'self'" |
+// static |
+base::Optional<CSPSource> CSPSource::Parse(const std::string& text) { |
+ CSPSource csp_source; |
+ if (ParseSource(&csp_source, text)) |
+ return csp_source; |
+ else |
+ return {}; |
+} |
+ |
+bool CSPSource::Allow(CSPContext* context, |
+ const GURL& url, |
+ bool is_redirect) const { |
+ if (IsSchemeOnly()) |
+ return AllowScheme(url, context); |
+ else |
+ return AllowScheme(url, context) && AllowHost(url) && AllowPort(url) && |
+ AllowPath(url, is_redirect); |
+} |
+ |
+std::string CSPSource::ToString() const { |
+ // scheme |
+ if (IsSchemeOnly()) |
+ return scheme; |
+ |
+ std::stringstream text; |
+ if (!scheme.empty()) |
+ text << scheme << "://"; |
+ |
+ // host |
+ if (is_host_wildcard) { |
+ if (host.empty()) |
+ text << "*"; |
+ else |
+ text << "*." << host; |
+ } else { |
+ text << host; |
+ } |
+ |
+ // port |
+ if (is_port_wildcard) |
+ text << ":*"; |
+ if (port != url::PORT_UNSPECIFIED) |
+ text << ":" << port; |
+ |
+ // path |
+ text << path; |
+ |
+ return text.str(); |
+} |
+ |
+bool CSPSource::IsSchemeOnly() const { |
+ return !has_host(); |
+} |
+ |
+bool CSPSource::has_port() const { |
+ return port != url::PORT_UNSPECIFIED || is_port_wildcard; |
+} |
+ |
+bool CSPSource::has_host() const { |
+ return !host.empty() || is_host_wildcard; |
+} |
+ |
+bool CSPSource::has_path() const { |
+ return !path.empty(); |
+} |
+ |
+bool CSPSource::AllowScheme(const GURL& url, CSPContext* context) const { |
+ if (scheme.empty()) |
+ return context->ProtocolMatchesSelf(url); |
+ if (scheme == url::kHttpScheme) |
+ return url.SchemeIsHTTPOrHTTPS(); |
+ if (scheme == url::kWsScheme) |
+ return url.SchemeIsWSOrWSS(); |
+ return url.SchemeIs(scheme); |
+} |
+ |
+bool CSPSource::AllowHost(const GURL& url) const { |
+ if (is_host_wildcard) { |
+ if (host.empty()) |
+ return true; |
+ // TODO(arthursonzogni): Chrome used to, incorrectly, match *.x.y to x.y. |
+ // The renderer version of this function count how many times it happens. |
+ // See third_party/WebKit/Source/core/frame/csp/CSPSource.cpp |
+ return base::EndsWith(url.host(), '.' + host, |
+ base::CompareCase::INSENSITIVE_ASCII); |
+ } else |
+ return url.host() == host; |
+} |
+ |
+bool CSPSource::AllowPort(const GURL& url) const { |
+ int url_port = url.EffectiveIntPort(); |
+ |
+ if (is_port_wildcard) |
+ return true; |
+ |
+ if (port == url::PORT_UNSPECIFIED) |
+ return DefaultPortForScheme(url.scheme()) == url_port; |
+ |
+ if (port == url_port) |
+ return true; |
+ |
+ if (port == 80 && url_port == 443) |
+ return true; |
+ |
+ return false; |
+} |
+ |
+bool CSPSource::AllowPath(const GURL& url, bool is_redirect) const { |
+ if (is_redirect) |
+ return true; |
+ |
+ if (path.empty() || url.path().empty()) |
+ return true; |
+ |
+ std::string url_path; |
+ if (!DecodePath(url.path(), &url_path)) { |
+ // TODO(arthursonzogni). Considering doing something else here. |
+ return false; |
+ } |
+ |
+ // If the path represents a directory. |
+ if (base::EndsWith(path, "/", base::CompareCase::SENSITIVE)) |
+ return base::StartsWith(url_path, path, base::CompareCase::SENSITIVE); |
+ |
+ // The path represents a file. |
+ return path == url_path; |
+} |
+ |
+} // namespace content |