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 "BlobResourceHandle.h" 34 35#include "AsyncFileStream.h" 36#include "BlobData.h" 37#include "FileStream.h" 38#include "FileSystem.h" 39#include "HTTPHeaderNames.h" 40#include "HTTPParsers.h" 41#include "URL.h" 42#include "ResourceError.h" 43#include "ResourceHandleClient.h" 44#include "ResourceRequest.h" 45#include "ResourceResponse.h" 46#include "SharedBuffer.h" 47#include <wtf/MainThread.h> 48#include <wtf/Ref.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*, unsigned, 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*, unsigned, 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(BlobData* 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(BlobData* 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(BlobData* 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 BlobResourceHandle::continueDidReceiveResponse() 205{ 206 // BlobResourceHandle doesn't wait for didReceiveResponse, and it currently cannot be used for downloading. 207} 208 209void BlobResourceHandle::start() 210{ 211 if (!m_async) { 212 doStart(); 213 return; 214 } 215 216 RefPtr<BlobResourceHandle> handle(this); 217 218 // Finish this async call quickly and return. 219 callOnMainThread([handle] { 220 handle->doStart(); 221 }); 222} 223 224void BlobResourceHandle::doStart() 225{ 226 ASSERT(isMainThread()); 227 228 // Do not continue if the request is aborted or an error occurs. 229 if (m_aborted || m_errorCode) 230 return; 231 232 // If the blob data is not found, fail now. 233 if (!m_blobData) { 234 m_errorCode = notFoundError; 235 notifyResponse(); 236 return; 237 } 238 239 // Parse the "Range" header we care about. 240 String range = firstRequest().httpHeaderField(HTTPHeaderName::Range); 241 if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) { 242 m_errorCode = rangeError; 243 notifyResponse(); 244 return; 245 } 246 247 if (m_async) 248 getSizeForNext(); 249 else { 250 Ref<BlobResourceHandle> protect(*this); // getSizeForNext calls the client 251 for (size_t i = 0; i < m_blobData->items().size() && !m_aborted && !m_errorCode; ++i) 252 getSizeForNext(); 253 notifyResponse(); 254 } 255} 256 257void BlobResourceHandle::getSizeForNext() 258{ 259 ASSERT(isMainThread()); 260 261 // Do we finish validating and counting size for all items? 262 if (m_sizeItemCount >= m_blobData->items().size()) { 263 seek(); 264 265 // Start reading if in asynchronous mode. 266 if (m_async) { 267 Ref<BlobResourceHandle> protect(*this); 268 notifyResponse(); 269 m_buffer.resize(bufferSize); 270 readAsync(); 271 } 272 return; 273 } 274 275 const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount); 276 switch (item.type) { 277 case BlobDataItem::Data: 278 didGetSize(item.length()); 279 break; 280 case BlobDataItem::File: 281 // Files know their sizes, but asking the stream to verify that the file wasn't modified. 282 if (m_async) 283 m_asyncStream->getSize(item.file->path(), item.file->expectedModificationTime()); 284 else 285 didGetSize(m_stream->getSize(item.file->path(), item.file->expectedModificationTime())); 286 break; 287 default: 288 ASSERT_NOT_REACHED(); 289 } 290} 291 292void BlobResourceHandle::didGetSize(long long size) 293{ 294 ASSERT(isMainThread()); 295 296 // Do not continue if the request is aborted or an error occurs. 297 if (m_aborted || m_errorCode) 298 return; 299 300 // If the size is -1, it means the file has been moved or changed. Fail now. 301 if (size == -1) { 302 m_errorCode = notFoundError; 303 notifyResponse(); 304 return; 305 } 306 307 // 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. 308 const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount); 309 size = item.length(); 310 311 // Cache the size. 312 m_itemLengthList.append(size); 313 314 // Count the size. 315 m_totalRemainingSize += size; 316 m_sizeItemCount++; 317 318 // Continue with the next item. 319 getSizeForNext(); 320} 321 322void BlobResourceHandle::seek() 323{ 324 ASSERT(isMainThread()); 325 326 // Convert from the suffix length to the range. 327 if (m_rangeSuffixLength != positionNotSpecified) { 328 m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength; 329 m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1; 330 } 331 332 // Bail out if the range is not provided. 333 if (m_rangeOffset == positionNotSpecified) 334 return; 335 336 // Skip the initial items that are not in the range. 337 long long offset = m_rangeOffset; 338 for (m_readItemCount = 0; m_readItemCount < m_blobData->items().size() && offset >= m_itemLengthList[m_readItemCount]; ++m_readItemCount) 339 offset -= m_itemLengthList[m_readItemCount]; 340 341 // Set the offset that need to jump to for the first item in the range. 342 m_currentItemReadSize = offset; 343 344 // Adjust the total remaining size in order not to go beyond the range. 345 if (m_rangeEnd != positionNotSpecified) { 346 long long rangeSize = m_rangeEnd - m_rangeOffset + 1; 347 if (m_totalRemainingSize > rangeSize) 348 m_totalRemainingSize = rangeSize; 349 } else 350 m_totalRemainingSize -= m_rangeOffset; 351} 352 353int BlobResourceHandle::readSync(char* buf, int length) 354{ 355 ASSERT(isMainThread()); 356 357 ASSERT(!m_async); 358 Ref<BlobResourceHandle> protect(*this); 359 360 int offset = 0; 361 int remaining = length; 362 while (remaining) { 363 // Do not continue if the request is aborted or an error occurs. 364 if (m_aborted || m_errorCode) 365 break; 366 367 // If there is no more remaining data to read, we are done. 368 if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) 369 break; 370 371 const BlobDataItem& item = m_blobData->items().at(m_readItemCount); 372 int bytesRead = 0; 373 if (item.type == BlobDataItem::Data) 374 bytesRead = readDataSync(item, buf + offset, remaining); 375 else if (item.type == BlobDataItem::File) 376 bytesRead = readFileSync(item, buf + offset, remaining); 377 else 378 ASSERT_NOT_REACHED(); 379 380 if (bytesRead > 0) { 381 offset += bytesRead; 382 remaining -= bytesRead; 383 } 384 } 385 386 int result; 387 if (m_aborted || m_errorCode) 388 result = -1; 389 else 390 result = length - remaining; 391 392 if (result > 0) 393 notifyReceiveData(buf, result); 394 395 if (!result) 396 notifyFinish(); 397 398 return result; 399} 400 401int BlobResourceHandle::readDataSync(const BlobDataItem& item, char* buf, int length) 402{ 403 ASSERT(isMainThread()); 404 405 ASSERT(!m_async); 406 407 long long remaining = item.length() - m_currentItemReadSize; 408 int bytesToRead = (length > remaining) ? static_cast<int>(remaining) : length; 409 if (bytesToRead > m_totalRemainingSize) 410 bytesToRead = static_cast<int>(m_totalRemainingSize); 411 memcpy(buf, item.data->data() + item.offset() + m_currentItemReadSize, bytesToRead); 412 m_totalRemainingSize -= bytesToRead; 413 414 m_currentItemReadSize += bytesToRead; 415 if (m_currentItemReadSize == item.length()) { 416 m_readItemCount++; 417 m_currentItemReadSize = 0; 418 } 419 420 return bytesToRead; 421} 422 423int BlobResourceHandle::readFileSync(const BlobDataItem& item, char* buf, int length) 424{ 425 ASSERT(isMainThread()); 426 427 ASSERT(!m_async); 428 429 if (!m_fileOpened) { 430 long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize; 431 if (bytesToRead > m_totalRemainingSize) 432 bytesToRead = m_totalRemainingSize; 433 bool success = m_stream->openForRead(item.file->path(), item.offset() + m_currentItemReadSize, bytesToRead); 434 m_currentItemReadSize = 0; 435 if (!success) { 436 m_errorCode = notReadableError; 437 return 0; 438 } 439 440 m_fileOpened = true; 441 } 442 443 int bytesRead = m_stream->read(buf, length); 444 if (bytesRead < 0) { 445 m_errorCode = notReadableError; 446 return 0; 447 } 448 if (!bytesRead) { 449 m_stream->close(); 450 m_fileOpened = false; 451 m_readItemCount++; 452 } else 453 m_totalRemainingSize -= bytesRead; 454 455 return bytesRead; 456} 457 458void BlobResourceHandle::readAsync() 459{ 460 ASSERT(isMainThread()); 461 ASSERT(m_async); 462 463 // Do not continue if the request is aborted or an error occurs. 464 if (m_aborted || m_errorCode) 465 return; 466 467 // If there is no more remaining data to read, we are done. 468 if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) { 469 notifyFinish(); 470 return; 471 } 472 473 const BlobDataItem& item = m_blobData->items().at(m_readItemCount); 474 if (item.type == BlobDataItem::Data) 475 readDataAsync(item); 476 else if (item.type == BlobDataItem::File) 477 readFileAsync(item); 478 else 479 ASSERT_NOT_REACHED(); 480} 481 482void BlobResourceHandle::readDataAsync(const BlobDataItem& item) 483{ 484 ASSERT(isMainThread()); 485 ASSERT(m_async); 486 Ref<BlobResourceHandle> protect(*this); 487 488 long long bytesToRead = item.length() - m_currentItemReadSize; 489 if (bytesToRead > m_totalRemainingSize) 490 bytesToRead = m_totalRemainingSize; 491 consumeData(item.data->data() + item.offset() + m_currentItemReadSize, static_cast<int>(bytesToRead)); 492 m_currentItemReadSize = 0; 493} 494 495void BlobResourceHandle::readFileAsync(const BlobDataItem& item) 496{ 497 ASSERT(isMainThread()); 498 ASSERT(m_async); 499 500 if (m_fileOpened) { 501 m_asyncStream->read(m_buffer.data(), m_buffer.size()); 502 return; 503 } 504 505 long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize; 506 if (bytesToRead > m_totalRemainingSize) 507 bytesToRead = static_cast<int>(m_totalRemainingSize); 508 m_asyncStream->openForRead(item.file->path(), item.offset() + m_currentItemReadSize, bytesToRead); 509 m_fileOpened = true; 510 m_currentItemReadSize = 0; 511} 512 513void BlobResourceHandle::didOpen(bool success) 514{ 515 ASSERT(m_async); 516 517 if (!success) { 518 failed(notReadableError); 519 return; 520 } 521 522 // Continue the reading. 523 readAsync(); 524} 525 526void BlobResourceHandle::didRead(int bytesRead) 527{ 528 if (bytesRead < 0) { 529 failed(notReadableError); 530 return; 531 } 532 533 consumeData(m_buffer.data(), bytesRead); 534} 535 536void BlobResourceHandle::consumeData(const char* data, int bytesRead) 537{ 538 ASSERT(m_async); 539 Ref<BlobResourceHandle> protect(*this); 540 541 m_totalRemainingSize -= bytesRead; 542 543 // Notify the client. 544 if (bytesRead) 545 notifyReceiveData(data, bytesRead); 546 547 if (m_fileOpened) { 548 // When the current item is a file item, the reading is completed only if bytesRead is 0. 549 if (!bytesRead) { 550 // Close the file. 551 m_fileOpened = false; 552 m_asyncStream->close(); 553 554 // Move to the next item. 555 m_readItemCount++; 556 } 557 } else { 558 // Otherwise, we read the current text item as a whole and move to the next item. 559 m_readItemCount++; 560 } 561 562 // Continue the reading. 563 readAsync(); 564} 565 566void BlobResourceHandle::failed(int errorCode) 567{ 568 ASSERT(m_async); 569 Ref<BlobResourceHandle> protect(*this); 570 571 // Notify the client. 572 notifyFail(errorCode); 573 574 // Close the file if needed. 575 if (m_fileOpened) { 576 m_fileOpened = false; 577 m_asyncStream->close(); 578 } 579} 580 581void BlobResourceHandle::notifyResponse() 582{ 583 if (!client()) 584 return; 585 586 if (m_errorCode) { 587 Ref<BlobResourceHandle> protect(*this); 588 notifyResponseOnError(); 589 notifyFinish(); 590 } else 591 notifyResponseOnSuccess(); 592} 593 594void BlobResourceHandle::notifyResponseOnSuccess() 595{ 596 ASSERT(isMainThread()); 597 598 bool isRangeRequest = m_rangeOffset != positionNotSpecified; 599 ResourceResponse response(firstRequest().url(), m_blobData->contentType(), m_totalRemainingSize, String(), String()); 600 response.setExpectedContentLength(m_totalRemainingSize); 601 response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK); 602 response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText); 603 // FIXME: If a resource identified with a blob: URL is a File object, user agents must use that file's name attribute, 604 // as if the response had a Content-Disposition header with the filename parameter set to the File's name attribute. 605 // Notably, this will affect a name suggested in "File Save As". 606 607 // BlobResourceHandle cannot be used with downloading, and doesn't even wait for continueDidReceiveResponse. 608 // It's currently client's responsibility to know that didReceiveResponseAsync cannot be used to convert a 609 // load into a download or blobs. 610 if (client()->usesAsyncCallbacks()) 611 client()->didReceiveResponseAsync(this, response); 612 else 613 client()->didReceiveResponse(this, response); 614} 615 616void BlobResourceHandle::notifyResponseOnError() 617{ 618 ASSERT(m_errorCode); 619 620 ResourceResponse response(firstRequest().url(), "text/plain", 0, String(), String()); 621 switch (m_errorCode) { 622 case rangeError: 623 response.setHTTPStatusCode(httpRequestedRangeNotSatisfiable); 624 response.setHTTPStatusText(httpRequestedRangeNotSatisfiableText); 625 break; 626 case notFoundError: 627 response.setHTTPStatusCode(httpNotFound); 628 response.setHTTPStatusText(httpNotFoundText); 629 break; 630 case securityError: 631 response.setHTTPStatusCode(httpNotAllowed); 632 response.setHTTPStatusText(httpNotAllowedText); 633 break; 634 default: 635 response.setHTTPStatusCode(httpInternalError); 636 response.setHTTPStatusText(httpInternalErrorText); 637 break; 638 } 639 640 // Note that we don't wait for continueDidReceiveResponse when using didReceiveResponseAsync. 641 // This is not formally correct, but the client has to be a no-op anyway, because blobs can't be downloaded. 642 if (client()->usesAsyncCallbacks()) 643 client()->didReceiveResponseAsync(this, response); 644 else 645 client()->didReceiveResponse(this, response); 646} 647 648void BlobResourceHandle::notifyReceiveData(const char* data, int bytesRead) 649{ 650 if (client()) 651 client()->didReceiveBuffer(this, SharedBuffer::create(data, bytesRead), bytesRead); 652} 653 654void BlobResourceHandle::notifyFail(int errorCode) 655{ 656 if (client()) 657 client()->didFail(this, ResourceError(webKitBlobResourceDomain, errorCode, firstRequest().url(), String())); 658} 659 660static void doNotifyFinish(void* context) 661{ 662 BlobResourceHandle* handle = static_cast<BlobResourceHandle*>(context); 663 if (!handle->aborted() && handle->client()) 664 handle->client()->didFinishLoading(handle, 0); 665 666 // Balance the ref() in BlobResourceHandle::notfyFinish(). 667 handle->deref(); 668} 669 670void BlobResourceHandle::notifyFinish() 671{ 672 // Balanced in doNotifyFinish(). 673 ref(); 674 675 if (m_async) { 676 // Schedule to notify the client from a standalone function because the client might dispose the handle immediately from the callback function 677 // while we still have BlobResourceHandle calls in the stack. 678 callOnMainThread(doNotifyFinish, this); 679 return; 680 } 681 682 doNotifyFinish(this); 683} 684 685} // namespace WebCore 686