blob: 54ddc5f9c1b5540c19eafb54c963f94dc017bedd [file] [log] [blame]
// 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->getExecutionContext()->getSecurityOrigin());
// FIXME: Set ForceOriginHeaderFlag.
request->setSameOriginDataURLFlag(true);
request->setReferrer(original->referrer());
request->setMode(original->mode());
request->setCredentials(original->credentials());
request->setAttachedCredential(original->attachedCredential());
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<SecurityOrigin> origin = scriptState->getExecutionContext()->getSecurityOrigin();
// 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->getExecutionContext()->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->getExecutionContext()->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.getString()));
}
}
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 (init.credentials == "password") {
if (!init.attachedCredential.get()) {
exceptionState.throwTypeError("Cannot construct a Request with a credential mode of 'password' without a PasswordCredential.");
return nullptr;
}
request->setCredentials(WebURLRequest::FetchCredentialsModePassword);
request->setAttachedCredential(init.attachedCredential);
request->setRedirect(WebURLRequest::FetchRedirectModeManual);
} 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->getExecutionContext(), 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->getHeaders()->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->getHeaders()->setGuard(Headers::RequestNoCORSGuard);
}
// "Fill |r|'s Headers object with |headers|. Rethrow any exceptions."
if (init.headers) {
ASSERT(init.headersDictionary.isUndefinedOrNull());
r->getHeaders()->fillWith(init.headers.get(), exceptionState);
} else if (!init.headersDictionary.isUndefinedOrNull()) {
r->getHeaders()->fillWith(init.headersDictionary, exceptionState);
} else {
ASSERT(headers);
r->getHeaders()->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 || request->credentials() == WebURLRequest::FetchCredentialsModePassword) {
if (request->method() == HTTPNames::GET || request->method() == HTTPNames::HEAD) {
exceptionState.throwTypeError("Request with GET/HEAD method cannot have body.");
return nullptr;
}
}
// TODO(mkwst): See the comment in RequestInit about serializing the attached credential
// prior to hitting the Service Worker machinery.
if (request->credentials() == WebURLRequest::FetchCredentialsModePassword) {
r->getHeaders()->append(HTTPNames::Content_Type, init.contentType, exceptionState);
// TODO(mkwst): This should be a registrable-domain match.
if (!origin->canRequest(r->url())) {
exceptionState.throwTypeError("Credentials may only be submitted to same-origin endpoints.");
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->getHeaders()->has(HTTPNames::Content_Type, exceptionState)) {
r->getHeaders()->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);
// "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->getExecutionContext(), 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->getExecutionContext(), 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";
case WebURLRequest::FetchCredentialsModePassword:
return "password";
}
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(getExecutionContext());
Headers* headers = Headers::create(request->headerList());
headers->setGuard(m_headers->getGuard());
return new Request(getExecutionContext(), request, headers);
}
FetchRequestData* Request::passRequestData()
{
ASSERT(!bodyUsed());
return m_request->pass(getExecutionContext());
}
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<WebReferrerPolicy>(m_request->getReferrerPolicy()));
// 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