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