blob: ef0565c629bc79f076b6e5df175fab77c156b9f1 [file] [log] [blame]
// Copyright 2020 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 "android_webview/common/aw_origin_matcher.h"
#include "base/strings/pattern.h"
#include "base/strings/stringprintf.h"
#include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h"
#include "net/base/parse_number.h"
#include "net/base/scheme_host_port_matcher_rule.h"
#include "net/base/url_util.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_constants.h"
#include "url/url_util.h"
namespace android_webview {
namespace {
// A rule to match all origins even the origin is opaque.
class MatchAllOriginsRule : public net::SchemeHostPortMatcherRule {
public:
MatchAllOriginsRule() = default;
// Don't allow copy and assign.
MatchAllOriginsRule(const MatchAllOriginsRule&) = delete;
MatchAllOriginsRule& operator=(const MatchAllOriginsRule&) = delete;
// net::SchemeHostPortMatcherRule implementation.
net::SchemeHostPortMatcherResult Evaluate(const GURL& url) const override {
return net::SchemeHostPortMatcherResult::kInclude;
}
std::string ToString() const override { return "*"; }
};
class SubdomainMatchingRule : public net ::SchemeHostPortMatcherRule {
public:
SubdomainMatchingRule(const std::string& scheme,
const std::string& optional_host,
int optional_port)
: scheme_(base::ToLowerASCII(scheme)),
optional_host_(base::ToLowerASCII(optional_host)),
optional_port_(optional_port) {
// We don't allow empty scheme.
DCHECK(!scheme.empty());
}
// Don't allow copy and assign.
SubdomainMatchingRule(const SubdomainMatchingRule&) = delete;
SubdomainMatchingRule& operator=(const SubdomainMatchingRule&) = delete;
// net::SchemeHostPortMatcherRule implementation.
net::SchemeHostPortMatcherResult Evaluate(const GURL& url) const override {
if (optional_port_ != -1 && url.EffectiveIntPort() != optional_port_) {
// Didn't match port expectation.
return net::SchemeHostPortMatcherResult::kNoMatch;
}
if (url.scheme() != scheme_) {
// Didn't match scheme expectation.
return net::SchemeHostPortMatcherResult::kNoMatch;
}
return base::MatchPattern(url.host(), optional_host_)
? net::SchemeHostPortMatcherResult::kInclude
: net::SchemeHostPortMatcherResult::kNoMatch;
}
std::string ToString() const override {
std::string str;
base::StringAppendF(&str, "%s://%s", scheme_.c_str(),
optional_host_.c_str());
if (optional_port_ != -1)
base::StringAppendF(&str, ":%d", optional_port_);
return str;
}
private:
const std::string scheme_;
// Empty string means no host provided.
const std::string optional_host_;
// -1 means no port provided.
const int optional_port_;
};
inline bool HostWildcardSanityCheck(const std::string& host) {
size_t wildcard_count = std::count(host.begin(), host.end(), '*');
if (wildcard_count == 0)
return true;
// We only allow one wildcard.
if (wildcard_count > 1)
return false;
// Start with "*." for subdomain matching.
if (base::StartsWith(host, "*.", base::CompareCase::SENSITIVE))
return true;
return false;
}
inline int GetDefaultPortForSchemeIfNoPortInfo(const std::string& scheme,
int port) {
// The input has explicit port information, so don't modify it.
if (port != -1)
return port;
// Hard code the port for http and https.
if (scheme == url::kHttpScheme)
return 80;
if (scheme == url::kHttpsScheme)
return 443;
return port;
}
inline bool CanHaveHostPort(const std::string& scheme) {
// We only allow http and https schemes to have host/port parts.
return scheme == url::kHttpScheme || scheme == url::kHttpsScheme;
}
} // namespace
AwOriginMatcher::AwOriginMatcher(const AwOriginMatcher& rhs) {
*this = rhs;
}
AwOriginMatcher& AwOriginMatcher::operator=(const AwOriginMatcher& rhs) {
rules_.clear();
for (const auto& rule : rhs.Serialize()) {
AddRuleFromString(rule);
}
return *this;
}
bool AwOriginMatcher::AddRuleFromString(const std::string& raw_untrimmed) {
std::string raw;
base::TrimWhitespaceASCII(raw_untrimmed, base::TRIM_ALL, &raw);
if (raw == "*") {
rules_.push_back(std::make_unique<MatchAllOriginsRule>());
return true;
}
// Extract scheme-restriction.
std::string::size_type scheme_pos = raw.find("://");
if (scheme_pos == std::string::npos)
return false;
std::string scheme = raw.substr(0, scheme_pos);
// Scheme is necessary for us and wildcard matching for scheme is not allowed.
if (scheme.empty() || scheme.find('*') != std::string::npos)
return false;
std::string host_and_port = raw.substr(scheme_pos + 3);
if (host_and_port.empty()) {
// Doesn't allow "https://" and "http://" rules.
if (CanHaveHostPort(scheme))
return false;
rules_.push_back(std::make_unique<SubdomainMatchingRule>(scheme, "", -1));
return true;
}
// Now host_and_port is non-empty so scheme can't be other than https or http.
if (!CanHaveHostPort(scheme))
return false;
// Now scheme is https or http and may have host and port.
// URL like rule is invalid.
if (host_and_port.find('/') != std::string::npos)
return false;
std::string host;
int port;
if (!net::ParseHostAndPort(host_and_port, &host, &port))
return false;
// Check if we have an <ip-address>[:port] input and try to canonicalize the
// IP literal.
net::IPAddress ip_address;
if (ip_address.AssignFromIPLiteral(host)) {
port = GetDefaultPortForSchemeIfNoPortInfo(scheme, port);
host = ip_address.ToString();
if (ip_address.IsIPv6()) {
host = '[' + host + ']';
}
rules_.push_back(
std::make_unique<SubdomainMatchingRule>(scheme, host, port));
return true;
}
// Otherwise assume we have <hostname-pattern>[:port].
if (!HostWildcardSanityCheck(host))
return false;
port = GetDefaultPortForSchemeIfNoPortInfo(scheme, port);
rules_.push_back(std::make_unique<SubdomainMatchingRule>(scheme, host, port));
return true;
}
bool AwOriginMatcher::Matches(const url::Origin& origin) const {
GURL origin_url = origin.GetURL();
// Since we only do kInclude vs kNoMatch, the order doesn't actually matter.
for (auto it = rules_.rbegin(); it != rules_.rend(); ++it) {
net::SchemeHostPortMatcherResult result = (*it)->Evaluate(origin_url);
if (result == net::SchemeHostPortMatcherResult::kInclude)
return true;
}
return false;
}
std::vector<std::string> AwOriginMatcher::Serialize() const {
std::vector<std::string> result;
result.reserve(rules_.size());
for (const auto& rule : rules_) {
result.push_back(rule->ToString());
}
return result;
}
} // namespace android_webview