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 "FetchBody.h"
31
32#include "Document.h"
33#include "FetchBodyOwner.h"
34#include "FetchBodySource.h"
35#include "FetchHeaders.h"
36#include "HTTPHeaderValues.h"
37#include "HTTPParsers.h"
38#include "ReadableStreamSource.h"
39#include <JavaScriptCore/ArrayBufferView.h>
40
41namespace WebCore {
42
43FetchBody FetchBody::extract(ScriptExecutionContext& context, Init&& value, String& contentType)
44{
45 return WTF::switchOn(value, [&](RefPtr<Blob>& value) mutable {
46 Ref<const Blob> blob = value.releaseNonNull();
47 if (!blob->type().isEmpty())
48 contentType = blob->type();
49 return FetchBody(WTFMove(blob));
50 }, [&](RefPtr<DOMFormData>& value) mutable {
51 Ref<DOMFormData> domFormData = value.releaseNonNull();
52 auto formData = FormData::createMultiPart(domFormData.get(), &downcast<Document>(context));
53 contentType = makeString("multipart/form-data; boundary=", formData->boundary().data());
54 return FetchBody(WTFMove(formData));
55 }, [&](RefPtr<URLSearchParams>& value) mutable {
56 Ref<const URLSearchParams> params = value.releaseNonNull();
57 contentType = HTTPHeaderValues::formURLEncodedContentType();
58 return FetchBody(WTFMove(params));
59 }, [&](RefPtr<ArrayBuffer>& value) mutable {
60 Ref<const ArrayBuffer> buffer = value.releaseNonNull();
61 return FetchBody(WTFMove(buffer));
62 }, [&](RefPtr<ArrayBufferView>& value) mutable {
63 Ref<const ArrayBufferView> buffer = value.releaseNonNull();
64 return FetchBody(WTFMove(buffer));
65 }, [&](RefPtr<ReadableStream>& stream) mutable {
66 return FetchBody(stream.releaseNonNull());
67 }, [&](String& value) {
68 contentType = HTTPHeaderValues::textPlainContentType();
69 return FetchBody(WTFMove(value));
70 });
71}
72
73Optional<FetchBody> FetchBody::fromFormData(FormData& formData)
74{
75 ASSERT(!formData.isEmpty());
76
77 if (auto buffer = formData.asSharedBuffer()) {
78 FetchBody body;
79 body.m_consumer.setData(buffer.releaseNonNull());
80 return body;
81 }
82
83 auto url = formData.asBlobURL();
84 if (!url.isNull()) {
85 // FIXME: Properly set mime type and size of the blob.
86 Ref<const Blob> blob = Blob::deserialize(url, { }, 0, { });
87 return FetchBody { WTFMove(blob) };
88 }
89
90 // FIXME: Support form data bodies.
91 return WTF::nullopt;
92}
93
94void FetchBody::arrayBuffer(FetchBodyOwner& owner, Ref<DeferredPromise>&& promise)
95{
96 m_consumer.setType(FetchBodyConsumer::Type::ArrayBuffer);
97 consume(owner, WTFMove(promise));
98}
99
100void FetchBody::blob(FetchBodyOwner& owner, Ref<DeferredPromise>&& promise, const String& contentType)
101{
102 m_consumer.setType(FetchBodyConsumer::Type::Blob);
103 m_consumer.setContentType(Blob::normalizedContentType(extractMIMETypeFromMediaType(contentType)));
104 consume(owner, WTFMove(promise));
105}
106
107void FetchBody::json(FetchBodyOwner& owner, Ref<DeferredPromise>&& promise)
108{
109 if (isText()) {
110 fulfillPromiseWithJSON(WTFMove(promise), textBody());
111 return;
112 }
113 m_consumer.setType(FetchBodyConsumer::Type::JSON);
114 consume(owner, WTFMove(promise));
115}
116
117void FetchBody::text(FetchBodyOwner& owner, Ref<DeferredPromise>&& promise)
118{
119 if (isText()) {
120 promise->resolve<IDLDOMString>(textBody());
121 return;
122 }
123 m_consumer.setType(FetchBodyConsumer::Type::Text);
124 consume(owner, WTFMove(promise));
125}
126
127void FetchBody::consumeOnceLoadingFinished(FetchBodyConsumer::Type type, Ref<DeferredPromise>&& promise, const String& contentType)
128{
129 m_consumer.setType(type);
130 m_consumer.setConsumePromise(WTFMove(promise));
131 if (type == FetchBodyConsumer::Type::Blob)
132 m_consumer.setContentType(Blob::normalizedContentType(extractMIMETypeFromMediaType(contentType)));
133}
134
135void FetchBody::consume(FetchBodyOwner& owner, Ref<DeferredPromise>&& promise)
136{
137 if (isArrayBuffer()) {
138 consumeArrayBuffer(WTFMove(promise));
139 return;
140 }
141 if (isArrayBufferView()) {
142 consumeArrayBufferView(WTFMove(promise));
143 return;
144 }
145 if (isText()) {
146 consumeText(WTFMove(promise), textBody());
147 return;
148 }
149 if (isURLSearchParams()) {
150 consumeText(WTFMove(promise), urlSearchParamsBody().toString());
151 return;
152 }
153 if (isBlob()) {
154 consumeBlob(owner, WTFMove(promise));
155 return;
156 }
157 if (isFormData()) {
158 // FIXME: Support consuming FormData.
159 promise->reject(NotSupportedError);
160 return;
161 }
162
163 m_consumer.resolve(WTFMove(promise), m_readableStream.get());
164}
165
166#if ENABLE(STREAMS_API)
167
168void FetchBody::consumeAsStream(FetchBodyOwner& owner, FetchBodySource& source)
169{
170 bool closeStream = false;
171 if (isArrayBuffer()) {
172 closeStream = source.enqueue(ArrayBuffer::tryCreate(arrayBufferBody().data(), arrayBufferBody().byteLength()));
173 m_data = nullptr;
174 } else if (isArrayBufferView()) {
175 closeStream = source.enqueue(ArrayBuffer::tryCreate(arrayBufferViewBody().baseAddress(), arrayBufferViewBody().byteLength()));
176 m_data = nullptr;
177 } else if (isText()) {
178 auto data = UTF8Encoding().encode(textBody(), UnencodableHandling::Entities);
179 closeStream = source.enqueue(ArrayBuffer::tryCreate(data.data(), data.size()));
180 m_data = nullptr;
181 } else if (isURLSearchParams()) {
182 auto data = UTF8Encoding().encode(urlSearchParamsBody().toString(), UnencodableHandling::Entities);
183 closeStream = source.enqueue(ArrayBuffer::tryCreate(data.data(), data.size()));
184 m_data = nullptr;
185 } else if (isBlob()) {
186 owner.loadBlob(blobBody(), nullptr);
187 m_data = nullptr;
188 } else if (isFormData())
189 source.error(Exception { NotSupportedError, "Not implemented"_s });
190 else if (m_consumer.hasData())
191 closeStream = source.enqueue(m_consumer.takeAsArrayBuffer());
192 else
193 closeStream = true;
194
195 if (closeStream)
196 source.close();
197}
198
199#endif
200
201void FetchBody::consumeArrayBuffer(Ref<DeferredPromise>&& promise)
202{
203 m_consumer.resolveWithData(WTFMove(promise), static_cast<const uint8_t*>(arrayBufferBody().data()), arrayBufferBody().byteLength());
204 m_data = nullptr;
205}
206
207void FetchBody::consumeArrayBufferView(Ref<DeferredPromise>&& promise)
208{
209 m_consumer.resolveWithData(WTFMove(promise), static_cast<const uint8_t*>(arrayBufferViewBody().baseAddress()), arrayBufferViewBody().byteLength());
210 m_data = nullptr;
211}
212
213void FetchBody::consumeText(Ref<DeferredPromise>&& promise, const String& text)
214{
215 auto data = UTF8Encoding().encode(text, UnencodableHandling::Entities);
216 m_consumer.resolveWithData(WTFMove(promise), data.data(), data.size());
217 m_data = nullptr;
218}
219
220void FetchBody::consumeBlob(FetchBodyOwner& owner, Ref<DeferredPromise>&& promise)
221{
222 m_consumer.setConsumePromise(WTFMove(promise));
223 owner.loadBlob(blobBody(), &m_consumer);
224 m_data = nullptr;
225}
226
227void FetchBody::loadingFailed(const Exception& exception)
228{
229 m_consumer.loadingFailed(exception);
230}
231
232void FetchBody::loadingSucceeded()
233{
234 m_consumer.loadingSucceeded();
235}
236
237RefPtr<FormData> FetchBody::bodyAsFormData(ScriptExecutionContext& context) const
238{
239 if (isText())
240 return FormData::create(UTF8Encoding().encode(textBody(), UnencodableHandling::Entities));
241 if (isURLSearchParams())
242 return FormData::create(UTF8Encoding().encode(urlSearchParamsBody().toString(), UnencodableHandling::Entities));
243 if (isBlob()) {
244 auto body = FormData::create();
245 body->appendBlob(blobBody().url());
246 return body;
247 }
248 if (isArrayBuffer())
249 return FormData::create(arrayBufferBody().data(), arrayBufferBody().byteLength());
250 if (isArrayBufferView())
251 return FormData::create(arrayBufferViewBody().baseAddress(), arrayBufferViewBody().byteLength());
252 if (isFormData()) {
253 ASSERT(!context.isWorkerGlobalScope());
254 auto body = makeRef(const_cast<FormData&>(formDataBody()));
255 body->generateFiles(&downcast<Document>(context));
256 return body;
257 }
258 if (auto* data = m_consumer.data())
259 return FormData::create(data->data(), data->size());
260
261 ASSERT_NOT_REACHED();
262 return nullptr;
263}
264
265FetchBody::TakenData FetchBody::take()
266{
267 if (m_consumer.hasData()) {
268 auto buffer = m_consumer.takeData();
269 if (!buffer)
270 return nullptr;
271 return buffer.releaseNonNull();
272 }
273
274 if (isBlob()) {
275 auto body = FormData::create();
276 body->appendBlob(blobBody().url());
277 return TakenData { WTFMove(body) };
278 }
279
280 if (isFormData())
281 return formDataBody();
282
283 if (isText())
284 return SharedBuffer::create(UTF8Encoding().encode(textBody(), UnencodableHandling::Entities));
285 if (isURLSearchParams())
286 return SharedBuffer::create(UTF8Encoding().encode(urlSearchParamsBody().toString(), UnencodableHandling::Entities));
287
288 if (isArrayBuffer())
289 return SharedBuffer::create(reinterpret_cast<const char*>(arrayBufferBody().data()), arrayBufferBody().byteLength());
290 if (isArrayBufferView())
291 return SharedBuffer::create(reinterpret_cast<const uint8_t*>(arrayBufferViewBody().baseAddress()), arrayBufferViewBody().byteLength());
292
293 return nullptr;
294}
295
296FetchBody FetchBody::clone()
297{
298 FetchBody clone(m_consumer);
299
300 if (isArrayBuffer())
301 clone.m_data = arrayBufferBody();
302 else if (isArrayBufferView())
303 clone.m_data = arrayBufferViewBody();
304 else if (isBlob())
305 clone.m_data = blobBody();
306 else if (isFormData())
307 clone.m_data = const_cast<FormData&>(formDataBody());
308 else if (isText())
309 clone.m_data = textBody();
310 else if (isURLSearchParams())
311 clone.m_data = urlSearchParamsBody();
312
313 if (m_readableStream) {
314 auto clones = m_readableStream->tee();
315 m_readableStream = WTFMove(clones.first);
316 clone.m_readableStream = WTFMove(clones.second);
317 }
318 return clone;
319}
320
321}
322