Opaque being-loaded responses should clone their body
[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 #include "FetchRequest.h"
33 #include "HTTPParsers.h"
34 #include "JSBlob.h"
35 #include "MIMETypeRegistry.h"
36 #include "ResourceError.h"
37 #include "ScriptExecutionContext.h"
38
39 namespace WebCore {
40
41 // https://fetch.spec.whatwg.org/#null-body-status
42 static inline bool isNullBodyStatus(int status)
43 {
44     return status == 101 || status == 204 || status == 205 || status == 304;
45 }
46
47 Ref<FetchResponse> FetchResponse::create(ScriptExecutionContext& context, std::optional<FetchBody>&& body, FetchHeaders::Guard guard, ResourceResponse&& response)
48 {
49     bool isSynthetic = response.type() == ResourceResponse::Type::Default || response.type() == ResourceResponse::Type::Error;
50     bool isOpaque = response.tainting() == ResourceResponse::Tainting::Opaque;
51     auto headers = isOpaque ? FetchHeaders::create(guard) : FetchHeaders::create(guard, HTTPHeaderMap { response.httpHeaderFields() });
52
53     auto fetchResponse = adoptRef(*new FetchResponse(context, WTFMove(body), WTFMove(headers), WTFMove(response)));
54     if (!isSynthetic)
55         fetchResponse->m_filteredResponse = ResourceResponseBase::filter(fetchResponse->m_internalResponse);
56     if (isOpaque)
57         fetchResponse->setBodyAsOpaque();
58     return fetchResponse;
59 }
60
61 ExceptionOr<Ref<FetchResponse>> FetchResponse::create(ScriptExecutionContext& context, std::optional<FetchBody::Init>&& body, Init&& init)
62 {
63     // 1. If init’s status member is not in the range 200 to 599, inclusive, then throw a RangeError.
64     if (init.status < 200  || init.status > 599)
65         return Exception { RangeError, ASCIILiteral("Status must be between 200 and 599") };
66
67     // 2. If init’s statusText member does not match the reason-phrase token production, then throw a TypeError.
68     if (!isValidReasonPhrase(init.statusText))
69         return Exception { TypeError, ASCIILiteral("Status text must be a valid reason-phrase.") };
70
71     // 3. Let r be a new Response object associated with a new response.
72     // NOTE: Creation of the Response object is delayed until all potential exceptional cases are handled.
73     
74     // 4. Set r’s headers to a new Headers object, whose header list is r’s response’s header list, and guard is "response".
75     auto headers = FetchHeaders::create(FetchHeaders::Guard::Response);
76
77     // 5. Set r’s response’s status to init’s status member.
78     auto status = init.status;
79     
80     // 6. Set r’s response’s status message to init’s statusText member.
81     auto statusText = init.statusText;
82     
83     // 7. If init’s headers member is present, then fill r’s headers with init’s headers member.
84     if (init.headers) {
85         auto result = headers->fill(*init.headers);
86         if (result.hasException())
87             return result.releaseException();
88     }
89
90     std::optional<FetchBody> extractedBody;
91
92     // 8. If body is non-null, run these substeps:
93     if (body) {
94         // 8.1 If init’s status member is a null body status, then throw a TypeError.
95         //     (NOTE: 101 is included in null body status due to its use elsewhere. It does not affect this step.)
96         if (isNullBodyStatus(init.status))
97             return Exception { TypeError, ASCIILiteral("Response cannot have a body with the given status.") };
98
99         // 8.2 Let Content-Type be null.
100         String contentType;
101
102         // 8.3 Set r’s response’s body and Content-Type to the result of extracting body.
103         extractedBody = FetchBody::extract(context, WTFMove(*body), contentType);
104
105         // 8.4 If Content-Type is non-null and r’s response’s header list does not contain `Content-Type`, then append
106         //     `Content-Type`/Content-Type to r’s response’s header list.
107         if (!contentType.isNull() && !headers->fastHas(HTTPHeaderName::ContentType))
108             headers->fastSet(HTTPHeaderName::ContentType, contentType);
109     }
110
111     // 9. Set r’s MIME type to the result of extracting a MIME type from r’s response’s header list.
112     auto contentType = headers->fastGet(HTTPHeaderName::ContentType);
113
114     // 10. Set r’s response’s HTTPS state to current settings object’s HTTPS state.
115     // FIXME: Implement.
116
117     // 11. Resolve r’s trailer promise with a new Headers object whose guard is "immutable".
118     // FIXME: Implement.
119     
120     // 12. Return r.
121     auto r = adoptRef(*new FetchResponse(context, WTFMove(extractedBody), WTFMove(headers), { }));
122
123     r->m_contentType = contentType;
124     auto mimeType = extractMIMETypeFromMediaType(contentType);
125     r->m_internalResponse.setMimeType(mimeType.isEmpty() ? defaultMIMEType() : mimeType);
126     r->m_internalResponse.setTextEncodingName(extractCharsetFromMediaType(contentType));
127
128     r->m_internalResponse.setHTTPStatusCode(status);
129     r->m_internalResponse.setHTTPStatusText(statusText);
130
131     return WTFMove(r);
132 }
133
134 Ref<FetchResponse> FetchResponse::error(ScriptExecutionContext& context)
135 {
136     auto response = adoptRef(*new FetchResponse(context, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { }));
137     response->m_internalResponse.setType(Type::Error);
138     return response;
139 }
140
141 ExceptionOr<Ref<FetchResponse>> FetchResponse::redirect(ScriptExecutionContext& context, const String& url, int status)
142 {
143     // FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser.
144     URL requestURL = context.completeURL(url);
145     if (!requestURL.isValid() || !requestURL.user().isEmpty() || !requestURL.pass().isEmpty())
146         return Exception { TypeError };
147     if (!ResourceResponse::isRedirectionStatusCode(status))
148         return Exception { RangeError };
149     auto redirectResponse = adoptRef(*new FetchResponse(context, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { }));
150     redirectResponse->m_internalResponse.setHTTPStatusCode(status);
151     redirectResponse->m_internalResponse.setHTTPHeaderField(HTTPHeaderName::Location, requestURL.string());
152     redirectResponse->m_headers->fastSet(HTTPHeaderName::Location, requestURL.string());
153     return WTFMove(redirectResponse);
154 }
155
156 FetchResponse::FetchResponse(ScriptExecutionContext& context, std::optional<FetchBody>&& body, Ref<FetchHeaders>&& headers, ResourceResponse&& response)
157     : FetchBodyOwner(context, WTFMove(body), WTFMove(headers))
158     , m_internalResponse(WTFMove(response))
159 {
160 }
161
162 ExceptionOr<Ref<FetchResponse>> FetchResponse::clone(ScriptExecutionContext& context)
163 {
164     if (isDisturbedOrLocked())
165         return Exception { TypeError };
166
167     ASSERT(scriptExecutionContext());
168
169     // If loading, let's create a stream so that data is teed on both clones.
170     if (isLoading() && !m_readableStreamSource)
171         createReadableStream(*context.execState());
172
173     // Synthetic responses do not store headers in m_internalResponse.
174     if (m_internalResponse.type() == ResourceResponse::Type::Default)
175         m_internalResponse.setHTTPHeaderFields(HTTPHeaderMap { headers().internalHeaders() });
176
177     auto clone = FetchResponse::create(context, std::nullopt, headers().guard(), ResourceResponse { m_internalResponse });
178     clone->cloneBody(*this);
179     clone->m_opaqueLoadIdentifier = m_opaqueLoadIdentifier;
180     clone->m_bodySizeWithPadding = m_bodySizeWithPadding;
181     return WTFMove(clone);
182 }
183
184 void FetchResponse::fetch(ScriptExecutionContext& context, FetchRequest& request, NotificationCallback&& responseCallback)
185 {
186     if (request.hasReadableStreamBody()) {
187         if (responseCallback)
188             responseCallback(Exception { NotSupportedError, "ReadableStream uploading is not supported" });
189         return;
190     }
191     auto response = adoptRef(*new FetchResponse(context, FetchBody { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), { }));
192
193     response->body().consumer().setAsLoading();
194
195     response->m_bodyLoader.emplace(response.get(), WTFMove(responseCallback));
196     if (!response->m_bodyLoader->start(context, request))
197         response->m_bodyLoader = std::nullopt;
198 }
199
200 const String& FetchResponse::url() const
201 {
202     if (m_responseURL.isNull()) {
203         URL url = m_internalResponse.url();
204         url.removeFragmentIdentifier();
205         m_responseURL = url.string();
206     }
207     return m_responseURL;
208 }
209
210 const ResourceResponse& FetchResponse::filteredResponse() const
211 {
212     if (m_filteredResponse)
213         return m_filteredResponse.value();
214     return m_internalResponse;
215 }
216
217 void FetchResponse::BodyLoader::didSucceed()
218 {
219     ASSERT(m_response.hasPendingActivity());
220     m_response.m_body->loadingSucceeded();
221
222 #if ENABLE(STREAMS_API)
223     if (m_response.m_readableStreamSource && !m_response.body().consumer().hasData())
224         m_response.closeStream();
225 #endif
226     if (auto consumeDataCallback = WTFMove(m_consumeDataCallback))
227         consumeDataCallback(m_response.body().consumer().takeData());
228
229     if (m_loader->isStarted()) {
230         Ref<FetchResponse> protector(m_response);
231         m_response.m_bodyLoader = std::nullopt;
232     }
233 }
234
235 void FetchResponse::BodyLoader::didFail(const ResourceError& error)
236 {
237     ASSERT(m_response.hasPendingActivity());
238     if (auto responseCallback = WTFMove(m_responseCallback))
239         responseCallback(Exception { TypeError, String(error.localizedDescription()) });
240
241     if (auto consumeDataCallback = WTFMove(m_consumeDataCallback))
242         consumeDataCallback(Exception { TypeError, String(error.localizedDescription()) });
243
244 #if ENABLE(STREAMS_API)
245     if (m_response.m_readableStreamSource) {
246         if (!m_response.m_readableStreamSource->isCancelling())
247             m_response.m_readableStreamSource->error(ASCIILiteral("Loading failed"));
248         m_response.m_readableStreamSource = nullptr;
249     }
250 #endif
251
252     // Check whether didFail is called as part of FetchLoader::start.
253     if (m_loader->isStarted()) {
254         Ref<FetchResponse> protector(m_response);
255         m_response.m_bodyLoader = std::nullopt;
256     }
257 }
258
259 FetchResponse::BodyLoader::BodyLoader(FetchResponse& response, NotificationCallback&& responseCallback)
260     : m_response(response)
261     , m_responseCallback(WTFMove(responseCallback))
262 {
263     m_response.setPendingActivity(&m_response);
264 }
265
266 FetchResponse::BodyLoader::~BodyLoader()
267 {
268     m_response.unsetPendingActivity(&m_response);
269 }
270
271 static uint64_t nextOpaqueLoadIdentifier { 0 };
272 void FetchResponse::BodyLoader::didReceiveResponse(const ResourceResponse& resourceResponse)
273 {
274     m_response.m_filteredResponse = ResourceResponseBase::filter(resourceResponse);
275     m_response.m_internalResponse = resourceResponse;
276     m_response.m_internalResponse.setType(m_response.m_filteredResponse->type());
277     if (resourceResponse.tainting() == ResourceResponse::Tainting::Opaque) {
278         m_response.m_opaqueLoadIdentifier = ++nextOpaqueLoadIdentifier;
279         m_response.setBodyAsOpaque();
280     }
281
282     m_response.m_headers->filterAndFill(m_response.m_filteredResponse->httpHeaderFields(), FetchHeaders::Guard::Response);
283     m_response.updateContentType();
284
285     if (auto responseCallback = WTFMove(m_responseCallback))
286         responseCallback(m_response);
287 }
288
289 void FetchResponse::BodyLoader::didReceiveData(const char* data, size_t size)
290 {
291 #if ENABLE(STREAMS_API)
292     ASSERT(m_response.m_readableStreamSource);
293     auto& source = *m_response.m_readableStreamSource;
294
295     if (!source.isPulling()) {
296         m_response.body().consumer().append(data, size);
297         return;
298     }
299
300     if (m_response.body().consumer().hasData() && !source.enqueue(m_response.body().consumer().takeAsArrayBuffer())) {
301         stop();
302         return;
303     }
304     if (!source.enqueue(ArrayBuffer::tryCreate(data, size))) {
305         stop();
306         return;
307     }
308     source.resolvePullPromise();
309 #else
310     UNUSED_PARAM(data);
311     UNUSED_PARAM(size);
312 #endif
313 }
314
315 bool FetchResponse::BodyLoader::start(ScriptExecutionContext& context, const FetchRequest& request)
316 {
317     m_loader = std::make_unique<FetchLoader>(*this, &m_response.m_body->consumer());
318     m_loader->start(context, request);
319     return m_loader->isStarted();
320 }
321
322 void FetchResponse::BodyLoader::stop()
323 {
324     m_responseCallback = { };
325     if (m_loader)
326         m_loader->stop();
327 }
328
329 FetchResponse::ResponseData FetchResponse::consumeBody()
330 {
331     ASSERT(!isLoading());
332
333     if (isBodyNull())
334         return nullptr;
335
336     ASSERT(!m_isDisturbed);
337     m_isDisturbed = true;
338
339     return body().take();
340 }
341
342 void FetchResponse::consumeBodyFromReadableStream(ConsumeDataCallback&& callback)
343 {
344     ASSERT(m_body);
345     ASSERT(m_body->readableStream());
346
347     ASSERT(!isDisturbed());
348     m_isDisturbed = true;
349
350     m_body->consumer().extract(*m_body->readableStream(), WTFMove(callback));
351 }
352
353 void FetchResponse::consumeBodyWhenLoaded(ConsumeDataCallback&& callback)
354 {
355     ASSERT(isLoading());
356
357     ASSERT(!isDisturbed());
358     m_isDisturbed = true;
359
360     m_bodyLoader->setConsumeDataCallback(WTFMove(callback));
361 }
362
363 void FetchResponse::setBodyData(ResponseData&& data, uint64_t bodySizeWithPadding)
364 {
365     m_bodySizeWithPadding = bodySizeWithPadding;
366     WTF::switchOn(data,
367         [this](Ref<FormData>& formData) {
368             if (isBodyNull())
369                 setBody({ });
370             body().setAsFormData(WTFMove(formData));
371         },
372         [this](Ref<SharedBuffer>& buffer) {
373             if (isBodyNull())
374                 setBody({ });
375             body().consumer().setData(WTFMove(buffer));
376         },
377         [](std::nullptr_t&) {
378         }
379     );
380 }
381
382 #if ENABLE(STREAMS_API)
383 void FetchResponse::consumeChunk(Ref<JSC::Uint8Array>&& chunk)
384 {
385     body().consumer().append(chunk->data(), chunk->byteLength());
386 }
387
388 void FetchResponse::consumeBodyAsStream()
389 {
390     ASSERT(m_readableStreamSource);
391     if (!isLoading()) {
392         FetchBodyOwner::consumeBodyAsStream();
393         return;
394     }
395
396     ASSERT(m_bodyLoader);
397
398     auto data = m_bodyLoader->startStreaming();
399     if (data) {
400         if (!m_readableStreamSource->enqueue(data->tryCreateArrayBuffer())) {
401             stop();
402             return;
403         }
404         m_readableStreamSource->resolvePullPromise();
405     }
406 }
407
408 void FetchResponse::closeStream()
409 {
410     ASSERT(m_readableStreamSource);
411     m_readableStreamSource->close();
412     m_readableStreamSource = nullptr;
413 }
414
415 void FetchResponse::feedStream()
416 {
417     ASSERT(m_readableStreamSource);
418     bool shouldCloseStream = !m_bodyLoader;
419
420     if (body().consumer().hasData()) {
421         if (!m_readableStreamSource->enqueue(body().consumer().takeAsArrayBuffer())) {
422             stop();
423             return;
424         }
425         if (!shouldCloseStream) {
426             m_readableStreamSource->resolvePullPromise();
427             return;
428         }
429     } else if (!shouldCloseStream)
430         return;
431
432     closeStream();
433 }
434
435 RefPtr<SharedBuffer> FetchResponse::BodyLoader::startStreaming()
436 {
437     ASSERT(m_loader);
438     return m_loader->startStreaming();
439 }
440
441 void FetchResponse::cancel()
442 {
443     m_isDisturbed = true;
444     stop();
445 }
446
447 #endif
448
449 void FetchResponse::stop()
450 {
451     RefPtr<FetchResponse> protectedThis(this);
452     FetchBodyOwner::stop();
453     if (m_bodyLoader) {
454         m_bodyLoader->stop();
455         m_bodyLoader = std::nullopt;
456     }
457 }
458
459 const char* FetchResponse::activeDOMObjectName() const
460 {
461     return "Response";
462 }
463
464 bool FetchResponse::canSuspendForDocumentSuspension() const
465 {
466     // FIXME: We can probably do the same strategy as XHR.
467     return !isActive();
468 }
469
470 ResourceResponse FetchResponse::resourceResponse() const
471 {
472     auto response = m_internalResponse;
473
474     if (headers().guard() != FetchHeaders::Guard::Immutable) {
475         // FIXME: Add a setHTTPHeaderFields on ResourceResponseBase.
476         for (auto& header : headers().internalHeaders())
477             response.setHTTPHeaderField(header.key, header.value);
478     }
479
480     return response;
481 }
482
483 } // namespace WebCore