void DocumentThreadableLoader::handleSuccessfulFinish(unsigned long identifier, double finishTime) { ASSERT(m_fallbackRequestForServiceWorker.isNull()); if (!m_actualRequest.isNull()) { // FIXME: Timeout should be applied to whole fetch, not for each of // preflight and actual request. m_timeoutTimer.stop(); ASSERT(!m_sameOriginRequest); ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); loadActualRequest(); return; } ThreadableLoaderClient* client = m_client; m_client = 0; // Don't clear the resource as the client may need to access the downloaded // file which will be released when the resource is destoryed. if (m_async) { m_timeoutTimer.stop(); m_requestStartedSeconds = 0.0; } client->didFinishLoading(identifier, finishTime); // |this| may be dead here in async mode. }
void DocumentThreadableLoader::handleResponse(unsigned long identifier, const ResourceResponse& response, PassOwnPtr<WebDataConsumerHandle> handle) { ASSERT(m_client); if (!m_actualRequest.isNull()) { reportResponseReceived(identifier, response); handlePreflightResponse(response); // |this| may be dead here in async mode. return; } if (response.wasFetchedViaServiceWorker()) { // It's still possible to reach here with null m_fallbackRequestForServiceWorker // if the request was for main resource loading (i.e. for SharedWorker), for which // we create DocumentLoader before the controller ServiceWorker is set. ASSERT(!m_fallbackRequestForServiceWorker.isNull() || m_requestContext == WebURLRequest::RequestContextSharedWorker); 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().) ASSERT(!m_fallbackRequestForServiceWorker.isNull()); reportResponseReceived(identifier, response); loadFallbackRequestForServiceWorker(); // |this| may be dead here in async mode. return; } m_fallbackRequestForServiceWorker = ResourceRequest(); m_client->didReceiveResponse(identifier, response, 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. ASSERT(m_fallbackRequestForServiceWorker.isNull() || securityOrigin()->canRequest(m_fallbackRequestForServiceWorker.url())); m_fallbackRequestForServiceWorker = ResourceRequest(); if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { String accessControlErrorDescription; if (!passesAccessControlCheck(response, effectiveAllowCredentials(), securityOrigin(), accessControlErrorDescription, m_requestContext)) { reportResponseReceived(identifier, response); ThreadableLoaderClient* client = m_client; clear(); client->didFailAccessControlCheck(ResourceError(errorDomainBlinkInternal, 0, response.url().string(), accessControlErrorDescription)); // |this| may be dead here. return; } } m_client->didReceiveResponse(identifier, response, handle); }
void DocumentThreadableLoader::redirectBlocked() { // Tells the client that a redirect was received but not followed (for an unknown reason). ThreadableLoaderClient* client = m_client; clear(); client->didFailRedirectCheck(); // |this| may be dead here }
void DocumentThreadableLoader::makeCrossOriginAccessRequest(const ResourceRequest& request) { ASSERT(m_options.crossOriginRequestPolicy == UseAccessControl); ASSERT(m_client); ASSERT(!resource()); // 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())) { ThreadableLoaderClient* client = m_client; clear(); client->didFailAccessControlCheck(ResourceError(errorDomainBlinkInternal, 0, request.url().string(), "Cross origin requests are only supported for protocol schemes: " + SchemeRegistry::listOfCORSEnabledURLSchemes() + ".")); // |this| may be dead here in async mode. return; } // 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 ((m_options.preflightPolicy == ConsiderPreflight && FetchUtils::isSimpleOrForbiddenRequest(request.httpMethod(), request.httpHeaderFields())) || m_options.preflightPolicy == PreventPreflight) { ResourceRequest crossOriginRequest(request); ResourceLoaderOptions crossOriginOptions(m_resourceLoaderOptions); updateRequestForAccessControl(crossOriginRequest, securityOrigin(), effectiveAllowCredentials()); // 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. crossOriginRequest.setFetchCredentialsMode(effectiveAllowCredentials() == AllowStoredCredentials ? WebURLRequest::FetchCredentialsModeInclude : WebURLRequest::FetchCredentialsModeOmit); loadRequest(crossOriginRequest, crossOriginOptions); } else { m_crossOriginNonSimpleRequest = true; ResourceRequest crossOriginRequest(request); ResourceLoaderOptions crossOriginOptions(m_resourceLoaderOptions); // Do not set the Origin header for preflight requests. updateRequestForAccessControl(crossOriginRequest, 0, effectiveAllowCredentials()); // 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. crossOriginRequest.setFetchCredentialsMode(effectiveAllowCredentials() == AllowStoredCredentials ? WebURLRequest::FetchCredentialsModeInclude : WebURLRequest::FetchCredentialsModeOmit); m_actualRequest = crossOriginRequest; m_actualOptions = crossOriginOptions; bool shouldForcePreflight = InspectorInstrumentation::shouldForceCORSPreflight(m_document); bool canSkipPreflight = CrossOriginPreflightResultCache::shared().canSkipPreflight(securityOrigin()->toString(), m_actualRequest.url(), effectiveAllowCredentials(), m_actualRequest.httpMethod(), m_actualRequest.httpHeaderFields()); if (canSkipPreflight && !shouldForcePreflight) { loadActualRequest(); } else { ResourceRequest preflightRequest = createAccessControlPreflightRequest(m_actualRequest, securityOrigin()); // Create a ResourceLoaderOptions for preflight. ResourceLoaderOptions preflightOptions = m_actualOptions; preflightOptions.allowCredentials = DoNotAllowStoredCredentials; loadRequest(preflightRequest, preflightOptions); } } }
void DocumentThreadableLoader::handleError(const ResourceError& error) { // Copy the ResourceError instance to make it sure that the passed // ResourceError is alive during didFail() even when the Resource is // destructed during didFail(). ResourceError copiedError = error; ThreadableLoaderClient* client = m_client; clear(); client->didFail(copiedError); // |this| may be dead here. }
void DocumentThreadableLoader::handlePreflightFailure( const String& url, const String& errorDescription) { ResourceError error(errorDomainBlinkInternal, 0, url, errorDescription); // Prevent handleSuccessfulFinish() from bypassing access check. m_actualRequest = ResourceRequest(); ThreadableLoaderClient* client = m_client; clear(); client->didFailAccessControlCheck(error); }
void DocumentThreadableLoader::loadRequestAsync( const ResourceRequest& request, ResourceLoaderOptions resourceLoaderOptions) { if (!m_actualRequest.isNull()) resourceLoaderOptions.dataBufferingPolicy = BufferData; if (m_options.timeoutMilliseconds > 0) { m_timeoutTimer.startOneShot(m_options.timeoutMilliseconds / 1000.0, BLINK_FROM_HERE); } FetchRequest newRequest(request, m_options.initiator, resourceLoaderOptions); if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) newRequest.setOriginRestriction(FetchRequest::NoOriginRestriction); DCHECK(!resource()); if (request.requestContext() == WebURLRequest::RequestContextVideo || request.requestContext() == WebURLRequest::RequestContextAudio) setResource(RawResource::fetchMedia(newRequest, document().fetcher())); else if (request.requestContext() == WebURLRequest::RequestContextManifest) setResource(RawResource::fetchManifest(newRequest, document().fetcher())); else setResource(RawResource::fetch(newRequest, document().fetcher())); if (!resource()) { InspectorInstrumentation:: documentThreadableLoaderFailedToStartLoadingForClient(m_document, m_client); ThreadableLoaderClient* client = m_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(errorDomainBlinkInternal, 0, request.url().getString(), "Failed to start loading.")); return; } if (resource()->isLoading()) { unsigned long identifier = resource()->identifier(); InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient( m_document, identifier, m_client); } else { InspectorInstrumentation:: documentThreadableLoaderFailedToStartLoadingForClient(m_document, m_client); } }
void DocumentThreadableLoader::cancelWithError(const ResourceError& error) { // Cancel can re-enter and m_resource might be null here as a result. if (!m_client || !resource()) { clear(); return; } ResourceError errorForCallback = error; if (errorForCallback.isNull()) { // FIXME: This error is sent to the client in didFail(), so it should not be an internal one. Use FrameLoaderClient::cancelledError() instead. errorForCallback = ResourceError(errorDomainBlinkInternal, 0, resource()->url().string(), "Load cancelled"); errorForCallback.setIsCancellation(true); } ThreadableLoaderClient* client = m_client; clear(); client->didFail(errorForCallback); // |this| may be dead here in async mode. }
void DocumentThreadableLoader::handleSuccessfulFinish(unsigned long identifier, double finishTime) { DCHECK(m_fallbackRequestForServiceWorker.isNull()); if (!m_actualRequest.isNull()) { // FIXME: Timeout should be applied to whole fetch, not for each of // preflight and actual request. m_timeoutTimer.stop(); DCHECK(!m_sameOriginRequest); DCHECK_EQ(m_options.crossOriginRequestPolicy, UseAccessControl); loadActualRequest(); return; } ThreadableLoaderClient* client = m_client; // Protect the resource in |didFinishLoading| in order not to release the // downloaded file. Persistent<Resource> protect = resource(); clear(); client->didFinishLoading(identifier, finishTime); }
void DocumentThreadableLoader::loadRequestSync( const ResourceRequest& request, ResourceLoaderOptions resourceLoaderOptions) { FetchRequest fetchRequest(request, m_options.initiator, resourceLoaderOptions); if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) fetchRequest.setOriginRestriction(FetchRequest::NoOriginRestriction); Resource* resource = RawResource::fetchSynchronously(fetchRequest, document().fetcher()); ResourceResponse response = resource ? resource->response() : ResourceResponse(); unsigned long identifier = resource ? resource->identifier() : std::numeric_limits<unsigned long>::max(); ResourceError error = resource ? resource->resourceError() : ResourceError(); InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient( m_document, identifier, m_client); ThreadableLoaderClient* client = m_client; if (!resource) { m_client = nullptr; client->didFail(error); return; } const KURL& requestURL = 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() && !requestURL.isLocalFile() && response.httpStatusCode() <= 0) { m_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 (requestURL != response.url() && !isAllowedRedirect(response.url())) { m_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 (!m_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 (!m_client) return; handleSuccessfulFinish(identifier, 0.0); }
void DocumentThreadableLoader::handleError(const ResourceError& error) { ThreadableLoaderClient* client = m_client; clear(); client->didFail(error); }
DocumentThreadableLoader::DocumentThreadableLoader(Document& document, ThreadableLoaderClient* client, BlockingBehavior blockingBehavior, const ResourceRequest& request, const ThreadableLoaderOptions& options, const ResourceLoaderOptions& resourceLoaderOptions) : m_client(client) , m_document(&document) , m_options(options) , m_resourceLoaderOptions(resourceLoaderOptions) , m_forceDoNotAllowStoredCredentials(false) , m_securityOrigin(m_resourceLoaderOptions.securityOrigin) , m_sameOriginRequest(securityOrigin()->canRequestNoSuborigin(request.url())) , m_crossOriginNonSimpleRequest(false) , m_isUsingDataConsumerHandle(false) , m_async(blockingBehavior == LoadAsynchronously) , m_requestContext(request.requestContext()) , m_timeoutTimer(this, &DocumentThreadableLoader::didTimeout) , m_requestStartedSeconds(0.0) , m_corsRedirectLimit(kMaxCORSRedirects) , m_redirectMode(request.fetchRedirectMode()) { ASSERT(client); // Setting an outgoing referer is only supported in the async code path. ASSERT(m_async || request.httpReferrer().isEmpty()); if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == DenyCrossOriginRequests) { ThreadableLoaderClient* client = m_client; clear(); client->didFail(ResourceError(errorDomainBlinkInternal, 0, request.url().string(), "Cross origin requests are not supported.")); // |this| may be dead here. return; } m_requestStartedSeconds = monotonicallyIncreasingTime(); // Save any CORS simple headers on the request here. If this request redirects cross-origin, we cancel the old request // create a new one, and copy these headers. const HTTPHeaderMap& headerMap = request.httpHeaderFields(); for (const auto& header : headerMap) { if (FetchUtils::isSimpleHeader(header.key, header.value)) { m_simpleRequestHeaders.add(header.key, header.value); } else if (equalIgnoringCase(header.key, HTTPNames::Range) && m_options.crossOriginRequestPolicy == UseAccessControl && m_options.preflightPolicy == PreventPreflight) { // Allow an exception for the "range" header for when CORS callers request no preflight, this ensures cross-origin // redirects work correctly for crossOrigin enabled WebURLRequest::RequestContextVideo type requests. m_simpleRequestHeaders.add(header.key, header.value); } } // 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) { if (Page* page = document.page()) page->chromeClient().didObserveNonGetFetchFromScript(); } // If the fetch request will be handled by the ServiceWorker, the // FetchRequestMode of the request must be FetchRequestModeCORS or // FetchRequestModeCORSWithForcedPreflight. Otherwise the ServiceWorker can // return a opaque response which is from the other origin site and the // script in the page can read the content. // // We assume that ServiceWorker is skipped for sync requests and unsupported // protocol requests by content/ code. if (m_async && !request.skipServiceWorker() && SchemeRegistry::shouldTreatURLSchemeAsAllowingServiceWorkers(request.url().protocol()) && document.fetcher()->isControlledByServiceWorker()) { ResourceRequest newRequest(request); // FetchRequestMode should be set by the caller. But the expected value // of FetchRequestMode is not speced yet except for XHR. So we set here. // FIXME: When we support fetch API in document, this value should not // be overridden here. if (options.preflightPolicy == ForcePreflight) newRequest.setFetchRequestMode(WebURLRequest::FetchRequestModeCORSWithForcedPreflight); else newRequest.setFetchRequestMode(WebURLRequest::FetchRequestModeCORS); m_fallbackRequestForServiceWorker = ResourceRequest(request); m_fallbackRequestForServiceWorker.setSkipServiceWorker(true); loadRequest(newRequest, m_resourceLoaderOptions); return; } dispatchInitialRequest(request); // |this| may be dead here in async mode. }
void DocumentThreadableLoader::handleResponse( unsigned long identifier, const ResourceResponse& response, std::unique_ptr<WebDataConsumerHandle> handle) { DCHECK(m_client); if (!m_actualRequest.isNull()) { reportResponseReceived(identifier, response); handlePreflightResponse(response); return; } if (response.wasFetchedViaServiceWorker()) { if (response.wasFetchedViaForeignFetch()) UseCounter::count(m_document, UseCounter::ForeignFetchInterception); 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(!m_fallbackRequestForServiceWorker.isNull()); reportResponseReceived(identifier, response); loadFallbackRequestForServiceWorker(); return; } m_fallbackRequestForServiceWorker = ResourceRequest(); m_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( m_fallbackRequestForServiceWorker.isNull() || getSecurityOrigin()->canRequest(m_fallbackRequestForServiceWorker.url())); m_fallbackRequestForServiceWorker = ResourceRequest(); if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { String accessControlErrorDescription; if (!passesAccessControlCheck( response, effectiveAllowCredentials(), getSecurityOrigin(), accessControlErrorDescription, m_requestContext)) { reportResponseReceived(identifier, response); ThreadableLoaderClient* client = m_client; clear(); client->didFailAccessControlCheck( ResourceError(errorDomainBlinkInternal, 0, response.url().getString(), accessControlErrorDescription)); return; } } m_client->didReceiveResponse(identifier, response, std::move(handle)); }
// 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& redirectResponse) { DCHECK(m_client); DCHECK_EQ(resource, this->resource()); DCHECK(m_async); m_checker.redirectReceived(); if (!m_actualRequest.isNull()) { reportResponseReceived(resource->identifier(), redirectResponse); handlePreflightFailure(redirectResponse.url().getString(), "Response for preflight is invalid (redirect)"); return false; } if (m_redirectMode == WebURLRequest::FetchRedirectModeManual) { // 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, redirectResponse, WTF::makeUnique<EmptyDataHandle>()); if (m_client) { DCHECK(m_actualRequest.isNull()); notifyFinished(resource); } return false; } if (m_redirectMode == WebURLRequest::FetchRedirectModeError) { ThreadableLoaderClient* client = m_client; clear(); client->didFailRedirectCheck(); return false; } // Allow same origin requests to continue after allowing clients to audit the // redirect. if (isAllowedRedirect(request.url())) { m_client->didReceiveRedirectTo(request.url()); if (m_client->isDocumentThreadableLoaderClient()) { return static_cast<DocumentThreadableLoaderClient*>(m_client) ->willFollowRedirect(request, redirectResponse); } return true; } if (m_corsRedirectLimit <= 0) { ThreadableLoaderClient* client = m_client; clear(); client->didFailRedirectCheck(); return false; } --m_corsRedirectLimit; InspectorInstrumentation::didReceiveCORSRedirectResponse( document().frame(), resource->identifier(), document().frame()->loader().documentLoader(), redirectResponse, resource); bool allowRedirect = false; String accessControlErrorDescription; if (!CrossOriginAccessControl::isLegalRedirectLocation( request.url(), accessControlErrorDescription)) { accessControlErrorDescription = "Redirect from '" + redirectResponse.url().getString() + "' has been blocked by CORS policy: " + accessControlErrorDescription; } else if (!m_sameOriginRequest && !passesAccessControlCheck( redirectResponse, effectiveAllowCredentials(), getSecurityOrigin(), accessControlErrorDescription, m_requestContext)) { // The redirect response must pass the access control check if the original // request was not same-origin. accessControlErrorDescription = "Redirect from '" + redirectResponse.url().getString() + "' to '" + request.url().getString() + "' has been blocked by CORS policy: " + accessControlErrorDescription; } else { allowRedirect = true; } if (!allowRedirect) { ThreadableLoaderClient* client = m_client; clear(); client->didFailAccessControlCheck(ResourceError( errorDomainBlinkInternal, 0, redirectResponse.url().getString(), accessControlErrorDescription)); return false; } m_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 (!m_sameOriginRequest) { RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(redirectResponse.url()); RefPtr<SecurityOrigin> requestOrigin = SecurityOrigin::create(request.url()); if (!originalOrigin->isSameSchemeHostPort(requestOrigin.get())) m_securityOrigin = SecurityOrigin::createUnique(); } // Force any subsequent requests to use these checks. m_sameOriginRequest = 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 (m_resourceLoaderOptions.credentialsRequested == ClientDidNotRequestCredentials) m_forceDoNotAllowStoredCredentials = true; // Save the referrer to use when following the redirect. m_overrideReferrer = true; m_referrerAfterRedirect = Referrer(request.httpReferrer(), request.getReferrerPolicy()); ResourceRequest crossOriginRequest(request); // Remove any headers that may have been added by the network layer that cause // access control to fail. crossOriginRequest.clearHTTPReferrer(); crossOriginRequest.clearHTTPOrigin(); crossOriginRequest.clearHTTPUserAgent(); // Add any request headers which we previously saved from the // original request. for (const auto& header : m_requestHeaders) crossOriginRequest.setHTTPHeaderField(header.key, header.value); makeCrossOriginAccessRequest(crossOriginRequest); return false; }
void DocumentThreadableLoader::makeCrossOriginAccessRequest( const ResourceRequest& request) { DCHECK(m_options.crossOriginRequestPolicy == UseAccessControl || request.isExternalRequest()); DCHECK(m_client); DCHECK(!resource()); // 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())) { InspectorInstrumentation:: documentThreadableLoaderFailedToStartLoadingForClient(m_document, m_client); ThreadableLoaderClient* client = m_client; clear(); client->didFailAccessControlCheck(ResourceError( errorDomainBlinkInternal, 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 (!document().isSecureContext() && request.isExternalRequest()) { ThreadableLoaderClient* client = m_client; clear(); client->didFailAccessControlCheck( ResourceError(errorDomainBlinkInternal, 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 crossOriginRequest(request); ResourceLoaderOptions crossOriginOptions(m_resourceLoaderOptions); crossOriginRequest.removeCredentials(); crossOriginRequest.setAllowStoredCredentials(effectiveAllowCredentials() == AllowStoredCredentials); // 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. crossOriginRequest.setFetchCredentialsMode( effectiveAllowCredentials() == AllowStoredCredentials ? WebURLRequest::FetchCredentialsModeInclude : WebURLRequest::FetchCredentialsModeOmit); // 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() && ((m_options.preflightPolicy == ConsiderPreflight && FetchUtils::isSimpleOrForbiddenRequest(request.httpMethod(), request.httpHeaderFields())) || m_options.preflightPolicy == PreventPreflight)) { prepareCrossOriginRequest(crossOriginRequest); loadRequest(crossOriginRequest, crossOriginOptions); } else { bool shouldForcePreflight = request.isExternalRequest() || InspectorInstrumentation::shouldForceCORSPreflight(m_document); bool canSkipPreflight = CrossOriginPreflightResultCache::shared().canSkipPreflight( getSecurityOrigin()->toString(), crossOriginRequest.url(), effectiveAllowCredentials(), crossOriginRequest.httpMethod(), crossOriginRequest.httpHeaderFields()); if (canSkipPreflight && !shouldForcePreflight) { if (getSecurityOrigin()) crossOriginRequest.setHTTPOrigin(getSecurityOrigin()); if (m_overrideReferrer) crossOriginRequest.setHTTPReferrer(m_referrerAfterRedirect); prepareCrossOriginRequest(crossOriginRequest); loadRequest(crossOriginRequest, crossOriginOptions); } else { ResourceRequest preflightRequest = createAccessControlPreflightRequest( crossOriginRequest, getSecurityOrigin()); // Create a ResourceLoaderOptions for preflight. ResourceLoaderOptions preflightOptions = crossOriginOptions; preflightOptions.allowCredentials = DoNotAllowStoredCredentials; m_actualRequest = crossOriginRequest; m_actualOptions = crossOriginOptions; prepareCrossOriginRequest(crossOriginRequest); loadRequest(preflightRequest, preflightOptions); } } }
void DocumentThreadableLoader::start(const ResourceRequest& request) { // Setting an outgoing referer is only supported in the async code path. DCHECK(m_async || request.httpReferrer().isEmpty()); m_sameOriginRequest = getSecurityOrigin()->canRequestNoSuborigin(request.url()); m_requestContext = request.requestContext(); m_redirectMode = request.fetchRedirectMode(); if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == DenyCrossOriginRequests) { InspectorInstrumentation:: documentThreadableLoaderFailedToStartLoadingForClient(m_document, m_client); ThreadableLoaderClient* client = m_client; clear(); client->didFail(ResourceError(errorDomainBlinkInternal, 0, request.url().getString(), "Cross origin requests are not supported.")); return; } m_requestStartedSeconds = 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. m_requestHeaders = 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) { if (Page* page = m_document->page()) page->chromeClient().didObserveNonGetFetchFromScript(); } ResourceRequest newRequest(request); if (m_requestContext != WebURLRequest::RequestContextFetch) { // 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 (m_options.crossOriginRequestPolicy) { case DenyCrossOriginRequests: newRequest.setFetchRequestMode( WebURLRequest::FetchRequestModeSameOrigin); break; case UseAccessControl: if (m_options.preflightPolicy == ForcePreflight) { newRequest.setFetchRequestMode( WebURLRequest::FetchRequestModeCORSWithForcedPreflight); } else { newRequest.setFetchRequestMode(WebURLRequest::FetchRequestModeCORS); } break; case AllowCrossOriginRequests: SECURITY_CHECK(IsNoCORSAllowedContext(m_requestContext, request.skipServiceWorker())); newRequest.setFetchRequestMode(WebURLRequest::FetchRequestModeNoCORS); break; } if (m_resourceLoaderOptions.allowCredentials == AllowStoredCredentials) { newRequest.setFetchCredentialsMode( WebURLRequest::FetchCredentialsModeInclude); } else { newRequest.setFetchCredentialsMode( WebURLRequest::FetchCredentialsModeSameOrigin); } } // We assume that ServiceWorker is skipped for sync requests and unsupported // protocol requests by content/ code. if (m_async && request.skipServiceWorker() == WebURLRequest::SkipServiceWorker::None && SchemeRegistry::shouldTreatURLSchemeAsAllowingServiceWorkers( request.url().protocol()) && m_document->fetcher()->isControlledByServiceWorker()) { if (newRequest.fetchRequestMode() == WebURLRequest::FetchRequestModeCORS || newRequest.fetchRequestMode() == WebURLRequest::FetchRequestModeCORSWithForcedPreflight) { m_fallbackRequestForServiceWorker = 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. m_fallbackRequestForServiceWorker.setSkipServiceWorker( WebURLRequest::SkipServiceWorker::Controlling); } loadRequest(newRequest, m_resourceLoaderOptions); return; } dispatchInitialRequest(newRequest); }
void DocumentThreadableLoader::loadRequest(const ResourceRequest& request, ResourceLoaderOptions resourceLoaderOptions) { // Any credential should have been removed from the cross-site requests. const KURL& requestURL = request.url(); ASSERT(m_sameOriginRequest || requestURL.user().isEmpty()); ASSERT(m_sameOriginRequest || requestURL.pass().isEmpty()); // Update resourceLoaderOptions with enforced values. if (m_forceDoNotAllowStoredCredentials) resourceLoaderOptions.allowCredentials = DoNotAllowStoredCredentials; resourceLoaderOptions.securityOrigin = m_securityOrigin; if (m_async) { if (!m_actualRequest.isNull()) resourceLoaderOptions.dataBufferingPolicy = BufferData; if (m_options.timeoutMilliseconds > 0) m_timeoutTimer.startOneShot(m_options.timeoutMilliseconds / 1000.0, BLINK_FROM_HERE); FetchRequest newRequest(request, m_options.initiator, resourceLoaderOptions); if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) newRequest.setOriginRestriction(FetchRequest::NoOriginRestriction); ASSERT(!resource()); WeakPtr<DocumentThreadableLoader> self(m_weakFactory.createWeakPtr()); if (request.requestContext() == WebURLRequest::RequestContextVideo || request.requestContext() == WebURLRequest::RequestContextAudio) setResource(RawResource::fetchMedia(newRequest, document().fetcher())); else if (request.requestContext() == WebURLRequest::RequestContextManifest) setResource(RawResource::fetchManifest(newRequest, document().fetcher())); else setResource(RawResource::fetch(newRequest, document().fetcher())); // setResource() might call notifyFinished() synchronously, and thus // clear() might be called and |this| may be dead here. if (!self) return; if (!resource()) { InspectorInstrumentation::documentThreadableLoaderFailedToStartLoadingForClient(m_document, m_client); ThreadableLoaderClient* client = m_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(errorDomainBlinkInternal, 0, requestURL.getString(), "Failed to start loading.")); // |this| may be dead here. return; } if (resource()->loader()) { unsigned long identifier = resource()->identifier(); InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient(m_document, identifier, m_client); } else { InspectorInstrumentation::documentThreadableLoaderFailedToStartLoadingForClient(m_document, m_client); } return; } FetchRequest fetchRequest(request, m_options.initiator, resourceLoaderOptions); if (m_options.crossOriginRequestPolicy == AllowCrossOriginRequests) fetchRequest.setOriginRestriction(FetchRequest::NoOriginRestriction); Resource* resource = RawResource::fetchSynchronously(fetchRequest, document().fetcher()); ResourceResponse response = resource ? resource->response() : ResourceResponse(); unsigned long identifier = resource ? resource->identifier() : std::numeric_limits<unsigned long>::max(); ResourceError error = resource ? resource->resourceError() : ResourceError(); InspectorInstrumentation::documentThreadableLoaderStartedLoadingForClient(m_document, identifier, m_client); if (!resource) { m_client->didFail(error); return; } // 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() && !requestURL.isLocalFile() && response.httpStatusCode() <= 0) { m_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 (requestURL != response.url() && !isAllowedRedirect(response.url())) { m_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 (!m_client) return; 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 (!m_client) return; handleSuccessfulFinish(identifier, 0.0); }
// 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(). void DocumentThreadableLoader::redirectReceived(Resource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse) { ASSERT(m_client); ASSERT_UNUSED(resource, resource == this->resource()); ASSERT(m_async); if (!m_actualRequest.isNull()) { reportResponseReceived(resource->identifier(), redirectResponse); handlePreflightFailure(redirectResponse.url().string(), "Response for preflight is invalid (redirect)"); // |this| may be dead here. request = ResourceRequest(); return; } if (m_redirectMode == WebURLRequest::FetchRedirectModeManual) { // Keep |this| alive even if the client release a reference in // responseReceived(). RefPtr<DocumentThreadableLoader> protect(this); // 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(). ASSERT(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, redirectResponse, adoptPtr(new EmptyDataHandle())); if (m_client) { ASSERT(m_actualRequest.isNull()); notifyFinished(resource); } request = ResourceRequest(); return; } if (m_redirectMode == WebURLRequest::FetchRedirectModeError || !isAllowedByContentSecurityPolicy(request.url(), ContentSecurityPolicy::DidRedirect)) { ThreadableLoaderClient* client = m_client; clear(); client->didFailRedirectCheck(); // |this| may be dead here. request = ResourceRequest(); return; } // Allow same origin requests to continue after allowing clients to audit the redirect. if (isAllowedRedirect(request.url())) { if (m_client->isDocumentThreadableLoaderClient()) static_cast<DocumentThreadableLoaderClient*>(m_client)->willFollowRedirect(request, redirectResponse); return; } if (m_corsRedirectLimit <= 0) { ThreadableLoaderClient* client = m_client; clear(); client->didFailRedirectCheck(); // |this| may be dead here. } else if (m_options.crossOriginRequestPolicy == UseAccessControl) { --m_corsRedirectLimit; InspectorInstrumentation::didReceiveCORSRedirectResponse(document().frame(), resource->identifier(), document().frame()->loader().documentLoader(), redirectResponse, 0); bool allowRedirect = false; String accessControlErrorDescription; // Non-simple cross origin requests (both preflight and actual one) are // not allowed to follow redirect. if (m_crossOriginNonSimpleRequest) { accessControlErrorDescription = "The request was redirected to '"+ request.url().string() + "', which is disallowed for cross-origin requests that require preflight."; } else { // The redirect response must pass the access control check if the // original request was not same-origin. allowRedirect = CrossOriginAccessControl::isLegalRedirectLocation(request.url(), accessControlErrorDescription) && (m_sameOriginRequest || passesAccessControlCheck(redirectResponse, effectiveAllowCredentials(), securityOrigin(), accessControlErrorDescription, m_requestContext)); } if (allowRedirect) { // FIXME: consider combining this with CORS redirect handling performed by // CrossOriginAccessControl::handleRedirect(). clearResource(); RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(redirectResponse.url()); RefPtr<SecurityOrigin> requestOrigin = SecurityOrigin::create(request.url()); // 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 (!m_sameOriginRequest && !originalOrigin->isSameSchemeHostPort(requestOrigin.get())) m_securityOrigin = SecurityOrigin::createUnique(); // Force any subsequent requests to use these checks. m_sameOriginRequest = 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 (m_resourceLoaderOptions.credentialsRequested == ClientDidNotRequestCredentials) m_forceDoNotAllowStoredCredentials = true; // Remove any headers that may have been added by the network layer that cause access control to fail. request.clearHTTPReferrer(); request.clearHTTPOrigin(); request.clearHTTPUserAgent(); // Add any CORS simple request headers which we previously saved from the original request. for (const auto& header : m_simpleRequestHeaders) request.setHTTPHeaderField(header.key, header.value); makeCrossOriginAccessRequest(request); // |this| may be dead here. return; } ThreadableLoaderClient* client = m_client; clear(); client->didFailAccessControlCheck(ResourceError(errorDomainBlinkInternal, 0, redirectResponse.url().string(), accessControlErrorDescription)); // |this| may be dead here. } else { ThreadableLoaderClient* client = m_client; clear(); client->didFailRedirectCheck(); // |this| may be dead here. } request = ResourceRequest(); }