blob: 5d2904a633b68591aa6981b2406c3bb727c47158 [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 "content/browser/frame_host/origin_policy_throttle.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/no_destructor.h"
#include "content/browser/frame_host/navigation_handle_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/origin.h"
namespace {
// Constants derived from the spec, https://github.com/WICG/origin-policy
static const char* kDefaultPolicy = "1";
static const char* kDeletePolicy = "0";
static const char* kWellKnown = "/.well-known/origin-policy/";
// Maximum policy size (implementation-defined limit in bytes).
// (Limit copied from network::SimpleURLLoader::kMaxBoundedStringDownloadSize.)
static const size_t kMaxPolicySize = 1024 * 1024;
} // namespace
namespace content {
// static
bool OriginPolicyThrottle::ShouldRequestOriginPolicy(
const GURL& url,
std::string* request_version) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!base::FeatureList::IsEnabled(features::kOriginPolicy))
return false;
if (!url.SchemeIs(url::kHttpsScheme))
return false;
if (request_version) {
const KnownVersionMap& versions = GetKnownVersions();
const auto iter = versions.find(url::Origin::Create(url));
*request_version =
iter == versions.end() ? std::string(kDefaultPolicy) : iter->second;
}
return true;
}
// static
std::unique_ptr<NavigationThrottle>
OriginPolicyThrottle::MaybeCreateThrottleFor(NavigationHandle* handle) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(handle);
// We use presence of the origin policy request header to determine
// whether we should create the throttle.
if (!handle->GetRequestHeaders().HasHeader(
net::HttpRequestHeaders::kSecOriginPolicy))
return nullptr;
DCHECK(base::FeatureList::IsEnabled(features::kOriginPolicy));
return base::WrapUnique(new OriginPolicyThrottle(handle));
}
OriginPolicyThrottle::~OriginPolicyThrottle() {}
NavigationThrottle::ThrottleCheckResult
OriginPolicyThrottle::WillStartRequest() {
// TODO(vogelheim): It might be faster in the common case to optimistically
// fetch the policy indicated in the request already here. This would
// be faster if the last known version is the current version, but
// slower (and wasteful of bandwidth) if the server sends us a new
// version. It's unclear how much the upside is, though.
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult
OriginPolicyThrottle::WillProcessResponse() {
DCHECK(navigation_handle());
// Per spec, Origin Policies are only fetched for https:-requests. So we
// should always have HTTP headers at this point.
// However, some unit tests generate responses without headers, so we still
// need to check.
if (!navigation_handle()->GetResponseHeaders())
return NavigationThrottle::PROCEED;
// This determines whether and which policy version applies and fetches it.
//
// Inputs are the kSecOriginPolicy HTTP header, and the version
// we've last seen from this particular origin.
//
// - header with kDeletePolicy received: No policy applies, and delete the
// last-known policy for this origin.
// - header received: Use header version and update last-known policy.
// - no header received, last-known version exists: Use last-known version
// - no header, no last-known version: No policy applies.
std::string response_version;
bool header_found =
navigation_handle()->GetResponseHeaders()->GetNormalizedHeader(
net::HttpRequestHeaders::kSecOriginPolicy, &response_version);
url::Origin origin = GetRequestOrigin();
DCHECK(!origin.Serialize().empty());
DCHECK(!origin.unique());
KnownVersionMap& versions = GetKnownVersions();
KnownVersionMap::iterator iter = versions.find(origin);
// Process policy deletion first!
if (header_found && response_version == kDeletePolicy) {
if (iter != versions.end())
versions.erase(iter);
return NavigationThrottle::PROCEED;
}
// No policy applies to this request?
if (!header_found && iter == versions.end()) {
return NavigationThrottle::PROCEED;
}
if (!header_found)
response_version = iter->second;
else if (iter == versions.end())
versions.insert(std::make_pair(origin, response_version));
else
iter->second = response_version;
GURL policy = GURL(origin.Serialize() + kWellKnown + response_version);
FetchCallback done =
base::BindOnce(&OriginPolicyThrottle::OnTheGloriousPolicyHasArrived,
base::Unretained(this));
FetchPolicy(policy, std::move(done));
return NavigationThrottle::DEFER;
}
const char* OriginPolicyThrottle::GetNameForLogging() {
return "OriginPolicyThrottle";
}
// static
OriginPolicyThrottle::KnownVersionMap&
OriginPolicyThrottle::GetKnownVersionsForTesting() {
return GetKnownVersions();
}
OriginPolicyThrottle::OriginPolicyThrottle(NavigationHandle* handle)
: NavigationThrottle(handle) {}
OriginPolicyThrottle::KnownVersionMap&
OriginPolicyThrottle::GetKnownVersions() {
static base::NoDestructor<KnownVersionMap> map_instance;
return *map_instance;
}
const url::Origin OriginPolicyThrottle::GetRequestOrigin() {
return url::Origin::Create(navigation_handle()->GetURL());
}
void OriginPolicyThrottle::FetchPolicy(const GURL& url, FetchCallback done) {
// Create the traffic annotation
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("origin_policy_loader", R"(
semantics {
sender: "Origin Policy URL Loader Throttle"
description:
"Fetches the Origin Policy with a given version from an origin."
trigger:
"In case the Origin Policy with a given version does not "
"exist in the cache, it is fetched from the origin at a "
"well-known location."
data:
"None, the URL itself contains the origin and Origin Policy "
"version."
destination: OTHER
}
policy {
cookies_allowed: NO
setting: "This feature cannot be disabled by settings. Server "
"opt-in or out of this mechanism."
policy_exception_justification:
"Not implemented, considered not useful."})");
// Create the SimpleURLLoader for the policy.
std::unique_ptr<network::ResourceRequest> policy_request =
std::make_unique<network::ResourceRequest>();
policy_request->url = url;
policy_request->request_initiator = url::Origin::Create(url);
policy_request->fetch_credentials_mode =
network::mojom::FetchCredentialsMode::kOmit;
policy_request->fetch_redirect_mode =
network::mojom::FetchRedirectMode::kError;
policy_request->load_flags = net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA;
url_loader_ = network::SimpleURLLoader::Create(std::move(policy_request),
traffic_annotation);
// Obtain the URLLoaderFactory from the NavigationHandle.
SiteInstance* site_instance = navigation_handle()->GetStartingSiteInstance();
content::StoragePartition* storage_partition =
BrowserContext::GetStoragePartition(site_instance->GetBrowserContext(),
site_instance);
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory =
storage_partition->GetURLLoaderFactoryForBrowserProcess();
// Start the download, and pass the callback for when we're finished.
url_loader_->DownloadToString(url_loader_factory.get(), std::move(done),
kMaxPolicySize);
}
void OriginPolicyThrottle::InjectPolicyForTesting(
const std::string& policy_content) {
OnTheGloriousPolicyHasArrived(std::make_unique<std::string>(policy_content));
}
void OriginPolicyThrottle::OnTheGloriousPolicyHasArrived(
std::unique_ptr<std::string> policy_content) {
// Release resources associated with the loading.
url_loader_.reset();
// Fail hard if the policy could not be loaded.
if (!policy_content) {
CancelDeferredNavigation(NavigationThrottle::CANCEL_AND_IGNORE);
return;
}
// TODO(vogelheim): Determine whether we need to parse or sanity check
// the policy content at this point.
static_cast<NavigationHandleImpl*>(navigation_handle())
->set_origin_policy(*policy_content);
Resume();
}
} // namespace content