blob: 4737cc7e647b47924967bab29fab5486fa38e947 [file] [log] [blame]
// Copyright 2019 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/cross_origin_resource_policy.h"
#include <string>
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "services/network/initiator_lock_compatibility.h"
#include "services/network/public/cpp/resource_response.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_constants.h"
namespace network {
namespace {
const char kHeaderName[] = "Cross-Origin-Resource-Policy";
// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header says:
// > ABNF:
// > Cross-Origin-Resource-Policy = %s"same-origin" / %s"same-site"
// > ; case-sensitive
//
// https://tools.ietf.org/html/rfc7405 says:
// > The following prefixes are allowed:
// > %s = case-sensitive
// > %i = case-insensitive
CrossOriginResourcePolicy::ParsedHeader ParseHeader(
const net::HttpResponseHeaders* headers) {
std::string header_value;
if (!headers || !headers->GetNormalizedHeader(kHeaderName, &header_value))
return CrossOriginResourcePolicy::kNoHeader;
if (header_value == "same-origin")
return CrossOriginResourcePolicy::kSameOrigin;
if (header_value == "same-site")
return CrossOriginResourcePolicy::kSameSite;
// TODO(lukasza): Once https://github.com/whatwg/fetch/issues/760 gets
// resolved, add support for parsing specific origins.
return CrossOriginResourcePolicy::kParsingError;
}
std::string GetDomain(const url::Origin& origin) {
return net::registry_controlled_domains::GetDomainAndRegistry(
origin.host(),
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
}
bool ShouldAllowSameSite(const url::Origin& initiator,
const url::Origin& target_origin) {
// Different sites might be served from the same IP address - they should
// still be considered to be different sites - see also
// https://url.spec.whatwg.org/#host-same-site which excludes IP addresses by
// imposing the requirement that one of the addresses has to have a non-null
// registrable domain.
if (initiator.GetURL().HostIsIPAddress() ||
target_origin.GetURL().HostIsIPAddress()) {
return false;
}
// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header, step 5
// says to allow "CORP: same-site" responses
// > [...] If the following are true:
// > - request’s origin’s host is same site with request’s current URL’s host
// > - [...]
if (GetDomain(initiator) != GetDomain(target_origin))
return false;
// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header, step 5
// says to allow "CORP: same-site" responses
// > [...] If the following are true:
// > - [...]
// > - request’s origin’s scheme is "https" or response’s HTTPS state is
// "none"
//
// |target_origin.scheme() != url::kHttpsScheme| is pretty much equivalent to
// |response’s HTTPS state is "none"| based on the following spec snippets:
// - https://fetch.spec.whatwg.org/#http-network-fetch says:
// > If response was retrieved over HTTPS, set its HTTPS state to either
// "deprecated" or "modern".
// and the same spec section also hints that broken responses should result
// in network errors (rather than "none" or other http state):
// > User agents are strongly encouraged to only succeed HTTPS connections
// with strong security properties and return network errors otherwise.
return initiator.scheme() == url::kHttpsScheme ||
target_origin.scheme() != url::kHttpsScheme;
}
} // namespace
// static
CrossOriginResourcePolicy::VerificationResult CrossOriginResourcePolicy::Verify(
const net::URLRequest& request,
const ResourceResponse& response,
mojom::FetchRequestMode fetch_mode,
base::Optional<url::Origin> request_initiator_site_lock) {
// From https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header:
// > 1. If request’s mode is not "no-cors", then return allowed.
if (fetch_mode != mojom::FetchRequestMode::kNoCors)
return kAllow;
// From https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header:
// > 3. Let policy be the result of getting `Cross-Origin-Resource-Policy`
// from response’s header list.
//
// We parse the header earlier than requested by the spec (i.e. we swap steps
// 2 and 3 from the spec), to return early if there was no header (before
// slightly more expensive steps needed to extract the origins below).
ParsedHeader policy = ParseHeader(response.head.headers.get());
if (policy == kNoHeader || policy == kParsingError) {
// The algorithm only returns kBlock from steps 4 and 6, when policy is
// either kSameOrigin or kSameSite. For other policy values we can
// immediately execute step 7 and return kAllow.
//
// From https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header:
// > 7. Return allowed.
return kAllow;
}
// From https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header:
// > 2. If request’s origin is same origin with request’s current URL’s
// origin, then return allowed.
url::Origin target_origin = url::Origin::Create(request.url());
url::Origin initiator =
GetTrustworthyInitiator(request_initiator_site_lock, request);
if (initiator == target_origin)
return kAllow;
// From https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header:
// > 4. If policy is `same-origin`, then return blocked.
if (policy == kSameOrigin)
return kBlock;
// From https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header:
// > 5. If the following are true
// > * request’s origin’s host is same site with request’s current URL’s
// > host
// > * request’s origin’s scheme is "https" or response’s HTTPS state is
// > "none"
// > then return allowed.
if (ShouldAllowSameSite(initiator, target_origin))
return kAllow;
// From https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header:
// > 6. If policy is `same-site`, then return blocked.
DCHECK_EQ(kSameSite, policy);
return kBlock;
}
// static
CrossOriginResourcePolicy::ParsedHeader
CrossOriginResourcePolicy::ParseHeaderForTesting(
const net::HttpResponseHeaders* headers) {
return ParseHeader(headers);
}
// static
bool CrossOriginResourcePolicy::ShouldAllowSameSiteForTesting(
const url::Origin& initiator,
const url::Origin& target_origin) {
return ShouldAllowSameSite(initiator, target_origin);
}
} // namespace network