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