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 "FetchBodyOwner.h"
31
32#include "FetchLoader.h"
33#include "HTTPParsers.h"
34#include "JSBlob.h"
35#include "ResourceError.h"
36#include "ResourceResponse.h"
37
38namespace WebCore {
39
40FetchBodyOwner::FetchBodyOwner(ScriptExecutionContext& context, Optional<FetchBody>&& body, Ref<FetchHeaders>&& headers)
41 : ActiveDOMObject(&context)
42 , m_body(WTFMove(body))
43 , m_headers(WTFMove(headers))
44{
45 suspendIfNeeded();
46}
47
48FetchBodyOwner::~FetchBodyOwner()
49{
50 if (m_readableStreamSource)
51 m_readableStreamSource->detach();
52}
53
54void FetchBodyOwner::stop()
55{
56 if (m_body)
57 m_body->cleanConsumer();
58
59 if (m_blobLoader) {
60 bool isUniqueReference = hasOneRef();
61 if (m_blobLoader->loader)
62 m_blobLoader->loader->stop();
63 // After that point, 'this' may be destroyed, since unsetPendingActivity should have been called.
64 ASSERT_UNUSED(isUniqueReference, isUniqueReference || !m_blobLoader);
65 }
66}
67
68bool FetchBodyOwner::isDisturbed() const
69{
70 if (isBodyNull())
71 return false;
72
73 if (m_isDisturbed)
74 return true;
75
76#if ENABLE(STREAMS_API)
77 if (body().readableStream())
78 return body().readableStream()->isDisturbed();
79#endif
80
81 return false;
82}
83
84bool FetchBodyOwner::isDisturbedOrLocked() const
85{
86 if (isBodyNull())
87 return false;
88
89 if (m_isDisturbed)
90 return true;
91
92#if ENABLE(STREAMS_API)
93 if (body().readableStream())
94 return body().readableStream()->isDisturbed() || body().readableStream()->isLocked();
95#endif
96
97 return false;
98}
99
100void FetchBodyOwner::arrayBuffer(Ref<DeferredPromise>&& promise)
101{
102 if (auto exception = loadingException()) {
103 promise->reject(*exception);
104 return;
105 }
106
107 if (isBodyNullOrOpaque()) {
108 fulfillPromiseWithArrayBuffer(WTFMove(promise), nullptr, 0);
109 return;
110 }
111 if (isDisturbedOrLocked()) {
112 promise->reject(Exception { TypeError, "Body is disturbed or locked"_s });
113 return;
114 }
115 m_isDisturbed = true;
116 m_body->arrayBuffer(*this, WTFMove(promise));
117}
118
119void FetchBodyOwner::blob(Ref<DeferredPromise>&& promise)
120{
121 if (auto exception = loadingException()) {
122 promise->reject(*exception);
123 return;
124 }
125
126 if (isBodyNullOrOpaque()) {
127 promise->resolve<IDLInterface<Blob>>(Blob::create(Vector<uint8_t> { }, Blob::normalizedContentType(extractMIMETypeFromMediaType(m_contentType))));
128 return;
129 }
130 if (isDisturbedOrLocked()) {
131 promise->reject(Exception { TypeError, "Body is disturbed or locked"_s });
132 return;
133 }
134 m_isDisturbed = true;
135 m_body->blob(*this, WTFMove(promise), m_contentType);
136}
137
138void FetchBodyOwner::cloneBody(FetchBodyOwner& owner)
139{
140 m_loadingError = owner.m_loadingError;
141
142 m_contentType = owner.m_contentType;
143 if (owner.isBodyNull())
144 return;
145 m_body = owner.m_body->clone();
146}
147
148void FetchBodyOwner::extractBody(ScriptExecutionContext& context, FetchBody::Init&& value)
149{
150 m_body = FetchBody::extract(context, WTFMove(value), m_contentType);
151}
152
153void FetchBodyOwner::updateContentType()
154{
155 String contentType = m_headers->fastGet(HTTPHeaderName::ContentType);
156 if (!contentType.isNull()) {
157 m_contentType = WTFMove(contentType);
158 return;
159 }
160 if (!m_contentType.isNull())
161 m_headers->fastSet(HTTPHeaderName::ContentType, m_contentType);
162}
163
164void FetchBodyOwner::consumeOnceLoadingFinished(FetchBodyConsumer::Type type, Ref<DeferredPromise>&& promise)
165{
166 if (isDisturbedOrLocked()) {
167 promise->reject(Exception { TypeError, "Body is disturbed or locked"_s });
168 return;
169 }
170 m_isDisturbed = true;
171 m_body->consumeOnceLoadingFinished(type, WTFMove(promise), m_contentType);
172}
173
174void FetchBodyOwner::formData(Ref<DeferredPromise>&& promise)
175{
176 if (auto exception = loadingException()) {
177 promise->reject(*exception);
178 return;
179 }
180
181 if (isBodyNullOrOpaque()) {
182 promise->reject();
183 return;
184 }
185 if (isDisturbedOrLocked()) {
186 promise->reject(Exception { TypeError, "Body is disturbed or locked"_s });
187 return;
188 }
189 m_isDisturbed = true;
190 m_body->formData(*this, WTFMove(promise));
191}
192
193void FetchBodyOwner::json(Ref<DeferredPromise>&& promise)
194{
195 if (auto exception = loadingException()) {
196 promise->reject(*exception);
197 return;
198 }
199
200 if (isBodyNullOrOpaque()) {
201 promise->reject(SyntaxError);
202 return;
203 }
204 if (isDisturbedOrLocked()) {
205 promise->reject(Exception { TypeError, "Body is disturbed or locked"_s });
206 return;
207 }
208 m_isDisturbed = true;
209 m_body->json(*this, WTFMove(promise));
210}
211
212void FetchBodyOwner::text(Ref<DeferredPromise>&& promise)
213{
214 if (auto exception = loadingException()) {
215 promise->reject(*exception);
216 return;
217 }
218
219 if (isBodyNullOrOpaque()) {
220 promise->resolve<IDLDOMString>({ });
221 return;
222 }
223 if (isDisturbedOrLocked()) {
224 promise->reject(Exception { TypeError, "Body is disturbed or locked"_s });
225 return;
226 }
227 m_isDisturbed = true;
228 m_body->text(*this, WTFMove(promise));
229}
230
231void FetchBodyOwner::loadBlob(const Blob& blob, FetchBodyConsumer* consumer)
232{
233 // Can only be called once for a body instance.
234 ASSERT(!m_blobLoader);
235 ASSERT(!isBodyNull());
236
237 if (!scriptExecutionContext()) {
238 m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
239 return;
240 }
241
242 m_blobLoader.emplace(*this);
243 m_blobLoader->loader = std::make_unique<FetchLoader>(*m_blobLoader, consumer);
244
245 m_blobLoader->loader->start(*scriptExecutionContext(), blob);
246 if (!m_blobLoader->loader->isStarted()) {
247 m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
248 m_blobLoader = WTF::nullopt;
249 return;
250 }
251 setPendingActivity(*this);
252}
253
254void FetchBodyOwner::finishBlobLoading()
255{
256 ASSERT(m_blobLoader);
257
258 m_blobLoader = WTF::nullopt;
259 unsetPendingActivity(*this);
260}
261
262void FetchBodyOwner::blobLoadingSucceeded()
263{
264 ASSERT(!isBodyNull());
265#if ENABLE(STREAMS_API)
266 if (m_readableStreamSource) {
267 m_readableStreamSource->close();
268 m_readableStreamSource = nullptr;
269 }
270#endif
271 m_body->loadingSucceeded();
272 finishBlobLoading();
273}
274
275void FetchBodyOwner::blobLoadingFailed()
276{
277 ASSERT(!isBodyNull());
278#if ENABLE(STREAMS_API)
279 if (m_readableStreamSource) {
280 if (!m_readableStreamSource->isCancelling())
281 m_readableStreamSource->error(Exception { TypeError, "Blob loading failed"_s});
282 m_readableStreamSource = nullptr;
283 } else
284#endif
285 m_body->loadingFailed(Exception { TypeError, "Blob loading failed"_s});
286
287 finishBlobLoading();
288}
289
290void FetchBodyOwner::blobChunk(const char* data, size_t size)
291{
292 ASSERT(data);
293#if ENABLE(STREAMS_API)
294 ASSERT(m_readableStreamSource);
295 if (!m_readableStreamSource->enqueue(ArrayBuffer::tryCreate(data, size)))
296 stop();
297#else
298 UNUSED_PARAM(data);
299 UNUSED_PARAM(size);
300#endif
301}
302
303FetchBodyOwner::BlobLoader::BlobLoader(FetchBodyOwner& owner)
304 : owner(owner)
305{
306}
307
308void FetchBodyOwner::BlobLoader::didReceiveResponse(const ResourceResponse& response)
309{
310 if (response.httpStatusCode() != 200)
311 didFail({ });
312}
313
314void FetchBodyOwner::BlobLoader::didFail(const ResourceError&)
315{
316 // didFail might be called within FetchLoader::start call.
317 if (loader->isStarted())
318 owner.blobLoadingFailed();
319}
320
321RefPtr<ReadableStream> FetchBodyOwner::readableStream(JSC::ExecState& state)
322{
323 if (isBodyNullOrOpaque())
324 return nullptr;
325
326 if (!m_body->hasReadableStream())
327 createReadableStream(state);
328
329 return m_body->readableStream();
330}
331
332void FetchBodyOwner::createReadableStream(JSC::ExecState& state)
333{
334 ASSERT(!m_readableStreamSource);
335 if (isDisturbed()) {
336 m_body->setReadableStream(ReadableStream::create(state, nullptr));
337 m_body->readableStream()->lock();
338 } else {
339 m_readableStreamSource = adoptRef(*new FetchBodySource(*this));
340 m_body->setReadableStream(ReadableStream::create(state, m_readableStreamSource));
341 }
342}
343
344void FetchBodyOwner::consumeBodyAsStream()
345{
346 ASSERT(m_readableStreamSource);
347
348 if (auto exception = loadingException()) {
349 m_readableStreamSource->error(*exception);
350 return;
351 }
352
353 body().consumeAsStream(*this, *m_readableStreamSource);
354 if (!m_readableStreamSource->isPulling())
355 m_readableStreamSource = nullptr;
356}
357
358ResourceError FetchBodyOwner::loadingError() const
359{
360 return WTF::switchOn(m_loadingError, [](const ResourceError& error) {
361 return ResourceError { error };
362 }, [](const Exception& exception) {
363 return ResourceError { errorDomainWebKitInternal, 0, { }, exception.message() };
364 }, [](auto&&) {
365 return ResourceError { };
366 });
367}
368
369Optional<Exception> FetchBodyOwner::loadingException() const
370{
371 return WTF::switchOn(m_loadingError, [](const ResourceError& error) {
372 return Exception { TypeError, error.localizedDescription().isEmpty() ? "Loading failed"_s : error.localizedDescription() };
373 }, [](const Exception& exception) {
374 return Exception { exception };
375 }, [](auto&&) -> Optional<Exception> {
376 return WTF::nullopt;
377 });
378}
379
380bool FetchBodyOwner::hasLoadingError() const
381{
382 return WTF::switchOn(m_loadingError, [](const ResourceError&) {
383 return true;
384 }, [](const Exception&) {
385 return true;
386 }, [](auto&&) {
387 return false;
388 });
389}
390
391void FetchBodyOwner::setLoadingError(Exception&& exception)
392{
393 if (hasLoadingError())
394 return;
395
396 m_loadingError = WTFMove(exception);
397}
398
399void FetchBodyOwner::setLoadingError(ResourceError&& error)
400{
401 if (hasLoadingError())
402 return;
403
404 m_loadingError = WTFMove(error);
405}
406
407} // namespace WebCore
408