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.
}
예제 #6
0
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);
}
예제 #7
0
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.
}
예제 #9
0
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);
}
예제 #10
0
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);
}
예제 #11
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.
}
예제 #13
0
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));
}
예제 #14
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().
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;
}
예제 #15
0
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);
    }
  }
}
예제 #16
0
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();
}