483773edca9b17e396f835f51ef01315ec495b48
[WebKit-https.git] / Source / WebCore / Modules / fetch / FetchResponse.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 "FetchResponse.h"
31
32 #if ENABLE(FETCH_API)
33
34 #include "Dictionary.h"
35 #include "ExceptionCode.h"
36 #include "FetchRequest.h"
37 #include "JSFetchResponse.h"
38 #include "ScriptExecutionContext.h"
39
40 namespace WebCore {
41
42 static inline bool isRedirectStatus(int status)
43 {
44     return status == 301 || status == 302 || status == 303 || status == 307 || status == 308;
45 }
46
47 static inline bool isNullBodyStatus(int status)
48 {
49     return status == 101 || status == 204 || status == 205 || status == 304;
50 }
51
52 Ref<FetchResponse> FetchResponse::error(ScriptExecutionContext& context)
53 {
54     return adoptRef(*new FetchResponse(context, Type::Error, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));
55 }
56
57 RefPtr<FetchResponse> FetchResponse::redirect(ScriptExecutionContext& context, const String& url, int status, ExceptionCode& ec)
58 {
59     // FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser.
60     URL requestURL = context.completeURL(url);
61     if (!requestURL.isValid() || !requestURL.user().isEmpty() || !requestURL.pass().isEmpty()) {
62         ec = TypeError;
63         return nullptr;
64     }
65     if (!isRedirectStatus(status)) {
66         ec = TypeError;
67         return nullptr;
68     }
69     RefPtr<FetchResponse> redirectResponse = adoptRef(*new FetchResponse(context, Type::Default, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));
70     redirectResponse->m_response.setHTTPStatusCode(status);
71     redirectResponse->m_headers->fastSet(HTTPHeaderName::Location, requestURL.string());
72     return redirectResponse;
73 }
74
75 void FetchResponse::initializeWith(const Dictionary& init, ExceptionCode& ec)
76 {
77     int status;
78     if (!init.get("status", status)) {
79         ec = TypeError;
80         return;
81     }
82     if (status < 200 || status > 599) {
83         ec = RangeError;
84         return;
85     }
86
87     // FIXME: Validate reason phrase (https://tools.ietf.org/html/rfc7230#section-3.1.2).
88     String statusText;
89     if (!init.get("statusText", statusText)) {
90         ec = TypeError;
91         return;
92     }
93     m_response.setHTTPStatusCode(status);
94     m_response.setHTTPStatusText(statusText);
95
96     RefPtr<FetchHeaders> initialHeaders;
97     if (init.get("headers", initialHeaders))
98         m_headers->fill(initialHeaders.get());
99
100     JSC::JSValue body;
101     if (init.get("body", body)) {
102         if (isNullBodyStatus(status)) {
103             ec = TypeError;
104             return;
105         }
106         m_body = FetchBody::extract(*init.execState(), body);
107         if (m_headers->fastGet(HTTPHeaderName::ContentType).isEmpty() && !m_body.mimeType().isEmpty())
108             m_headers->fastSet(HTTPHeaderName::ContentType, m_body.mimeType());
109     }
110 }
111
112 FetchResponse::FetchResponse(ScriptExecutionContext& context, Type type, FetchBody&& body, Ref<FetchHeaders>&& headers, ResourceResponse&& response)
113     : FetchBodyOwner(context, WTFMove(body))
114     , m_type(type)
115     , m_response(WTFMove(response))
116     , m_headers(WTFMove(headers))
117 {
118 }
119
120 RefPtr<FetchResponse> FetchResponse::clone(ScriptExecutionContext& context, ExceptionCode& ec)
121 {
122     if (isDisturbed()) {
123         ec = TypeError;
124         return nullptr;
125     }
126     RefPtr<FetchResponse> cloned = adoptRef(*new FetchResponse(context, m_type, FetchBody(m_body), FetchHeaders::create(headers()), ResourceResponse(m_response)));
127     cloned->m_isRedirected = m_isRedirected;
128     return cloned;
129 }
130
131 String FetchResponse::type() const
132 {
133     switch (m_type) {
134     case Type::Basic:
135         return ASCIILiteral("basic");
136     case Type::Cors:
137         return ASCIILiteral("cors");
138     case Type::Default:
139         return ASCIILiteral("default");
140     case Type::Error:
141         return ASCIILiteral("error");
142     case Type::Opaque:
143         return ASCIILiteral("opaque");
144     case Type::OpaqueRedirect:
145         return ASCIILiteral("opaqueredirect");
146     };
147     ASSERT_NOT_REACHED();
148     return String();
149 }
150
151 void FetchResponse::startFetching(ScriptExecutionContext& context, const FetchRequest& request, FetchPromise&& promise)
152 {
153     Ref<FetchResponse> response = adoptRef(*new FetchResponse(context, Type::Basic, FetchBody::loadingBody(), FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));
154
155     // Setting pending activity until BodyLoader didFail or didSucceed callback is called.
156     response->setPendingActivity(response.ptr());
157
158     response->m_bodyLoader = BodyLoader(response.get(), WTFMove(promise));
159     if (!response->m_bodyLoader->start(context, request))
160         response->m_bodyLoader = Nullopt;
161 }
162
163 void FetchResponse::fetch(ScriptExecutionContext& context, FetchRequest& input, const Dictionary& dictionary, FetchPromise&& promise)
164 {
165     ExceptionCode ec = 0;
166     RefPtr<FetchRequest> fetchRequest = FetchRequest::create(context, input, dictionary, ec);
167     if (ec) {
168         promise.reject(ec);
169         return;
170     }
171     ASSERT(fetchRequest);
172     startFetching(context, *fetchRequest, WTFMove(promise));
173 }
174
175 void FetchResponse::fetch(ScriptExecutionContext& context, const String& url, const Dictionary& dictionary, FetchPromise&& promise)
176 {
177     ExceptionCode ec = 0;
178     RefPtr<FetchRequest> fetchRequest = FetchRequest::create(context, url, dictionary, ec);
179     if (ec) {
180         promise.reject(ec);
181         return;
182     }
183     ASSERT(fetchRequest);
184     startFetching(context, *fetchRequest, WTFMove(promise));
185 }
186
187 void FetchResponse::BodyLoader::didSucceed()
188 {
189     ASSERT(m_response.hasPendingActivity());
190 #if ENABLE(STREAMS_API)
191     if (m_response.m_readableStreamSource) {
192         m_response.m_readableStreamSource->close();
193         m_response.m_readableStreamSource = nullptr;
194     }
195 #endif
196     m_response.m_bodyLoader = Nullopt;
197     m_response.unsetPendingActivity(&m_response);
198 }
199
200 void FetchResponse::BodyLoader::didFail()
201 {
202     ASSERT(m_response.hasPendingActivity());
203     if (m_promise)
204         std::exchange(m_promise, Nullopt)->reject(TypeError);
205
206 #if ENABLE(STREAMS_API)
207     if (m_response.m_readableStreamSource) {
208         if (!m_response.m_readableStreamSource->isCancelling())
209             m_response.m_readableStreamSource->error(ASCIILiteral("Loading failed"));
210         m_response.m_readableStreamSource = nullptr;
211     }
212 #endif
213
214     // Check whether didFail is called as part of FetchLoader::start.
215     if (m_loader->isStarted())
216         m_response.m_bodyLoader = Nullopt;
217
218     // FIXME: Handle the case of failing after didReceiveResponse is called.
219
220     m_response.unsetPendingActivity(&m_response);
221 }
222
223 FetchResponse::BodyLoader::BodyLoader(FetchResponse& response, FetchPromise&& promise)
224     : m_response(response)
225     , m_promise(WTFMove(promise))
226 {
227 }
228
229 void FetchResponse::BodyLoader::didReceiveResponse(const ResourceResponse& resourceResponse)
230 {
231     ASSERT(m_promise);
232
233     m_response.m_response = resourceResponse;
234     m_response.m_headers->filterAndFill(resourceResponse.httpHeaderFields(), FetchHeaders::Guard::Response);
235
236     std::exchange(m_promise, Nullopt)->resolve(&m_response);
237 }
238
239 void FetchResponse::BodyLoader::didReceiveData(const char* data, size_t size)
240 {
241 #if ENABLE(STREAMS_API)
242     ASSERT(m_response.m_readableStreamSource);
243
244     // FIXME: If ArrayBuffer::tryCreate returns null, we should probably cancel the load.
245     m_response.m_readableStreamSource->enqueue(ArrayBuffer::tryCreate(data, size));
246 #else
247     UNUSED_PARAM(data);
248     UNUSED_PARAM(size);
249 #endif
250 }
251
252 void FetchResponse::BodyLoader::didFinishLoadingAsArrayBuffer(RefPtr<ArrayBuffer>&& buffer)
253 {
254     m_response.body().loadedAsArrayBuffer(WTFMove(buffer));
255 }
256
257 bool FetchResponse::BodyLoader::start(ScriptExecutionContext& context, const FetchRequest& request)
258 {
259     m_loader = std::make_unique<FetchLoader>(FetchLoader::Type::ArrayBuffer, *this);
260     m_loader->start(context, request);
261     return m_loader->isStarted();
262 }
263
264 void FetchResponse::BodyLoader::stop()
265 {
266     if (m_loader)
267         m_loader->stop();
268 }
269
270 #if ENABLE(STREAMS_API)
271 void FetchResponse::consumeBodyAsStream()
272 {
273     ASSERT(m_readableStreamSource);
274     m_isDisturbed = true;
275     if (body().type() != FetchBody::Type::Loading) {
276         body().consumeAsStream(*this, *m_readableStreamSource);
277         if (!m_readableStreamSource->isStarting())
278             m_readableStreamSource = nullptr;        
279         return;
280     }
281
282     ASSERT(m_bodyLoader);
283
284     RefPtr<SharedBuffer> data = m_bodyLoader->startStreaming();
285     if (data) {
286         // FIXME: We might want to enqueue each internal SharedBuffer chunk as an individual ArrayBuffer.
287         // Also, createArrayBuffer might return nullptr which will lead to erroring the stream.
288         // We might want to cancel the load and rename createArrayBuffer to tryCreateArrayBuffer.
289         m_readableStreamSource->enqueue(data->createArrayBuffer());
290     }
291 }
292
293 ReadableStreamSource* FetchResponse::createReadableStreamSource()
294 {
295     ASSERT(!m_readableStreamSource);
296     ASSERT(!isDisturbed());
297
298     if (body().isEmpty())
299         return nullptr;
300
301     m_readableStreamSource = adoptRef(*new FetchResponseSource(*this));
302     return m_readableStreamSource.get();
303 }
304
305 RefPtr<SharedBuffer> FetchResponse::BodyLoader::startStreaming()
306 {
307     ASSERT(m_loader);
308     return m_loader->startStreaming();
309 }
310 #endif
311
312 void FetchResponse::stop()
313 {
314     RefPtr<FetchResponse> protect(this);
315     FetchBodyOwner::stop();
316     if (m_bodyLoader) {
317         m_bodyLoader->stop();
318         ASSERT(!m_bodyLoader);
319     }
320 }
321
322 const char* FetchResponse::activeDOMObjectName() const
323 {
324     return "Response";
325 }
326
327 bool FetchResponse::canSuspendForDocumentSuspension() const
328 {
329     // FIXME: We can probably do the same strategy as XHR.
330     return !isActive();
331 }
332
333 } // namespace WebCore
334
335 #endif // ENABLE(FETCH_API)