| 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
|
|
|