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 | |
41 | namespace WebCore { |
42 | |
43 | FetchBody 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 | |
73 | Optional<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 | |
94 | void FetchBody::arrayBuffer(FetchBodyOwner& owner, Ref<DeferredPromise>&& promise) |
95 | { |
96 | m_consumer.setType(FetchBodyConsumer::Type::ArrayBuffer); |
97 | consume(owner, WTFMove(promise)); |
98 | } |
99 | |
100 | void 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 | |
107 | void 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 | |
117 | void 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 | |
127 | void 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 | |
135 | void 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 | |
168 | void 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 | |
201 | void 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 | |
207 | void 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 | |
213 | void 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 | |
220 | void 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 | |
227 | void FetchBody::loadingFailed(const Exception& exception) |
228 | { |
229 | m_consumer.loadingFailed(exception); |
230 | } |
231 | |
232 | void FetchBody::loadingSucceeded() |
233 | { |
234 | m_consumer.loadingSucceeded(); |
235 | } |
236 | |
237 | RefPtr<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 | |
265 | FetchBody::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 | |
296 | FetchBody 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 | |