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