db26eee35df8c3cd782e37c7fad8c42065a31dc8
[WebKit-https.git] / Source / WebCore / Modules / fetch / FetchBodyConsumer.cpp
1 /*
2  * Copyright (C) 2016 Apple 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 Apple 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 APPLE 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 APPLE 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 "FetchBodyConsumer.h"
31
32 #include "JSBlob.h"
33 #include "ReadableStreamChunk.h"
34 #include "TextResourceDecoder.h"
35
36 namespace WebCore {
37
38 static inline Ref<Blob> blobFromData(PAL::SessionID sessionID, const unsigned char* data, unsigned length, const String& contentType)
39 {
40     Vector<uint8_t> value(length);
41     memcpy(value.data(), data, length);
42     return Blob::create(sessionID, WTFMove(value), contentType);
43 }
44
45 static inline bool shouldPrependBOM(const unsigned char* data, unsigned length)
46 {
47     if (length < 3)
48         return true;
49     return data[0] != 0xef || data[1] != 0xbb || data[2] != 0xbf;
50 }
51
52 static String textFromUTF8(const unsigned char* data, unsigned length)
53 {
54     auto decoder = TextResourceDecoder::create("text/plain", "UTF-8");
55     if (shouldPrependBOM(data, length))
56         decoder->decode("\xef\xbb\xbf", 3);
57     return decoder->decodeAndFlush(reinterpret_cast<const char*>(data), length);
58 }
59
60 static void resolveWithTypeAndData(Ref<DeferredPromise>&& promise, FetchBodyConsumer::Type type, const String& contentType, const unsigned char* data, unsigned length)
61 {
62     switch (type) {
63     case FetchBodyConsumer::Type::ArrayBuffer:
64         fulfillPromiseWithArrayBuffer(WTFMove(promise), data, length);
65         return;
66     case FetchBodyConsumer::Type::Blob:
67         promise->resolveCallbackValueWithNewlyCreated<IDLInterface<Blob>>([&data, &length, &contentType](auto& context) {
68             return blobFromData(context.sessionID(), data, length, contentType);
69         });
70         return;
71     case FetchBodyConsumer::Type::JSON:
72         fulfillPromiseWithJSON(WTFMove(promise), textFromUTF8(data, length));
73         return;
74     case FetchBodyConsumer::Type::Text:
75         promise->resolve<IDLDOMString>(textFromUTF8(data, length));
76         return;
77     case FetchBodyConsumer::Type::None:
78         ASSERT_NOT_REACHED();
79         return;
80     }
81 }
82
83 void FetchBodyConsumer::clean()
84 {
85     m_buffer = nullptr;
86     m_consumePromise = nullptr;
87     if (m_sink) {
88         m_sink->clearCallback();
89         return;
90     }
91 }
92
93 void FetchBodyConsumer::resolveWithData(Ref<DeferredPromise>&& promise, const unsigned char* data, unsigned length)
94 {
95     resolveWithTypeAndData(WTFMove(promise), m_type, m_contentType, data, length);
96 }
97
98 void FetchBodyConsumer::extract(ReadableStream& stream, ReadableStreamToSharedBufferSink::Callback&& callback)
99 {
100     ASSERT(!m_sink);
101     m_sink = ReadableStreamToSharedBufferSink::create(WTFMove(callback));
102     m_sink->pipeFrom(stream);
103 }
104
105 void FetchBodyConsumer::resolve(Ref<DeferredPromise>&& promise, ReadableStream* stream)
106 {
107     if (stream) {
108         ASSERT(!m_sink);
109         m_sink = ReadableStreamToSharedBufferSink::create([promise = WTFMove(promise), data = SharedBuffer::create(), type = m_type, contentType = m_contentType](auto&& result) mutable {
110             if (result.hasException()) {
111                 promise->reject(result.releaseException());
112                 return;
113             }
114
115             if (auto chunk = result.returnValue())
116                 data->append(reinterpret_cast<const char*>(chunk->data), chunk->size);
117             else
118                 resolveWithTypeAndData(WTFMove(promise), type, contentType, reinterpret_cast<const unsigned char*>(data->data()), data->size());
119         });
120         m_sink->pipeFrom(*stream);
121         return;
122     }
123
124     if (m_isLoading) {
125         m_consumePromise = WTFMove(promise);
126         return;
127     }
128
129     ASSERT(m_type != Type::None);
130     switch (m_type) {
131     case Type::ArrayBuffer:
132         fulfillPromiseWithArrayBuffer(WTFMove(promise), takeAsArrayBuffer().get());
133         return;
134     case Type::Blob:
135         promise->resolveCallbackValueWithNewlyCreated<IDLInterface<Blob>>([this](auto& context) {
136             return takeAsBlob(context.sessionID());
137         });
138         return;
139     case Type::JSON:
140         fulfillPromiseWithJSON(WTFMove(promise), takeAsText());
141         return;
142     case Type::Text:
143         promise->resolve<IDLDOMString>(takeAsText());
144         return;
145     case Type::None:
146         ASSERT_NOT_REACHED();
147         return;
148     }
149 }
150
151 void FetchBodyConsumer::append(const char* data, unsigned size)
152 {
153     if (m_source) {
154         m_source->enqueue(ArrayBuffer::tryCreate(data, size));
155         return;
156     }
157     if (!m_buffer) {
158         m_buffer = SharedBuffer::create(data, size);
159         return;
160     }
161     m_buffer->append(data, size);
162 }
163
164 void FetchBodyConsumer::append(const unsigned char* data, unsigned size)
165 {
166     append(reinterpret_cast<const char*>(data), size);
167 }
168
169 RefPtr<SharedBuffer> FetchBodyConsumer::takeData()
170 {
171     return WTFMove(m_buffer);
172 }
173
174 RefPtr<JSC::ArrayBuffer> FetchBodyConsumer::takeAsArrayBuffer()
175 {
176     if (!m_buffer)
177         return ArrayBuffer::tryCreate(nullptr, 0);
178
179     auto arrayBuffer = m_buffer->tryCreateArrayBuffer();
180     m_buffer = nullptr;
181     return arrayBuffer;
182 }
183
184 Ref<Blob> FetchBodyConsumer::takeAsBlob(PAL::SessionID sessionID)
185 {
186     if (!m_buffer)
187         return Blob::create(sessionID, Vector<uint8_t>(), m_contentType);
188
189     // FIXME: We should try to move m_buffer to Blob without doing extra copy.
190     return blobFromData(sessionID, reinterpret_cast<const unsigned char*>(m_buffer->data()), m_buffer->size(), m_contentType);
191 }
192
193 String FetchBodyConsumer::takeAsText()
194 {
195     // FIXME: We could probably text decode on the fly as soon as m_type is set to JSON or Text.
196     if (!m_buffer)
197         return String();
198
199     auto text = textFromUTF8(reinterpret_cast<const unsigned char*>(m_buffer->data()), m_buffer->size());
200     m_buffer = nullptr;
201     return text;
202 }
203
204 void FetchBodyConsumer::setConsumePromise(Ref<DeferredPromise>&& promise)
205 {
206     ASSERT(!m_consumePromise);
207     m_consumePromise = WTFMove(promise);
208 }
209
210 void FetchBodyConsumer::setSource(Ref<FetchBodySource>&& source)
211 {
212     m_source = WTFMove(source);
213     if (m_buffer) {
214         m_source->enqueue(m_buffer->tryCreateArrayBuffer());
215         m_buffer = nullptr;
216     }
217 }
218
219 void FetchBodyConsumer::loadingFailed(const Exception& exception)
220 {
221     m_isLoading = false;
222     if (m_consumePromise) {
223         m_consumePromise->reject(exception);
224         m_consumePromise = nullptr;
225     }
226     if (m_source) {
227         m_source->error(exception);
228         m_source = nullptr;
229     }
230 }
231
232 void FetchBodyConsumer::loadingSucceeded()
233 {
234     m_isLoading = false;
235
236     if (m_consumePromise)
237         resolve(m_consumePromise.releaseNonNull(), nullptr);
238     if (m_source) {
239         m_source->close();
240         m_source = nullptr;
241     }
242 }
243
244 } // namespace WebCore