Deprecate ActiveDOMObject::canSuspendForDocumentSuspension()
[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 "Document.h"
33 #include "HTTPParsers.h"
34 #include "JSAbortSignal.h"
35 #include "Logging.h"
36 #include "Quirks.h"
37 #include "ScriptExecutionContext.h"
38 #include "SecurityOrigin.h"
39 #include "Settings.h"
40
41 namespace WebCore {
42
43 static Optional<Exception> setMethod(ResourceRequest& request, const String& initMethod)
44 {
45     if (!isValidHTTPToken(initMethod))
46         return Exception { TypeError, "Method is not a valid HTTP token."_s };
47     if (isForbiddenMethod(initMethod))
48         return Exception { TypeError, "Method is forbidden."_s };
49     request.setHTTPMethod(normalizeHTTPMethod(initMethod));
50     return WTF::nullopt;
51 }
52
53 static ExceptionOr<String> computeReferrer(ScriptExecutionContext& context, const String& referrer)
54 {
55     if (referrer.isEmpty())
56         return "no-referrer"_str;
57
58     // FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser.
59     URL referrerURL = context.completeURL(referrer);
60     if (!referrerURL.isValid())
61         return Exception { TypeError, "Referrer is not a valid URL."_s };
62
63     if (referrerURL.protocolIs("about") && referrerURL.path() == "client")
64         return "client"_str;
65
66     if (!(context.securityOrigin() && context.securityOrigin()->canRequest(referrerURL)))
67         return "client"_str;
68
69     return String { referrerURL.string() };
70 }
71
72 static Optional<Exception> buildOptions(FetchOptions& options, ResourceRequest& request, String& referrer, ScriptExecutionContext& context, const FetchRequest::Init& init)
73 {
74     if (!init.window.isUndefinedOrNull() && !init.window.isEmpty())
75         return Exception { TypeError, "Window can only be null."_s };
76
77     if (init.hasMembers()) {
78         if (options.mode == FetchOptions::Mode::Navigate)
79             options.mode = FetchOptions::Mode::SameOrigin;
80         referrer = "client"_s;
81         options.referrerPolicy = { };
82     }
83
84     if (!init.referrer.isNull()) {
85         auto result = computeReferrer(context, init.referrer);
86         if (result.hasException())
87             return result.releaseException();
88         referrer = result.releaseReturnValue();
89     }
90
91     if (init.referrerPolicy)
92         options.referrerPolicy = init.referrerPolicy.value();
93
94     if (init.mode) {
95         options.mode = init.mode.value();
96         if (options.mode == FetchOptions::Mode::Navigate)
97             return Exception { TypeError, "Request constructor does not accept navigate fetch mode."_s };
98     }
99
100     if (init.credentials)
101         options.credentials = init.credentials.value();
102
103     if (init.cache)
104         options.cache = init.cache.value();
105     if (options.cache == FetchOptions::Cache::OnlyIfCached && options.mode != FetchOptions::Mode::SameOrigin)
106         return Exception { TypeError, "only-if-cached cache option requires fetch mode to be same-origin."_s  };
107
108     if (init.redirect)
109         options.redirect = init.redirect.value();
110
111     if (!init.integrity.isNull())
112         options.integrity = init.integrity;
113
114     if (init.keepalive && init.keepalive.value())
115         options.keepAlive = true;
116
117     if (!init.method.isNull()) {
118         if (auto exception = setMethod(request, init.method))
119             return exception;
120     }
121
122     return WTF::nullopt;
123 }
124
125 static bool methodCanHaveBody(const ResourceRequest& request)
126 {
127     return request.httpMethod() != "GET" && request.httpMethod() != "HEAD";
128 }
129
130 ExceptionOr<void> FetchRequest::initializeOptions(const Init& init)
131 {
132     ASSERT(scriptExecutionContext());
133
134     auto exception = buildOptions(m_options, m_request, m_referrer, *scriptExecutionContext(), init);
135     if (exception)
136         return WTFMove(exception.value());
137
138     if (m_options.mode == FetchOptions::Mode::NoCors) {
139         const String& method = m_request.httpMethod();
140         if (method != "GET" && method != "POST" && method != "HEAD")
141             return Exception { TypeError, "Method must be GET, POST or HEAD in no-cors mode."_s };
142         m_headers->setGuard(FetchHeaders::Guard::RequestNoCors);
143     }
144     
145     return { };
146 }
147
148 static inline Optional<Exception> processInvalidSignal(ScriptExecutionContext& context)
149 {
150     ASCIILiteral message { "FetchRequestInit.signal should be undefined, null or an AbortSignal object. This will throw in a future release."_s };
151     context.addConsoleMessage(MessageSource::JS, MessageLevel::Warning, message);
152
153     if (is<Document>(context) && downcast<Document>(context).quirks().shouldIgnoreInvalidSignal())
154         return { };
155
156     RELEASE_LOG_ERROR(ResourceLoading, "FetchRequestInit.signal should be undefined, null or an AbortSignal object.");
157     return Exception { TypeError, message };
158 }
159
160 ExceptionOr<void> FetchRequest::initializeWith(const String& url, Init&& init)
161 {
162     ASSERT(scriptExecutionContext());
163     // FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser.
164     URL requestURL = scriptExecutionContext()->completeURL(url);
165     if (!requestURL.isValid() || !requestURL.user().isEmpty() || !requestURL.pass().isEmpty())
166         return Exception { TypeError, "URL is not valid or contains user credentials."_s };
167
168     m_options.mode = Mode::Cors;
169     m_options.credentials = Credentials::SameOrigin;
170     m_referrer = "client"_s;
171     m_request.setURL(requestURL);
172     m_request.setRequester(ResourceRequest::Requester::Fetch);
173     m_request.setInitiatorIdentifier(scriptExecutionContext()->resourceRequestIdentifier());
174
175     auto optionsResult = initializeOptions(init);
176     if (optionsResult.hasException())
177         return optionsResult.releaseException();
178
179     if (init.signal) {
180         if (auto* signal = JSAbortSignal::toWrapped(scriptExecutionContext()->vm(), init.signal))
181             m_signal->follow(*signal);
182         else if (!init.signal.isUndefinedOrNull())  {
183             if (auto exception = processInvalidSignal(*scriptExecutionContext()))
184                 return WTFMove(*exception);
185         }
186     }
187
188     if (init.headers) {
189         auto fillResult = m_headers->fill(*init.headers);
190         if (fillResult.hasException())
191             return fillResult.releaseException();
192     }
193
194     if (init.body) {
195         auto setBodyResult = setBody(WTFMove(*init.body));
196         if (setBodyResult.hasException())
197             return setBodyResult.releaseException();
198     }
199
200     updateContentType();
201     return { };
202 }
203
204 ExceptionOr<void> FetchRequest::initializeWith(FetchRequest& input, Init&& init)
205 {
206     m_request = input.m_request;
207     m_options = input.m_options;
208     m_referrer = input.m_referrer;
209
210     auto optionsResult = initializeOptions(init);
211     if (optionsResult.hasException())
212         return optionsResult.releaseException();
213
214     if (init.signal && !init.signal.isUndefined()) {
215         if (auto* signal = JSAbortSignal::toWrapped(scriptExecutionContext()->vm(), init.signal))
216             m_signal->follow(*signal);
217         else if (!init.signal.isNull()) {
218             if (auto exception = processInvalidSignal(*scriptExecutionContext()))
219                 return WTFMove(*exception);
220         }
221
222     } else
223         m_signal->follow(input.m_signal.get());
224
225     auto fillResult = init.headers ? m_headers->fill(*init.headers) : m_headers->fill(input.headers());
226     if (fillResult.hasException())
227         return fillResult;
228
229     auto setBodyResult = init.body ? setBody(WTFMove(*init.body)) : setBody(input);
230     if (setBodyResult.hasException())
231         return setBodyResult;
232
233     updateContentType();
234     return { };
235 }
236
237 ExceptionOr<void> FetchRequest::setBody(FetchBody::Init&& body)
238 {
239     if (!methodCanHaveBody(m_request))
240         return Exception { TypeError, makeString("Request has method '", m_request.httpMethod(), "' and cannot have a body") };
241
242     ASSERT(scriptExecutionContext());
243     auto result = extractBody(WTFMove(body));
244     if (result.hasException())
245         return result;
246
247     if (m_options.keepAlive && hasReadableStreamBody())
248         return Exception { TypeError, "Request cannot have a ReadableStream body and keepalive set to true"_s };
249     return { };
250 }
251
252 ExceptionOr<void> FetchRequest::setBody(FetchRequest& request)
253 {
254     if (request.isDisturbedOrLocked())
255         return Exception { TypeError, "Request input is disturbed or locked."_s };
256
257     if (!request.isBodyNull()) {
258         if (!methodCanHaveBody(m_request))
259             return Exception { TypeError, makeString("Request has method '", m_request.httpMethod(), "' and cannot have a body") };
260         // FIXME: If body has a readable stream, we should pipe it to this new body stream.
261         m_body = WTFMove(*request.m_body);
262         request.setDisturbed();
263     }
264
265     if (m_options.keepAlive && hasReadableStreamBody())
266         return Exception { TypeError, "Request cannot have a ReadableStream body and keepalive set to true"_s };
267     return { };
268 }
269
270 ExceptionOr<Ref<FetchRequest>> FetchRequest::create(ScriptExecutionContext& context, Info&& input, Init&& init)
271 {
272     auto request = adoptRef(*new FetchRequest(context, WTF::nullopt, FetchHeaders::create(FetchHeaders::Guard::Request), { }, { }, { }));
273
274     if (WTF::holds_alternative<String>(input)) {
275         auto result = request->initializeWith(WTF::get<String>(input), WTFMove(init));
276         if (result.hasException())
277             return result.releaseException();
278     } else {
279         auto result = request->initializeWith(*WTF::get<RefPtr<FetchRequest>>(input), WTFMove(init));
280         if (result.hasException())
281             return result.releaseException();
282     }
283
284     return request;
285 }
286
287 String FetchRequest::referrer() const
288 {
289     if (m_referrer == "no-referrer")
290         return String();
291     if (m_referrer == "client")
292         return "about:client"_s;
293     return m_referrer;
294 }
295
296 const String& FetchRequest::urlString() const
297 {
298     if (m_requestURL.isNull())
299         m_requestURL = m_request.url();
300     return m_requestURL;
301 }
302
303 ResourceRequest FetchRequest::resourceRequest() const
304 {
305     ASSERT(scriptExecutionContext());
306
307     ResourceRequest request = m_request;
308     request.setHTTPHeaderFields(m_headers->internalHeaders());
309
310     if (!isBodyNull())
311         request.setHTTPBody(body().bodyAsFormData(*scriptExecutionContext()));
312
313     return request;
314 }
315
316 ExceptionOr<Ref<FetchRequest>> FetchRequest::clone(ScriptExecutionContext& context)
317 {
318     if (isDisturbedOrLocked())
319         return Exception { TypeError, "Body is disturbed or locked"_s };
320
321     auto clone = adoptRef(*new FetchRequest(context, WTF::nullopt, FetchHeaders::create(m_headers.get()), ResourceRequest { m_request }, FetchOptions { m_options}, String { m_referrer }));
322     clone->cloneBody(*this);
323     clone->m_signal->follow(m_signal);
324     return clone;
325 }
326
327 const char* FetchRequest::activeDOMObjectName() const
328 {
329     return "Request";
330 }
331
332 bool FetchRequest::shouldPreventEnteringBackForwardCache_DEPRECATED() const
333 {
334     // FIXME: This should never prevent entering the back/forward cache.
335     return isActive();
336 }
337
338 } // namespace WebCore
339