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#if ENABLE(BLOB) 34 35#include "BlobResourceHandle.h" 36 37#include "AsyncFileStream.h" 38#include "BlobStorageData.h" 39#include "FileStream.h" 40#include "FileSystem.h" 41#include "HTTPParsers.h" 42#include "KURL.h" 43#include "ResourceError.h" 44#include "ResourceHandleClient.h" 45#include "ResourceRequest.h" 46#include "ResourceResponse.h" 47#include "SharedBuffer.h" 48#include <wtf/MainThread.h> 49 50namespace WebCore { 51 52static const unsigned bufferSize = 512 * 1024; 53static const long long positionNotSpecified = -1; 54 55static const int httpOK = 200; 56static const int httpPartialContent = 206; 57static const int httpNotAllowed = 403; 58static const int httpNotFound = 404; 59static const int httpRequestedRangeNotSatisfiable = 416; 60static const int httpInternalError = 500; 61static const char* httpOKText = "OK"; 62static const char* httpPartialContentText = "Partial Content"; 63static const char* httpNotAllowedText = "Not Allowed"; 64static const char* httpNotFoundText = "Not Found"; 65static const char* httpRequestedRangeNotSatisfiableText = "Requested Range Not Satisfiable"; 66static const char* httpInternalErrorText = "Internal Server Error"; 67 68static const char* const webKitBlobResourceDomain = "WebKitBlobResource"; 69enum { 70 notFoundError = 1, 71 securityError = 2, 72 rangeError = 3, 73 notReadableError = 4, 74 methodNotAllowed = 5 75}; 76 77/////////////////////////////////////////////////////////////////////////////// 78// BlobResourceSynchronousLoader 79 80namespace { 81 82class BlobResourceSynchronousLoader : public ResourceHandleClient { 83public: 84 BlobResourceSynchronousLoader(ResourceError&, ResourceResponse&, Vector<char>&); 85 86 virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&) OVERRIDE; 87 virtual void didReceiveData(ResourceHandle*, const char*, int, int /*encodedDataLength*/) OVERRIDE; 88 virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/) OVERRIDE; 89 virtual void didFail(ResourceHandle*, const ResourceError&) OVERRIDE; 90 91private: 92 ResourceError& m_error; 93 ResourceResponse& m_response; 94 Vector<char>& m_data; 95}; 96 97BlobResourceSynchronousLoader::BlobResourceSynchronousLoader(ResourceError& error, ResourceResponse& response, Vector<char>& data) 98 : m_error(error) 99 , m_response(response) 100 , m_data(data) 101{ 102} 103 104void BlobResourceSynchronousLoader::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response) 105{ 106 // We cannot handle the size that is more than maximum integer. 107 if (response.expectedContentLength() > INT_MAX) { 108 m_error = ResourceError(webKitBlobResourceDomain, notReadableError, response.url(), "File is too large"); 109 return; 110 } 111 112 m_response = response; 113 114 // Read all the data. 115 m_data.resize(static_cast<size_t>(response.expectedContentLength())); 116 static_cast<BlobResourceHandle*>(handle)->readSync(m_data.data(), static_cast<int>(m_data.size())); 117} 118 119void BlobResourceSynchronousLoader::didReceiveData(ResourceHandle*, const char*, int, int) 120{ 121} 122 123void BlobResourceSynchronousLoader::didFinishLoading(ResourceHandle*, double) 124{ 125} 126 127void BlobResourceSynchronousLoader::didFail(ResourceHandle*, const ResourceError& error) 128{ 129 m_error = error; 130} 131 132} 133 134/////////////////////////////////////////////////////////////////////////////// 135// BlobResourceHandle 136 137PassRefPtr<BlobResourceHandle> BlobResourceHandle::createAsync(BlobStorageData* blobData, const ResourceRequest& request, ResourceHandleClient* client) 138{ 139 // FIXME: Should probably call didFail() instead of blocking the load without explanation. 140 if (!equalIgnoringCase(request.httpMethod(), "GET")) 141 return 0; 142 143 return adoptRef(new BlobResourceHandle(blobData, request, client, true)); 144} 145 146void BlobResourceHandle::loadResourceSynchronously(BlobStorageData* blobData, const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data) 147{ 148 if (!equalIgnoringCase(request.httpMethod(), "GET")) { 149 error = ResourceError(webKitBlobResourceDomain, methodNotAllowed, response.url(), "Request method must be GET"); 150 return; 151 } 152 153 BlobResourceSynchronousLoader loader(error, response, data); 154 RefPtr<BlobResourceHandle> handle = adoptRef(new BlobResourceHandle(blobData, request, &loader, false)); 155 handle->start(); 156} 157 158BlobResourceHandle::BlobResourceHandle(PassRefPtr<BlobStorageData> blobData, const ResourceRequest& request, ResourceHandleClient* client, bool async) 159 : ResourceHandle(0, request, client, false, false) 160 , m_blobData(blobData) 161 , m_async(async) 162 , m_errorCode(0) 163 , m_aborted(false) 164 , m_rangeOffset(positionNotSpecified) 165 , m_rangeEnd(positionNotSpecified) 166 , m_rangeSuffixLength(positionNotSpecified) 167 , m_totalRemainingSize(0) 168 , m_currentItemReadSize(0) 169 , m_sizeItemCount(0) 170 , m_readItemCount(0) 171 , m_fileOpened(false) 172{ 173 if (m_async) 174 m_asyncStream = AsyncFileStream::create(this); 175 else 176 m_stream = FileStream::create(); 177} 178 179BlobResourceHandle::~BlobResourceHandle() 180{ 181 if (m_async) { 182 if (m_asyncStream) 183 m_asyncStream->stop(); 184 } else { 185 if (m_stream) 186 m_stream->stop(); 187 } 188} 189 190void BlobResourceHandle::cancel() 191{ 192 if (m_async) { 193 if (m_asyncStream) { 194 m_asyncStream->stop(); 195 m_asyncStream = 0; 196 } 197 } 198 199 m_aborted = true; 200 201 ResourceHandle::cancel(); 202} 203 204void delayedStartBlobResourceHandle(void* context) 205{ 206 RefPtr<BlobResourceHandle> handle = adoptRef(static_cast<BlobResourceHandle*>(context)); 207 handle->doStart(); 208} 209 210void BlobResourceHandle::start() 211{ 212 if (m_async) { 213 // Keep BlobResourceHandle alive until delayedStartBlobResourceHandle runs. 214 ref(); 215 216 // Finish this async call quickly and return. 217 callOnMainThread(delayedStartBlobResourceHandle, this); 218 return; 219 } 220 221 doStart(); 222} 223 224void BlobResourceHandle::doStart() 225{ 226 // Do not continue if the request is aborted or an error occurs. 227 if (m_aborted || m_errorCode) 228 return; 229 230 // If the blob data is not found, fail now. 231 if (!m_blobData) { 232 m_errorCode = notFoundError; 233 notifyResponse(); 234 return; 235 } 236 237 // Parse the "Range" header we care about. 238 String range = firstRequest().httpHeaderField("Range"); 239 if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) { 240 m_errorCode = rangeError; 241 notifyResponse(); 242 return; 243 } 244 245 if (m_async) 246 getSizeForNext(); 247 else { 248 RefPtr<BlobResourceHandle> protect(this); // getSizeForNext calls the client 249 for (size_t i = 0; i < m_blobData->items().size() && !m_aborted && !m_errorCode; ++i) 250 getSizeForNext(); 251 notifyResponse(); 252 } 253} 254 255void BlobResourceHandle::getSizeForNext() 256{ 257 // Do we finish validating and counting size for all items? 258 if (m_sizeItemCount >= m_blobData->items().size()) { 259 seek(); 260 261 // Start reading if in asynchronous mode. 262 if (m_async) { 263 RefPtr<BlobResourceHandle> protect(this); 264 notifyResponse(); 265 m_buffer.resize(bufferSize); 266 readAsync(); 267 } 268 return; 269 } 270 271 const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount); 272 switch (item.type) { 273 case BlobDataItem::Data: 274 didGetSize(item.length); 275 break; 276 case BlobDataItem::File: 277 if (m_async) 278 m_asyncStream->getSize(item.path, item.expectedModificationTime); 279 else 280 didGetSize(m_stream->getSize(item.path, item.expectedModificationTime)); 281 break; 282 default: 283 ASSERT_NOT_REACHED(); 284 } 285} 286 287void BlobResourceHandle::didGetSize(long long size) 288{ 289 // Do not continue if the request is aborted or an error occurs. 290 if (m_aborted || m_errorCode) 291 return; 292 293 // If the size is -1, it means the file has been moved or changed. Fail now. 294 if (size == -1) { 295 m_errorCode = notFoundError; 296 notifyResponse(); 297 return; 298 } 299 300 // The size passed back is the size of the whole file. If the underlying item is a sliced file, we need to use the slice length. 301 const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount); 302 if (item.type == BlobDataItem::File && item.length != BlobDataItem::toEndOfFile) 303 size = item.length; 304 305 // Cache the size. 306 m_itemLengthList.append(size); 307 308 // Count the size. 309 m_totalRemainingSize += size; 310 m_sizeItemCount++; 311 312 // Continue with the next item. 313 getSizeForNext(); 314} 315 316void BlobResourceHandle::seek() 317{ 318 // Convert from the suffix length to the range. 319 if (m_rangeSuffixLength != positionNotSpecified) { 320 m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength; 321 m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1; 322 } 323 324 // Bail out if the range is not provided. 325 if (m_rangeOffset == positionNotSpecified) 326 return; 327 328 // Skip the initial items that are not in the range. 329 long long offset = m_rangeOffset; 330 for (m_readItemCount = 0; m_readItemCount < m_blobData->items().size() && offset >= m_itemLengthList[m_readItemCount]; ++m_readItemCount) 331 offset -= m_itemLengthList[m_readItemCount]; 332 333 // Set the offset that need to jump to for the first item in the range. 334 m_currentItemReadSize = offset; 335 336 // Adjust the total remaining size in order not to go beyond the range. 337 if (m_rangeEnd != positionNotSpecified) { 338 long long rangeSize = m_rangeEnd - m_rangeOffset + 1; 339 if (m_totalRemainingSize > rangeSize) 340 m_totalRemainingSize = rangeSize; 341 } else 342 m_totalRemainingSize -= m_rangeOffset; 343} 344 345int BlobResourceHandle::readSync(char* buf, int length) 346{ 347 ASSERT(!m_async); 348 RefPtr<BlobResourceHandle> protect(this); 349 350 int offset = 0; 351 int remaining = length; 352 while (remaining) { 353 // Do not continue if the request is aborted or an error occurs. 354 if (m_aborted || m_errorCode) 355 break; 356 357 // If there is no more remaining data to read, we are done. 358 if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) 359 break; 360 361 const BlobDataItem& item = m_blobData->items().at(m_readItemCount); 362 int bytesRead = 0; 363 if (item.type == BlobDataItem::Data) 364 bytesRead = readDataSync(item, buf + offset, remaining); 365 else if (item.type == BlobDataItem::File) 366 bytesRead = readFileSync(item, buf + offset, remaining); 367 else 368 ASSERT_NOT_REACHED(); 369 370 if (bytesRead > 0) { 371 offset += bytesRead; 372 remaining -= bytesRead; 373 } 374 } 375 376 int result; 377 if (m_aborted || m_errorCode) 378 result = -1; 379 else 380 result = length - remaining; 381 382 if (result > 0) 383 notifyReceiveData(buf, result); 384 385 if (!result) 386 notifyFinish(); 387 388 return result; 389} 390 391int BlobResourceHandle::readDataSync(const BlobDataItem& item, char* buf, int length) 392{ 393 ASSERT(!m_async); 394 395 long long remaining = item.length - m_currentItemReadSize; 396 int bytesToRead = (length > remaining) ? static_cast<int>(remaining) : length; 397 if (bytesToRead > m_totalRemainingSize) 398 bytesToRead = static_cast<int>(m_totalRemainingSize); 399 memcpy(buf, item.data->data() + item.offset + m_currentItemReadSize, bytesToRead); 400 m_totalRemainingSize -= bytesToRead; 401 402 m_currentItemReadSize += bytesToRead; 403 if (m_currentItemReadSize == item.length) { 404 m_readItemCount++; 405 m_currentItemReadSize = 0; 406 } 407 408 return bytesToRead; 409} 410 411int BlobResourceHandle::readFileSync(const BlobDataItem& item, char* buf, int length) 412{ 413 ASSERT(!m_async); 414 415 if (!m_fileOpened) { 416 long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize; 417 if (bytesToRead > m_totalRemainingSize) 418 bytesToRead = m_totalRemainingSize; 419 bool success = m_stream->openForRead(item.path, item.offset + m_currentItemReadSize, bytesToRead); 420 m_currentItemReadSize = 0; 421 if (!success) { 422 m_errorCode = notReadableError; 423 return 0; 424 } 425 426 m_fileOpened = true; 427 } 428 429 int bytesRead = m_stream->read(buf, length); 430 if (bytesRead < 0) { 431 m_errorCode = notReadableError; 432 return 0; 433 } 434 if (!bytesRead) { 435 m_stream->close(); 436 m_fileOpened = false; 437 m_readItemCount++; 438 } else 439 m_totalRemainingSize -= bytesRead; 440 441 return bytesRead; 442} 443 444void BlobResourceHandle::readAsync() 445{ 446 ASSERT(m_async); 447 448 // Do not continue if the request is aborted or an error occurs. 449 if (m_aborted || m_errorCode) 450 return; 451 452 // If there is no more remaining data to read, we are done. 453 if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) { 454 notifyFinish(); 455 return; 456 } 457 458 const BlobDataItem& item = m_blobData->items().at(m_readItemCount); 459 if (item.type == BlobDataItem::Data) 460 readDataAsync(item); 461 else if (item.type == BlobDataItem::File) 462 readFileAsync(item); 463 else 464 ASSERT_NOT_REACHED(); 465} 466 467void BlobResourceHandle::readDataAsync(const BlobDataItem& item) 468{ 469 ASSERT(m_async); 470 RefPtr<BlobResourceHandle> protect(this); 471 472 long long bytesToRead = item.length - m_currentItemReadSize; 473 if (bytesToRead > m_totalRemainingSize) 474 bytesToRead = m_totalRemainingSize; 475 consumeData(item.data->data() + item.offset + m_currentItemReadSize, static_cast<int>(bytesToRead)); 476 m_currentItemReadSize = 0; 477} 478 479void BlobResourceHandle::readFileAsync(const BlobDataItem& item) 480{ 481 ASSERT(m_async); 482 483 if (m_fileOpened) { 484 m_asyncStream->read(m_buffer.data(), m_buffer.size()); 485 return; 486 } 487 488 long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize; 489 if (bytesToRead > m_totalRemainingSize) 490 bytesToRead = static_cast<int>(m_totalRemainingSize); 491 m_asyncStream->openForRead(item.path, item.offset + m_currentItemReadSize, bytesToRead); 492 m_fileOpened = true; 493 m_currentItemReadSize = 0; 494} 495 496void BlobResourceHandle::didOpen(bool success) 497{ 498 ASSERT(m_async); 499 500 if (!success) { 501 failed(notReadableError); 502 return; 503 } 504 505 // Continue the reading. 506 readAsync(); 507} 508 509void BlobResourceHandle::didRead(int bytesRead) 510{ 511 if (bytesRead < 0) { 512 failed(notReadableError); 513 return; 514 } 515 516 consumeData(m_buffer.data(), bytesRead); 517} 518 519void BlobResourceHandle::consumeData(const char* data, int bytesRead) 520{ 521 ASSERT(m_async); 522 RefPtr<BlobResourceHandle> protect(this); 523 524 m_totalRemainingSize -= bytesRead; 525 526 // Notify the client. 527 if (bytesRead) 528 notifyReceiveData(data, bytesRead); 529 530 if (m_fileOpened) { 531 // When the current item is a file item, the reading is completed only if bytesRead is 0. 532 if (!bytesRead) { 533 // Close the file. 534 m_fileOpened = false; 535 m_asyncStream->close(); 536 537 // Move to the next item. 538 m_readItemCount++; 539 } 540 } else { 541 // Otherwise, we read the current text item as a whole and move to the next item. 542 m_readItemCount++; 543 } 544 545 // Continue the reading. 546 readAsync(); 547} 548 549void BlobResourceHandle::failed(int errorCode) 550{ 551 ASSERT(m_async); 552 RefPtr<BlobResourceHandle> protect(this); 553 554 // Notify the client. 555 notifyFail(errorCode); 556 557 // Close the file if needed. 558 if (m_fileOpened) { 559 m_fileOpened = false; 560 m_asyncStream->close(); 561 } 562} 563 564void BlobResourceHandle::notifyResponse() 565{ 566 if (!client()) 567 return; 568 569 if (m_errorCode) { 570 RefPtr<BlobResourceHandle> protect(this); 571 notifyResponseOnError(); 572 notifyFinish(); 573 } else 574 notifyResponseOnSuccess(); 575} 576 577void BlobResourceHandle::notifyResponseOnSuccess() 578{ 579 bool isRangeRequest = m_rangeOffset != positionNotSpecified; 580 ResourceResponse response(firstRequest().url(), m_blobData->contentType(), m_totalRemainingSize, String(), String()); 581 response.setExpectedContentLength(m_totalRemainingSize); 582 response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK); 583 response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText); 584 if (!m_blobData->contentDisposition().isEmpty()) 585 response.setHTTPHeaderField("Content-Disposition", m_blobData->contentDisposition()); 586 587 // BlobResourceHandle cannot be used with downloading, and doesn't even wait for continueDidReceiveResponse. 588 // It's currently client's responsibility to know that didReceiveResponseAsync cannot be used to convert a 589 // load into a download or blobs. 590 if (client()->usesAsyncCallbacks()) 591 client()->didReceiveResponseAsync(this, response); 592 else 593 client()->didReceiveResponse(this, response); 594} 595 596void BlobResourceHandle::notifyResponseOnError() 597{ 598 ASSERT(m_errorCode); 599 600 ResourceResponse response(firstRequest().url(), "text/plain", 0, String(), String()); 601 switch (m_errorCode) { 602 case rangeError: 603 response.setHTTPStatusCode(httpRequestedRangeNotSatisfiable); 604 response.setHTTPStatusText(httpRequestedRangeNotSatisfiableText); 605 break; 606 case notFoundError: 607 response.setHTTPStatusCode(httpNotFound); 608 response.setHTTPStatusText(httpNotFoundText); 609 break; 610 case securityError: 611 response.setHTTPStatusCode(httpNotAllowed); 612 response.setHTTPStatusText(httpNotAllowedText); 613 break; 614 default: 615 response.setHTTPStatusCode(httpInternalError); 616 response.setHTTPStatusText(httpInternalErrorText); 617 break; 618 } 619 620 // Note that we don't wait for continueDidReceiveResponse when using didReceiveResponseAsync. 621 // This is not formally correct, but the client has to be a no-op anyway, because blobs can't be downloaded. 622 if (client()->usesAsyncCallbacks()) 623 client()->didReceiveResponseAsync(this, response); 624 else 625 client()->didReceiveResponse(this, response); 626} 627 628void BlobResourceHandle::notifyReceiveData(const char* data, int bytesRead) 629{ 630 if (client()) 631 client()->didReceiveBuffer(this, SharedBuffer::create(data, bytesRead), bytesRead); 632} 633 634void BlobResourceHandle::notifyFail(int errorCode) 635{ 636 if (client()) 637 client()->didFail(this, ResourceError(webKitBlobResourceDomain, errorCode, firstRequest().url(), String())); 638} 639 640static void doNotifyFinish(void* context) 641{ 642 BlobResourceHandle* handle = static_cast<BlobResourceHandle*>(context); 643 if (!handle->aborted() && handle->client()) 644 handle->client()->didFinishLoading(handle, 0); 645 646 // Balance the ref() in BlobResourceHandle::notfyFinish(). 647 handle->deref(); 648} 649 650void BlobResourceHandle::notifyFinish() 651{ 652 // Balanced in doNotifyFinish(). 653 ref(); 654 655 if (m_async) { 656 // Schedule to notify the client from a standalone function because the client might dispose the handle immediately from the callback function 657 // while we still have BlobResourceHandle calls in the stack. 658 callOnMainThread(doNotifyFinish, this); 659 return; 660 } 661 662 doNotifyFinish(this); 663} 664 665} // namespace WebCore 666 667#endif // ENABLE(BLOB) 668