void DocumentThreadableLoader::didReceiveResponse(SubresourceLoader* loader, const ResourceResponse& response) { ASSERT(m_client); ASSERT_UNUSED(loader, loader == m_loader); if (m_actualRequest) { if (!passesAccessControlCheck(response, m_options.allowCredentials, m_document->securityOrigin())) { preflightFailure(); return; } OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult(new CrossOriginPreflightResultCacheItem(m_options.allowCredentials)); if (!preflightResult->parse(response) || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod()) || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields())) { preflightFailure(); return; } CrossOriginPreflightResultCache::shared().appendEntry(m_document->securityOrigin()->toString(), m_actualRequest->url(), preflightResult.release()); } else { if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { if (!passesAccessControlCheck(response, m_options.allowCredentials, m_document->securityOrigin())) { m_client->didFail(ResourceError()); return; } } m_client->didReceiveResponse(response); } }
void DocumentThreadableLoader::handlePreflightResponse(const ResourceResponse& response) { String accessControlErrorDescription; if (!passesAccessControlCheck(response, effectiveAllowCredentials(), securityOrigin(), accessControlErrorDescription, m_requestContext)) { handlePreflightFailure(response.url().string(), "Response to preflight request doesn't pass access control check: " + accessControlErrorDescription); // |this| may be dead here in async mode. return; } if (!passesPreflightStatusCheck(response, accessControlErrorDescription)) { handlePreflightFailure(response.url().string(), accessControlErrorDescription); // |this| may be dead here in async mode. return; } OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult = adoptPtr(new CrossOriginPreflightResultCacheItem(effectiveAllowCredentials())); if (!preflightResult->parse(response, accessControlErrorDescription) || !preflightResult->allowsCrossOriginMethod(m_actualRequest.httpMethod(), accessControlErrorDescription) || !preflightResult->allowsCrossOriginHeaders(m_actualRequest.httpHeaderFields(), accessControlErrorDescription)) { handlePreflightFailure(response.url().string(), accessControlErrorDescription); // |this| may be dead here in async mode. return; } CrossOriginPreflightResultCache::shared().appendEntry(securityOrigin()->toString(), m_actualRequest.url(), preflightResult.release()); }
bool SubresourceLoader::checkRedirectionCrossOriginAccessControl(const ResourceRequest& previousRequest, const ResourceResponse& redirectResponse, ResourceRequest& newRequest) { ASSERT(options().mode != FetchOptions::Mode::SameOrigin); bool shouldCheckCrossOrigin = options().mode == FetchOptions::Mode::Cors && m_resource->isCrossOrigin(); if (!(m_origin && m_origin->canRequest(newRequest.url()))) m_resource->setCrossOrigin(); if (!shouldCheckCrossOrigin) return true; ASSERT(m_origin); String errorDescription; bool responsePassesCORS = m_origin->canRequest(previousRequest.url()) || passesAccessControlCheck(redirectResponse, options().allowCredentials(), *m_origin, errorDescription); if (!responsePassesCORS || !isValidCrossOriginRedirectionURL(newRequest.url())) { if (m_frame && m_frame->document()) { String errorMessage = "Cross-origin redirection denied by Cross-Origin Resource Sharing policy: " + (!responsePassesCORS ? errorDescription : "Redirected to either a non-HTTP URL or a URL that contains credentials."); m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, errorMessage); } return false; } // If the request URL origin is not the same as the original origin, the request origin should be set to a globally unique identifier. m_origin = SecurityOrigin::createUnique(); cleanRedirectedRequestForAccessControl(newRequest); updateRequestForAccessControl(newRequest, *m_origin, options().allowCredentials()); return true; }
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); }
bool SubresourceLoader::checkResponseCrossOriginAccessControl(const ResourceResponse& response, String& errorDescription) { if (!m_resource->isCrossOrigin() || options().mode != FetchOptions::Mode::Cors) return true; ASSERT(m_origin); return passesAccessControlCheck(response, options().allowCredentials, *m_origin, errorDescription); }
bool CachedImage::isOriginClean(SecurityOrigin* securityOrigin) { if (!image()->hasSingleSecurityOrigin()) return false; if (passesAccessControlCheck(*securityOrigin)) return true; return !securityOrigin->taintsCanvas(responseForSameOriginPolicyChecks().url()); }
void DocumentThreadableLoader::redirectReceived(CachedResource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse) { ASSERT(m_client); ASSERT_UNUSED(resource, resource == m_resource); Ref<DocumentThreadableLoader> protectedThis(*this); if (!isAllowedByContentSecurityPolicy(request.url(), !redirectResponse.isNull())) { m_client->didFailRedirectCheck(); request = ResourceRequest(); return; } // Allow same origin requests to continue after allowing clients to audit the redirect. if (isAllowedRedirect(request.url())) return; // When using access control, only simple cross origin requests are allowed to redirect. The new request URL must have a supported // scheme and not contain the userinfo production. In addition, the redirect response must pass the access control check if the // original request was not same-origin. if (m_options.crossOriginRequestPolicy == UseAccessControl) { bool allowRedirect = false; if (m_simpleRequest) { String accessControlErrorDescription; allowRedirect = isValidCrossOriginRedirectionURL(request.url()) && (m_sameOriginRequest || passesAccessControlCheck(redirectResponse, m_options.allowCredentials(), securityOrigin(), accessControlErrorDescription)); } if (allowRedirect) { if (m_resource) clearResource(); RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::createFromString(redirectResponse.url()); RefPtr<SecurityOrigin> requestOrigin = SecurityOrigin::createFromString(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_options.securityOrigin = SecurityOrigin::createUnique(); // Force any subsequent request 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_options.credentialRequest() == ClientDidNotRequestCredentials) m_options.setAllowCredentials(DoNotAllowStoredCredentials); cleanRedirectedRequestForAccessControl(request); makeCrossOriginAccessRequest(request); return; } } m_client->didFailRedirectCheck(); request = ResourceRequest(); }
bool ImageResource::isAccessAllowed(SecurityOrigin* securityOrigin) { if (response().wasFetchedViaServiceWorker()) return response().serviceWorkerResponseType() != WebServiceWorkerResponseTypeOpaque; if (!image()->currentFrameHasSingleSecurityOrigin()) return false; if (passesAccessControlCheck(securityOrigin)) return true; return !securityOrigin->taintsCanvas(response().url()); }
void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response) { ASSERT(m_client); String accessControlErrorDescription; if (m_actualRequest) { // Notifying the inspector here is necessary because a call to preflightFailure() might synchronously // cause the underlying ResourceLoader to be cancelled before it tells the inspector about the response. // In that case, if we don't tell the inspector about the response now, the resource type in the inspector // will default to "other" instead of something more descriptive. DocumentLoader* loader = m_document->frame()->loader().documentLoader(); InspectorInstrumentation::didReceiveResourceResponse(m_document->frame(), identifier, loader, response, resource() ? resource()->loader() : 0); if (!passesAccessControlCheck(response, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)) { preflightFailure(response.url().string(), accessControlErrorDescription); return; } if (!passesPreflightStatusCheck(response, accessControlErrorDescription)) { preflightFailure(response.url().string(), accessControlErrorDescription); return; } OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult = adoptPtr(new CrossOriginPreflightResultCacheItem(m_options.allowCredentials)); if (!preflightResult->parse(response, accessControlErrorDescription) || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod(), accessControlErrorDescription) || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields(), accessControlErrorDescription)) { preflightFailure(response.url().string(), accessControlErrorDescription); return; } CrossOriginPreflightResultCache::shared().appendEntry(securityOrigin()->toString(), m_actualRequest->url(), preflightResult.release()); } else { if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { if (!passesAccessControlCheck(response, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)) { m_client->didFailAccessControlCheck(ResourceError(errorDomainBlinkInternal, 0, response.url().string(), accessControlErrorDescription)); return; } } m_client->didReceiveResponse(identifier, response); } }
void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response) { ASSERT(m_client); #if ENABLE(INSPECTOR) if (m_preflightRequestIdentifier) { DocumentLoader* loader = m_document->frame()->loader()->documentLoader(); InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceResponse(m_document->frame(), m_preflightRequestIdentifier, response); InspectorInstrumentation::didReceiveResourceResponse(cookie, m_preflightRequestIdentifier, loader, response, 0); } #endif String accessControlErrorDescription; if (m_actualRequest) { if (!passesAccessControlCheck(response, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)) { preflightFailure(response.url(), accessControlErrorDescription); return; } OwnPtr<CrossOriginPreflightResultCacheItem> preflightResult = adoptPtr(new CrossOriginPreflightResultCacheItem(m_options.allowCredentials)); if (!preflightResult->parse(response, accessControlErrorDescription) || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod(), accessControlErrorDescription) || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields(), accessControlErrorDescription)) { preflightFailure(response.url(), accessControlErrorDescription); return; } CrossOriginPreflightResultCache::shared().appendEntry(securityOrigin()->toString(), m_actualRequest->url(), preflightResult.release()); } else { if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { if (!passesAccessControlCheck(response, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)) { m_client->didFail(ResourceError(errorDomainWebKitInternal, 0, response.url().string(), accessControlErrorDescription)); return; } } m_client->didReceiveResponse(identifier, response); } }
void DocumentThreadableLoader::redirectReceived(CachedResource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse) { ASSERT(m_client); ASSERT_UNUSED(resource, resource == m_resource); RefPtr<DocumentThreadableLoader> protect(this); // 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)->willSendRequest(request, redirectResponse); return; } // When using access control, only simple cross origin requests are allowed to redirect. The new request URL must have a supported // scheme and not contain the userinfo production. In addition, the redirect response must pass the access control check. if (m_options.crossOriginRequestPolicy == UseAccessControl) { bool allowRedirect = false; if (m_simpleRequest) { String accessControlErrorDescription; allowRedirect = SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(request.url().protocol()) && request.url().user().isEmpty() && request.url().pass().isEmpty() && passesAccessControlCheck(redirectResponse, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription); } if (allowRedirect) { if (m_resource) clearResource(); RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::createFromString(redirectResponse.url()); RefPtr<SecurityOrigin> requestOrigin = SecurityOrigin::createFromString(request.url()); // If the request URL origin is not same origin with the original URL origin, set source origin to a globally unique identifier. if (!originalOrigin->isSameSchemeHostPort(requestOrigin.get())) m_options.securityOrigin = SecurityOrigin::createUnique(); // Force any subsequent requests to use these checks. m_sameOriginRequest = false; // Remove any headers that may have been added by the network layer that cause access control to fail. request.clearHTTPContentType(); request.clearHTTPReferrer(); request.clearHTTPOrigin(); request.clearHTTPUserAgent(); request.clearHTTPAccept(); makeCrossOriginAccessRequest(request); return; } } m_client->didFailRedirectCheck(); request = ResourceRequest(); }
void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response) { ASSERT(m_client); String accessControlErrorDescription; if (m_actualRequest) { DocumentLoader* loader = m_document.frame()->loader().documentLoader(); InspectorInstrumentationCookie cookie = InspectorInstrumentation::willReceiveResourceResponse(m_document.frame()); InspectorInstrumentation::didReceiveResourceResponse(cookie, identifier, loader, response, 0); if (!passesAccessControlCheck(response, m_options.allowCredentials(), securityOrigin(), accessControlErrorDescription)) { preflightFailure(identifier, response.url(), accessControlErrorDescription); return; } StoredCredentials allowCredentials = m_options.allowCredentials(); auto preflightResult = std::make_unique<CrossOriginPreflightResultCacheItem>(allowCredentials); if (!preflightResult->parse(response, accessControlErrorDescription) || !preflightResult->allowsCrossOriginMethod(m_actualRequest->httpMethod(), accessControlErrorDescription) || !preflightResult->allowsCrossOriginHeaders(m_actualRequest->httpHeaderFields(), accessControlErrorDescription)) { preflightFailure(identifier, response.url(), accessControlErrorDescription); return; } CrossOriginPreflightResultCache::singleton().appendEntry(securityOrigin()->toString(), m_actualRequest->url(), WTFMove(preflightResult)); } else { if (!m_sameOriginRequest && m_options.crossOriginRequestPolicy == UseAccessControl) { if (!passesAccessControlCheck(response, m_options.allowCredentials(), securityOrigin(), accessControlErrorDescription)) { m_client->didFailAccessControlCheck(ResourceError(errorDomainWebKitInternal, 0, response.url(), accessControlErrorDescription)); return; } } m_client->didReceiveResponse(identifier, response); } }
bool CrossOriginAccessControl::handleRedirect(Resource* resource, SecurityOrigin* securityOrigin, ResourceRequest& request, const ResourceResponse& redirectResponse, ResourceLoaderOptions& options, String& errorMessage) { // http://www.w3.org/TR/cors/#redirect-steps terminology: const KURL& originalURL = redirectResponse.url(); const KURL& requestURL = request.url(); bool redirectCrossOrigin = !securityOrigin->canRequest(requestURL); // Same-origin request URLs that redirect are allowed without checking access. if (!securityOrigin->canRequest(originalURL)) { // Follow http://www.w3.org/TR/cors/#redirect-steps String errorDescription; // Steps 3 & 4 - check if scheme and other URL restrictions hold. bool allowRedirect = isLegalRedirectLocation(requestURL, errorDescription); if (allowRedirect) { // Step 5: perform resource sharing access check. StoredCredentials withCredentials = resource->resourceRequest().allowCookies() ? AllowStoredCredentials : DoNotAllowStoredCredentials; allowRedirect = passesAccessControlCheck(redirectResponse, withCredentials, securityOrigin, errorDescription); if (allowRedirect) { RefPtr<SecurityOrigin> originalOrigin = SecurityOrigin::create(originalURL); // Step 6: if the request URL origin is not same origin as the original URL's, // set the source origin to a globally unique identifier. if (!originalOrigin->canRequest(requestURL)) { options.securityOrigin = SecurityOrigin::createUnique(); securityOrigin = options.securityOrigin.get(); } } } if (!allowRedirect) { const String& originalOrigin = SecurityOrigin::create(originalURL)->toString(); errorMessage = "Redirect at origin '" + originalOrigin + "' has been blocked from loading by Cross-Origin Resource Sharing policy: " + errorDescription; return false; } } if (redirectCrossOrigin) { // If now to a different origin, update/set Origin:. request.clearHTTPOrigin(); request.setHTTPOrigin(securityOrigin->toAtomicString()); // 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 (options.credentialsRequested == ClientDidNotRequestCredentials) options.allowCredentials = DoNotAllowStoredCredentials; } return true; }
void DocumentThreadableLoader::handlePreflightResponse( const ResourceResponse& response) { String accessControlErrorDescription; if (!passesAccessControlCheck( response, effectiveAllowCredentials(), getSecurityOrigin(), accessControlErrorDescription, m_requestContext)) { handlePreflightFailure( response.url().getString(), "Response to preflight request doesn't pass access control check: " + accessControlErrorDescription); return; } if (!passesPreflightStatusCheck(response, accessControlErrorDescription)) { handlePreflightFailure(response.url().getString(), accessControlErrorDescription); return; } if (m_actualRequest.isExternalRequest() && !passesExternalPreflightCheck(response, accessControlErrorDescription)) { handlePreflightFailure(response.url().getString(), accessControlErrorDescription); return; } std::unique_ptr<CrossOriginPreflightResultCacheItem> preflightResult = WTF::wrapUnique( new CrossOriginPreflightResultCacheItem(effectiveAllowCredentials())); if (!preflightResult->parse(response, accessControlErrorDescription) || !preflightResult->allowsCrossOriginMethod( m_actualRequest.httpMethod(), accessControlErrorDescription) || !preflightResult->allowsCrossOriginHeaders( m_actualRequest.httpHeaderFields(), accessControlErrorDescription)) { handlePreflightFailure(response.url().getString(), accessControlErrorDescription); return; } CrossOriginPreflightResultCache::shared().appendEntry( getSecurityOrigin()->toString(), m_actualRequest.url(), std::move(preflightResult)); }
bool SubresourceLoader::checkRedirectionCrossOriginAccessControl(const ResourceRequest& previousRequest, const ResourceResponse& redirectResponse, ResourceRequest& newRequest, String& errorMessage) { bool crossOriginFlag = m_resource->isCrossOrigin(); bool isNextRequestCrossOrigin = m_origin && !m_origin->canRequest(newRequest.url()); if (isNextRequestCrossOrigin) m_resource->setCrossOrigin(); ASSERT(options().mode != FetchOptions::Mode::SameOrigin || !m_resource->isCrossOrigin()); if (options().mode != FetchOptions::Mode::Cors) return true; // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 8 & 9. if (m_resource->isCrossOrigin() && !isValidCrossOriginRedirectionURL(newRequest.url())) { errorMessage = ASCIILiteral("URL is either a non-HTTP URL or contains credentials."); return false; } ASSERT(m_origin); if (crossOriginFlag && !passesAccessControlCheck(redirectResponse, options().allowCredentials, *m_origin, errorMessage)) return false; bool redirectingToNewOrigin = false; if (m_resource->isCrossOrigin()) { if (!crossOriginFlag && isNextRequestCrossOrigin) redirectingToNewOrigin = true; else redirectingToNewOrigin = !SecurityOrigin::create(previousRequest.url())->canRequest(newRequest.url()); } // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 10. if (crossOriginFlag && redirectingToNewOrigin) m_origin = SecurityOrigin::createUnique(); if (redirectingToNewOrigin) { cleanRedirectedRequestForAccessControl(newRequest); updateRequestForAccessControl(newRequest, *m_origin, options().allowCredentials); } return true; }
// 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; }
bool Resource::isEligibleForIntegrityCheck(SecurityOrigin* securityOrigin) const { String ignoredErrorDescription; return securityOrigin->canRequest(resourceRequest().url()) || passesAccessControlCheck(securityOrigin, ignoredErrorDescription); }
bool Resource::passesAccessControlCheck(SecurityOrigin* securityOrigin) const { String ignoredErrorDescription; return passesAccessControlCheck(securityOrigin, ignoredErrorDescription); }
// 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(); }
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)); }
void DocumentThreadableLoader::redirectReceived(Resource* resource, ResourceRequest& request, const ResourceResponse& redirectResponse) { ASSERT(m_client); ASSERT_UNUSED(resource, resource == this->resource()); RefPtr<DocumentThreadableLoader> protect(this); if (!isAllowedByPolicy(request.url())) { m_client->didFailRedirectCheck(); 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)->willSendRequest(request, redirectResponse); return; } // When using access control, only simple cross origin requests are allowed to redirect. The new request URL must have a supported // scheme and not contain the userinfo production. In addition, the redirect response must pass the access control check if the // original request was not same-origin. if (m_options.crossOriginRequestPolicy == UseAccessControl) { InspectorInstrumentation::didReceiveCORSRedirectResponse(m_document->frame(), resource->identifier(), m_document->frame()->loader().documentLoader(), redirectResponse, 0); bool allowRedirect = false; String accessControlErrorDescription; if (m_simpleRequest) { allowRedirect = CrossOriginAccessControl::isLegalRedirectLocation(request.url(), accessControlErrorDescription) && (m_sameOriginRequest || passesAccessControlCheck(redirectResponse, m_options.allowCredentials, securityOrigin(), accessControlErrorDescription)); } else { accessControlErrorDescription = "The request was redirected to '"+ request.url().string() + "', which is disallowed for cross-origin requests that require preflight."; } 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_options.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_options.credentialsRequested == ClientDidNotRequestCredentials) m_options.allowCredentials = DoNotAllowStoredCredentials; // Remove any headers that may have been added by the network layer that cause access control to fail. request.clearHTTPContentType(); request.clearHTTPReferrer(); request.clearHTTPOrigin(); request.clearHTTPUserAgent(); request.clearHTTPAccept(); makeCrossOriginAccessRequest(request); return; } ResourceError error(errorDomainBlinkInternal, 0, redirectResponse.url().string(), accessControlErrorDescription); m_client->didFailAccessControlCheck(error); } else { m_client->didFailRedirectCheck(); } request = ResourceRequest(); }