blob: 26361e43d125af42c59bf7426b0fc06731a910cf [file] [log] [blame]
// Copyright 2018 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 "services/network/public/cpp/cors/origin_access_entry.h"
#include "base/strings/string_util.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "url/origin.h"
#include "url/url_util.h"
namespace network {
namespace cors {
namespace {
bool IsSubdomainOfHost(const std::string& subdomain, const std::string& host) {
if (subdomain.length() <= host.length())
return false;
if (subdomain[subdomain.length() - host.length() - 1] != '.')
return false;
if (!base::EndsWith(subdomain, host, base::CompareCase::SENSITIVE))
return false;
return true;
}
} // namespace
OriginAccessEntry::OriginAccessEntry(
const std::string& protocol,
const std::string& host,
MatchMode match_mode,
const network::mojom::CORSOriginAccessMatchPriority priority)
: protocol_(protocol),
host_(host),
match_mode_(match_mode),
priority_(priority),
host_is_ip_address_(url::HostIsIPAddress(host)),
host_is_public_suffix_(false) {
if (host_is_ip_address_)
return;
// Look for top-level domains, either with or without an additional dot.
// Call sites in Blink passes some things that aren't technically hosts like
// "*.foo", so use the permissive variant.
size_t public_suffix_length =
net::registry_controlled_domains::PermissiveGetHostRegistryLength(
host_, net::registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
if (public_suffix_length == 0)
public_suffix_length = host_.length();
if (host_.length() <= public_suffix_length + 1) {
host_is_public_suffix_ = true;
} else if (match_mode_ == kAllowRegisterableDomains && public_suffix_length) {
// The "2" in the next line is 1 for the '.', plus a 1-char minimum label
// length.
const size_t dot =
host_.rfind('.', host_.length() - public_suffix_length - 2);
if (dot == std::string::npos)
registerable_domain_ = host_;
else
registerable_domain_ = host_.substr(dot + 1);
}
}
OriginAccessEntry::OriginAccessEntry(OriginAccessEntry&& from) = default;
OriginAccessEntry::MatchResult OriginAccessEntry::MatchesOrigin(
const url::Origin& origin) const {
if (protocol_ != origin.scheme())
return kDoesNotMatchOrigin;
return MatchesDomain(origin);
}
OriginAccessEntry::MatchResult OriginAccessEntry::MatchesDomain(
const url::Origin& origin) const {
// Special case: Include subdomains and empty host means "all hosts, including
// ip addresses".
if (match_mode_ != kDisallowSubdomains && host_.empty())
return kMatchesOrigin;
// Exact match.
if (host_ == origin.host())
return kMatchesOrigin;
// Don't try to do subdomain matching on IP addresses.
if (host_is_ip_address_)
return kDoesNotMatchOrigin;
// Match subdomains.
switch (match_mode_) {
case kDisallowSubdomains:
return kDoesNotMatchOrigin;
case kAllowSubdomains:
if (!IsSubdomainOfHost(origin.host(), host_))
return kDoesNotMatchOrigin;
break;
case kAllowRegisterableDomains:
// Fall back to a simple subdomain check if no registerable domain could
// be found:
if (registerable_domain_.empty()) {
if (!IsSubdomainOfHost(origin.host(), host_))
return kDoesNotMatchOrigin;
} else if (registerable_domain_ != origin.host() &&
!IsSubdomainOfHost(origin.host(), registerable_domain_)) {
return kDoesNotMatchOrigin;
}
break;
};
if (host_is_public_suffix_)
return kMatchesOriginButIsPublicSuffix;
return kMatchesOrigin;
}
} // namespace cors
} // namespace network