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::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::handleError(const ResourceError& error) { ThreadableLoaderClient* client = m_client; clear(); client->didFail(error); }
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::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); }
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::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); }