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