[go: nahoru, domu]

blob: ec39e14c37959c8b28199468a170b35a2fe5f483 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <sstream>
#include "services/network/public/cpp/content_security_policy/csp_source.h"
#include "base/check_op.h"
#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/mojom/content_security_policy.mojom.h"
#include "url/url_canon.h"
#include "url/url_util.h"
namespace network {
namespace {
bool HasHost(const mojom::CSPSource& source) {
return !source.host.empty() || source.is_host_wildcard;
}
bool DecodePath(const base::StringPiece& path, std::string* output) {
url::RawCanonOutputT<char16_t> unescaped;
url::DecodeURLEscapeSequences(path.data(), path.size(),
url::DecodeURLMode::kUTF8OrIsomorphic,
&unescaped);
return base::UTF16ToUTF8(unescaped.data(), unescaped.length(), output);
}
int DefaultPortForScheme(const std::string& scheme) {
return url::DefaultPortForScheme(scheme.data(), scheme.size());
}
// NotMatching is the only negative member, the rest are different types of
// matches. NotMatching should always be 0 to let if statements work nicely
enum class PortMatchingResult {
NotMatching,
MatchingWildcard,
MatchingUpgrade,
MatchingExact
};
enum class SchemeMatchingResult { NotMatching, MatchingUpgrade, MatchingExact };
SchemeMatchingResult MatchScheme(const std::string& scheme_a,
const std::string& scheme_b) {
if (scheme_a == scheme_b)
return SchemeMatchingResult::MatchingExact;
if ((scheme_a == url::kHttpScheme && scheme_b == url::kHttpsScheme) ||
(scheme_a == url::kWsScheme && scheme_b == url::kWssScheme)) {
return SchemeMatchingResult::MatchingUpgrade;
}
return SchemeMatchingResult::NotMatching;
}
SchemeMatchingResult SourceAllowScheme(const mojom::CSPSource& source,
const GURL& url,
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() && self_source.scheme.empty())
return SchemeMatchingResult::NotMatching;
// |allowed_scheme| is guaranteed to be non-empty.
const std::string& allowed_scheme =
source.scheme.empty() ? self_source.scheme : source.scheme;
return MatchScheme(allowed_scheme, url.scheme());
}
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,
base::CompareCase::INSENSITIVE_ASCII);
} else {
return base::EqualsCaseInsensitiveASCII(host, source.host);
}
}
bool SourceAllowHost(const mojom::CSPSource& source, const GURL& url) {
return SourceAllowHost(source, url.host());
}
PortMatchingResult SourceAllowPort(const mojom::CSPSource& source,
int port,
const std::string& scheme) {
if (source.is_port_wildcard)
return PortMatchingResult::MatchingWildcard;
if (source.port == port) {
if (source.port == url::PORT_UNSPECIFIED)
return PortMatchingResult::MatchingWildcard;
return PortMatchingResult::MatchingExact;
}
if (source.port == url::PORT_UNSPECIFIED) {
if (DefaultPortForScheme(scheme) == port)
return PortMatchingResult::MatchingWildcard;
}
if (port == url::PORT_UNSPECIFIED) {
if (source.port == DefaultPortForScheme(scheme))
return PortMatchingResult::MatchingWildcard;
}
int source_port = source.port;
if (source_port == url::PORT_UNSPECIFIED)
source_port = DefaultPortForScheme(source.scheme);
if (port == url::PORT_UNSPECIFIED)
port = DefaultPortForScheme(scheme);
if (source_port == 80 && port == 443)
return PortMatchingResult::MatchingUpgrade;
return PortMatchingResult::NotMatching;
}
PortMatchingResult SourceAllowPort(const mojom::CSPSource& source,
const GURL& url) {
return SourceAllowPort(source, url.EffectiveIntPort(), url.scheme());
}
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
// handle it.
return false;
}
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,
base::CompareCase::SENSITIVE);
}
// The path represents a file.
return source.path == path_decoded;
}
bool SourceAllowPath(const mojom::CSPSource& source,
const GURL& url,
bool has_followed_redirect) {
if (has_followed_redirect)
return true;
return SourceAllowPath(source, url.path());
}
bool requiresUpgrade(const PortMatchingResult result) {
return result == PortMatchingResult::MatchingUpgrade;
}
bool requiresUpgrade(const SchemeMatchingResult result) {
return result == SchemeMatchingResult::MatchingUpgrade;
}
bool canUpgrade(const PortMatchingResult result) {
return result == PortMatchingResult::MatchingUpgrade ||
result == PortMatchingResult::MatchingWildcard;
}
bool canUpgrade(const SchemeMatchingResult result) {
return result == SchemeMatchingResult::MatchingUpgrade;
}
} // namespace
bool CSPSourceIsSchemeOnly(const mojom::CSPSource& source) {
return !HasHost(source);
}
bool CheckCSPSource(const mojom::CSPSource& source,
const GURL& url,
const mojom::CSPSource& self_source,
bool has_followed_redirect,
bool is_opaque_fenced_frame) {
// Opaque fenced frames only allow https urls.
// TODO(crbug.com/1243568): Update the DCHECK and the return condition below
// if opaque fenced frames can map to non-https potentially trustworthy urls.
if (is_opaque_fenced_frame)
DCHECK_EQ(url.scheme(), url::kHttpsScheme);
if (CSPSourceIsSchemeOnly(source)) {
// Opaque fenced frames only match 'https:' scheme source. Returns true for
// 'https:' scheme source assuming that the url has been validated on the
// fenced frame side.
if (is_opaque_fenced_frame)
return source.scheme == url::kHttpsScheme;
return SourceAllowScheme(source, url, self_source) !=
SchemeMatchingResult::NotMatching;
}
// Only allow 'https://*:*' for opaque fenced frames. 'https://*' is not
// allowed as it could leak data about ports.
if (is_opaque_fenced_frame) {
return source.scheme == url::kHttpsScheme && source.is_host_wildcard &&
source.is_port_wildcard;
}
PortMatchingResult portResult = SourceAllowPort(source, url);
SchemeMatchingResult schemeResult =
SourceAllowScheme(source, url, self_source);
if (requiresUpgrade(schemeResult) && !canUpgrade(portResult))
return false;
if (requiresUpgrade(portResult) && !canUpgrade(schemeResult))
return false;
return schemeResult != SchemeMatchingResult::NotMatching &&
SourceAllowHost(source, url) &&
portResult != PortMatchingResult::NotMatching &&
SourceAllowPath(source, url, has_followed_redirect);
}
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());
auto result = mojom::CSPSource::New();
if (MatchScheme(source_a.scheme, source_b.scheme) !=
SchemeMatchingResult::NotMatching) {
result->scheme = source_b.scheme;
} else if (MatchScheme(source_b.scheme, source_a.scheme) !=
SchemeMatchingResult::NotMatching) {
result->scheme = source_a.scheme;
} else {
return nullptr;
}
if (CSPSourceIsSchemeOnly(source_a)) {
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();
new_result->scheme = result->scheme;
return new_result;
}
const std::string host_a =
(source_a.is_host_wildcard ? "*." : "") + source_a.host;
const std::string host_b =
(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;
} else if (SourceAllowHost(source_b, host_a)) {
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) !=
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) !=
PortMatchingResult::NotMatching) {
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;
else
return nullptr;
return result;
}
// Check whether |source_a| subsumes |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());
if (MatchScheme(source_a.scheme, source_b.scheme) ==
SchemeMatchingResult::NotMatching) {
return false;
}
if (CSPSourceIsSchemeOnly(source_a))
return true;
if (CSPSourceIsSchemeOnly(source_b))
return false;
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)
return false;
PortMatchingResult port_matching =
SourceAllowPort(source_a, source_b.port, source_b.scheme);
if (port_matching == PortMatchingResult::NotMatching)
return false;
if (!SourceAllowPath(source_a, source_b.path))
return false;
return true;
}
std::string ToString(const mojom::CSPSource& source) {
// scheme
if (CSPSourceIsSchemeOnly(source))
return source.scheme + ":";
std::stringstream text;
if (!source.scheme.empty())
text << source.scheme << "://";
// host
if (source.is_host_wildcard) {
if (source.host.empty())
text << "*";
else
text << "*." << source.host;
} else {
text << source.host;
}
// port
if (source.is_port_wildcard)
text << ":*";
if (source.port != url::PORT_UNSPECIFIED)
text << ":" << source.port;
// path
text << source.path;
return text.str();
}
} // namespace network