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