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(); }
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 ResourceHandle::willSendRequest(ResourceRequest& request, const ResourceResponse& redirectResponse) { const URL& url = request.url(); d->m_user = url.user(); d->m_pass = url.pass(); d->m_lastHTTPMethod = request.httpMethod(); request.removeCredentials(); if (!protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) { // The network layer might carry over some headers from the original request that // we want to strip here because the redirect is cross-origin. request.clearHTTPAuthorization(); request.clearHTTPOrigin(); } else { // Only consider applying authentication credentials if this is actually a redirect and the redirect // URL didn't include credentials of its own. if (d->m_user.isEmpty() && d->m_pass.isEmpty() && !redirectResponse.isNull()) { Credential credential = CredentialStorage::get(request.url()); if (!credential.isEmpty()) { d->m_initialCredential = credential; // FIXME: Support Digest authentication, and Proxy-Authorization. applyBasicAuthorizationHeader(request, d->m_initialCredential); } } } Ref<ResourceHandle> protect(*this); if (client()->usesAsyncCallbacks()) client()->willSendRequestAsync(this, request, redirectResponse); else { client()->willSendRequest(this, request, redirectResponse); // Client call may not preserve the session, especially if the request is sent over IPC. if (!request.isNull()) { request.setStorageSession(d->m_storageSession.get()); d->m_currentRequest = request; } } }
// 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::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(); }