0e8742a7c6fbda6417fe9b556fac1a04196ef10f
[WebKit-https.git] / Source / WebCore / Modules / fetch / FetchRequest.cpp
1 /*
2  * Copyright (C) 2016 Canon Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted, provided that the following conditions
6  * are required to be met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Canon Inc. nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
21  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "FetchRequest.h"
31
32 #include "HTTPParsers.h"
33 #include "ScriptExecutionContext.h"
34 #include "SecurityOrigin.h"
35
36 namespace WebCore {
37
38 static std::optional<Exception> setMethod(ResourceRequest& request, const String& initMethod)
39 {
40     if (!isValidHTTPToken(initMethod))
41         return Exception { TypeError, ASCIILiteral("Method is not a valid HTTP token.") };
42     if (isForbiddenMethod(initMethod))
43         return Exception { TypeError, ASCIILiteral("Method is forbidden.") };
44     request.setHTTPMethod(normalizeHTTPMethod(initMethod));
45     return std::nullopt;
46 }
47
48 static ExceptionOr<String> computeReferrer(ScriptExecutionContext& context, const String& referrer)
49 {
50     if (referrer.isEmpty())
51         return String { "no-referrer" };
52
53     // FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser.
54     URL referrerURL = context.completeURL(referrer);
55     if (!referrerURL.isValid())
56         return Exception { TypeError, ASCIILiteral("Referrer is not a valid URL.") };
57
58     if (referrerURL.protocolIs("about") && referrerURL.path() == "client")
59         return String { "client" };
60
61     if (!(context.securityOrigin() && context.securityOrigin()->canRequest(referrerURL)))
62         return Exception { TypeError, ASCIILiteral("Referrer is not same-origin.") };
63
64     return String { referrerURL.string() };
65 }
66
67 static std::optional<Exception> buildOptions(FetchOptions& options, ResourceRequest& request, String& referrer, ScriptExecutionContext& context, const FetchRequest::Init& init)
68 {
69     if (!init.window.isUndefinedOrNull() && !init.window.isEmpty())
70         return Exception { TypeError, ASCIILiteral("Window can only be null.") };
71
72     if (!init.referrer.isNull()) {
73         auto result = computeReferrer(context, init.referrer);
74         if (result.hasException())
75             return result.releaseException();
76         referrer = result.releaseReturnValue();
77     }
78
79     if (init.referrerPolicy)
80         options.referrerPolicy = init.referrerPolicy.value();
81
82     if (init.mode)
83         options.mode = init.mode.value();
84     if (options.mode == FetchOptions::Mode::Navigate)
85         return Exception { TypeError, ASCIILiteral("Request constructor does not accept navigate fetch mode.") };
86
87     if (init.credentials)
88         options.credentials = init.credentials.value();
89
90     if (init.cache)
91         options.cache = init.cache.value();
92     if (options.cache == FetchOptions::Cache::OnlyIfCached && options.mode != FetchOptions::Mode::SameOrigin)
93         return Exception { TypeError, ASCIILiteral("only-if-cached cache option requires fetch mode to be same-origin.")  };
94
95     if (init.redirect)
96         options.redirect = init.redirect.value();
97
98     if (!init.integrity.isNull())
99         options.integrity = init.integrity;
100
101     if (init.keepalive && init.keepalive.value())
102         options.keepAlive = true;
103
104     if (!init.method.isNull()) {
105         if (auto exception = setMethod(request, init.method))
106             return exception;
107     }
108
109     return std::nullopt;
110 }
111
112 static bool methodCanHaveBody(const ResourceRequest& request)
113 {
114     return request.httpMethod() != "GET" && request.httpMethod() != "HEAD";
115 }
116
117 ExceptionOr<void> FetchRequest::initializeOptions(const Init& init)
118 {
119     ASSERT(scriptExecutionContext());
120
121     auto exception = buildOptions(m_options, m_request, m_referrer, *scriptExecutionContext(), init);
122     if (exception)
123         return WTFMove(exception.value());
124
125     if (m_options.mode == FetchOptions::Mode::NoCors) {
126         const String& method = m_request.httpMethod();
127         if (method != "GET" && method != "POST" && method != "HEAD")
128             return Exception { TypeError, ASCIILiteral("Method must be GET, POST or HEAD in no-cors mode.") };
129         if (!m_options.integrity.isEmpty())
130             return Exception { TypeError, ASCIILiteral("There cannot be an integrity in no-cors mode.") };
131         m_headers->setGuard(FetchHeaders::Guard::RequestNoCors);
132     }
133     
134     return { };
135 }
136
137 ExceptionOr<void> FetchRequest::initializeWith(const String& url, Init&& init)
138 {
139     ASSERT(scriptExecutionContext());
140     // FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser.
141     URL requestURL = scriptExecutionContext()->completeURL(url);
142     if (!requestURL.isValid() || !requestURL.user().isEmpty() || !requestURL.pass().isEmpty())
143         return Exception { TypeError, ASCIILiteral("URL is not valid or contains user credentials.") };
144
145     m_options.mode = Mode::Cors;
146     m_options.credentials = Credentials::Omit;
147     m_referrer = ASCIILiteral("client");
148     m_request.setURL(requestURL);
149     m_request.setRequester(ResourceRequest::Requester::Fetch);
150     m_request.setInitiatorIdentifier(scriptExecutionContext()->resourceRequestIdentifier());
151
152     auto optionsResult = initializeOptions(init);
153     if (optionsResult.hasException())
154         return optionsResult.releaseException();
155
156     if (init.headers) {
157         auto fillResult = m_headers->fill(*init.headers);
158         if (fillResult.hasException())
159             return fillResult.releaseException();
160     }
161
162     if (init.body) {
163         auto setBodyResult = setBody(WTFMove(*init.body));
164         if (setBodyResult.hasException())
165             return setBodyResult.releaseException();
166     }
167
168     updateContentType();
169     return { };
170 }
171
172 ExceptionOr<void> FetchRequest::initializeWith(FetchRequest& input, Init&& init)
173 {
174     if (input.isDisturbedOrLocked())
175         return Exception {TypeError, ASCIILiteral("Request input is disturbed or locked.") };
176
177     m_request = input.m_request;
178     m_options = input.m_options;
179     m_referrer = input.m_referrer;
180
181     auto optionsResult = initializeOptions(init);
182     if (optionsResult.hasException())
183         return optionsResult.releaseException();
184
185     if (init.headers) {
186         auto fillResult = m_headers->fill(*init.headers);
187         if (fillResult.hasException())
188             return fillResult.releaseException();
189     } else {
190         auto fillResult = m_headers->fill(input.headers());
191         if (fillResult.hasException())
192             return fillResult.releaseException();
193     }
194
195     if (init.body) {
196         auto setBodyResult = setBody(WTFMove(*init.body));
197         if (setBodyResult.hasException())
198             return setBodyResult.releaseException();
199     } else {
200         auto setBodyResult = setBody(input);
201         if (setBodyResult.hasException())
202             return setBodyResult.releaseException();
203     }
204
205     updateContentType();
206     return { };
207 }
208
209 ExceptionOr<void> FetchRequest::setBody(FetchBody::Init&& body)
210 {
211     if (!methodCanHaveBody(m_request))
212         return Exception { TypeError };
213
214     ASSERT(scriptExecutionContext());
215     extractBody(*scriptExecutionContext(), WTFMove(body));
216
217     if (m_options.keepAlive && hasReadableStreamBody())
218         return Exception { TypeError, ASCIILiteral("Request cannot have a ReadableStream body and keepalive set to true") };
219     return { };
220 }
221
222 ExceptionOr<void> FetchRequest::setBody(FetchRequest& request)
223 {
224     if (!request.isBodyNull()) {
225         if (!methodCanHaveBody(m_request))
226             return Exception { TypeError };
227         // FIXME: If body has a readable stream, we should pipe it to this new body stream.
228         m_body = WTFMove(request.m_body);
229         request.setDisturbed();
230     }
231
232     if (m_options.keepAlive && hasReadableStreamBody())
233         return Exception { TypeError, ASCIILiteral("Request cannot have a ReadableStream body and keepalive set to true") };
234     return { };
235 }
236
237 ExceptionOr<Ref<FetchRequest>> FetchRequest::create(ScriptExecutionContext& context, Info&& input, Init&& init)
238 {
239     auto request = adoptRef(*new FetchRequest(context, std::nullopt, FetchHeaders::create(FetchHeaders::Guard::Request), { }, { }, { }));
240
241     if (WTF::holds_alternative<String>(input)) {
242         auto result = request->initializeWith(WTF::get<String>(input), WTFMove(init));
243         if (result.hasException())
244             return result.releaseException();
245     } else {
246         auto result = request->initializeWith(*WTF::get<RefPtr<FetchRequest>>(input), WTFMove(init));
247         if (result.hasException())
248             return result.releaseException();
249     }
250
251     return WTFMove(request);
252 }
253
254 String FetchRequest::referrer() const
255 {
256     if (m_referrer == "no-referrer")
257         return String();
258     if (m_referrer == "client")
259         return ASCIILiteral("about:client");
260     return m_referrer;
261 }
262
263 const String& FetchRequest::urlString() const
264 {
265     if (m_requestURL.isNull())
266         m_requestURL = m_request.url().serialize();
267     return m_requestURL;
268 }
269
270 ResourceRequest FetchRequest::resourceRequest() const
271 {
272     ASSERT(scriptExecutionContext());
273
274     ResourceRequest request = m_request;
275     request.setHTTPHeaderFields(m_headers->internalHeaders());
276
277     if (!isBodyNull())
278         request.setHTTPBody(body().bodyAsFormData(*scriptExecutionContext()));
279
280     return request;
281 }
282
283 ExceptionOr<Ref<FetchRequest>> FetchRequest::clone(ScriptExecutionContext& context)
284 {
285     if (isDisturbedOrLocked())
286         return Exception { TypeError };
287
288     auto clone = adoptRef(*new FetchRequest(context, std::nullopt, FetchHeaders::create(m_headers.get()), ResourceRequest { m_request }, FetchOptions { m_options}, String { m_referrer }));
289     clone->cloneBody(*this);
290     return WTFMove(clone);
291 }
292
293 const char* FetchRequest::activeDOMObjectName() const
294 {
295     return "Request";
296 }
297
298 bool FetchRequest::canSuspendForDocumentSuspension() const
299 {
300     // FIXME: We can probably do the same strategy as XHR.
301     return !isActive();
302 }
303
304 } // namespace WebCore
305