// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "modules/fetch/Request.h" #include "bindings/core/v8/Dictionary.h" #include "core/dom/Document.h" #include "core/dom/ExecutionContext.h" #include "core/fetch/FetchUtils.h" #include "core/fetch/ResourceLoaderOptions.h" #include "core/loader/ThreadableLoader.h" #include "modules/fetch/BodyStreamBuffer.h" #include "modules/fetch/DataConsumerHandleUtil.h" #include "modules/fetch/FetchBlobDataConsumerHandle.h" #include "modules/fetch/FetchManager.h" #include "modules/fetch/RequestInit.h" #include "platform/HTTPNames.h" #include "platform/network/HTTPParsers.h" #include "platform/network/ResourceRequest.h" #include "platform/weborigin/Referrer.h" #include "public/platform/WebURLRequest.h" #include "public/platform/modules/serviceworker/WebServiceWorkerRequest.h" namespace blink { FetchRequestData* createCopyOfFetchRequestDataForFetch(ScriptState* scriptState, const FetchRequestData* original) { FetchRequestData* request = FetchRequestData::create(); request->setURL(original->url()); request->setMethod(original->method()); request->setHeaderList(original->headerList()->clone()); request->setUnsafeRequestFlag(true); // FIXME: Set client. DOMWrapperWorld& world = scriptState->world(); if (world.isIsolatedWorld()) request->setOrigin(world.isolatedWorldSecurityOrigin()); else request->setOrigin(scriptState->executionContext()->securityOrigin()); // FIXME: Set ForceOriginHeaderFlag. request->setSameOriginDataURLFlag(true); request->setReferrer(original->referrer()); request->setMode(original->mode()); request->setCredentials(original->credentials()); request->setRedirect(original->redirect()); request->setIntegrity(original->integrity()); // FIXME: Set cache mode. // TODO(yhirano): Set redirect mode. return request; } Request* Request::createRequestWithRequestOrString(ScriptState* scriptState, Request* inputRequest, const String& inputString, RequestInit& init, ExceptionState& exceptionState) { // - "If |input| is a Request object and it is disturbed, throw a // TypeError." if (inputRequest && inputRequest->bodyUsed()) { exceptionState.throwTypeError("Cannot construct a Request with a Request object that has already been used."); return nullptr; } // - "Let |temporaryBody| be |input|'s request's body if |input| is a // Request object, and null otherwise." BodyStreamBuffer* temporaryBody = inputRequest ? inputRequest->bodyBuffer() : nullptr; // "Let |request| be |input|'s request, if |input| is a Request object, // and a new request otherwise." RefPtr origin = scriptState->executionContext()->securityOrigin(); // TODO(yhirano): Implement the following steps: // - "Let |window| be client." // - "If |request|'s window is an environment settings object and its // origin is same origin with entry settings object's origin, set // |window| to |request|'s window." // - "If |init|'s window member is present and it is not null, throw a // TypeError." // - "If |init|'s window member is present, set |window| to no-window." // // "Set |request| to a new request whose url is |request|'s current url, // method is |request|'s method, header list is a copy of |request|'s // header list, unsafe-request flag is set, client is entry settings object, // window is |window|, origin is "client", omit-Origin-header flag is // |request|'s omit-Origin-header flag, same-origin data-URL flag is set, // referrer is |request|'s referrer, referrer policy is |request|'s // referrer policy, destination is the empty string, mode is |request|'s // mode, credentials mode is |request|'s credentials mode, cache mode is // |request|'s cache mode, redirect mode is |request|'s redirect mode, and // integrity metadata is |request|'s integrity metadata." FetchRequestData* request = createCopyOfFetchRequestDataForFetch(scriptState, inputRequest ? inputRequest->request() : FetchRequestData::create()); // We don't use fallback values. We set these flags directly in below. // - "Let |fallbackMode| be null." // - "Let |fallbackCredentials| be null." // - "Let |baseURL| be entry settings object's API base URL." // "If |input| is a string, run these substeps:" if (!inputRequest) { // "Let |parsedURL| be the result of parsing |input| with |baseURL|." KURL parsedURL = scriptState->executionContext()->completeURL(inputString); // "If |parsedURL| is failure, throw a TypeError." if (!parsedURL.isValid()) { exceptionState.throwTypeError("Failed to parse URL from " + inputString); return nullptr; } // "If |parsedURL| includes credentials, throw a TypeError." if (!parsedURL.user().isEmpty() || !parsedURL.pass().isEmpty()) { exceptionState.throwTypeError("Request cannot be constructed from a URL that includes credentials: " + inputString); return nullptr; } // "Set |request|'s url to |parsedURL| and replace |request|'s url list // single URL with a copy of |parsedURL|." request->setURL(parsedURL); // We don't use fallback values. We set these flags directly in below. // - "Set |fallbackMode| to "cors"." // - "Set |fallbackCredentials| to "omit"." } // "If any of |init|'s members are present, run these substeps:" if (init.areAnyMembersSet) { // "If |request|'s |mode| is "navigate", throw a TypeError." if (request->mode() == WebURLRequest::FetchRequestModeNavigate) { exceptionState.throwTypeError("Cannot construct a Request with a Request whose mode is 'navigate' and a non-empty RequestInit."); return nullptr; } // TODO(yhirano): Implement the following substep: // "Unset |request|'s omit-Origin-header flag." // The substep "Set |request|'s referrer to "client"." is performed by // the code below as follows: // - |init.referrer.referrer| gets initialized by the RequestInit // constructor to "about:client" when any of |options|'s members are // present. // - The code below does the equivalent as the step specified in the // spec by processing the "about:client". // The substep "Set |request|'s referrer policy to the empty string." // is also performed by the code below similarly. } // The following if-clause performs the following two steps: // - "If |init|'s referrer member is present, run these substeps:" // - TODO(yhirano): Implement the following step: // "If |init|'s referrerPolicy member is present, set |request|'s // referrer policy to it." // // The condition "if any of |init|'s members are present" // (areAnyMembersSet) is used for the if-clause instead of conditions // indicating presence of each member as specified in the spec. This is to // perform the substeps in the previous step together here. if (init.areAnyMembersSet) { // Nothing to do for the step "Let |referrer| be |init|'s referrer // member." if (init.referrer.referrer.isEmpty()) { // "If |referrer| is the empty string, set |request|'s referrer to // "no-referrer" and terminate these substeps." request->setReferrerString(FetchRequestData::noReferrerString()); } else { // "Let |parsedReferrer| be the result of parsing |referrer| with // |baseURL|." KURL parsedReferrer = scriptState->executionContext()->completeURL(init.referrer.referrer); if (!parsedReferrer.isValid()) { // "If |parsedReferrer| is failure, throw a TypeError." exceptionState.throwTypeError("Referrer '" + init.referrer.referrer + "' is not a valid URL."); return nullptr; } if (parsedReferrer.protocolIsAbout() && parsedReferrer.host().isEmpty() && parsedReferrer.path() == "client") { // "If |parsedReferrer|'s non-relative flag is set, scheme is // "about", and path contains a single string "client", set // request's referrer to "client" and terminate these // substeps." request->setReferrerString(FetchRequestData::clientReferrerString()); } else if (!origin->isSameSchemeHostPortAndSuborigin(SecurityOrigin::create(parsedReferrer).get())) { // "If |parsedReferrer|'s origin is not same origin with // |origin|, throw a TypeError." exceptionState.throwTypeError("The origin of '" + init.referrer.referrer + "' should be same as '" + origin->toString() + "'"); return nullptr; } else { // "Set |request|'s referrer to |parsedReferrer|." request->setReferrerString(AtomicString(parsedReferrer.string())); } } request->setReferrerPolicy(init.referrer.referrerPolicy); } // The following code performs the following steps: // - "Let |mode| be |init|'s mode member if it is present, and // |fallbackMode| otherwise." // - "If |mode| is "navigate", throw a TypeError." // - "If |mode| is non-null, set |request|'s mode to |mode|." if (init.mode == "navigate") { exceptionState.throwTypeError("Cannot construct a Request with a RequestInit whose mode member is set as 'navigate'."); return nullptr; } if (init.mode == "same-origin") { request->setMode(WebURLRequest::FetchRequestModeSameOrigin); } else if (init.mode == "no-cors") { request->setMode(WebURLRequest::FetchRequestModeNoCORS); } else if (init.mode == "cors") { request->setMode(WebURLRequest::FetchRequestModeCORS); } else { // |inputRequest| is directly checked here instead of setting and // checking |fallbackMode| as specified in the spec. if (!inputRequest) request->setMode(WebURLRequest::FetchRequestModeCORS); } // "Let |credentials| be |init|'s credentials member if it is present, and // |fallbackCredentials| otherwise." // "If |credentials| is non-null, set |request|'s credentials mode to // |credentials|." if (init.credentials == "omit") { request->setCredentials(WebURLRequest::FetchCredentialsModeOmit); } else if (init.credentials == "same-origin") { request->setCredentials(WebURLRequest::FetchCredentialsModeSameOrigin); } else if (init.credentials == "include") { request->setCredentials(WebURLRequest::FetchCredentialsModeInclude); } else { if (!inputRequest) request->setCredentials(WebURLRequest::FetchCredentialsModeOmit); } // TODO(yhirano): Implement the following step: // "If |init|'s cache member is present, set |request|'s cache mode to // it." // "If |init|'s redirect member is present, set |request|'s redirect mode // to it." if (init.redirect == "follow") { request->setRedirect(WebURLRequest::FetchRedirectModeFollow); } else if (init.redirect == "error") { request->setRedirect(WebURLRequest::FetchRedirectModeError); } else if (init.redirect == "manual") { request->setRedirect(WebURLRequest::FetchRedirectModeManual); } // "If |init|'s integrity member is present, set |request|'s // integrity metadata to it." if (!init.integrity.isNull()) request->setIntegrity(init.integrity); // "If |init|'s method member is present, let |method| be it and run these // substeps:" if (!init.method.isNull()) { // "If |method| is not a method or method is a forbidden method, throw // a TypeError." if (!isValidHTTPToken(init.method)) { exceptionState.throwTypeError("'" + init.method + "' is not a valid HTTP method."); return nullptr; } if (FetchUtils::isForbiddenMethod(init.method)) { exceptionState.throwTypeError("'" + init.method + "' HTTP method is unsupported."); return nullptr; } // "Normalize |method|." // "Set |request|'s method to |method|." request->setMethod(FetchUtils::normalizeMethod(AtomicString(init.method))); } // "Let |r| be a new Request object associated with |request| and a new // Headers object whose guard is "request"." Request* r = Request::create(scriptState->executionContext(), request); // Perform the following steps: // - "Let |headers| be a copy of |r|'s Headers object." // - "If |init|'s headers member is present, set |headers| to |init|'s // headers member." // // We don't create a copy of r's Headers object when init's headers member // is present. Headers* headers = nullptr; if (!init.headers && init.headersDictionary.isUndefinedOrNull()) { headers = r->headers()->clone(); } // "Empty |r|'s request's header list." r->m_request->headerList()->clearList(); // "If |r|'s request's mode is "no-cors", run these substeps: if (r->request()->mode() == WebURLRequest::FetchRequestModeNoCORS) { // "If |r|'s request's method is not a simple method, throw a // TypeError." if (!FetchUtils::isSimpleMethod(r->request()->method())) { exceptionState.throwTypeError("'" + r->request()->method() + "' is unsupported in no-cors mode."); return nullptr; } // "If |request|'s integrity metadata is not the empty string, throw a // TypeError." if (!request->integrity().isEmpty()) { exceptionState.throwTypeError("The integrity attribute is unsupported in no-cors mode."); return nullptr; } // "Set |r|'s Headers object's guard to "request-no-cors"." r->headers()->setGuard(Headers::RequestNoCORSGuard); } // "Fill |r|'s Headers object with |headers|. Rethrow any exceptions." if (init.headers) { ASSERT(init.headersDictionary.isUndefinedOrNull()); r->headers()->fillWith(init.headers.get(), exceptionState); } else if (!init.headersDictionary.isUndefinedOrNull()) { r->headers()->fillWith(init.headersDictionary, exceptionState); } else { ASSERT(headers); r->headers()->fillWith(headers, exceptionState); } if (exceptionState.hadException()) return nullptr; // "If either |init|'s body member is present or |temporaryBody| is // non-null, and |request|'s method is `GET` or `HEAD`, throw a TypeError. if (init.body || temporaryBody) { if (request->method() == HTTPNames::GET || request->method() == HTTPNames::HEAD) { exceptionState.throwTypeError("Request with GET/HEAD method cannot have body."); return nullptr; } } // "If |init|'s body member is present, run these substeps:" if (init.body) { // Perform the following steps: // - "Let |stream| and |Content-Type| be the result of extracting // |init|'s body member." // - "Set |temporaryBody| to |stream|. // - "If |Content-Type| is non-null and |r|'s request's header list // contains no header named `Content-Type`, append // `Content-Type`/|Content-Type| to |r|'s Headers object. Rethrow any // exception." temporaryBody = new BodyStreamBuffer(init.body.release()); if (!init.contentType.isEmpty() && !r->headers()->has(HTTPNames::Content_Type, exceptionState)) { r->headers()->append(HTTPNames::Content_Type, init.contentType, exceptionState); } if (exceptionState.hadException()) return nullptr; } // "Set |r|'s request's body to |temporaryBody|. if (temporaryBody) r->m_request->setBuffer(temporaryBody); // https://w3c.github.io/webappsec-credential-management/#monkey-patching-fetch-3 // "If |init|'s body member is a 'Credential' object:" if (init.isCredentialRequest) { // "1. If |r|'s url is not the same as |r|'s client’s origin, throw a TypeError." if (!origin->canRequest(r->url())) { exceptionState.throwTypeError("Credentials may only be submitted to same-origin endpoints."); return nullptr; } // "2. Set |r|'s redirect mode to "error"." r->m_request->setRedirect(WebURLRequest::FetchRedirectModeError); // "3. Set |r|'s skip-service-worker flag." // TODO(mkwst): Set this flag. // "4. Set |r|'s opaque flag." r->setOpaque(); } // "Set |r|'s MIME type to the result of extracting a MIME type from |r|'s // request's header list." r->m_request->setMIMEType(r->m_request->headerList()->extractMIMEType()); // "If |input| is a Request object and |input|'s request's body is // non-null, run these substeps:" if (inputRequest && inputRequest->bodyBuffer()) { // "Set |input|'s body to an empty byte stream." inputRequest->m_request->setBuffer(new BodyStreamBuffer(createFetchDataConsumerHandleFromWebHandle(createDoneDataConsumerHandle()))); // "Set |input|'s disturbed flag." inputRequest->bodyBuffer()->stream()->setIsDisturbed(); } // "Return |r|." return r; } Request* Request::create(ScriptState* scriptState, const RequestInfo& input, const Dictionary& init, ExceptionState& exceptionState) { ASSERT(!input.isNull()); if (input.isUSVString()) return create(scriptState, input.getAsUSVString(), init, exceptionState); return create(scriptState, input.getAsRequest(), init, exceptionState); } Request* Request::create(ScriptState* scriptState, const String& input, ExceptionState& exceptionState) { return create(scriptState, input, Dictionary(), exceptionState); } Request* Request::create(ScriptState* scriptState, const String& input, const Dictionary& init, ExceptionState& exceptionState) { RequestInit requestInit(scriptState->executionContext(), init, exceptionState); return createRequestWithRequestOrString(scriptState, nullptr, input, requestInit, exceptionState); } Request* Request::create(ScriptState* scriptState, Request* input, ExceptionState& exceptionState) { return create(scriptState, input, Dictionary(), exceptionState); } Request* Request::create(ScriptState* scriptState, Request* input, const Dictionary& init, ExceptionState& exceptionState) { RequestInit requestInit(scriptState->executionContext(), init, exceptionState); return createRequestWithRequestOrString(scriptState, input, String(), requestInit, exceptionState); } Request* Request::create(ExecutionContext* context, FetchRequestData* request) { return new Request(context, request); } Request::Request(ExecutionContext* context, FetchRequestData* request) : Body(context) , m_request(request) , m_headers(Headers::create(m_request->headerList())) { m_headers->setGuard(Headers::RequestGuard); } Request::Request(ExecutionContext* context, FetchRequestData* request, Headers* headers) : Body(context) , m_request(request) , m_headers(headers) {} Request* Request::create(ExecutionContext* context, const WebServiceWorkerRequest& webRequest) { return new Request(context, webRequest); } Request::Request(ExecutionContext* context, const WebServiceWorkerRequest& webRequest) : Body(context) , m_request(FetchRequestData::create(context, webRequest)) , m_headers(Headers::create(m_request->headerList())) { m_headers->setGuard(Headers::RequestGuard); } String Request::method() const { // "The method attribute's getter must return request's method." return m_request->method(); } KURL Request::url() const { // The url attribute's getter must return request's url, serialized with the exclude fragment flag set. if (!m_request->url().hasFragmentIdentifier()) return m_request->url(); KURL url(m_request->url()); url.removeFragmentIdentifier(); return url; } String Request::context() const { // "The context attribute's getter must return request's context" switch (m_request->context()) { case WebURLRequest::RequestContextUnspecified: return ""; case WebURLRequest::RequestContextAudio: return "audio"; case WebURLRequest::RequestContextBeacon: return "beacon"; case WebURLRequest::RequestContextCSPReport: return "cspreport"; case WebURLRequest::RequestContextDownload: return "download"; case WebURLRequest::RequestContextEmbed: return "embed"; case WebURLRequest::RequestContextEventSource: return "eventsource"; case WebURLRequest::RequestContextFavicon: return "favicon"; case WebURLRequest::RequestContextFetch: return "fetch"; case WebURLRequest::RequestContextFont: return "font"; case WebURLRequest::RequestContextForm: return "form"; case WebURLRequest::RequestContextFrame: return "frame"; case WebURLRequest::RequestContextHyperlink: return "hyperlink"; case WebURLRequest::RequestContextIframe: return "iframe"; case WebURLRequest::RequestContextImage: return "image"; case WebURLRequest::RequestContextImageSet: return "imageset"; case WebURLRequest::RequestContextImport: return "import"; case WebURLRequest::RequestContextInternal: return "internal"; case WebURLRequest::RequestContextLocation: return "location"; case WebURLRequest::RequestContextManifest: return "manifest"; case WebURLRequest::RequestContextObject: return "object"; case WebURLRequest::RequestContextPing: return "ping"; case WebURLRequest::RequestContextPlugin: return "plugin"; case WebURLRequest::RequestContextPrefetch: return "prefetch"; case WebURLRequest::RequestContextScript: return "script"; case WebURLRequest::RequestContextServiceWorker: return "serviceworker"; case WebURLRequest::RequestContextSharedWorker: return "sharedworker"; case WebURLRequest::RequestContextSubresource: return "subresource"; case WebURLRequest::RequestContextStyle: return "style"; case WebURLRequest::RequestContextTrack: return "track"; case WebURLRequest::RequestContextVideo: return "video"; case WebURLRequest::RequestContextWorker: return "worker"; case WebURLRequest::RequestContextXMLHttpRequest: return "xmlhttprequest"; case WebURLRequest::RequestContextXSLT: return "xslt"; } ASSERT_NOT_REACHED(); return ""; } String Request::referrer() const { // "The referrer attribute's getter must return the empty string if // request's referrer is no referrer, "about:client" if request's referrer // is client and request's referrer, serialized, otherwise." ASSERT(FetchRequestData::noReferrerString() == AtomicString()); ASSERT(FetchRequestData::clientReferrerString() == AtomicString("about:client")); return m_request->referrerString(); } String Request::mode() const { // "The mode attribute's getter must return the value corresponding to the // first matching statement, switching on request's mode:" switch (m_request->mode()) { case WebURLRequest::FetchRequestModeSameOrigin: return "same-origin"; case WebURLRequest::FetchRequestModeNoCORS: return "no-cors"; case WebURLRequest::FetchRequestModeCORS: case WebURLRequest::FetchRequestModeCORSWithForcedPreflight: return "cors"; case WebURLRequest::FetchRequestModeNavigate: return "navigate"; } ASSERT_NOT_REACHED(); return ""; } String Request::credentials() const { // "The credentials attribute's getter must return the value corresponding // to the first matching statement, switching on request's credentials // mode:" switch (m_request->credentials()) { case WebURLRequest::FetchCredentialsModeOmit: return "omit"; case WebURLRequest::FetchCredentialsModeSameOrigin: return "same-origin"; case WebURLRequest::FetchCredentialsModeInclude: return "include"; } ASSERT_NOT_REACHED(); return ""; } String Request::redirect() const { // "The redirect attribute's getter must return request's redirect mode." switch (m_request->redirect()) { case WebURLRequest::FetchRedirectModeFollow: return "follow"; case WebURLRequest::FetchRedirectModeError: return "error"; case WebURLRequest::FetchRedirectModeManual: return "manual"; } ASSERT_NOT_REACHED(); return ""; } String Request::integrity() const { return m_request->integrity(); } Request* Request::clone(ExceptionState& exceptionState) { if (isBodyLocked() || bodyUsed()) { exceptionState.throwTypeError("Request body is already used"); return nullptr; } FetchRequestData* request = m_request->clone(executionContext()); Headers* headers = Headers::create(request->headerList()); headers->setGuard(m_headers->guard()); return new Request(executionContext(), request, headers); } FetchRequestData* Request::passRequestData() { ASSERT(!bodyUsed()); return m_request->pass(executionContext()); } bool Request::hasBody() const { return bodyBuffer(); } void Request::stop() { if (bodyBuffer()) bodyBuffer()->stop(); } void Request::populateWebServiceWorkerRequest(WebServiceWorkerRequest& webRequest) const { webRequest.setMethod(method()); webRequest.setRequestContext(m_request->context()); // This strips off the fragment part. webRequest.setURL(url()); const FetchHeaderList* headerList = m_headers->headerList(); for (size_t i = 0, size = headerList->size(); i < size; ++i) { const FetchHeaderList::Header& header = headerList->entry(i); webRequest.appendHeader(header.first, header.second); } webRequest.setReferrer(m_request->referrerString(), static_cast(m_request->referrerPolicy())); // FIXME: How can we set isReload properly? What is the correct place to load it in to the Request object? We should investigate the right way // to plumb this information in to here. } String Request::mimeType() const { return m_request->mimeType(); } DEFINE_TRACE(Request) { Body::trace(visitor); visitor->trace(m_request); visitor->trace(m_headers); } } // namespace blink