blob: 18f4dbd1bbac14d05872c5d529ed11325c77fc0b [file] [log] [blame]
/*
* Copyright (C) 2011, 2012 Google Inc. All rights reserved.
* Copyright (C) 2013, Intel Corporation
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/loader/document_threadable_loader.h"
#include <memory>
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "services/network/public/mojom/cors.mojom-blink.h"
#include "services/network/public/mojom/fetch_api.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/public/platform/task_type.h"
#include "third_party/blink/public/platform/web_cors.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/frame_console.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/inspector/inspector_network_agent.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/loader/base_fetch_context.h"
#include "third_party/blink/renderer/core/loader/document_threadable_loader_client.h"
#include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/loader/threadable_loader_client.h"
#include "third_party/blink/renderer/core/loader/threadable_loading_context.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/platform/exported/wrapped_resource_request.h"
#include "third_party/blink/renderer/platform/loader/cors/cors.h"
#include "third_party/blink/renderer/platform/loader/cors/cors_error_string.h"
#include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/shared_buffer.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
namespace blink {
namespace {
// Fetch API Spec: https://fetch.spec.whatwg.org/#cors-preflight-fetch-0
AtomicString CreateAccessControlRequestHeadersHeader(
const HTTPHeaderMap& headers) {
Vector<String> filtered_headers;
for (const auto& header : headers) {
// Exclude CORS-safelisted headers.
if (CORS::IsCORSSafelistedHeader(header.key, header.value))
continue;
// Calling a deprecated function, but eventually this function,
// |CreateAccessControlRequestHeadersHeader| will be removed.
// When the request is from a Worker, referrer header was added by
// WorkerThreadableLoader. But it should not be added to
// Access-Control-Request-Headers header.
if (DeprecatedEqualIgnoringCase(header.key, "referer"))
continue;
filtered_headers.push_back(header.key.DeprecatedLower());
}
if (!filtered_headers.size())
return g_null_atom;
// Sort header names lexicographically.
std::sort(filtered_headers.begin(), filtered_headers.end(),
WTF::CodePointCompareLessThan);
StringBuilder header_buffer;
for (const String& header : filtered_headers) {
if (!header_buffer.IsEmpty())
header_buffer.Append(",");
header_buffer.Append(header);
}
return header_buffer.ToAtomicString();
}
class EmptyDataHandle final : public WebDataConsumerHandle {
private:
class EmptyDataReader final : public WebDataConsumerHandle::Reader {
public:
explicit EmptyDataReader(
WebDataConsumerHandle::Client* client,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: factory_(this) {
task_runner->PostTask(
FROM_HERE, WTF::Bind(&EmptyDataReader::Notify, factory_.GetWeakPtr(),
WTF::Unretained(client)));
}
private:
Result BeginRead(const void** buffer,
WebDataConsumerHandle::Flags,
size_t* available) override {
*available = 0;
*buffer = nullptr;
return kDone;
}
Result EndRead(size_t) override {
return WebDataConsumerHandle::kUnexpectedError;
}
void Notify(WebDataConsumerHandle::Client* client) {
client->DidGetReadable();
}
base::WeakPtrFactory<EmptyDataReader> factory_;
};
std::unique_ptr<Reader> ObtainReader(
Client* client,
scoped_refptr<base::SingleThreadTaskRunner> task_runner) override {
return std::make_unique<EmptyDataReader>(client, std::move(task_runner));
}
const char* DebugName() const override { return "EmptyDataHandle"; }
};
} // namespace
// DetachedClient is a ThreadableLoaderClient for a "detached"
// DocumentThreadableLoader. It's for fetch requests with keepalive set, so
// it keeps itself alive during loading.
class DocumentThreadableLoader::DetachedClient final
: public GarbageCollectedFinalized<DetachedClient>,
public ThreadableLoaderClient {
public:
explicit DetachedClient(DocumentThreadableLoader* loader)
: self_keep_alive_(this), loader_(loader) {}
~DetachedClient() override {}
void DidFinishLoading(unsigned long identifier) override {
self_keep_alive_.Clear();
}
void DidFail(const ResourceError&) override { self_keep_alive_.Clear(); }
void DidFailRedirectCheck() override { self_keep_alive_.Clear(); }
void Trace(Visitor* visitor) { visitor->Trace(loader_); }
private:
SelfKeepAlive<DetachedClient> self_keep_alive_;
// Keep it alive.
const Member<DocumentThreadableLoader> loader_;
};
// Max number of CORS redirects handled in DocumentThreadableLoader. Same number
// as net/url_request/url_request.cc, and same number as
// https://fetch.spec.whatwg.org/#concept-http-fetch, Step 4.
// FIXME: currently the number of redirects is counted and limited here and in
// net/url_request/url_request.cc separately.
static const int kMaxCORSRedirects = 20;
// static
void DocumentThreadableLoader::LoadResourceSynchronously(
ThreadableLoadingContext& loading_context,
const ResourceRequest& request,
ThreadableLoaderClient& client,
const ThreadableLoaderOptions& options,
const ResourceLoaderOptions& resource_loader_options) {
(new DocumentThreadableLoader(loading_context, &client, kLoadSynchronously,
options, resource_loader_options))
->Start(request);
}
// static
std::unique_ptr<ResourceRequest>
DocumentThreadableLoader::CreateAccessControlPreflightRequest(
const ResourceRequest& request,
const SecurityOrigin* origin) {
const KURL& request_url = request.Url();
DCHECK(request_url.User().IsEmpty());
DCHECK(request_url.Pass().IsEmpty());
std::unique_ptr<ResourceRequest> preflight_request =
std::make_unique<ResourceRequest>(request_url);
preflight_request->SetHTTPMethod(HTTPNames::OPTIONS);
preflight_request->SetHTTPHeaderField(
HTTPNames::Access_Control_Request_Method, request.HttpMethod());
preflight_request->SetPriority(request.Priority());
preflight_request->SetRequestContext(request.GetRequestContext());
preflight_request->SetFetchCredentialsMode(
network::mojom::FetchCredentialsMode::kOmit);
preflight_request->SetSkipServiceWorker(true);
preflight_request->SetHTTPReferrer(
Referrer(request.HttpReferrer(), request.GetReferrerPolicy()));
if (request.IsExternalRequest()) {
preflight_request->SetHTTPHeaderField(
HTTPNames::Access_Control_Request_External, "true");
}
const AtomicString request_headers =
CreateAccessControlRequestHeadersHeader(request.HttpHeaderFields());
if (request_headers != g_null_atom) {
preflight_request->SetHTTPHeaderField(
HTTPNames::Access_Control_Request_Headers, request_headers);
}
if (origin)
preflight_request->SetHTTPOrigin(origin);
return preflight_request;
}
// static
std::unique_ptr<ResourceRequest>
DocumentThreadableLoader::CreateAccessControlPreflightRequestForTesting(
const ResourceRequest& request) {
return CreateAccessControlPreflightRequest(request, nullptr);
}
// static
DocumentThreadableLoader* DocumentThreadableLoader::Create(
ThreadableLoadingContext& loading_context,
ThreadableLoaderClient* client,
const ThreadableLoaderOptions& options,
const ResourceLoaderOptions& resource_loader_options) {
return new DocumentThreadableLoader(loading_context, client,
kLoadAsynchronously, options,
resource_loader_options);
}
DocumentThreadableLoader::DocumentThreadableLoader(
ThreadableLoadingContext& loading_context,
ThreadableLoaderClient* client,
BlockingBehavior blocking_behavior,
const ThreadableLoaderOptions& options,
const ResourceLoaderOptions& resource_loader_options)
: client_(client),
loading_context_(&loading_context),
options_(options),
resource_loader_options_(resource_loader_options),
out_of_blink_cors_(RuntimeEnabledFeatures::OutOfBlinkCORSEnabled()),
cors_flag_(false),
security_origin_(resource_loader_options_.security_origin),
is_using_data_consumer_handle_(false),
async_(blocking_behavior == kLoadAsynchronously),
request_context_(WebURLRequest::kRequestContextUnspecified),
fetch_request_mode_(network::mojom::FetchRequestMode::kSameOrigin),
fetch_credentials_mode_(network::mojom::FetchCredentialsMode::kOmit),
timeout_timer_(
GetExecutionContext()->GetTaskRunner(TaskType::kNetworking),
this,
&DocumentThreadableLoader::DidTimeout),
cors_redirect_limit_(0),
redirect_mode_(network::mojom::FetchRedirectMode::kFollow),
override_referrer_(false) {
DCHECK(client);
}
void DocumentThreadableLoader::Start(const ResourceRequest& request) {
// Setting an outgoing referer is only supported in the async code path.
DCHECK(async_ || request.HttpReferrer().IsEmpty());
bool cors_enabled =
CORS::IsCORSEnabledRequestMode(request.GetFetchRequestMode());
// kPreventPreflight can be used only when the CORS is enabled.
DCHECK(request.CORSPreflightPolicy() ==
network::mojom::CORSPreflightPolicy::kConsiderPreflight ||
cors_enabled);
if (cors_enabled)
cors_redirect_limit_ = kMaxCORSRedirects;
request_context_ = request.GetRequestContext();
fetch_request_mode_ = request.GetFetchRequestMode();
fetch_credentials_mode_ = request.GetFetchCredentialsMode();
redirect_mode_ = request.GetFetchRedirectMode();
if (request.GetFetchRequestMode() ==
network::mojom::FetchRequestMode::kNoCORS) {
SECURITY_CHECK(WebCORS::IsNoCORSAllowedContext(request_context_));
} else {
cors_flag_ = !GetSecurityOrigin()->CanRequest(request.Url());
}
// The CORS flag variable is not yet used at the step in the spec that
// corresponds to this line, but divert |cors_flag_| here for convenience.
if (cors_flag_ && request.GetFetchRequestMode() ==
network::mojom::FetchRequestMode::kSameOrigin) {
ThreadableLoaderClient* client = client_;
Clear();
ResourceError error = ResourceError::CancelledDueToAccessCheckError(
request.Url(), ResourceRequestBlockedReason::kOther,
CORS::GetErrorString(
CORS::ErrorParameter::CreateForDisallowedByMode(request.Url())));
GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kErrorMessageLevel, error.LocalizedDescription()));
client->DidFail(error);
return;
}
request_started_ = CurrentTimeTicks();
// Save any headers on the request here. If this request redirects
// cross-origin, we cancel the old request create a new one, and copy these
// headers.
request_headers_ = request.HttpHeaderFields();
ResourceRequest new_request(request);
// Set the service worker mode to none if "bypass for network" in DevTools is
// enabled.
bool should_bypass_service_worker = false;
probe::shouldBypassServiceWorker(GetExecutionContext(),
&should_bypass_service_worker);
if (should_bypass_service_worker)
new_request.SetSkipServiceWorker(true);
// Process the CORS protocol inside the DocumentThreadableLoader for the
// following cases:
//
// - When the request is sync or the protocol is unsupported since we can
// assume that any service worker (SW) is skipped for such requests by
// content/ code.
// - When |skip_service_worker| is true, any SW will be skipped.
// - If we're not yet controlled by a SW, then we're sure that this
// request won't be intercepted by a SW. In case we end up with
// sending a CORS preflight request, the actual request to be sent later
// may be intercepted. This is taken care of in LoadPreflightRequest() by
// setting |skip_service_worker| to true.
//
// From the above analysis, you can see that the request can never be
// intercepted by a SW inside this if-block. It's because:
// - |skip_service_worker| needs to be false, and
// - we're controlled by a SW at this point
// to allow a SW to intercept the request. Even when the request gets issued
// asynchronously after performing the CORS preflight, it doesn't get
// intercepted since LoadPreflightRequest() sets the flag to kNone in advance.
if (!async_ || new_request.GetSkipServiceWorker() ||
!SchemeRegistry::ShouldTreatURLSchemeAsAllowingServiceWorkers(
new_request.Url().Protocol()) ||
!loading_context_->GetResourceFetcher()->IsControlledByServiceWorker()) {
DispatchInitialRequest(new_request);
return;
}
if (CORS::IsCORSEnabledRequestMode(request.GetFetchRequestMode())) {
// Save the request to fallback_request_for_service_worker to use when the
// service worker doesn't handle (call respondWith()) a CORS enabled
// request.
fallback_request_for_service_worker_ = ResourceRequest(request);
// Skip the service worker for the fallback request.
fallback_request_for_service_worker_.SetSkipServiceWorker(true);
}
LoadRequest(new_request, resource_loader_options_);
}
void DocumentThreadableLoader::DispatchInitialRequest(
ResourceRequest& request) {
if (out_of_blink_cors_ || (!request.IsExternalRequest() && !cors_flag_)) {
LoadRequest(request, resource_loader_options_);
return;
}
DCHECK(CORS::IsCORSEnabledRequestMode(request.GetFetchRequestMode()) ||
request.IsExternalRequest());
MakeCrossOriginAccessRequest(request);
}
void DocumentThreadableLoader::PrepareCrossOriginRequest(
ResourceRequest& request) const {
if (GetSecurityOrigin())
request.SetHTTPOrigin(GetSecurityOrigin());
if (override_referrer_)
request.SetHTTPReferrer(referrer_after_redirect_);
}
void DocumentThreadableLoader::LoadPreflightRequest(
const ResourceRequest& actual_request,
const ResourceLoaderOptions& actual_options) {
std::unique_ptr<ResourceRequest> preflight_request =
CreateAccessControlPreflightRequest(actual_request, GetSecurityOrigin());
actual_request_ = actual_request;
actual_options_ = actual_options;
// Explicitly set |skip_service_worker| to true here. Although the page is
// not controlled by a SW at this point, a new SW may be controlling the
// page when this actual request gets sent later. We should not send the
// actual request to the SW. See https://crbug.com/604583.
actual_request_.SetSkipServiceWorker(true);
// Create a ResourceLoaderOptions for preflight.
ResourceLoaderOptions preflight_options = actual_options;
LoadRequest(*preflight_request, preflight_options);
}
void DocumentThreadableLoader::MakeCrossOriginAccessRequest(
const ResourceRequest& request) {
DCHECK(CORS::IsCORSEnabledRequestMode(request.GetFetchRequestMode()) ||
request.IsExternalRequest());
DCHECK(client_);
DCHECK(!GetResource());
// Cross-origin requests are only allowed certain registered schemes. We would
// catch this when checking response headers later, but there is no reason to
// send a request, preflighted or not, that's guaranteed to be denied.
if (!SchemeRegistry::ShouldTreatURLSchemeAsCORSEnabled(
request.Url().Protocol())) {
DispatchDidFailAccessControlCheck(
ResourceError::CancelledDueToAccessCheckError(
request.Url(), ResourceRequestBlockedReason::kOther,
String::Format(
"Cross origin requests are only supported for protocol "
"schemes: %s.",
SchemeRegistry::ListOfCORSEnabledURLSchemes().Ascii().data())));
return;
}
// Non-secure origins may not make "external requests":
// https://wicg.github.io/cors-rfc1918/#integration-fetch
String error_message;
if (!GetExecutionContext()->IsSecureContext(error_message) &&
request.IsExternalRequest()) {
DispatchDidFailAccessControlCheck(
ResourceError::CancelledDueToAccessCheckError(
request.Url(), ResourceRequestBlockedReason::kOrigin,
"Requests to internal network resources are not allowed "
"from non-secure contexts (see https://goo.gl/Y0ZkNV). "
"This is an experimental restriction which is part of "
"'https://mikewest.github.io/cors-rfc1918/'."));
return;
}
ResourceRequest cross_origin_request(request);
ResourceLoaderOptions cross_origin_options(resource_loader_options_);
cross_origin_request.RemoveUserAndPassFromURL();
// Enforce the CORS preflight for checking the Access-Control-Allow-External
// header. The CORS preflight cache doesn't help for this purpose.
if (request.IsExternalRequest()) {
LoadPreflightRequest(cross_origin_request, cross_origin_options);
return;
}
if (request.GetFetchRequestMode() !=
network::mojom::FetchRequestMode::kCORSWithForcedPreflight) {
if (request.CORSPreflightPolicy() ==
network::mojom::CORSPreflightPolicy::kPreventPreflight) {
PrepareCrossOriginRequest(cross_origin_request);
LoadRequest(cross_origin_request, cross_origin_options);
return;
}
DCHECK_EQ(request.CORSPreflightPolicy(),
network::mojom::CORSPreflightPolicy::kConsiderPreflight);
// We use ContainsOnlyCORSSafelistedOrForbiddenHeaders() here since
// |request| may have been modified in the process of loading (not from
// the user's input). For example, referrer. We need to accept them. For
// security, we must reject forbidden headers/methods at the point we
// accept user's input. Not here.
if (CORS::IsCORSSafelistedMethod(request.HttpMethod()) &&
CORS::ContainsOnlyCORSSafelistedOrForbiddenHeaders(
request.HttpHeaderFields())) {
PrepareCrossOriginRequest(cross_origin_request);
LoadRequest(cross_origin_request, cross_origin_options);
return;
}
}
// Now, we need to check that the request passes the CORS preflight either by
// issuing a CORS preflight or based on an entry in the CORS preflight cache.
bool should_ignore_preflight_cache = false;
// Prevent use of the CORS preflight cache when instructed by the DevTools
// not to use caches.
probe::shouldForceCORSPreflight(GetExecutionContext(),
&should_ignore_preflight_cache);
if (should_ignore_preflight_cache ||
!CORS::CheckIfRequestCanSkipPreflight(
GetSecurityOrigin()->ToString(), cross_origin_request.Url(),
cross_origin_request.GetFetchCredentialsMode(),
cross_origin_request.HttpMethod(),
cross_origin_request.HttpHeaderFields())) {
LoadPreflightRequest(cross_origin_request, cross_origin_options);
return;
}
// We don't want any requests that could involve a CORS preflight to get
// intercepted by a foreign SW, even if we have the result of the preflight
// cached already. See https://crbug.com/674370.
cross_origin_request.SetSkipServiceWorker(true);
PrepareCrossOriginRequest(cross_origin_request);
LoadRequest(cross_origin_request, cross_origin_options);
}
DocumentThreadableLoader::~DocumentThreadableLoader() {
CHECK(!client_);
DCHECK(!GetResource());
}
void DocumentThreadableLoader::OverrideTimeout(
unsigned long timeout_milliseconds) {
DCHECK(async_);
// |m_requestStartedSeconds| == 0.0 indicates loading is already finished and
// |m_timeoutTimer| is already stopped, and thus we do nothing for such cases.
// See https://crbug.com/551663 for details.
if (request_started_ <= TimeTicks())
return;
timeout_timer_.Stop();
// At the time of this method's implementation, it is only ever called by
// XMLHttpRequest, when the timeout attribute is set after sending the
// request.
//
// The XHR request says to resolve the time relative to when the request
// was initially sent, however other uses of this method may need to
// behave differently, in which case this should be re-arranged somehow.
if (timeout_milliseconds) {
TimeDelta elapsed_time = CurrentTimeTicks() - request_started_;
TimeDelta next_fire = TimeDelta::FromMilliseconds(timeout_milliseconds);
TimeDelta resolved_time = std::max(next_fire - elapsed_time, TimeDelta());
timeout_timer_.StartOneShot(resolved_time, FROM_HERE);
}
}
void DocumentThreadableLoader::Cancel() {
// Cancel can re-enter, and therefore |resource()| might be null here as a
// result.
if (!client_ || !GetResource()) {
Clear();
return;
}
DispatchDidFail(ResourceError::CancelledError(GetResource()->Url()));
}
void DocumentThreadableLoader::Detach() {
Resource* resource = GetResource();
if (!resource)
return;
client_ = new DetachedClient(this);
}
void DocumentThreadableLoader::SetDefersLoading(bool value) {
if (GetResource() && GetResource()->Loader())
GetResource()->Loader()->SetDefersLoading(value);
}
void DocumentThreadableLoader::Clear() {
client_ = nullptr;
timeout_timer_.Stop();
request_started_ = TimeTicks();
if (GetResource())
checker_.WillRemoveClient();
ClearResource();
}
// In this method, we can clear |request| to tell content::WebURLLoaderImpl of
// Chromium not to follow the redirect. This works only when this method is
// called by RawResource::willSendRequest(). If called by
// RawResource::didAddClient(), clearing |request| won't be propagated to
// content::WebURLLoaderImpl. So, this loader must also get detached from the
// resource by calling clearResource().
// TODO(toyoshim): Implement OOR-CORS mode specific redirect code.
bool DocumentThreadableLoader::RedirectReceived(
Resource* resource,
const ResourceRequest& new_request,
const ResourceResponse& redirect_response) {
DCHECK(client_);
DCHECK_EQ(resource, GetResource());
checker_.RedirectReceived();
const KURL& new_url = new_request.Url();
const KURL& original_url = redirect_response.Url();
if (!actual_request_.IsNull()) {
ReportResponseReceived(resource->Identifier(), redirect_response);
HandlePreflightFailure(
original_url, CORS::GetErrorString(
CORS::ErrorParameter::CreateForDisallowedRedirect()));
return false;
}
if (redirect_mode_ == network::mojom::FetchRedirectMode::kManual) {
// We use |redirect_mode_| to check the original redirect mode.
// |new_request| is a new request for redirect. So we don't set the
// redirect mode of it in WebURLLoaderImpl::Context::OnReceivedRedirect().
DCHECK(new_request.UseStreamOnResponse());
// There is no need to read the body of redirect response because there is
// no way to read the body of opaque-redirect filtered response's internal
// response.
// TODO(horo): If we support any API which expose the internal body, we will
// have to read the body. And also HTTPCache changes will be needed because
// it doesn't store the body of redirect responses.
ResponseReceived(resource, redirect_response,
std::make_unique<EmptyDataHandle>());
if (client_) {
DCHECK(actual_request_.IsNull());
NotifyFinished(resource);
}
return false;
}
if (redirect_mode_ == network::mojom::FetchRedirectMode::kError) {
ThreadableLoaderClient* client = client_;
Clear();
client->DidFailRedirectCheck();
return false;
}
// Allow same origin requests to continue after allowing clients to audit the
// redirect.
if (IsAllowedRedirect(new_request.GetFetchRequestMode(), new_url)) {
client_->DidReceiveRedirectTo(new_url);
if (client_->IsDocumentThreadableLoaderClient()) {
return static_cast<DocumentThreadableLoaderClient*>(client_)
->WillFollowRedirect(new_url, redirect_response);
}
return true;
}
if (cors_redirect_limit_ <= 0) {
ThreadableLoaderClient* client = client_;
Clear();
client->DidFailRedirectCheck();
return false;
}
--cors_redirect_limit_;
probe::didReceiveCORSRedirectResponse(
GetExecutionContext(), resource->Identifier(),
GetDocument() && GetDocument()->GetFrame()
? GetDocument()->GetFrame()->Loader().GetDocumentLoader()
: nullptr,
redirect_response, resource);
base::Optional<network::mojom::CORSError> redirect_error =
CORS::CheckRedirectLocation(new_url);
if (redirect_error) {
DispatchDidFailAccessControlCheck(
ResourceError::CancelledDueToAccessCheckError(
original_url, ResourceRequestBlockedReason::kOther,
CORS::GetErrorString(CORS::ErrorParameter::CreateForRedirectCheck(
*redirect_error, original_url, new_url))));
return false;
}
if (cors_flag_) {
// The redirect response must pass the access control check if the CORS
// flag is set.
base::Optional<network::mojom::CORSError> access_error = CORS::CheckAccess(
original_url, redirect_response.HttpStatusCode(),
redirect_response.HttpHeaderFields(),
new_request.GetFetchCredentialsMode(), *GetSecurityOrigin());
if (access_error) {
DispatchDidFailAccessControlCheck(
ResourceError::CancelledDueToAccessCheckError(
original_url, ResourceRequestBlockedReason::kOther,
CORS::GetErrorString(CORS::ErrorParameter::CreateForAccessCheck(
*access_error, original_url,
redirect_response.HttpStatusCode(),
redirect_response.HttpHeaderFields(), *GetSecurityOrigin(),
request_context_, new_url))));
return false;
}
}
client_->DidReceiveRedirectTo(new_url);
// FIXME: consider combining this with CORS redirect handling performed by
// CrossOriginAccessControl::handleRedirect().
if (GetResource())
checker_.WillRemoveClient();
ClearResource();
// If
// - CORS flag is set, and
// - the origin of the redirect target URL is not same origin with the origin
// of the current request's URL
// set the source origin to a unique opaque origin.
//
// See https://fetch.spec.whatwg.org/#http-redirect-fetch.
if (cors_flag_) {
scoped_refptr<const SecurityOrigin> original_origin =
SecurityOrigin::Create(original_url);
scoped_refptr<const SecurityOrigin> new_origin =
SecurityOrigin::Create(new_url);
if (!original_origin->IsSameSchemeHostPort(new_origin.get()))
security_origin_ = SecurityOrigin::CreateUniqueOpaque();
}
// Set |cors_flag_| so that further logic (corresponds to the main fetch in
// the spec) will be performed with CORS flag set.
// See https://fetch.spec.whatwg.org/#http-redirect-fetch.
cors_flag_ = true;
// Save the referrer to use when following the redirect.
override_referrer_ = true;
referrer_after_redirect_ =
Referrer(new_request.HttpReferrer(), new_request.GetReferrerPolicy());
ResourceRequest cross_origin_request(new_request);
// Remove any headers that may have been added by the network layer that cause
// access control to fail.
cross_origin_request.ClearHTTPReferrer();
cross_origin_request.ClearHTTPOrigin();
cross_origin_request.ClearHTTPUserAgent();
// Add any request headers which we previously saved from the
// original request.
for (const auto& header : request_headers_)
cross_origin_request.SetHTTPHeaderField(header.key, header.value);
MakeCrossOriginAccessRequest(cross_origin_request);
return false;
}
void DocumentThreadableLoader::RedirectBlocked() {
checker_.RedirectBlocked();
// Tells the client that a redirect was received but not followed (for an
// unknown reason).
ThreadableLoaderClient* client = client_;
Clear();
client->DidFailRedirectCheck();
}
void DocumentThreadableLoader::DataSent(
Resource* resource,
unsigned long long bytes_sent,
unsigned long long total_bytes_to_be_sent) {
DCHECK(client_);
DCHECK_EQ(resource, GetResource());
DCHECK(async_);
checker_.DataSent();
client_->DidSendData(bytes_sent, total_bytes_to_be_sent);
}
void DocumentThreadableLoader::DataDownloaded(Resource* resource,
int data_length) {
DCHECK(client_);
DCHECK_EQ(resource, GetResource());
DCHECK(actual_request_.IsNull());
checker_.DataDownloaded();
client_->DidDownloadData(data_length);
}
void DocumentThreadableLoader::DidReceiveResourceTiming(
Resource* resource,
const ResourceTimingInfo& info) {
DCHECK(client_);
DCHECK_EQ(resource, GetResource());
client_->DidReceiveResourceTiming(info);
}
void DocumentThreadableLoader::DidDownloadToBlob(
Resource* resource,
scoped_refptr<BlobDataHandle> blob) {
DCHECK(client_);
DCHECK_EQ(resource, GetResource());
checker_.DidDownloadToBlob();
client_->DidDownloadToBlob(std::move(blob));
}
void DocumentThreadableLoader::ResponseReceived(
Resource* resource,
const ResourceResponse& response,
std::unique_ptr<WebDataConsumerHandle> handle) {
DCHECK_EQ(resource, GetResource());
checker_.ResponseReceived();
if (handle)
is_using_data_consumer_handle_ = true;
HandleResponse(resource->Identifier(), fetch_request_mode_,
fetch_credentials_mode_, response, std::move(handle));
}
void DocumentThreadableLoader::HandlePreflightResponse(
const ResourceResponse& response) {
base::Optional<network::mojom::CORSError> cors_error =
CORS::CheckPreflightAccess(response.Url(), response.HttpStatusCode(),
response.HttpHeaderFields(),
actual_request_.GetFetchCredentialsMode(),
*GetSecurityOrigin());
if (cors_error) {
HandlePreflightFailure(
response.Url(),
CORS::GetErrorString(CORS::ErrorParameter::CreateForAccessCheck(
*cors_error, response.Url(), 0 /* do not provide the status_code */,
response.HttpHeaderFields(), *GetSecurityOrigin(),
request_context_)));
return;
}
base::Optional<network::mojom::CORSError> preflight_error =
CORS::CheckPreflight(response.HttpStatusCode());
if (preflight_error) {
HandlePreflightFailure(
response.Url(), CORS::GetErrorString(
CORS::ErrorParameter::CreateForPreflightStatusCheck(
response.HttpStatusCode())));
return;
}
if (actual_request_.IsExternalRequest()) {
base::Optional<network::mojom::CORSError> external_preflight_status =
CORS::CheckExternalPreflight(response.HttpHeaderFields());
if (external_preflight_status) {
HandlePreflightFailure(
response.Url(),
CORS::GetErrorString(
CORS::ErrorParameter::CreateForExternalPreflightCheck(
*external_preflight_status, response.HttpHeaderFields())));
return;
}
}
String access_control_error_description;
if (!CORS::EnsurePreflightResultAndCacheOnSuccess(
response.HttpHeaderFields(), GetSecurityOrigin()->ToString(),
actual_request_.Url(), actual_request_.HttpMethod(),
actual_request_.HttpHeaderFields(),
actual_request_.GetFetchCredentialsMode(),
&access_control_error_description)) {
HandlePreflightFailure(response.Url(), access_control_error_description);
}
}
void DocumentThreadableLoader::ReportResponseReceived(
unsigned long identifier,
const ResourceResponse& response) {
LocalFrame* frame = GetDocument() ? GetDocument()->GetFrame() : nullptr;
if (!frame)
return;
DocumentLoader* loader = frame->Loader().GetDocumentLoader();
probe::didReceiveResourceResponse(GetExecutionContext(), identifier, loader,
response, GetResource());
frame->Console().ReportResourceResponseReceived(loader, identifier, response);
}
void DocumentThreadableLoader::HandleResponse(
unsigned long identifier,
network::mojom::FetchRequestMode request_mode,
network::mojom::FetchCredentialsMode credentials_mode,
const ResourceResponse& response,
std::unique_ptr<WebDataConsumerHandle> handle) {
DCHECK(client_);
// TODO(toyoshim): Support OOR-CORS preflight and Service Worker case.
// Note that CORS-preflight is usually handled in the Network Service side,
// but still done in Blink side when it is needed on redirects.
// https://crbug.com/736308.
if (out_of_blink_cors_ && actual_request_.IsNull() &&
!response.WasFetchedViaServiceWorker()) {
client_->DidReceiveResponse(identifier, response, std::move(handle));
return;
}
// Code path for legacy Blink CORS.
if (!actual_request_.IsNull()) {
ReportResponseReceived(identifier, response);
HandlePreflightResponse(response);
return;
}
if (response.WasFetchedViaServiceWorker()) {
if (response.WasFallbackRequiredByServiceWorker()) {
// At this point we must have m_fallbackRequestForServiceWorker. (For
// SharedWorker the request won't be CORS or CORS-with-preflight,
// therefore fallback-to-network is handled in the browser process when
// the ServiceWorker does not call respondWith().)
DCHECK(!fallback_request_for_service_worker_.IsNull());
ReportResponseReceived(identifier, response);
LoadFallbackRequestForServiceWorker();
return;
}
// It's possible that we issue a fetch with request with non "no-cors"
// mode but get an opaque filtered response if a service worker is involved.
// We dispatch a CORS failure for the case.
// TODO(yhirano): This is probably not spec conformant. Fix it after
// https://github.com/w3c/preload/issues/100 is addressed.
if (request_mode != network::mojom::FetchRequestMode::kNoCORS &&
response.ResponseTypeViaServiceWorker() ==
network::mojom::FetchResponseType::kOpaque) {
DispatchDidFailAccessControlCheck(
ResourceError::CancelledDueToAccessCheckError(
response.Url(), ResourceRequestBlockedReason::kOther,
CORS::GetErrorString(
CORS::ErrorParameter::CreateForInvalidResponse(
response.Url(), *GetSecurityOrigin()))));
return;
}
fallback_request_for_service_worker_ = ResourceRequest();
client_->DidReceiveResponse(identifier, response, std::move(handle));
return;
}
// Even if the request met the conditions to get handled by a Service Worker
// in the constructor of this class (and therefore
// |m_fallbackRequestForServiceWorker| is set), the Service Worker may skip
// processing the request. Only if the request is same origin, the skipped
// response may come here (wasFetchedViaServiceWorker() returns false) since
// such a request doesn't have to go through the CORS algorithm by calling
// loadFallbackRequestForServiceWorker().
DCHECK(fallback_request_for_service_worker_.IsNull() ||
GetSecurityOrigin()->CanRequest(
fallback_request_for_service_worker_.Url()));
fallback_request_for_service_worker_ = ResourceRequest();
if (CORS::IsCORSEnabledRequestMode(request_mode) && cors_flag_) {
base::Optional<network::mojom::CORSError> access_error = CORS::CheckAccess(
response.Url(), response.HttpStatusCode(), response.HttpHeaderFields(),
credentials_mode, *GetSecurityOrigin());
if (access_error) {
ReportResponseReceived(identifier, response);
DispatchDidFailAccessControlCheck(
ResourceError::CancelledDueToAccessCheckError(
response.Url(), ResourceRequestBlockedReason::kOther,
CORS::GetErrorString(CORS::ErrorParameter::CreateForAccessCheck(
*access_error, response.Url(), response.HttpStatusCode(),
response.HttpHeaderFields(), *GetSecurityOrigin(),
request_context_))));
return;
}
}
client_->DidReceiveResponse(identifier, response, std::move(handle));
}
void DocumentThreadableLoader::SetSerializedCachedMetadata(Resource*,
const char* data,
size_t size) {
checker_.SetSerializedCachedMetadata();
if (!actual_request_.IsNull())
return;
client_->DidReceiveCachedMetadata(data, size);
}
void DocumentThreadableLoader::DataReceived(Resource* resource,
const char* data,
size_t data_length) {
DCHECK_EQ(resource, GetResource());
checker_.DataReceived();
if (is_using_data_consumer_handle_)
return;
// TODO(junov): Fix the ThreadableLoader ecosystem to use size_t. Until then,
// we use safeCast to trap potential overflows.
HandleReceivedData(data, SafeCast<unsigned>(data_length));
}
void DocumentThreadableLoader::HandleReceivedData(const char* data,
size_t data_length) {
DCHECK(client_);
// Preflight data should be invisible to clients.
if (!actual_request_.IsNull())
return;
DCHECK(fallback_request_for_service_worker_.IsNull());
client_->DidReceiveData(data, data_length);
}
void DocumentThreadableLoader::NotifyFinished(Resource* resource) {
DCHECK(client_);
DCHECK_EQ(resource, GetResource());
checker_.NotifyFinished(resource);
// Don't throw an exception for failed sync local file loads.
// TODO(japhet): This logic has been moved around but unchanged since 2007.
// Tested by fast/xmlhttprequest/xmlhttprequest-missing-file-exception.html
// Do we still need this?
bool is_sync_to_local_file = resource->Url().IsLocalFile() && !async_;
if (resource->ErrorOccurred() && !is_sync_to_local_file) {
DispatchDidFail(resource->GetResourceError());
} else {
HandleSuccessfulFinish(resource->Identifier());
}
}
void DocumentThreadableLoader::HandleSuccessfulFinish(
unsigned long identifier) {
DCHECK(fallback_request_for_service_worker_.IsNull());
if (!actual_request_.IsNull()) {
DCHECK(actual_request_.IsExternalRequest() || cors_flag_);
LoadActualRequest();
return;
}
ThreadableLoaderClient* client = client_;
// Protect the resource in |didFinishLoading| in order not to release the
// downloaded file.
Persistent<Resource> protect = GetResource();
Clear();
client->DidFinishLoading(identifier);
}
void DocumentThreadableLoader::DidTimeout(TimerBase* timer) {
DCHECK(async_);
DCHECK_EQ(timer, &timeout_timer_);
// clearResource() may be called in clear() and some other places. clear()
// calls stop() on |m_timeoutTimer|. In the other places, the resource is set
// again. If the creation fails, clear() is called. So, here, resource() is
// always non-nullptr.
DCHECK(GetResource());
// When |m_client| is set to nullptr only in clear() where |m_timeoutTimer|
// is stopped. So, |m_client| is always non-nullptr here.
DCHECK(client_);
DispatchDidFail(ResourceError::TimeoutError(GetResource()->Url()));
}
void DocumentThreadableLoader::LoadFallbackRequestForServiceWorker() {
if (GetResource())
checker_.WillRemoveClient();
ClearResource();
ResourceRequest fallback_request(fallback_request_for_service_worker_);
fallback_request_for_service_worker_ = ResourceRequest();
DispatchInitialRequest(fallback_request);
}
void DocumentThreadableLoader::LoadActualRequest() {
ResourceRequest actual_request = actual_request_;
ResourceLoaderOptions actual_options = actual_options_;
actual_request_ = ResourceRequest();
actual_options_ = ResourceLoaderOptions();
if (GetResource())
checker_.WillRemoveClient();
ClearResource();
PrepareCrossOriginRequest(actual_request);
LoadRequest(actual_request, actual_options);
}
void DocumentThreadableLoader::HandlePreflightFailure(
const KURL& url,
const String& error_description) {
// Prevent handleSuccessfulFinish() from bypassing access check.
actual_request_ = ResourceRequest();
DispatchDidFailAccessControlCheck(
ResourceError::CancelledDueToAccessCheckError(
url, ResourceRequestBlockedReason::kOther, error_description));
}
void DocumentThreadableLoader::DispatchDidFailAccessControlCheck(
const ResourceError& error) {
const String message = "Failed to load " + error.FailingURL() + ": " +
error.LocalizedDescription();
GetExecutionContext()->AddConsoleMessage(
ConsoleMessage::Create(kJSMessageSource, kErrorMessageLevel, message));
ThreadableLoaderClient* client = client_;
Clear();
client->DidFail(error);
}
void DocumentThreadableLoader::DispatchDidFail(const ResourceError& error) {
if (error.CORSErrorStatus()) {
DCHECK(out_of_blink_cors_);
// TODO(toyoshim): Should consider to pass correct arguments instead of
// KURL(), 0, and HTTPHeaderMap() to GetErrorString().
// We still need plumbing some more information.
GetExecutionContext()->AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kErrorMessageLevel,
"Failed to load " + error.FailingURL() + ": " +
CORS::GetErrorString(
CORS::ErrorParameter::Create(
*error.CORSErrorStatus(), KURL(error.FailingURL()), KURL(),
0, HTTPHeaderMap(), *GetSecurityOrigin(), request_context_))
.Utf8()
.data()));
}
ThreadableLoaderClient* client = client_;
Clear();
client->DidFail(error);
}
void DocumentThreadableLoader::LoadRequestAsync(
const ResourceRequest& request,
ResourceLoaderOptions resource_loader_options) {
if (!actual_request_.IsNull())
resource_loader_options.data_buffering_policy = kBufferData;
// The timer can be active if this is the actual request of a
// CORS-with-preflight request.
if (options_.timeout_milliseconds > 0 && !timeout_timer_.IsActive()) {
timeout_timer_.StartOneShot(options_.timeout_milliseconds / 1000.0,
FROM_HERE);
}
FetchParameters new_params(request, resource_loader_options);
if (request.GetFetchRequestMode() ==
network::mojom::FetchRequestMode::kNoCORS) {
new_params.SetOriginRestriction(FetchParameters::kNoOriginRestriction);
}
DCHECK(!GetResource());
ResourceFetcher* fetcher = loading_context_->GetResourceFetcher();
if (request.GetRequestContext() == WebURLRequest::kRequestContextVideo ||
request.GetRequestContext() == WebURLRequest::kRequestContextAudio) {
RawResource::FetchMedia(new_params, fetcher, this);
} else if (request.GetRequestContext() ==
WebURLRequest::kRequestContextManifest) {
RawResource::FetchManifest(new_params, fetcher, this);
} else {
RawResource::Fetch(new_params, fetcher, this);
}
checker_.WillAddClient();
}
void DocumentThreadableLoader::LoadRequestSync(
const ResourceRequest& request,
ResourceLoaderOptions resource_loader_options) {
FetchParameters fetch_params(request, resource_loader_options);
if (request.GetFetchRequestMode() ==
network::mojom::FetchRequestMode::kNoCORS) {
fetch_params.SetOriginRestriction(FetchParameters::kNoOriginRestriction);
}
if (options_.timeout_milliseconds > 0) {
fetch_params.MutableResourceRequest().SetTimeoutInterval(
base::TimeDelta::FromMilliseconds(options_.timeout_milliseconds));
}
checker_.WillAddClient();
RawResource::FetchSynchronously(fetch_params,
loading_context_->GetResourceFetcher(), this);
}
void DocumentThreadableLoader::LoadRequest(
ResourceRequest& request,
ResourceLoaderOptions resource_loader_options) {
resource_loader_options.cors_handling_by_resource_fetcher =
kDisableCORSHandlingByResourceFetcher;
bool allow_stored_credentials = false;
switch (request.GetFetchCredentialsMode()) {
case network::mojom::FetchCredentialsMode::kOmit:
break;
case network::mojom::FetchCredentialsMode::kSameOrigin:
// TODO(toyoshim): It's wrong to use |cors_flag| here. Fix it to use the
// response tainting.
//
// TODO(toyoshim): The credentials mode must work even when the "no-cors"
// mode is in use. See the following issues:
// - https://github.com/whatwg/fetch/issues/130
// - https://github.com/whatwg/fetch/issues/169
allow_stored_credentials = !cors_flag_;
break;
case network::mojom::FetchCredentialsMode::kInclude:
allow_stored_credentials = true;
break;
}
request.SetAllowStoredCredentials(allow_stored_credentials);
resource_loader_options.security_origin = security_origin_;
if (async_)
LoadRequestAsync(request, resource_loader_options);
else
LoadRequestSync(request, resource_loader_options);
}
bool DocumentThreadableLoader::IsAllowedRedirect(
network::mojom::FetchRequestMode fetch_request_mode,
const KURL& url) const {
if (fetch_request_mode == network::mojom::FetchRequestMode::kNoCORS)
return true;
return !cors_flag_ && GetSecurityOrigin()->CanRequest(url);
}
const SecurityOrigin* DocumentThreadableLoader::GetSecurityOrigin() const {
return security_origin_
? security_origin_.get()
: loading_context_->GetFetchContext()->GetSecurityOrigin();
}
Document* DocumentThreadableLoader::GetDocument() const {
ExecutionContext* context = GetExecutionContext();
if (context->IsDocument())
return ToDocument(context);
return nullptr;
}
ExecutionContext* DocumentThreadableLoader::GetExecutionContext() const {
DCHECK(loading_context_);
return loading_context_->GetExecutionContext();
}
void DocumentThreadableLoader::Trace(blink::Visitor* visitor) {
visitor->Trace(loading_context_);
ThreadableLoader::Trace(visitor);
RawResourceClient::Trace(visitor);
}
} // namespace blink