Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(138)

Unified Diff: content/common/content_security_policy/csp_source.cc

Issue 2612793002: Implement ContentSecurityPolicy on the browser-side. (Closed)
Patch Set: Temporary re-add the parser + transmit parsed CSP over IPC. Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698