1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32
33#include "FileReaderLoader.h"
34
35#include "Blob.h"
36#include "BlobURL.h"
37#include "FileReaderLoaderClient.h"
38#include "HTTPHeaderNames.h"
39#include "ResourceError.h"
40#include "ResourceRequest.h"
41#include "ResourceResponse.h"
42#include "ScriptExecutionContext.h"
43#include "TextResourceDecoder.h"
44#include "ThreadableBlobRegistry.h"
45#include "ThreadableLoader.h"
46#include <JavaScriptCore/ArrayBuffer.h>
47#include <wtf/RefPtr.h>
48#include <wtf/Vector.h>
49#include <wtf/text/Base64.h>
50#include <wtf/text/StringBuilder.h>
51
52namespace WebCore {
53
54const int defaultBufferLength = 32768;
55
56FileReaderLoader::FileReaderLoader(ReadType readType, FileReaderLoaderClient* client)
57 : m_readType(readType)
58 , m_client(client)
59 , m_isRawDataConverted(false)
60 , m_stringResult(emptyString())
61 , m_variableLength(false)
62 , m_bytesLoaded(0)
63 , m_totalBytes(0)
64{
65}
66
67FileReaderLoader::~FileReaderLoader()
68{
69 terminate();
70 if (!m_urlForReading.isEmpty())
71 ThreadableBlobRegistry::unregisterBlobURL(m_urlForReading);
72}
73
74void FileReaderLoader::start(ScriptExecutionContext* scriptExecutionContext, Blob& blob)
75{
76 ASSERT(scriptExecutionContext);
77
78 // The blob is read by routing through the request handling layer given a temporary public url.
79 m_urlForReading = BlobURL::createPublicURL(scriptExecutionContext->securityOrigin());
80 if (m_urlForReading.isEmpty()) {
81 failed(FileError::SECURITY_ERR);
82 return;
83 }
84 ThreadableBlobRegistry::registerBlobURL(scriptExecutionContext->securityOrigin(), m_urlForReading, blob.url());
85
86 // Construct and load the request.
87 ResourceRequest request(m_urlForReading);
88 request.setHTTPMethod("GET");
89
90 ThreadableLoaderOptions options;
91 options.sendLoadCallbacks = SendCallbackPolicy::SendCallbacks;
92 options.dataBufferingPolicy = DataBufferingPolicy::DoNotBufferData;
93 options.credentials = FetchOptions::Credentials::Include;
94 options.mode = FetchOptions::Mode::SameOrigin;
95 options.contentSecurityPolicyEnforcement = ContentSecurityPolicyEnforcement::DoNotEnforce;
96
97 if (m_client)
98 m_loader = ThreadableLoader::create(*scriptExecutionContext, *this, WTFMove(request), options);
99 else
100 ThreadableLoader::loadResourceSynchronously(*scriptExecutionContext, WTFMove(request), *this, options);
101}
102
103void FileReaderLoader::cancel()
104{
105 m_errorCode = FileError::ABORT_ERR;
106 terminate();
107}
108
109void FileReaderLoader::terminate()
110{
111 if (m_loader) {
112 m_loader->cancel();
113 cleanup();
114 }
115}
116
117void FileReaderLoader::cleanup()
118{
119 m_loader = nullptr;
120
121 // If we get any error, we do not need to keep a buffer around.
122 if (m_errorCode) {
123 m_rawData = nullptr;
124 m_stringResult = emptyString();
125 }
126}
127
128void FileReaderLoader::didReceiveResponse(unsigned long, const ResourceResponse& response)
129{
130 if (response.httpStatusCode() != 200) {
131 failed(httpStatusCodeToErrorCode(response.httpStatusCode()));
132 return;
133 }
134
135 long long length = response.expectedContentLength();
136
137 // A negative value means that the content length wasn't specified, so the buffer will need to be dynamically grown.
138 if (length < 0) {
139 m_variableLength = true;
140 length = defaultBufferLength;
141 }
142
143 // Check that we can cast to unsigned since we have to do
144 // so to call ArrayBuffer's create function.
145 // FIXME: Support reading more than the current size limit of ArrayBuffer.
146 if (length > std::numeric_limits<unsigned>::max()) {
147 failed(FileError::NOT_READABLE_ERR);
148 return;
149 }
150
151 ASSERT(!m_rawData);
152 m_rawData = ArrayBuffer::tryCreate(static_cast<unsigned>(length), 1);
153
154 if (!m_rawData) {
155 failed(FileError::NOT_READABLE_ERR);
156 return;
157 }
158
159 m_totalBytes = static_cast<unsigned>(length);
160
161 if (m_client)
162 m_client->didStartLoading();
163}
164
165void FileReaderLoader::didReceiveData(const char* data, int dataLength)
166{
167 ASSERT(data);
168 ASSERT(dataLength > 0);
169
170 // Bail out if we already encountered an error.
171 if (m_errorCode)
172 return;
173
174 int length = dataLength;
175 unsigned remainingBufferSpace = m_totalBytes - m_bytesLoaded;
176 if (length > static_cast<long long>(remainingBufferSpace)) {
177 // If the buffer has hit maximum size, it can't be grown any more.
178 if (m_totalBytes >= std::numeric_limits<unsigned>::max()) {
179 failed(FileError::NOT_READABLE_ERR);
180 return;
181 }
182 if (m_variableLength) {
183 unsigned newLength = m_totalBytes + static_cast<unsigned>(dataLength);
184 if (newLength < m_totalBytes) {
185 failed(FileError::NOT_READABLE_ERR);
186 return;
187 }
188 newLength = std::max(newLength, m_totalBytes + m_totalBytes / 4 + 1);
189 auto newData = ArrayBuffer::tryCreate(newLength, 1);
190 if (!newData) {
191 // Not enough memory.
192 failed(FileError::NOT_READABLE_ERR);
193 return;
194 }
195 memcpy(static_cast<char*>(newData->data()), static_cast<char*>(m_rawData->data()), m_bytesLoaded);
196
197 m_rawData = newData;
198 m_totalBytes = static_cast<unsigned>(newLength);
199 } else {
200 // This can only happen if we get more data than indicated in expected content length (i.e. never, unless the networking layer is buggy).
201 length = remainingBufferSpace;
202 }
203 }
204
205 if (length <= 0)
206 return;
207
208 memcpy(static_cast<char*>(m_rawData->data()) + m_bytesLoaded, data, length);
209 m_bytesLoaded += length;
210
211 m_isRawDataConverted = false;
212
213 if (m_client)
214 m_client->didReceiveData();
215}
216
217void FileReaderLoader::didFinishLoading(unsigned long)
218{
219 if (m_variableLength && m_totalBytes > m_bytesLoaded) {
220 m_rawData = m_rawData->slice(0, m_bytesLoaded);
221 m_totalBytes = m_bytesLoaded;
222 }
223 cleanup();
224 if (m_client)
225 m_client->didFinishLoading();
226}
227
228void FileReaderLoader::didFail(const ResourceError& error)
229{
230 // If we're aborting, do not proceed with normal error handling since it is covered in aborting code.
231 if (m_errorCode == FileError::ABORT_ERR)
232 return;
233
234 failed(toErrorCode(static_cast<BlobResourceHandle::Error>(error.errorCode())));
235}
236
237void FileReaderLoader::failed(FileError::ErrorCode errorCode)
238{
239 m_errorCode = errorCode;
240 cleanup();
241 if (m_client)
242 m_client->didFail(m_errorCode);
243}
244
245FileError::ErrorCode FileReaderLoader::toErrorCode(BlobResourceHandle::Error error)
246{
247 switch (error) {
248 case BlobResourceHandle::Error::NotFoundError:
249 return FileError::NOT_FOUND_ERR;
250 default:
251 return FileError::NOT_READABLE_ERR;
252 }
253}
254
255FileError::ErrorCode FileReaderLoader::httpStatusCodeToErrorCode(int httpStatusCode)
256{
257 switch (httpStatusCode) {
258 case 403:
259 return FileError::SECURITY_ERR;
260 default:
261 return FileError::NOT_READABLE_ERR;
262 }
263}
264
265RefPtr<ArrayBuffer> FileReaderLoader::arrayBufferResult() const
266{
267 ASSERT(m_readType == ReadAsArrayBuffer);
268
269 // If the loading is not started or an error occurs, return an empty result.
270 if (!m_rawData || m_errorCode)
271 return nullptr;
272
273 // If completed, we can simply return our buffer.
274 if (isCompleted())
275 return m_rawData;
276
277 // Otherwise, return a copy.
278 return ArrayBuffer::create(*m_rawData);
279}
280
281String FileReaderLoader::stringResult()
282{
283 ASSERT(m_readType != ReadAsArrayBuffer && m_readType != ReadAsBlob);
284
285 // If the loading is not started or an error occurs, return an empty result.
286 if (!m_rawData || m_errorCode)
287 return m_stringResult;
288
289 // If already converted from the raw data, return the result now.
290 if (m_isRawDataConverted)
291 return m_stringResult;
292
293 switch (m_readType) {
294 case ReadAsArrayBuffer:
295 // No conversion is needed.
296 break;
297 case ReadAsBinaryString:
298 m_stringResult = String(static_cast<const char*>(m_rawData->data()), m_bytesLoaded);
299 break;
300 case ReadAsText:
301 convertToText();
302 break;
303 case ReadAsDataURL:
304 // Partial data is not supported when reading as data URL.
305 if (isCompleted())
306 convertToDataURL();
307 break;
308 default:
309 ASSERT_NOT_REACHED();
310 }
311
312 return m_stringResult;
313}
314
315void FileReaderLoader::convertToText()
316{
317 if (!m_bytesLoaded)
318 return;
319
320 // Decode the data.
321 // The File API spec says that we should use the supplied encoding if it is valid. However, we choose to ignore this
322 // requirement in order to be consistent with how WebKit decodes the web content: always has the BOM override the
323 // provided encoding.
324 // FIXME: consider supporting incremental decoding to improve the perf.
325 if (!m_decoder)
326 m_decoder = TextResourceDecoder::create("text/plain", m_encoding.isValid() ? m_encoding : UTF8Encoding());
327 if (isCompleted())
328 m_stringResult = m_decoder->decodeAndFlush(static_cast<const char*>(m_rawData->data()), m_bytesLoaded);
329 else
330 m_stringResult = m_decoder->decode(static_cast<const char*>(m_rawData->data()), m_bytesLoaded);
331}
332
333void FileReaderLoader::convertToDataURL()
334{
335 StringBuilder builder;
336 builder.appendLiteral("data:");
337
338 if (!m_bytesLoaded) {
339 m_stringResult = builder.toString();
340 return;
341 }
342
343 builder.append(m_dataType);
344 builder.appendLiteral(";base64,");
345
346 Vector<char> out;
347 base64Encode(m_rawData->data(), m_bytesLoaded, out);
348 out.append('\0');
349 builder.append(out.data());
350
351 m_stringResult = builder.toString();
352}
353
354bool FileReaderLoader::isCompleted() const
355{
356 return m_bytesLoaded == m_totalBytes;
357}
358
359void FileReaderLoader::setEncoding(const String& encoding)
360{
361 if (!encoding.isEmpty())
362 m_encoding = TextEncoding(encoding);
363}
364
365} // namespace WebCore
366