1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "config.h"
6#include "Request.h"
7
8#include "bindings/core/v8/Dictionary.h"
9#include "core/dom/ExecutionContext.h"
10#include "core/fetch/FetchUtils.h"
11#include "core/fetch/ResourceLoaderOptions.h"
12#include "core/loader/ThreadableLoader.h"
13#include "core/xml/XMLHttpRequest.h"
14#include "modules/serviceworkers/FetchManager.h"
15#include "modules/serviceworkers/HeadersForEachCallback.h"
16#include "modules/serviceworkers/RequestInit.h"
17#include "platform/NotImplemented.h"
18#include "platform/network/HTTPParsers.h"
19#include "platform/network/ResourceRequest.h"
20#include "platform/weborigin/Referrer.h"
21#include "public/platform/WebServiceWorkerRequest.h"
22
23namespace blink {
24
25namespace {
26
27class FillWebRequestHeaders : public HeadersForEachCallback {
28public:
29    FillWebRequestHeaders(WebServiceWorkerRequest* webRequest) : m_webRequest(webRequest) { }
30
31    virtual bool handleItem(ScriptValue, const String&, const String&, Headers*)
32    {
33        ASSERT_NOT_REACHED();
34        return false;
35    }
36
37    virtual bool handleItem(const String& value, const String& key, Headers*)
38    {
39        m_webRequest->appendHeader(key, value);
40        return true;
41    }
42
43private:
44    WebServiceWorkerRequest* m_webRequest;
45};
46
47} // namespace
48
49Request* Request::createRequestWithRequestData(ExecutionContext* context, FetchRequestData* request, const RequestInit& init, FetchRequestData::Mode mode, FetchRequestData::Credentials credentials, ExceptionState& exceptionState)
50{
51    // "7. Let |mode| be |init|'s mode member if it is present, and
52    // |fallbackMode| otherwise."
53    // "8. If |mode| is non-null, set |request|'s mode to |mode|."
54    if (init.mode == "same-origin") {
55        request->setMode(FetchRequestData::SameOriginMode);
56    } else if (init.mode == "no-cors") {
57        request->setMode(mode = FetchRequestData::NoCORSMode);
58    } else if (init.mode == "cors") {
59        request->setMode(FetchRequestData::CORSMode);
60    } else {
61        // Instead of using null as a special fallback value, we pass the
62        // current mode in Request::create(). So we just set here.
63        request->setMode(mode);
64    }
65
66    // "9. Let |credentials| be |init|'s credentials member if it is present,
67    // and |fallbackCredentials| otherwise."
68    // "10. If |credentials| is non-null, set |request|'s credentials mode to
69    // |credentials|.
70    if (init.credentials == "omit") {
71        request->setCredentials(FetchRequestData::OmitCredentials);
72    } else if (init.credentials == "same-origin") {
73        request->setCredentials(FetchRequestData::SameOriginCredentials);
74    } else if (init.credentials == "include") {
75        request->setCredentials(FetchRequestData::IncludeCredentials);
76    } else {
77        // Instead of using null as a special fallback value, we pass the
78        // current credentials in Request::create(). So we just set here.
79        request->setCredentials(credentials);
80    }
81
82    // "11. If |init|'s method member is present, let |method| be it and run
83    // these substeps:"
84    if (!init.method.isEmpty()) {
85        // "1. If |method| is not a useful method, throw a TypeError."
86        if (!FetchUtils::isUsefulMethod(init.method)) {
87            exceptionState.throwTypeError("'" + init.method + "' HTTP method is unsupported.");
88            return 0;
89        }
90        if (!isValidHTTPToken(init.method)) {
91            exceptionState.throwTypeError("'" + init.method + "' is not a valid HTTP method.");
92            return 0;
93        }
94        // FIXME: "2. Add case correction as in XMLHttpRequest?"
95        // "3. Set |request|'s method to |method|."
96        request->setMethod(XMLHttpRequest::uppercaseKnownHTTPMethod(AtomicString(init.method)));
97    }
98    // "12. Let |r| be a new Request object associated with |request|, Headers
99    // object."
100    Request* r = Request::create(context, request);
101
102    // "13. Let |headers| be a copy of |r|'s Headers object."
103    // "14. If |init|'s headers member is present, set |headers| to |init|'s
104    // headers member."
105    // We don't create a copy of r's Headers object when init's headers member
106    // is present.
107    Headers* headers = 0;
108    if (!init.headers && init.headersDictionary.isUndefinedOrNull()) {
109        headers = r->headers()->createCopy();
110    }
111    // "15. Empty |r|'s request's header list."
112    r->clearHeaderList();
113
114    // "16. If |r|'s request's mode is no CORS, run these substeps:
115    if (r->request()->mode() == FetchRequestData::NoCORSMode) {
116        // "1. If |r|'s request's method is not a simple method, throw a
117        // TypeError."
118        if (!FetchUtils::isSimpleMethod(r->request()->method())) {
119            exceptionState.throwTypeError("'" + r->request()->method() + "' is unsupported in no-cors mode.");
120            return 0;
121        }
122        // "Set |r|'s Headers object's guard to |request-no-CORS|.
123        r->headers()->setGuard(Headers::RequestNoCORSGuard);
124    }
125
126    // "17. Fill |r|'s Headers object with |headers|. Rethrow any exceptions."
127    if (init.headers) {
128        ASSERT(init.headersDictionary.isUndefinedOrNull());
129        r->headers()->fillWith(init.headers.get(), exceptionState);
130    } else if (!init.headersDictionary.isUndefinedOrNull()) {
131        r->headers()->fillWith(init.headersDictionary, exceptionState);
132    } else {
133        ASSERT(headers);
134        r->headers()->fillWith(headers, exceptionState);
135    }
136    if (exceptionState.hadException())
137        return 0;
138    // "18. If |init|'s body member is present, run these substeps:"
139    if (init.bodyBlobHandle) {
140        // "1. Let |stream| and |Content-Type| be the result of extracting
141        // |init|'s body member."
142        // "2. Set |r|'s request's body to |stream|."
143        // "3.If |Content-Type| is non-null and |r|'s request's header list
144        //  contains no header named `Content-Type`, append
145        // `Content-Type`/|Content-Type| to |r|'s Headers object. Rethrow any
146        // exception."
147        r->setBodyBlobHandle(init.bodyBlobHandle);
148        if (!init.bodyBlobHandle->type().isEmpty() && !r->headers()->has("Content-Type", exceptionState)) {
149            r->headers()->append("Content-Type", init.bodyBlobHandle->type(), exceptionState);
150        }
151        if (exceptionState.hadException())
152            return 0;
153    }
154    // "19. Set |r|'s MIME type to the result of extracting a MIME type from
155    // |r|'s request's header list."
156    // FIXME: We don't have MIME type in Request object yet.
157
158    // "20. Return |r|."
159    return r;
160}
161
162Request* Request::create(ExecutionContext* context, const String& input, ExceptionState& exceptionState)
163{
164    return create(context, input, Dictionary(), exceptionState);
165}
166
167Request* Request::create(ExecutionContext* context, const String& input, const Dictionary& init, ExceptionState& exceptionState)
168{
169    // "2. Let |request| be |input|'s associated request, if |input| is a
170    // Request object, and a new request otherwise."
171    FetchRequestData* request(FetchRequestData::create(context));
172    // "3. Set |request| to a restricted copy of itself."
173    request = request->createRestrictedCopy(context, SecurityOrigin::create(context->url()));
174    // "6. If |input| is a string, run these substeps:"
175    // "1. Let |parsedURL| be the result of parsing |input| with entry settings
176    // object's API base URL."
177    KURL parsedURL = context->completeURL(input);
178    // "2. If |parsedURL| is failure, throw a TypeError."
179    if (!parsedURL.isValid()) {
180        exceptionState.throwTypeError("Invalid URL");
181        return 0;
182    }
183    // "3. Set |request|'s url to |parsedURL|."
184    request->setURL(parsedURL);
185    // "4. Set |fallbackMode| to CORS."
186    // "5. Set |fallbackCredentials| to omit."
187    return createRequestWithRequestData(context, request, RequestInit(context, init, exceptionState), FetchRequestData::CORSMode, FetchRequestData::OmitCredentials, exceptionState);
188}
189
190Request* Request::create(ExecutionContext* context, Request* input, ExceptionState& exceptionState)
191{
192    return create(context, input, Dictionary(), exceptionState);
193}
194
195Request* Request::create(ExecutionContext* context, Request* input, const Dictionary& init, ExceptionState& exceptionState)
196{
197    // "1. If input is a Request object, run these substeps:"
198    // "  1. If input's used flag is set, throw a TypeError."
199    // "  2. Set input's used flag."
200    if (input->bodyUsed()) {
201        exceptionState.throwTypeError(
202            "Cannot construct a Request with a Request object that has already been used.");
203        return 0;
204    }
205    input->setBodyUsed();
206    // "2. Let |request| be |input|'s associated request, if |input| is a
207    // Request object, and a new request otherwise."
208    // "3. Set |request| to a restricted copy of itself."
209    FetchRequestData* request(input->request()->createRestrictedCopy(context, SecurityOrigin::create(context->url())));
210    // "4. Let |fallbackMode| be null."
211    // "5. Let |fallbackCredentials| be null."
212    // Instead of using null as a special fallback value, just pass the current
213    // mode and credentials; it has the same effect.
214    const FetchRequestData::Mode currentMode = request->mode();
215    const FetchRequestData::Credentials currentCredentials = request->credentials();
216    return createRequestWithRequestData(context, request, RequestInit(context, init, exceptionState), currentMode, currentCredentials, exceptionState);
217}
218
219Request* Request::create(ExecutionContext* context, FetchRequestData* request)
220{
221    Request* r = new Request(context, request);
222    r->suspendIfNeeded();
223    return r;
224}
225
226Request::Request(ExecutionContext* context, FetchRequestData* request)
227    : Body(context)
228    , m_request(request)
229    , m_headers(Headers::create(m_request->headerList()))
230{
231    m_headers->setGuard(Headers::RequestGuard);
232}
233
234Request* Request::create(ExecutionContext* context, const WebServiceWorkerRequest& webRequest)
235{
236    Request* r = new Request(context, webRequest);
237    r->suspendIfNeeded();
238    return r;
239}
240
241Request* Request::create(const Request& copyFrom)
242{
243    Request* r = new Request(copyFrom);
244    r->suspendIfNeeded();
245    return r;
246}
247
248Request::Request(ExecutionContext* context, const WebServiceWorkerRequest& webRequest)
249    : Body(context)
250    , m_request(FetchRequestData::create(webRequest))
251    , m_headers(Headers::create(m_request->headerList()))
252{
253    m_headers->setGuard(Headers::RequestGuard);
254}
255
256Request::Request(const Request& copy_from)
257    : Body(copy_from)
258    , m_request(copy_from.m_request)
259    , m_headers(copy_from.m_headers->createCopy())
260{
261}
262
263
264String Request::method() const
265{
266    // "The method attribute's getter must return request's method."
267    return m_request->method();
268}
269
270String Request::url() const
271{
272    // The url attribute's getter must return request's url, serialized with the exclude fragment flag set.
273    if (!m_request->url().hasFragmentIdentifier())
274        return m_request->url();
275    KURL url(m_request->url());
276    url.removeFragmentIdentifier();
277    return url;
278}
279
280String Request::referrer() const
281{
282    // "The referrer attribute's getter must return the empty string if
283    // request's referrer is none, and request's referrer, serialized,
284    // otherwise."
285    return m_request->referrer().referrer().referrer;
286}
287
288String Request::mode() const
289{
290    // "The mode attribute's getter must return the value corresponding to the
291    // first matching statement, switching on request's mode:"
292    switch (m_request->mode()) {
293    case FetchRequestData::SameOriginMode:
294        return "same-origin";
295    case FetchRequestData::NoCORSMode:
296        return "no-cors";
297    case FetchRequestData::CORSMode:
298    case FetchRequestData::CORSWithForcedPreflight:
299        return "cors";
300    }
301    ASSERT_NOT_REACHED();
302    return "";
303}
304
305String Request::credentials() const
306{
307    // "The credentials attribute's getter must return the value corresponding
308    // to the first matching statement, switching on request's credentials
309    // mode:"
310    switch (m_request->credentials()) {
311    case FetchRequestData::OmitCredentials:
312        return "omit";
313    case FetchRequestData::SameOriginCredentials:
314        return "same-origin";
315    case FetchRequestData::IncludeCredentials:
316        return "include";
317    }
318    ASSERT_NOT_REACHED();
319    return "";
320}
321
322Request* Request::clone() const
323{
324    return Request::create(*this);
325}
326
327void Request::populateWebServiceWorkerRequest(WebServiceWorkerRequest& webRequest)
328{
329    webRequest.setMethod(method());
330    webRequest.setURL(m_request->url());
331    m_headers->forEach(new FillWebRequestHeaders(&webRequest));
332    webRequest.setReferrer(m_request->referrer().referrer().referrer, static_cast<WebReferrerPolicy>(m_request->referrer().referrer().referrerPolicy));
333    // 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
334    // to plumb this information in to here.
335}
336
337void Request::setBodyBlobHandle(PassRefPtr<BlobDataHandle> blobDataHandle)
338{
339    m_request->setBlobDataHandle(blobDataHandle);
340}
341
342void Request::clearHeaderList()
343{
344    m_request->headerList()->clearList();
345}
346
347PassRefPtr<BlobDataHandle> Request::blobDataHandle()
348{
349    return m_request->blobDataHandle();
350}
351
352void Request::trace(Visitor* visitor)
353{
354    Body::trace(visitor);
355    visitor->trace(m_request);
356    visitor->trace(m_headers);
357}
358
359} // namespace blink
360