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