| /* |
| * 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 "core/loader/DocumentThreadableLoader.h" |
| |
| #include <memory> |
| #include "core/dom/Document.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/frame/FrameConsole.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/LocalFrameClient.h" |
| #include "core/inspector/InspectorNetworkAgent.h" |
| #include "core/inspector/InspectorTraceEvents.h" |
| #include "core/loader/DocumentThreadableLoaderClient.h" |
| #include "core/loader/FrameLoader.h" |
| #include "core/loader/ThreadableLoaderClient.h" |
| #include "core/loader/ThreadableLoadingContext.h" |
| #include "core/loader/private/CrossOriginPreflightResultCache.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/Page.h" |
| #include "core/probe/CoreProbes.h" |
| #include "platform/SharedBuffer.h" |
| #include "platform/loader/fetch/CrossOriginAccessControl.h" |
| #include "platform/loader/fetch/FetchParameters.h" |
| #include "platform/loader/fetch/FetchUtils.h" |
| #include "platform/loader/fetch/Resource.h" |
| #include "platform/loader/fetch/ResourceFetcher.h" |
| #include "platform/loader/fetch/ResourceRequest.h" |
| #include "platform/weborigin/SchemeRegistry.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "platform/weborigin/SecurityPolicy.h" |
| #include "platform/wtf/Assertions.h" |
| #include "platform/wtf/PtrUtil.h" |
| #include "platform/wtf/WeakPtr.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebURLRequest.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| class EmptyDataHandle final : public WebDataConsumerHandle { |
| private: |
| class EmptyDataReader final : public WebDataConsumerHandle::Reader { |
| public: |
| explicit EmptyDataReader(WebDataConsumerHandle::Client* client) |
| : factory_(this) { |
| Platform::Current()->CurrentThread()->GetWebTaskRunner()->PostTask( |
| BLINK_FROM_HERE, |
| WTF::Bind(&EmptyDataReader::Notify, factory_.CreateWeakPtr(), |
| 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(); |
| } |
| WeakPtrFactory<EmptyDataReader> factory_; |
| }; |
| |
| std::unique_ptr<Reader> ObtainReader(Client* client) override { |
| return WTF::MakeUnique<EmptyDataReader>(client); |
| } |
| const char* DebugName() const override { return "EmptyDataHandle"; } |
| }; |
| |
| // No-CORS requests are allowed for all these contexts, and plugin contexts with |
| // private permission when we set ServiceWorkerMode to None in |
| // PepperURLLoaderHost. |
| bool IsNoCORSAllowedContext( |
| WebURLRequest::RequestContext context, |
| WebURLRequest::ServiceWorkerMode service_worker_mode) { |
| switch (context) { |
| case WebURLRequest::kRequestContextAudio: |
| case WebURLRequest::kRequestContextVideo: |
| case WebURLRequest::kRequestContextObject: |
| case WebURLRequest::kRequestContextFavicon: |
| case WebURLRequest::kRequestContextImage: |
| case WebURLRequest::kRequestContextScript: |
| case WebURLRequest::kRequestContextWorker: |
| case WebURLRequest::kRequestContextSharedWorker: |
| return true; |
| case WebURLRequest::kRequestContextPlugin: |
| return service_worker_mode == WebURLRequest::ServiceWorkerMode::kNone; |
| default: |
| return false; |
| } |
| } |
| |
| } // namespace |
| |
| // 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; |
| |
| void DocumentThreadableLoader::LoadResourceSynchronously( |
| Document& document, |
| const ResourceRequest& request, |
| ThreadableLoaderClient& client, |
| const ThreadableLoaderOptions& options, |
| const ResourceLoaderOptions& resource_loader_options) { |
| (new DocumentThreadableLoader(*ThreadableLoadingContext::Create(document), |
| &client, kLoadSynchronously, options, |
| resource_loader_options)) |
| ->Start(request); |
| } |
| |
| 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), |
| force_do_not_allow_stored_credentials_(false), |
| security_origin_(resource_loader_options_.security_origin), |
| same_origin_request_(false), |
| is_using_data_consumer_handle_(false), |
| async_(blocking_behavior == kLoadAsynchronously), |
| request_context_(WebURLRequest::kRequestContextUnspecified), |
| timeout_timer_(loading_context_->GetTaskRunner(TaskType::kNetworking), |
| this, |
| &DocumentThreadableLoader::DidTimeout), |
| request_started_seconds_(0.0), |
| cors_redirect_limit_(options_.cross_origin_request_policy == |
| kUseAccessControl |
| ? kMaxCORSRedirects |
| : 0), |
| redirect_mode_(WebURLRequest::kFetchRedirectModeFollow), |
| 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()); |
| |
| same_origin_request_ = |
| GetSecurityOrigin()->CanRequestNoSuborigin(request.Url()); |
| request_context_ = request.GetRequestContext(); |
| redirect_mode_ = request.GetFetchRedirectMode(); |
| |
| if (!same_origin_request_ && |
| options_.cross_origin_request_policy == kDenyCrossOriginRequests) { |
| probe::documentThreadableLoaderFailedToStartLoadingForClient(GetDocument(), |
| client_); |
| ThreadableLoaderClient* client = client_; |
| Clear(); |
| client->DidFail(ResourceError(kErrorDomainBlinkInternal, 0, |
| request.Url().GetString(), |
| "Cross origin requests are not supported.")); |
| return; |
| } |
| |
| request_started_seconds_ = MonotonicallyIncreasingTime(); |
| |
| // 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(); |
| |
| // DocumentThreadableLoader is used by all javascript initiated fetch, so we |
| // use this chance to record non-GET fetch script requests. However, this is |
| // based on the following assumptions, so please be careful when adding |
| // similar logic: |
| // - ThreadableLoader is used as backend for all javascript initiated network |
| // fetches. |
| // - Note that ThreadableLoader is also used for non-network fetch such as |
| // FileReaderLoader. However it emulates GET method so signal is not |
| // recorded here. |
| // - ThreadableLoader w/ non-GET request is only created from javascript |
| // initiated fetch. |
| // - Some non-script initiated fetches such as WorkerScriptLoader also use |
| // ThreadableLoader, but they are guaranteed to use GET method. |
| if (request.HttpMethod() != HTTPNames::GET && GetDocument()) { |
| if (Page* page = GetDocument()->GetPage()) |
| page->GetChromeClient().DidObserveNonGetFetchFromScript(); |
| } |
| |
| ResourceRequest new_request(request); |
| if (request_context_ != WebURLRequest::kRequestContextFetch) { |
| // When the request context is not "fetch", |crossOriginRequestPolicy| |
| // represents the fetch request mode, and |credentialsRequested| represents |
| // the fetch credentials mode. So we set those flags here so that we can see |
| // the correct request mode and credentials mode in the service worker's |
| // fetch event handler. |
| switch (options_.cross_origin_request_policy) { |
| case kDenyCrossOriginRequests: |
| new_request.SetFetchRequestMode( |
| WebURLRequest::kFetchRequestModeSameOrigin); |
| break; |
| case kUseAccessControl: |
| if (options_.preflight_policy == kForcePreflight) { |
| new_request.SetFetchRequestMode( |
| WebURLRequest::kFetchRequestModeCORSWithForcedPreflight); |
| } else { |
| new_request.SetFetchRequestMode(WebURLRequest::kFetchRequestModeCORS); |
| } |
| break; |
| case kAllowCrossOriginRequests: |
| SECURITY_CHECK(IsNoCORSAllowedContext(request_context_, |
| request.GetServiceWorkerMode())); |
| new_request.SetFetchRequestMode(WebURLRequest::kFetchRequestModeNoCORS); |
| break; |
| } |
| if (resource_loader_options_.allow_credentials == kAllowStoredCredentials) { |
| new_request.SetFetchCredentialsMode( |
| WebURLRequest::kFetchCredentialsModeInclude); |
| } else { |
| new_request.SetFetchCredentialsMode( |
| WebURLRequest::kFetchCredentialsModeSameOrigin); |
| } |
| } |
| |
| // We assume that ServiceWorker is skipped for sync requests and unsupported |
| // protocol requests by content/ code. |
| if (async_ && |
| request.GetServiceWorkerMode() == |
| WebURLRequest::ServiceWorkerMode::kAll && |
| SchemeRegistry::ShouldTreatURLSchemeAsAllowingServiceWorkers( |
| request.Url().Protocol()) && |
| loading_context_->GetResourceFetcher()->IsControlledByServiceWorker()) { |
| if (new_request.GetFetchRequestMode() == |
| WebURLRequest::kFetchRequestModeCORS || |
| new_request.GetFetchRequestMode() == |
| WebURLRequest::kFetchRequestModeCORSWithForcedPreflight) { |
| fallback_request_for_service_worker_ = ResourceRequest(request); |
| // m_fallbackRequestForServiceWorker is used when a regular controlling |
| // service worker doesn't handle a cross origin request. When this happens |
| // we still want to give foreign fetch a chance to handle the request, so |
| // only skip the controlling service worker for the fallback request. This |
| // is currently safe because of http://crbug.com/604084 the |
| // wasFallbackRequiredByServiceWorker flag is never set when foreign fetch |
| // handled a request. |
| fallback_request_for_service_worker_.SetServiceWorkerMode( |
| WebURLRequest::ServiceWorkerMode::kForeign); |
| } |
| LoadRequest(new_request, resource_loader_options_); |
| return; |
| } |
| |
| DispatchInitialRequest(new_request); |
| } |
| |
| void DocumentThreadableLoader::DispatchInitialRequest( |
| const ResourceRequest& request) { |
| if (!request.IsExternalRequest() && |
| (same_origin_request_ || |
| options_.cross_origin_request_policy == kAllowCrossOriginRequests)) { |
| LoadRequest(request, resource_loader_options_); |
| return; |
| } |
| |
| DCHECK(options_.cross_origin_request_policy == kUseAccessControl || |
| request.IsExternalRequest()); |
| |
| MakeCrossOriginAccessRequest(request); |
| } |
| |
| void DocumentThreadableLoader::PrepareCrossOriginRequest( |
| ResourceRequest& request) { |
| if (GetSecurityOrigin()) |
| request.SetHTTPOrigin(GetSecurityOrigin()); |
| if (override_referrer_) |
| request.SetHTTPReferrer(referrer_after_redirect_); |
| } |
| |
| void DocumentThreadableLoader::MakeCrossOriginAccessRequest( |
| const ResourceRequest& request) { |
| DCHECK(options_.cross_origin_request_policy == kUseAccessControl || |
| 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())) { |
| probe::documentThreadableLoaderFailedToStartLoadingForClient(GetDocument(), |
| client_); |
| DispatchDidFailAccessControlCheck(ResourceError( |
| kErrorDomainBlinkInternal, 0, request.Url().GetString(), |
| "Cross origin requests are only supported for protocol schemes: " + |
| SchemeRegistry::ListOfCORSEnabledURLSchemes() + ".")); |
| return; |
| } |
| |
| // Non-secure origins may not make "external requests": |
| // https://mikewest.github.io/cors-rfc1918/#integration-fetch |
| if (!loading_context_->IsSecureContext() && request.IsExternalRequest()) { |
| DispatchDidFailAccessControlCheck( |
| ResourceError(kErrorDomainBlinkInternal, 0, request.Url().GetString(), |
| "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(); |
| |
| cross_origin_request.SetAllowStoredCredentials(EffectiveAllowCredentials() == |
| kAllowStoredCredentials); |
| |
| // We update the credentials mode according to effectiveAllowCredentials() |
| // here for backward compatibility. But this is not correct. |
| // FIXME: We should set it in the caller of DocumentThreadableLoader. |
| cross_origin_request.SetFetchCredentialsMode( |
| EffectiveAllowCredentials() == kAllowStoredCredentials |
| ? WebURLRequest::kFetchCredentialsModeInclude |
| : WebURLRequest::kFetchCredentialsModeOmit); |
| |
| // We use isSimpleOrForbiddenRequest() 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 (!request.IsExternalRequest() && |
| ((options_.preflight_policy == kConsiderPreflight && |
| FetchUtils::IsSimpleOrForbiddenRequest(request.HttpMethod(), |
| request.HttpHeaderFields())) || |
| options_.preflight_policy == kPreventPreflight)) { |
| PrepareCrossOriginRequest(cross_origin_request); |
| LoadRequest(cross_origin_request, cross_origin_options); |
| } else { |
| // Explicitly set the ServiceWorkerMode to None here. Although the page is |
| // not controlled by a SW at this point, a new SW may be controlling the |
| // page when this request gets sent later. We should not send the actual |
| // request to the SW. https://crbug.com/604583 |
| // Similarly we don't want any requests that could involve a CORS preflight |
| // to get intercepted by a foreign fetch service worker, even if we have the |
| // result of the preflight cached already. https://crbug.com/674370 |
| cross_origin_request.SetServiceWorkerMode( |
| WebURLRequest::ServiceWorkerMode::kNone); |
| |
| bool should_force_preflight = request.IsExternalRequest(); |
| if (!should_force_preflight) |
| probe::shouldForceCORSPreflight(GetDocument(), &should_force_preflight); |
| // TODO(horo): Currently we don't support the CORS preflight cache on worker |
| // thread when off-main-thread-fetch is enabled. https://crbug.com/443374 |
| bool can_skip_preflight = |
| IsMainThread() && |
| CrossOriginPreflightResultCache::Shared().CanSkipPreflight( |
| GetSecurityOrigin()->ToString(), cross_origin_request.Url(), |
| EffectiveAllowCredentials(), cross_origin_request.HttpMethod(), |
| cross_origin_request.HttpHeaderFields()); |
| if (can_skip_preflight && !should_force_preflight) { |
| PrepareCrossOriginRequest(cross_origin_request); |
| LoadRequest(cross_origin_request, cross_origin_options); |
| } else { |
| ResourceRequest preflight_request = |
| CreateAccessControlPreflightRequest(cross_origin_request); |
| // TODO(tyoshino): Call prepareCrossOriginRequest(preflightRequest) to |
| // also set the referrer header. |
| if (GetSecurityOrigin()) |
| preflight_request.SetHTTPOrigin(GetSecurityOrigin()); |
| |
| // Create a ResourceLoaderOptions for preflight. |
| ResourceLoaderOptions preflight_options = cross_origin_options; |
| preflight_options.allow_credentials = kDoNotAllowStoredCredentials; |
| |
| actual_request_ = cross_origin_request; |
| actual_options_ = cross_origin_options; |
| |
| LoadRequest(preflight_request, preflight_options); |
| } |
| } |
| } |
| |
| DocumentThreadableLoader::~DocumentThreadableLoader() { |
| CHECK(!client_); |
| DCHECK(!resource_); |
| } |
| |
| 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_seconds_ <= 0.0) |
| 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) { |
| double elapsed_time = |
| MonotonicallyIncreasingTime() - request_started_seconds_; |
| double next_fire = timeout_milliseconds / 1000.0; |
| double resolved_time = std::max(next_fire - elapsed_time, 0.0); |
| timeout_timer_.StartOneShot(resolved_time, BLINK_FROM_HERE); |
| } |
| } |
| |
| void DocumentThreadableLoader::Cancel() { |
| // Cancel can re-enter, and therefore |resource()| might be null here as a |
| // result. |
| if (!client_ || !GetResource()) { |
| Clear(); |
| return; |
| } |
| |
| // FIXME: This error is sent to the client in didFail(), so it should not be |
| // an internal one. Use LocalFrameClient::cancelledError() instead. |
| ResourceError error(kErrorDomainBlinkInternal, 0, GetResource()->Url(), |
| "Load cancelled"); |
| error.SetIsCancellation(true); |
| |
| DispatchDidFail(error); |
| } |
| |
| void DocumentThreadableLoader::SetDefersLoading(bool value) { |
| if (GetResource()) |
| GetResource()->SetDefersLoading(value); |
| } |
| |
| void DocumentThreadableLoader::Clear() { |
| client_ = nullptr; |
| timeout_timer_.Stop(); |
| request_started_seconds_ = 0.0; |
| 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(). |
| bool DocumentThreadableLoader::RedirectReceived( |
| Resource* resource, |
| const ResourceRequest& request, |
| const ResourceResponse& redirect_response) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, this->GetResource()); |
| DCHECK(async_); |
| |
| checker_.RedirectReceived(); |
| |
| if (!actual_request_.IsNull()) { |
| ReportResponseReceived(resource->Identifier(), redirect_response); |
| |
| HandlePreflightFailure(redirect_response.Url().GetString(), |
| "Response for preflight is invalid (redirect)"); |
| |
| return false; |
| } |
| |
| if (redirect_mode_ == WebURLRequest::kFetchRedirectModeManual) { |
| // We use |m_redirectMode| to check the original redirect mode. |request| is |
| // a new request for redirect. So we don't set the redirect mode of it in |
| // WebURLLoaderImpl::Context::OnReceivedRedirect(). |
| DCHECK(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, |
| WTF::MakeUnique<EmptyDataHandle>()); |
| |
| if (client_) { |
| DCHECK(actual_request_.IsNull()); |
| NotifyFinished(resource); |
| } |
| |
| return false; |
| } |
| |
| if (redirect_mode_ == WebURLRequest::kFetchRedirectModeError) { |
| ThreadableLoaderClient* client = client_; |
| Clear(); |
| client->DidFailRedirectCheck(); |
| |
| return false; |
| } |
| |
| // Allow same origin requests to continue after allowing clients to audit the |
| // redirect. |
| if (IsAllowedRedirect(request.Url())) { |
| client_->DidReceiveRedirectTo(request.Url()); |
| if (client_->IsDocumentThreadableLoaderClient()) { |
| return static_cast<DocumentThreadableLoaderClient*>(client_) |
| ->WillFollowRedirect(request, redirect_response); |
| } |
| return true; |
| } |
| |
| if (cors_redirect_limit_ <= 0) { |
| ThreadableLoaderClient* client = client_; |
| Clear(); |
| client->DidFailRedirectCheck(); |
| return false; |
| } |
| |
| --cors_redirect_limit_; |
| |
| if (GetDocument() && GetDocument()->GetFrame()) { |
| probe::didReceiveCORSRedirectResponse( |
| GetDocument()->GetFrame(), resource->Identifier(), |
| GetDocument()->GetFrame()->Loader().GetDocumentLoader(), |
| redirect_response, resource); |
| } |
| |
| String access_control_error_description; |
| |
| CrossOriginAccessControl::RedirectStatus redirect_status = |
| CrossOriginAccessControl::CheckRedirectLocation(request.Url()); |
| bool allow_redirect = |
| redirect_status == CrossOriginAccessControl::kRedirectSuccess; |
| if (!allow_redirect) { |
| StringBuilder builder; |
| builder.Append("Redirect from '"); |
| builder.Append(redirect_response.Url().GetString()); |
| builder.Append("' has been blocked by CORS policy: "); |
| CrossOriginAccessControl::RedirectErrorString(builder, redirect_status, |
| request.Url()); |
| access_control_error_description = builder.ToString(); |
| } else if (!same_origin_request_) { |
| // The redirect response must pass the access control check if the original |
| // request was not same-origin. |
| CrossOriginAccessControl::AccessStatus cors_status = |
| CrossOriginAccessControl::CheckAccess(redirect_response, |
| EffectiveAllowCredentials(), |
| GetSecurityOrigin()); |
| allow_redirect = cors_status == CrossOriginAccessControl::kAccessAllowed; |
| if (!allow_redirect) { |
| StringBuilder builder; |
| builder.Append("Redirect from '"); |
| builder.Append(redirect_response.Url().GetString()); |
| builder.Append("' to '"); |
| builder.Append(request.Url().GetString()); |
| builder.Append("' has been blocked by CORS policy: "); |
| CrossOriginAccessControl::AccessControlErrorString( |
| builder, cors_status, redirect_response, GetSecurityOrigin(), |
| request_context_); |
| access_control_error_description = builder.ToString(); |
| } |
| } |
| |
| if (!allow_redirect) { |
| DispatchDidFailAccessControlCheck(ResourceError( |
| kErrorDomainBlinkInternal, 0, redirect_response.Url().GetString(), |
| access_control_error_description)); |
| return false; |
| } |
| |
| client_->DidReceiveRedirectTo(request.Url()); |
| |
| // FIXME: consider combining this with CORS redirect handling performed by |
| // CrossOriginAccessControl::handleRedirect(). |
| ClearResource(); |
| |
| // If the original request wasn't same-origin, then if the request URL origin |
| // is not same origin with the original URL origin, set the source origin to a |
| // globally unique identifier. (If the original request was same-origin, the |
| // origin of the new request should be the original URL origin.) |
| if (!same_origin_request_) { |
| RefPtr<SecurityOrigin> original_origin = |
| SecurityOrigin::Create(redirect_response.Url()); |
| RefPtr<SecurityOrigin> request_origin = |
| SecurityOrigin::Create(request.Url()); |
| if (!original_origin->IsSameSchemeHostPort(request_origin.Get())) |
| security_origin_ = SecurityOrigin::CreateUnique(); |
| } |
| // Force any subsequent requests to use these checks. |
| same_origin_request_ = false; |
| |
| // Since the request is no longer same-origin, if the user didn't request |
| // credentials in the first place, update our state so we neither request them |
| // nor expect they must be allowed. |
| if (resource_loader_options_.credentials_requested == |
| kClientDidNotRequestCredentials) |
| force_do_not_allow_stored_credentials_ = true; |
| |
| // Save the referrer to use when following the redirect. |
| override_referrer_ = true; |
| referrer_after_redirect_ = |
| Referrer(request.HttpReferrer(), request.GetReferrerPolicy()); |
| |
| ResourceRequest cross_origin_request(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, this->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, this->GetResource()); |
| DCHECK(actual_request_.IsNull()); |
| DCHECK(async_); |
| |
| checker_.DataDownloaded(); |
| client_->DidDownloadData(data_length); |
| } |
| |
| void DocumentThreadableLoader::DidReceiveResourceTiming( |
| Resource* resource, |
| const ResourceTimingInfo& info) { |
| DCHECK(client_); |
| DCHECK_EQ(resource, this->GetResource()); |
| DCHECK(async_); |
| |
| client_->DidReceiveResourceTiming(info); |
| } |
| |
| void DocumentThreadableLoader::ResponseReceived( |
| Resource* resource, |
| const ResourceResponse& response, |
| std::unique_ptr<WebDataConsumerHandle> handle) { |
| DCHECK_EQ(resource, this->GetResource()); |
| DCHECK(async_); |
| |
| checker_.ResponseReceived(); |
| |
| if (handle) |
| is_using_data_consumer_handle_ = true; |
| |
| HandleResponse(resource->Identifier(), response, std::move(handle)); |
| } |
| |
| void DocumentThreadableLoader::HandlePreflightResponse( |
| const ResourceResponse& response) { |
| String access_control_error_description; |
| |
| CrossOriginAccessControl::AccessStatus cors_status = |
| CrossOriginAccessControl::CheckAccess( |
| response, EffectiveAllowCredentials(), GetSecurityOrigin()); |
| if (cors_status != CrossOriginAccessControl::kAccessAllowed) { |
| StringBuilder builder; |
| builder.Append( |
| "Response to preflight request doesn't pass access " |
| "control check: "); |
| CrossOriginAccessControl::AccessControlErrorString( |
| builder, cors_status, response, GetSecurityOrigin(), request_context_); |
| HandlePreflightFailure(response.Url().GetString(), builder.ToString()); |
| return; |
| } |
| |
| CrossOriginAccessControl::PreflightStatus preflight_status = |
| CrossOriginAccessControl::CheckPreflight(response); |
| if (preflight_status != CrossOriginAccessControl::kPreflightSuccess) { |
| StringBuilder builder; |
| CrossOriginAccessControl::PreflightErrorString(builder, preflight_status, |
| response); |
| HandlePreflightFailure(response.Url().GetString(), builder.ToString()); |
| return; |
| } |
| |
| if (actual_request_.IsExternalRequest()) { |
| CrossOriginAccessControl::PreflightStatus external_preflight_status = |
| CrossOriginAccessControl::CheckExternalPreflight(response); |
| if (external_preflight_status != |
| CrossOriginAccessControl::kPreflightSuccess) { |
| StringBuilder builder; |
| CrossOriginAccessControl::PreflightErrorString( |
| builder, external_preflight_status, response); |
| HandlePreflightFailure(response.Url().GetString(), builder.ToString()); |
| return; |
| } |
| } |
| |
| std::unique_ptr<CrossOriginPreflightResultCacheItem> preflight_result = |
| WTF::WrapUnique( |
| new CrossOriginPreflightResultCacheItem(EffectiveAllowCredentials())); |
| if (!preflight_result->Parse(response, access_control_error_description) || |
| !preflight_result->AllowsCrossOriginMethod( |
| actual_request_.HttpMethod(), access_control_error_description) || |
| !preflight_result->AllowsCrossOriginHeaders( |
| actual_request_.HttpHeaderFields(), |
| access_control_error_description)) { |
| HandlePreflightFailure(response.Url().GetString(), |
| access_control_error_description); |
| return; |
| } |
| |
| if (IsMainThread()) { |
| // TODO(horo): Currently we don't support the CORS preflight cache on worker |
| // thread when off-main-thread-fetch is enabled. https://crbug.com/443374 |
| CrossOriginPreflightResultCache::Shared().AppendEntry( |
| GetSecurityOrigin()->ToString(), actual_request_.Url(), |
| std::move(preflight_result)); |
| } |
| } |
| |
| 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(frame, identifier, loader, response, |
| GetResource()); |
| frame->Console().ReportResourceResponseReceived(loader, identifier, response); |
| } |
| |
| void DocumentThreadableLoader::HandleResponse( |
| unsigned long identifier, |
| const ResourceResponse& response, |
| std::unique_ptr<WebDataConsumerHandle> handle) { |
| DCHECK(client_); |
| |
| if (!actual_request_.IsNull()) { |
| ReportResponseReceived(identifier, response); |
| HandlePreflightResponse(response); |
| return; |
| } |
| |
| if (response.WasFetchedViaServiceWorker()) { |
| if (response.WasFetchedViaForeignFetch()) |
| loading_context_->RecordUseCount(UseCounter::kForeignFetchInterception); |
| 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; |
| } |
| 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(). |
| // FIXME: We should use |m_sameOriginRequest| when we will support Suborigins |
| // (crbug.com/336894) for Service Worker. |
| DCHECK(fallback_request_for_service_worker_.IsNull() || |
| GetSecurityOrigin()->CanRequest( |
| fallback_request_for_service_worker_.Url())); |
| fallback_request_for_service_worker_ = ResourceRequest(); |
| |
| if (!same_origin_request_ && |
| options_.cross_origin_request_policy == kUseAccessControl) { |
| CrossOriginAccessControl::AccessStatus cors_status = |
| CrossOriginAccessControl::CheckAccess( |
| response, EffectiveAllowCredentials(), GetSecurityOrigin()); |
| if (cors_status != CrossOriginAccessControl::kAccessAllowed) { |
| ReportResponseReceived(identifier, response); |
| StringBuilder builder; |
| CrossOriginAccessControl::AccessControlErrorString( |
| builder, cors_status, response, GetSecurityOrigin(), |
| request_context_); |
| DispatchDidFailAccessControlCheck( |
| ResourceError(kErrorDomainBlinkInternal, 0, |
| response.Url().GetString(), builder.ToString())); |
| 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, this->GetResource()); |
| DCHECK(async_); |
| |
| 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, this->GetResource()); |
| DCHECK(async_); |
| |
| checker_.NotifyFinished(resource); |
| |
| if (resource->ErrorOccurred()) { |
| DispatchDidFail(resource->GetResourceError()); |
| } else { |
| HandleSuccessfulFinish(resource->Identifier(), resource->LoadFinishTime()); |
| } |
| } |
| |
| void DocumentThreadableLoader::HandleSuccessfulFinish(unsigned long identifier, |
| double finish_time) { |
| DCHECK(fallback_request_for_service_worker_.IsNull()); |
| |
| if (!actual_request_.IsNull()) { |
| DCHECK(!same_origin_request_); |
| DCHECK_EQ(options_.cross_origin_request_policy, kUseAccessControl); |
| 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, finish_time); |
| } |
| |
| 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_); |
| |
| // Using values from net/base/net_error_list.h ERR_TIMED_OUT, Same as existing |
| // FIXME above - this error should be coming from LocalFrameClient to be |
| // identifiable. |
| static const int kTimeoutError = -7; |
| ResourceError error("net", kTimeoutError, GetResource()->Url(), String()); |
| error.SetIsTimeout(true); |
| |
| DispatchDidFail(error); |
| } |
| |
| void DocumentThreadableLoader::LoadFallbackRequestForServiceWorker() { |
| 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(); |
| |
| ClearResource(); |
| |
| PrepareCrossOriginRequest(actual_request); |
| LoadRequest(actual_request, actual_options); |
| } |
| |
| void DocumentThreadableLoader::HandlePreflightFailure( |
| const String& url, |
| const String& error_description) { |
| ResourceError error(kErrorDomainBlinkInternal, 0, url, error_description); |
| |
| // Prevent handleSuccessfulFinish() from bypassing access check. |
| actual_request_ = ResourceRequest(); |
| |
| DispatchDidFailAccessControlCheck(error); |
| } |
| |
| void DocumentThreadableLoader::DispatchDidFailAccessControlCheck( |
| const ResourceError& error) { |
| ThreadableLoaderClient* client = client_; |
| Clear(); |
| client->DidFailAccessControlCheck(error); |
| } |
| |
| void DocumentThreadableLoader::DispatchDidFail(const ResourceError& error) { |
| 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, |
| BLINK_FROM_HERE); |
| } |
| |
| FetchParameters new_params(request, options_.initiator, |
| resource_loader_options); |
| if (options_.cross_origin_request_policy == kAllowCrossOriginRequests) |
| new_params.SetOriginRestriction(FetchParameters::kNoOriginRestriction); |
| DCHECK(!GetResource()); |
| |
| ResourceFetcher* fetcher = loading_context_->GetResourceFetcher(); |
| if (request.GetRequestContext() == WebURLRequest::kRequestContextVideo || |
| request.GetRequestContext() == WebURLRequest::kRequestContextAudio) |
| SetResource(RawResource::FetchMedia(new_params, fetcher)); |
| else if (request.GetRequestContext() == |
| WebURLRequest::kRequestContextManifest) |
| SetResource(RawResource::FetchManifest(new_params, fetcher)); |
| else |
| SetResource(RawResource::Fetch(new_params, fetcher)); |
| |
| if (!GetResource()) { |
| probe::documentThreadableLoaderFailedToStartLoadingForClient(GetDocument(), |
| client_); |
| ThreadableLoaderClient* client = client_; |
| Clear(); |
| // setResource() might call notifyFinished() and thus clear() |
| // synchronously, and in such cases ThreadableLoaderClient is already |
| // notified and |client| is null. |
| if (!client) |
| return; |
| client->DidFail(ResourceError(kErrorDomainBlinkInternal, 0, |
| request.Url().GetString(), |
| "Failed to start loading.")); |
| return; |
| } |
| |
| if (GetResource()->IsLoading()) { |
| unsigned long identifier = GetResource()->Identifier(); |
| probe::documentThreadableLoaderStartedLoadingForClient(GetDocument(), |
| identifier, client_); |
| } else { |
| probe::documentThreadableLoaderFailedToStartLoadingForClient(GetDocument(), |
| client_); |
| } |
| } |
| |
| void DocumentThreadableLoader::LoadRequestSync( |
| const ResourceRequest& request, |
| ResourceLoaderOptions resource_loader_options) { |
| FetchParameters fetch_params(request, options_.initiator, |
| resource_loader_options); |
| if (options_.cross_origin_request_policy == kAllowCrossOriginRequests) |
| fetch_params.SetOriginRestriction(FetchParameters::kNoOriginRestriction); |
| Resource* resource = RawResource::FetchSynchronously( |
| fetch_params, loading_context_->GetResourceFetcher()); |
| ResourceResponse response = |
| resource ? resource->GetResponse() : ResourceResponse(); |
| unsigned long identifier = resource |
| ? resource->Identifier() |
| : std::numeric_limits<unsigned long>::max(); |
| ResourceError error = |
| resource ? resource->GetResourceError() : ResourceError(); |
| |
| probe::documentThreadableLoaderStartedLoadingForClient(GetDocument(), |
| identifier, client_); |
| ThreadableLoaderClient* client = client_; |
| |
| if (!resource) { |
| client_ = nullptr; |
| client->DidFail(error); |
| return; |
| } |
| |
| const KURL& request_url = request.Url(); |
| |
| // No exception for file:/// resources, see <rdar://problem/4962298>. Also, if |
| // we have an HTTP response, then it wasn't a network error in fact. |
| if (!error.IsNull() && !request_url.IsLocalFile() && |
| response.HttpStatusCode() <= 0) { |
| client_ = nullptr; |
| client->DidFail(error); |
| return; |
| } |
| |
| // FIXME: A synchronous request does not tell us whether a redirect happened |
| // or not, so we guess by comparing the request and response URLs. This isn't |
| // a perfect test though, since a server can serve a redirect to the same URL |
| // that was requested. Also comparing the request and response URLs as strings |
| // will fail if the requestURL still has its credentials. |
| if (request_url != response.Url() && !IsAllowedRedirect(response.Url())) { |
| client_ = nullptr; |
| client->DidFailRedirectCheck(); |
| return; |
| } |
| |
| HandleResponse(identifier, response, nullptr); |
| |
| // handleResponse() may detect an error. In such a case (check |m_client| as |
| // it gets reset by clear() call), skip the rest. |
| // |
| // |this| is alive here since loadResourceSynchronously() keeps it alive until |
| // the end of the function. |
| if (!client_) |
| return; |
| |
| RefPtr<const SharedBuffer> data = resource->ResourceBuffer(); |
| if (data) |
| HandleReceivedData(data->Data(), data->size()); |
| |
| // The client may cancel this loader in handleReceivedData(). In such a case, |
| // skip the rest. |
| if (!client_) |
| return; |
| |
| HandleSuccessfulFinish(identifier, 0.0); |
| } |
| |
| void DocumentThreadableLoader::LoadRequest( |
| const ResourceRequest& request, |
| ResourceLoaderOptions resource_loader_options) { |
| // Any credential should have been removed from the cross-site requests. |
| const KURL& request_url = request.Url(); |
| DCHECK(same_origin_request_ || request_url.User().IsEmpty()); |
| DCHECK(same_origin_request_ || request_url.Pass().IsEmpty()); |
| |
| // Update resourceLoaderOptions with enforced values. |
| if (force_do_not_allow_stored_credentials_) |
| resource_loader_options.allow_credentials = kDoNotAllowStoredCredentials; |
| resource_loader_options.security_origin = security_origin_; |
| if (async_) |
| LoadRequestAsync(request, resource_loader_options); |
| else |
| LoadRequestSync(request, resource_loader_options); |
| } |
| |
| bool DocumentThreadableLoader::IsAllowedRedirect(const KURL& url) const { |
| if (options_.cross_origin_request_policy == kAllowCrossOriginRequests) |
| return true; |
| |
| return same_origin_request_ && GetSecurityOrigin()->CanRequest(url); |
| } |
| |
| StoredCredentials DocumentThreadableLoader::EffectiveAllowCredentials() const { |
| if (force_do_not_allow_stored_credentials_) |
| return kDoNotAllowStoredCredentials; |
| return resource_loader_options_.allow_credentials; |
| } |
| |
| const SecurityOrigin* DocumentThreadableLoader::GetSecurityOrigin() const { |
| return security_origin_ ? security_origin_.Get() |
| : loading_context_->GetSecurityOrigin(); |
| } |
| |
| Document* DocumentThreadableLoader::GetDocument() const { |
| DCHECK(loading_context_); |
| return loading_context_->GetLoadingDocument(); |
| } |
| |
| DEFINE_TRACE(DocumentThreadableLoader) { |
| visitor->Trace(resource_); |
| visitor->Trace(loading_context_); |
| ThreadableLoader::Trace(visitor); |
| RawResourceClient::Trace(visitor); |
| } |
| |
| } // namespace blink |