[Fetch API] Add basic loading of resources
[WebKit-https.git] / Source / WebCore / Modules / fetch / FetchBody.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 "FetchBody.h"
31
32 #if ENABLE(FETCH_API)
33
34 #include "DOMRequestState.h"
35 #include "Dictionary.h"
36 #include "ExceptionCode.h"
37 #include "FetchBodyOwner.h"
38 #include "FormData.h"
39 #include "HTTPParsers.h"
40 #include "JSBlob.h"
41 #include "JSDOMFormData.h"
42
43 namespace WebCore {
44
45 static RefPtr<Blob> blobFromArrayBuffer(ArrayBuffer*, const String&);
46
47 FetchBody::FetchBody(Ref<Blob>&& blob)
48     : m_type(Type::Blob)
49     , m_mimeType(blob->type())
50     , m_blob(WTFMove(blob))
51 {
52 }
53
54 FetchBody::FetchBody(Ref<DOMFormData>&& formData)
55     : m_type(Type::FormData)
56     , m_mimeType(ASCIILiteral("multipart/form-data"))
57     , m_formData(WTFMove(formData))
58 {
59     // FIXME: Handle the boundary parameter of multipart/form-data MIME type.
60 }
61
62 FetchBody::FetchBody(String&& text)
63     : m_type(Type::Text)
64     , m_mimeType(ASCIILiteral("text/plain;charset=UTF-8"))
65     , m_text(WTFMove(text))
66 {
67 }
68
69 FetchBody FetchBody::extract(JSC::ExecState& state, JSC::JSValue value)
70 {
71     if (value.inherits(JSBlob::info()))
72         return FetchBody(*JSBlob::toWrapped(value));
73     if (value.inherits(JSDOMFormData::info()))
74         return FetchBody(*JSDOMFormData::toWrapped(value));
75     if (value.isString())
76         return FetchBody(value.toWTFString(&state));
77     return { };
78 }
79
80 FetchBody FetchBody::extractFromBody(FetchBody* body)
81 {
82     if (!body)
83         return { };
84
85     body->m_isDisturbed = true;
86     return FetchBody(WTFMove(*body));
87 }
88
89 bool FetchBody::processIfEmptyOrDisturbed(Consumer::Type type, DeferredWrapper& promise)
90 {
91     if (m_type == Type::None) {
92         switch (type) {
93         case Consumer::Type::Text:
94             promise.resolve(String());
95             return true;
96         case Consumer::Type::Blob:
97             promise.resolve<RefPtr<Blob>>(Blob::create());
98             return true;
99         case Consumer::Type::JSON:
100             promise.reject<ExceptionCode>(SYNTAX_ERR);
101             return true;
102         case Consumer::Type::ArrayBuffer:
103             fulfillPromiseWithArrayBuffer(promise, nullptr, 0);
104             return true;
105         default:
106             ASSERT_NOT_REACHED();
107             promise.reject<ExceptionCode>(0);
108             return true;
109         };
110     }
111
112     if (m_isDisturbed) {
113         promise.reject<ExceptionCode>(TypeError);
114         return true;
115     }
116     m_isDisturbed = true;
117     return false;
118 }
119
120 void FetchBody::arrayBuffer(FetchBodyOwner& owner, DeferredWrapper&& promise)
121 {
122     if (processIfEmptyOrDisturbed(Consumer::Type::ArrayBuffer, promise))
123         return;
124     consume(owner, Consumer::Type::ArrayBuffer, WTFMove(promise));
125 }
126
127 void FetchBody::blob(FetchBodyOwner& owner, DeferredWrapper&& promise)
128 {
129     if (processIfEmptyOrDisturbed(Consumer::Type::Blob, promise))
130         return;
131
132     consume(owner, Consumer::Type::Blob, WTFMove(promise));
133 }
134
135 void FetchBody::json(FetchBodyOwner& owner, DeferredWrapper&& promise)
136 {
137     if (processIfEmptyOrDisturbed(Consumer::Type::JSON, promise))
138         return;
139
140     if (m_type == Type::Text) {
141         fulfillPromiseWithJSON(promise, m_text);
142         return;
143     }
144     consume(owner, Consumer::Type::JSON, WTFMove(promise));
145 }
146
147 void FetchBody::text(FetchBodyOwner& owner, DeferredWrapper&& promise)
148 {
149     if (processIfEmptyOrDisturbed(Consumer::Type::Text, promise))
150         return;
151
152     if (m_type == Type::Text) {
153         promise.resolve(m_text);
154         return;
155     }
156     consume(owner, Consumer::Type::Text, WTFMove(promise));
157 }
158
159 void FetchBody::consume(FetchBodyOwner& owner, Consumer::Type type, DeferredWrapper&& promise)
160 {
161     if (m_type == Type::ArrayBuffer) {
162         consumeArrayBuffer(type, promise);
163         return;
164     }
165     if (m_type == Type::Text) {
166         consumeText(type, promise);
167         return;
168     }
169     if (m_type == Type::Blob) {
170         consumeBlob(owner, type, WTFMove(promise));
171         return;
172     }
173     if (m_type == Type::Loading) {
174         // FIXME: We should be able to change the loading type to text if consumer type is JSON or Text.
175         m_consumer = Consumer({type, WTFMove(promise)});
176         return;
177     }
178
179     // FIXME: Support other types.
180     promise.reject<ExceptionCode>(0);
181 }
182
183 void FetchBody::consumeArrayBuffer(Consumer::Type type, DeferredWrapper& promise)
184 {
185     if (type == Consumer::Type::ArrayBuffer) {
186         fulfillPromiseWithArrayBuffer(promise, m_data.get());
187         return;
188     }
189     if (type == Consumer::Type::Blob) {
190         promise.resolve(blobFromArrayBuffer(m_data.get(), Blob::normalizedContentType(extractMIMETypeFromMediaType(m_mimeType))));
191         return;
192     }
193
194     ASSERT(type == Consumer::Type::Text || type == Consumer::Type::JSON);
195     // FIXME: Do we need TextResourceDecoder to create a String to decode UTF-8 data.
196     fulfillTextPromise(type, TextResourceDecoder::create(ASCIILiteral("text/plain"), "UTF-8")->decodeAndFlush(static_cast<const char*>(m_data->data()), m_data->byteLength()), promise);
197 }
198
199 void FetchBody::consumeText(Consumer::Type type, DeferredWrapper& promise)
200 {
201     ASSERT(type == Consumer::Type::ArrayBuffer || type == Consumer::Type::Blob);
202
203     if (type == Consumer::Type::ArrayBuffer) {
204         Vector<char> data = extractFromText();
205         fulfillPromiseWithArrayBuffer(promise, data.data(), data.size());
206         return;
207     }
208     String contentType = Blob::normalizedContentType(extractMIMETypeFromMediaType(m_mimeType));
209     promise.resolve<RefPtr<Blob>>(Blob::create(extractFromText(), contentType));
210 }
211
212 FetchLoader::Type FetchBody::loadingType(Consumer::Type type)
213 {
214     switch (type) {
215     case Consumer::Type::JSON:
216     case Consumer::Type::Text:
217         return FetchLoader::Type::Text;
218     case Consumer::Type::Blob:
219     case Consumer::Type::ArrayBuffer:
220         return FetchLoader::Type::ArrayBuffer;
221     default:
222         ASSERT_NOT_REACHED();
223         return FetchLoader::Type::ArrayBuffer;
224     };
225 }
226
227 void FetchBody::consumeBlob(FetchBodyOwner& owner, Consumer::Type type, DeferredWrapper&& promise)
228 {
229     ASSERT(m_blob);
230
231     m_consumer = Consumer({type, WTFMove(promise)});
232     owner.loadBlob(*m_blob, loadingType(type));
233 }
234
235 Vector<char> FetchBody::extractFromText() const
236 {
237     ASSERT(m_type == Type::Text);
238     // FIXME: This double allocation is not efficient. Might want to fix that at WTFString level.
239     CString data = m_text.utf8();
240     Vector<char> value(data.length());
241     memcpy(value.data(), data.data(), data.length());
242     return value;
243 }
244
245 static inline RefPtr<Blob> blobFromArrayBuffer(ArrayBuffer* buffer, const String& contentType)
246 {
247     if (!buffer)
248         return Blob::create(Vector<char>(), contentType);
249
250     // FIXME: We should try to move buffer to Blob without doing this copy.
251     Vector<char> value(buffer->byteLength());
252     memcpy(value.data(), buffer->data(), buffer->byteLength());
253     return Blob::create(WTFMove(value), contentType);
254 }
255
256 void FetchBody::fulfillTextPromise(FetchBody::Consumer::Type type, const String& text, DeferredWrapper& promise)
257 {
258     ASSERT(type == Consumer::Type::Text || type == Consumer::Type::JSON);
259     if (type == FetchBody::Consumer::Type::Text)
260         promise.resolve(text);
261     else
262         fulfillPromiseWithJSON(promise, text);
263 }
264
265 void FetchBody::loadingFailed()
266 {
267     ASSERT(m_consumer);
268     m_consumer->promise.reject<ExceptionCode>(0);
269     m_consumer = Nullopt;
270 }
271
272 void FetchBody::loadedAsArrayBuffer(RefPtr<ArrayBuffer>&& buffer)
273 {
274     if (m_type == Type::Loading) {
275         m_type = Type::ArrayBuffer;
276         m_data = buffer;
277         if (m_consumer) {
278             consumeArrayBuffer(m_consumer->type, m_consumer->promise);
279             m_consumer = Nullopt;
280         }
281         return;
282     }
283
284     ASSERT(m_consumer);
285     ASSERT(m_consumer->type == Consumer::Type::Blob || m_consumer->type == Consumer::Type::ArrayBuffer);
286     if (m_consumer->type == Consumer::Type::ArrayBuffer)
287         fulfillPromiseWithArrayBuffer(m_consumer->promise, buffer.get());
288     else {
289         ASSERT(m_blob);
290         m_consumer->promise.resolve(blobFromArrayBuffer(buffer.get(), m_blob->type()));
291     }
292     m_consumer = Nullopt;
293 }
294
295 void FetchBody::loadedAsText(String&& text)
296 {
297     ASSERT(m_consumer);
298     ASSERT(m_consumer->type == Consumer::Type::Text || m_consumer->type == Consumer::Type::JSON);
299
300     fulfillTextPromise(m_consumer->type, text, m_consumer->promise);
301     m_consumer = Nullopt;
302 }
303
304 RefPtr<FormData> FetchBody::bodyForInternalRequest() const
305 {
306     if (m_type == Type::None)
307         return nullptr;
308     if (m_type == Type::Text)
309         return FormData::create(UTF8Encoding().encode(m_text, EntitiesForUnencodables));
310     if (m_type == Type::Blob) {
311         RefPtr<FormData> body = FormData::create();
312         body->appendBlob(m_blob->url());
313         return body;
314     }
315     ASSERT_NOT_REACHED();
316     return nullptr;
317 }
318
319 }
320
321 #endif // ENABLE(FETCH_API)