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 "core/fileapi/FileReaderLoader.h"
34
35#include "core/dom/ScriptExecutionContext.h"
36#include "core/fileapi/Blob.h"
37#include "core/fileapi/BlobRegistry.h"
38#include "core/fileapi/BlobURL.h"
39#include "core/fileapi/FileReaderLoaderClient.h"
40#include "core/fileapi/Stream.h"
41#include "core/loader/TextResourceDecoder.h"
42#include "core/loader/ThreadableLoader.h"
43#include "core/platform/network/ResourceRequest.h"
44#include "core/platform/network/ResourceResponse.h"
45#include "wtf/ArrayBuffer.h"
46#include "wtf/PassRefPtr.h"
47#include "wtf/RefPtr.h"
48#include "wtf/Vector.h"
49#include "wtf/text/Base64.h"
50#include "wtf/text/StringBuilder.h"
51
52using namespace std;
53
54namespace WebCore {
55
56const int defaultBufferLength = 32768;
57
58FileReaderLoader::FileReaderLoader(ReadType readType, FileReaderLoaderClient* client)
59    : m_readType(readType)
60    , m_client(client)
61    , m_isRawDataConverted(false)
62    , m_stringResult("")
63    , m_variableLength(false)
64    , m_bytesLoaded(0)
65    , m_totalBytes(0)
66    , m_hasRange(false)
67    , m_rangeStart(0)
68    , m_rangeEnd(0)
69    , m_errorCode(FileError::OK)
70{
71}
72
73FileReaderLoader::~FileReaderLoader()
74{
75    terminate();
76    if (!m_urlForReading.isEmpty())
77        BlobRegistry::unregisterBlobURL(m_urlForReading);
78}
79
80void FileReaderLoader::startForURL(ScriptExecutionContext* scriptExecutionContext, const KURL& url)
81{
82    // The blob is read by routing through the request handling layer given a temporary public url.
83    m_urlForReading = BlobURL::createPublicURL(scriptExecutionContext->securityOrigin());
84    if (m_urlForReading.isEmpty()) {
85        failed(FileError::SECURITY_ERR);
86        return;
87    }
88    BlobRegistry::registerBlobURL(scriptExecutionContext->securityOrigin(), m_urlForReading, url);
89
90    // Construct and load the request.
91    ResourceRequest request(m_urlForReading);
92    request.setHTTPMethod("GET");
93    if (m_hasRange)
94        request.setHTTPHeaderField("Range", String::format("bytes=%d-%d", m_rangeStart, m_rangeEnd));
95
96    ThreadableLoaderOptions options;
97    options.sendLoadCallbacks = SendCallbacks;
98    options.sniffContent = DoNotSniffContent;
99    options.preflightPolicy = ConsiderPreflight;
100    options.allowCredentials = AllowStoredCredentials;
101    options.crossOriginRequestPolicy = DenyCrossOriginRequests;
102    // FIXME: Is there a directive to which this load should be subject?
103    options.contentSecurityPolicyEnforcement = DoNotEnforceContentSecurityPolicy;
104
105    if (m_client)
106        m_loader = ThreadableLoader::create(scriptExecutionContext, this, request, options);
107    else
108        ThreadableLoader::loadResourceSynchronously(scriptExecutionContext, request, *this, options);
109}
110
111void FileReaderLoader::start(ScriptExecutionContext* scriptExecutionContext, const Blob& blob)
112{
113    startForURL(scriptExecutionContext, blob.url());
114}
115
116void FileReaderLoader::start(ScriptExecutionContext* scriptExecutionContext, const Stream& stream)
117{
118    startForURL(scriptExecutionContext, stream.url());
119}
120
121void FileReaderLoader::cancel()
122{
123    m_errorCode = FileError::ABORT_ERR;
124    terminate();
125}
126
127void FileReaderLoader::terminate()
128{
129    if (m_loader) {
130        m_loader->cancel();
131        cleanup();
132    }
133}
134
135void FileReaderLoader::cleanup()
136{
137    m_loader = 0;
138
139    // If we get any error, we do not need to keep a buffer around.
140    if (m_errorCode) {
141        m_rawData = 0;
142        m_stringResult = "";
143    }
144}
145
146void FileReaderLoader::didReceiveResponse(unsigned long, const ResourceResponse& response)
147{
148    if (response.httpStatusCode() != 200) {
149        failed(httpStatusCodeToErrorCode(response.httpStatusCode()));
150        return;
151    }
152
153    unsigned long long length = response.expectedContentLength();
154
155    // A value larger than INT_MAX means that the content length wasn't
156    // specified, so the buffer will need to be dynamically grown.
157    if (length > INT_MAX) {
158        m_variableLength = true;
159        if (m_hasRange)
160            length = 1 + m_rangeEnd - m_rangeStart;
161        else
162            length = defaultBufferLength;
163    }
164
165    // Check that we can cast to unsigned since we have to do
166    // so to call ArrayBuffer's create function.
167    // FIXME: Support reading more than the current size limit of ArrayBuffer.
168    if (length > numeric_limits<unsigned>::max()) {
169        failed(FileError::NOT_READABLE_ERR);
170        return;
171    }
172
173    ASSERT(!m_rawData);
174    m_rawData = ArrayBuffer::create(static_cast<unsigned>(length), 1);
175
176    if (!m_rawData) {
177        failed(FileError::NOT_READABLE_ERR);
178        return;
179    }
180
181    m_totalBytes = static_cast<unsigned>(length);
182
183    if (m_client)
184        m_client->didStartLoading();
185}
186
187void FileReaderLoader::didReceiveData(const char* data, int dataLength)
188{
189    ASSERT(data);
190    ASSERT(dataLength > 0);
191
192    // Bail out if we already encountered an error.
193    if (m_errorCode)
194        return;
195
196    int length = dataLength;
197    unsigned remainingBufferSpace = m_totalBytes - m_bytesLoaded;
198    if (length > static_cast<long long>(remainingBufferSpace)) {
199        // If the buffer has hit maximum size, it can't be grown any more.
200        if (m_totalBytes >= numeric_limits<unsigned>::max()) {
201            failed(FileError::NOT_READABLE_ERR);
202            return;
203        }
204        if (m_variableLength) {
205            unsigned long long newLength = m_totalBytes * 2;
206            if (newLength > numeric_limits<unsigned>::max())
207                newLength = numeric_limits<unsigned>::max();
208            RefPtr<ArrayBuffer> newData =
209                ArrayBuffer::create(static_cast<unsigned>(newLength), 1);
210            memcpy(static_cast<char*>(newData->data()), static_cast<char*>(m_rawData->data()), m_bytesLoaded);
211
212            m_rawData = newData;
213            m_totalBytes = static_cast<unsigned>(newLength);
214        } else
215            length = remainingBufferSpace;
216    }
217
218    if (length <= 0)
219        return;
220
221    memcpy(static_cast<char*>(m_rawData->data()) + m_bytesLoaded, data, length);
222    m_bytesLoaded += length;
223
224    m_isRawDataConverted = false;
225
226    if (m_client)
227        m_client->didReceiveData();
228}
229
230void FileReaderLoader::didFinishLoading(unsigned long, double)
231{
232    if (m_variableLength && m_totalBytes > m_bytesLoaded) {
233        RefPtr<ArrayBuffer> newData = m_rawData->slice(0, m_bytesLoaded);
234
235        m_rawData = newData;
236        m_totalBytes = m_bytesLoaded;
237    }
238    cleanup();
239    if (m_client)
240        m_client->didFinishLoading();
241}
242
243void FileReaderLoader::didFail(const ResourceError&)
244{
245    // If we're aborting, do not proceed with normal error handling since it is covered in aborting code.
246    if (m_errorCode == FileError::ABORT_ERR)
247        return;
248
249    failed(FileError::NOT_READABLE_ERR);
250}
251
252void FileReaderLoader::failed(FileError::ErrorCode errorCode)
253{
254    m_errorCode = errorCode;
255    cleanup();
256    if (m_client)
257        m_client->didFail(m_errorCode);
258}
259
260FileError::ErrorCode FileReaderLoader::httpStatusCodeToErrorCode(int httpStatusCode)
261{
262    switch (httpStatusCode) {
263    case 403:
264        return FileError::SECURITY_ERR;
265    case 404:
266        return FileError::NOT_FOUND_ERR;
267    default:
268        return FileError::NOT_READABLE_ERR;
269    }
270}
271
272PassRefPtr<ArrayBuffer> FileReaderLoader::arrayBufferResult() const
273{
274    ASSERT(m_readType == ReadAsArrayBuffer);
275
276    // If the loading is not started or an error occurs, return an empty result.
277    if (!m_rawData || m_errorCode)
278        return 0;
279
280    // If completed, we can simply return our buffer.
281    if (isCompleted())
282        return m_rawData;
283
284    // Otherwise, return a copy.
285    return m_rawData->slice(0, m_bytesLoaded);
286}
287
288String FileReaderLoader::stringResult()
289{
290    ASSERT(m_readType != ReadAsArrayBuffer && m_readType != ReadAsBlob);
291
292    // If the loading is not started or an error occurs, return an empty result.
293    if (!m_rawData || m_errorCode)
294        return m_stringResult;
295
296    // If already converted from the raw data, return the result now.
297    if (m_isRawDataConverted)
298        return m_stringResult;
299
300    switch (m_readType) {
301    case ReadAsArrayBuffer:
302        // No conversion is needed.
303        break;
304    case ReadAsBinaryString:
305        m_stringResult = String(static_cast<const char*>(m_rawData->data()), m_bytesLoaded);
306        break;
307    case ReadAsText:
308        convertToText();
309        break;
310    case ReadAsDataURL:
311        // Partial data is not supported when reading as data URL.
312        if (isCompleted())
313            convertToDataURL();
314        break;
315    default:
316        ASSERT_NOT_REACHED();
317    }
318
319    return m_stringResult;
320}
321
322void FileReaderLoader::convertToText()
323{
324    if (!m_bytesLoaded)
325        return;
326
327    // Decode the data.
328    // The File API spec says that we should use the supplied encoding if it is valid. However, we choose to ignore this
329    // requirement in order to be consistent with how WebKit decodes the web content: always has the BOM override the
330    // provided encoding.
331    // FIXME: consider supporting incremental decoding to improve the perf.
332    StringBuilder builder;
333    if (!m_decoder)
334        m_decoder = TextResourceDecoder::create("text/plain", m_encoding.isValid() ? m_encoding : UTF8Encoding());
335    builder.append(m_decoder->decode(static_cast<const char*>(m_rawData->data()), m_bytesLoaded));
336
337    if (isCompleted())
338        builder.append(m_decoder->flush());
339
340    m_stringResult = builder.toString();
341}
342
343void FileReaderLoader::convertToDataURL()
344{
345    StringBuilder builder;
346    builder.append("data:");
347
348    if (!m_bytesLoaded) {
349        m_stringResult = builder.toString();
350        return;
351    }
352
353    builder.append(m_dataType);
354    builder.append(";base64,");
355
356    Vector<char> out;
357    base64Encode(static_cast<const char*>(m_rawData->data()), m_bytesLoaded, out);
358    out.append('\0');
359    builder.append(out.data());
360
361    m_stringResult = builder.toString();
362}
363
364bool FileReaderLoader::isCompleted() const
365{
366    return m_bytesLoaded == m_totalBytes;
367}
368
369void FileReaderLoader::setEncoding(const String& encoding)
370{
371    if (!encoding.isEmpty())
372        m_encoding = WTF::TextEncoding(encoding);
373}
374
375} // namespace WebCore
376