text: /*
   * Copyright (C) 2010 Google Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are
   * met:
   *
   *     * Redistributions of source code must retain the above copyright
   * notice, this list of conditions and the following disclaimer.
   *     * Redistributions in binary form must reproduce the above
   * copyright notice, this list of conditions and the following disclaimer
   * in the documentation and/or other materials provided with the
   * distribution.
   *     * Neither the name of Google Inc. nor the names of its
   * contributors may be used to endorse or promote products derived from
   * this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
   * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
   * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
   * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "config.h"
  
  #if ENABLE(BLOB)
  
  #include "BlobResourceHandle.h"
  
  #include "AsyncFileStream.h"
  #include "BlobStorageData.h"
  #include "FileStream.h"
  #include "FileSystem.h"
  #include "HTTPParsers.h"
  #include "KURL.h"
  #include "ResourceError.h"
  #include "ResourceHandleClient.h"
  #include "ResourceRequest.h"
  #include "ResourceResponse.h"
  #include "SharedBuffer.h"
  #include <wtf/MainThread.h>
  
  namespace WebCore {
  
  static const unsigned bufferSize = 512 * 1024;
  static const long long positionNotSpecified = -1;
  
  static const int httpOK = 200;
  static const int httpPartialContent = 206;
  static const int httpNotAllowed = 403;
  static const int httpNotFound = 404;
  static const int httpRequestedRangeNotSatisfiable = 416;
  static const int httpInternalError = 500;
  static const char* httpOKText = "OK";
  static const char* httpPartialContentText = "Partial Content";
  static const char* httpNotAllowedText = "Not Allowed";
  static const char* httpNotFoundText = "Not Found";
  static const char* httpRequestedRangeNotSatisfiableText = "Requested Range Not Satisfiable";
  static const char* httpInternalErrorText = "Internal Server Error";
  
  static const char* const webKitBlobResourceDomain = "WebKitBlobResource";
  enum {
      notFoundError = 1,
      securityError = 2,
      rangeError = 3,
      notReadableError = 4,
      methodNotAllowed = 5
  };
  
  ///////////////////////////////////////////////////////////////////////////////
  // BlobResourceSynchronousLoader
  
  namespace {
  
  class BlobResourceSynchronousLoader : public ResourceHandleClient {
  public:
      BlobResourceSynchronousLoader(ResourceError&, ResourceResponse&, Vector<char>&);
  
      virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&) OVERRIDE;
      virtual void didReceiveData(ResourceHandle*, const char*, int, int /*encodedDataLength*/) OVERRIDE;
      virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/) OVERRIDE;
      virtual void didFail(ResourceHandle*, const ResourceError&) OVERRIDE;
  
  private:
      ResourceError& m_error;
      ResourceResponse& m_response;
      Vector<char>& m_data;
  };
  
  BlobResourceSynchronousLoader::BlobResourceSynchronousLoader(ResourceError& error, ResourceResponse& response, Vector<char>& data)
      : m_error(error)
      , m_response(response)
      , m_data(data)
  {
  }
  
  void BlobResourceSynchronousLoader::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response)
  {
      // We cannot handle the size that is more than maximum integer.
      if (response.expectedContentLength() > INT_MAX) {
          m_error = ResourceError(webKitBlobResourceDomain, notReadableError, response.url(), "File is too large");
          return;
      }
  
      m_response = response;
  
      // Read all the data.
      m_data.resize(static_cast<size_t>(response.expectedContentLength()));
      static_cast<BlobResourceHandle*>(handle)->readSync(m_data.data(), static_cast<int>(m_data.size()));
  }
  
  void BlobResourceSynchronousLoader::didReceiveData(ResourceHandle*, const char*, int, int)
  {
  }
  
  void BlobResourceSynchronousLoader::didFinishLoading(ResourceHandle*, double)
  {
  }
  
  void BlobResourceSynchronousLoader::didFail(ResourceHandle*, const ResourceError& error)
  {
      m_error = error;
  }
  
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  // BlobResourceHandle
  
  PassRefPtr<BlobResourceHandle> BlobResourceHandle::createAsync(BlobStorageData* blobData, const ResourceRequest& request, ResourceHandleClient* client)
  {
      // FIXME: Should probably call didFail() instead of blocking the load without explanation.
      if (!equalIgnoringCase(request.httpMethod(), "GET"))
          return 0;
  
      return adoptRef(new BlobResourceHandle(blobData, request, client, true));
  }
  
  void BlobResourceHandle::loadResourceSynchronously(BlobStorageData* blobData, const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
  {
      if (!equalIgnoringCase(request.httpMethod(), "GET")) {
          error = ResourceError(webKitBlobResourceDomain, methodNotAllowed, response.url(), "Request method must be GET");
          return;
      }
  
      BlobResourceSynchronousLoader loader(error, response, data);
      RefPtr<BlobResourceHandle> handle = adoptRef(new BlobResourceHandle(blobData, request, &loader, false));
      handle->start();
  }
  
  BlobResourceHandle::BlobResourceHandle(PassRefPtr<BlobStorageData> blobData, const ResourceRequest& request, ResourceHandleClient* client, bool async)
      : ResourceHandle(0, request, client, false, false)
      , m_blobData(blobData)
      , m_async(async)
      , m_errorCode(0)
      , m_aborted(false)
      , m_rangeOffset(positionNotSpecified)
      , m_rangeEnd(positionNotSpecified)
      , m_rangeSuffixLength(positionNotSpecified)
      , m_totalRemainingSize(0)
      , m_currentItemReadSize(0)
      , m_sizeItemCount(0)
      , m_readItemCount(0)
      , m_fileOpened(false)
  {
      if (m_async)
          m_asyncStream = AsyncFileStream::create(this);
      else
          m_stream = FileStream::create();
  }
  
  BlobResourceHandle::~BlobResourceHandle()
  {
      if (m_async) {
          if (m_asyncStream)
              m_asyncStream->stop();
      } else {
          if (m_stream)
              m_stream->stop();
      }
  }
  
  void BlobResourceHandle::cancel()
  {
      if (m_async) {
          if (m_asyncStream) {
              m_asyncStream->stop();
              m_asyncStream = 0;
          }
      }
  
      m_aborted = true;
  
      ResourceHandle::cancel();
  }
  
  void delayedStartBlobResourceHandle(void* context)
  {
      RefPtr<BlobResourceHandle> handle = adoptRef(static_cast<BlobResourceHandle*>(context));
      handle->doStart();
  }
  
  void BlobResourceHandle::start()
  {
      if (m_async) {
          // Keep BlobResourceHandle alive until delayedStartBlobResourceHandle runs.
          ref();
  
          // Finish this async call quickly and return.
          callOnMainThread(delayedStartBlobResourceHandle, this);
          return;
      }
  
      doStart();
  }
  
  void BlobResourceHandle::doStart()
  {
      // Do not continue if the request is aborted or an error occurs.
      if (m_aborted || m_errorCode)
          return;
  
      // If the blob data is not found, fail now.
      if (!m_blobData) {
          m_errorCode = notFoundError;
          notifyResponse();
          return;
      }
  
      // Parse the "Range" header we care about.
      String range = firstRequest().httpHeaderField("Range");
      if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) {
          m_errorCode = rangeError;
          notifyResponse();
          return;
      }
  
      if (m_async)
          getSizeForNext();
      else {
          RefPtr<BlobResourceHandle> protect(this); // getSizeForNext calls the client
          for (size_t i = 0; i < m_blobData->items().size() && !m_aborted && !m_errorCode; ++i)
              getSizeForNext();
          notifyResponse();
      }
  }
  
  void BlobResourceHandle::getSizeForNext()
  {
      // Do we finish validating and counting size for all items?
      if (m_sizeItemCount >= m_blobData->items().size()) {
          seek();
  
          // Start reading if in asynchronous mode.
          if (m_async) {
              RefPtr<BlobResourceHandle> protect(this);
              notifyResponse();
              m_buffer.resize(bufferSize);
              readAsync();
          }
          return;
      }
  
      const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
      switch (item.type) {
      case BlobDataItem::Data:
          didGetSize(item.length);
          break;
      case BlobDataItem::File:
          if (m_async)
              m_asyncStream->getSize(item.path, item.expectedModificationTime);
          else
              didGetSize(m_stream->getSize(item.path, item.expectedModificationTime));
          break;
      default:
          ASSERT_NOT_REACHED();
      }
  }
  
  void BlobResourceHandle::didGetSize(long long size)
  {
      // Do not continue if the request is aborted or an error occurs.
      if (m_aborted || m_errorCode)
          return;
  
      // If the size is -1, it means the file has been moved or changed. Fail now.
      if (size == -1) {
          m_errorCode = notFoundError;
          notifyResponse();
          return;
      }
  
      // 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.
      const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
      if (item.type == BlobDataItem::File && item.length != BlobDataItem::toEndOfFile)
          size = item.length;
  
      // Cache the size.
      m_itemLengthList.append(size);
  
      // Count the size.
      m_totalRemainingSize += size;
      m_sizeItemCount++;
  
      // Continue with the next item.
      getSizeForNext();
  }
  
  void BlobResourceHandle::seek()
  {
      // Convert from the suffix length to the range.
      if (m_rangeSuffixLength != positionNotSpecified) {
          m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength;
          m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1;
      }
  
      // Bail out if the range is not provided.
      if (m_rangeOffset == positionNotSpecified)
          return;
  
      // Skip the initial items that are not in the range.
      long long offset = m_rangeOffset;
      for (m_readItemCount = 0; m_readItemCount < m_blobData->items().size() && offset >= m_itemLengthList[m_readItemCount]; ++m_readItemCount)
          offset -= m_itemLengthList[m_readItemCount];
  
      // Set the offset that need to jump to for the first item in the range.
      m_currentItemReadSize = offset;
  
      // Adjust the total remaining size in order not to go beyond the range.
      if (m_rangeEnd != positionNotSpecified) {
          long long rangeSize = m_rangeEnd - m_rangeOffset + 1;
          if (m_totalRemainingSize > rangeSize)
              m_totalRemainingSize = rangeSize;
      } else
          m_totalRemainingSize -= m_rangeOffset;
  }
  
  int BlobResourceHandle::readSync(char* buf, int length)
  {
      ASSERT(!m_async);
      RefPtr<BlobResourceHandle> protect(this);
  
      int offset = 0;
      int remaining = length;
      while (remaining) {
          // Do not continue if the request is aborted or an error occurs.
          if (m_aborted || m_errorCode)
              break;
  
          // If there is no more remaining data to read, we are done.
          if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size())
              break;
          
          const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
          int bytesRead = 0;
          if (item.type == BlobDataItem::Data)
              bytesRead = readDataSync(item, buf + offset, remaining);
          else if (item.type == BlobDataItem::File)
              bytesRead = readFileSync(item, buf + offset, remaining);
          else
              ASSERT_NOT_REACHED();
  
          if (bytesRead > 0) {
              offset += bytesRead;
              remaining -= bytesRead;
          }
      }
  
      int result;
      if (m_aborted || m_errorCode)
          result = -1;
      else
          result = length - remaining;
  
      if (result > 0)
          notifyReceiveData(buf, result);
  
      if (!result)
          notifyFinish();
  
      return result;
  }
  
  int BlobResourceHandle::readDataSync(const BlobDataItem& item, char* buf, int length)
  {
      ASSERT(!m_async);
  
      long long remaining = item.length - m_currentItemReadSize;
      int bytesToRead = (length > remaining) ? static_cast<int>(remaining) : length;
      if (bytesToRead > m_totalRemainingSize)
          bytesToRead = static_cast<int>(m_totalRemainingSize);
      memcpy(buf, item.data->data() + item.offset + m_currentItemReadSize, bytesToRead);
      m_totalRemainingSize -= bytesToRead;
  
      m_currentItemReadSize += bytesToRead;
      if (m_currentItemReadSize == item.length) {
          m_readItemCount++;
          m_currentItemReadSize = 0;
      }
  
      return bytesToRead;
  }
  
  int BlobResourceHandle::readFileSync(const BlobDataItem& item, char* buf, int length)
  {
      ASSERT(!m_async);
  
      if (!m_fileOpened) {
          long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize;
          if (bytesToRead > m_totalRemainingSize)
              bytesToRead = m_totalRemainingSize;
          bool success = m_stream->openForRead(item.path, item.offset + m_currentItemReadSize, bytesToRead);
          m_currentItemReadSize = 0;
          if (!success) {
              m_errorCode = notReadableError;
              return 0;
          }
  
          m_fileOpened = true;
      }
  
      int bytesRead = m_stream->read(buf, length);
      if (bytesRead < 0) {
          m_errorCode = notReadableError;
          return 0;
      }
      if (!bytesRead) {
          m_stream->close();
          m_fileOpened = false;
          m_readItemCount++;
      } else
          m_totalRemainingSize -= bytesRead;
  
      return bytesRead;
  }
  
  void BlobResourceHandle::readAsync()
  {
      ASSERT(m_async);
  
      // Do not continue if the request is aborted or an error occurs.
      if (m_aborted || m_errorCode)
          return;
  
      // If there is no more remaining data to read, we are done.
      if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) {
          notifyFinish();
          return;
      }
  
      const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
      if (item.type == BlobDataItem::Data)
          readDataAsync(item);
      else if (item.type == BlobDataItem::File)
          readFileAsync(item);
      else
          ASSERT_NOT_REACHED();
  }
  
  void BlobResourceHandle::readDataAsync(const BlobDataItem& item)
  {
      ASSERT(m_async);
      RefPtr<BlobResourceHandle> protect(this);
  
      long long bytesToRead = item.length - m_currentItemReadSize;
      if (bytesToRead > m_totalRemainingSize)
          bytesToRead = m_totalRemainingSize;
      consumeData(item.data->data() + item.offset + m_currentItemReadSize, static_cast<int>(bytesToRead));
      m_currentItemReadSize = 0;
  }
  
  void BlobResourceHandle::readFileAsync(const BlobDataItem& item)
  {
      ASSERT(m_async);
  
      if (m_fileOpened) {
          m_asyncStream->read(m_buffer.data(), m_buffer.size());
          return;
      }
  
      long long bytesToRead = m_itemLengthList[m_readItemCount] - m_currentItemReadSize;
      if (bytesToRead > m_totalRemainingSize)
          bytesToRead = static_cast<int>(m_totalRemainingSize);
      m_asyncStream->openForRead(item.path, item.offset + m_currentItemReadSize, bytesToRead);
      m_fileOpened = true;
      m_currentItemReadSize = 0;
  }
  
  void BlobResourceHandle::didOpen(bool success)
  {
      ASSERT(m_async);
  
      if (!success) {
          failed(notReadableError);
          return;
      }
  
      // Continue the reading.
      readAsync();
  }
  
  void BlobResourceHandle::didRead(int bytesRead)
  {
      if (bytesRead < 0) {
          failed(notReadableError);
          return;
      }
  
      consumeData(m_buffer.data(), bytesRead);
  }
  
  void BlobResourceHandle::consumeData(const char* data, int bytesRead)
  {
      ASSERT(m_async);
      RefPtr<BlobResourceHandle> protect(this);
  
      m_totalRemainingSize -= bytesRead;
  
      // Notify the client.
      if (bytesRead)
          notifyReceiveData(data, bytesRead);
  
      if (m_fileOpened) {
          // When the current item is a file item, the reading is completed only if bytesRead is 0.
          if (!bytesRead) {
              // Close the file.
              m_fileOpened = false;
              m_asyncStream->close();
  
              // Move to the next item.
              m_readItemCount++;
          }
      } else {
          // Otherwise, we read the current text item as a whole and move to the next item.
          m_readItemCount++;
      }
  
      // Continue the reading.
      readAsync();
  }
  
  void BlobResourceHandle::failed(int errorCode)
  {
      ASSERT(m_async);
      RefPtr<BlobResourceHandle> protect(this);
  
      // Notify the client.
      notifyFail(errorCode);
  
      // Close the file if needed.
      if (m_fileOpened) {
          m_fileOpened = false;
          m_asyncStream->close();
      }
  }
  
  void BlobResourceHandle::notifyResponse()
  {
      if (!client())
          return;
  
      if (m_errorCode) {
          RefPtr<BlobResourceHandle> protect(this);
          notifyResponseOnError();
          notifyFinish();
      } else
          notifyResponseOnSuccess();
  }
  
  void BlobResourceHandle::notifyResponseOnSuccess()
  {
      bool isRangeRequest = m_rangeOffset != positionNotSpecified;
      ResourceResponse response(firstRequest().url(), m_blobData->contentType(), m_totalRemainingSize, String(), String());
      response.setExpectedContentLength(m_totalRemainingSize);
      response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK);
      response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText);
      if (!m_blobData->contentDisposition().isEmpty())
          response.setHTTPHeaderField("Content-Disposition", m_blobData->contentDisposition());
  
      // BlobResourceHandle cannot be used with downloading, and doesn't even wait for continueDidReceiveResponse.
      // It's currently client's responsibility to know that didReceiveResponseAsync cannot be used to convert a
      // load into a download or blobs.
      if (client()->usesAsyncCallbacks())
          client()->didReceiveResponseAsync(this, response);
      else
          client()->didReceiveResponse(this, response);
  }
  
  void BlobResourceHandle::notifyResponseOnError()
  {
      ASSERT(m_errorCode);
  
      ResourceResponse response(firstRequest().url(), "text/plain", 0, String(), String());
      switch (m_errorCode) {
      case rangeError:
          response.setHTTPStatusCode(httpRequestedRangeNotSatisfiable);
          response.setHTTPStatusText(httpRequestedRangeNotSatisfiableText);
          break;
      case notFoundError:
          response.setHTTPStatusCode(httpNotFound);
          response.setHTTPStatusText(httpNotFoundText);
          break;
      case securityError:
          response.setHTTPStatusCode(httpNotAllowed);
          response.setHTTPStatusText(httpNotAllowedText);
          break;
      default:
          response.setHTTPStatusCode(httpInternalError);
          response.setHTTPStatusText(httpInternalErrorText);
          break;
      }
  
      // Note that we don't wait for continueDidReceiveResponse when using didReceiveResponseAsync.
      // This is not formally correct, but the client has to be a no-op anyway, because blobs can't be downloaded.
      if (client()->usesAsyncCallbacks())
          client()->didReceiveResponseAsync(this, response);
      else
          client()->didReceiveResponse(this, response);
  }
  
  void BlobResourceHandle::notifyReceiveData(const char* data, int bytesRead)
  {
      if (client())
          client()->didReceiveBuffer(this, SharedBuffer::create(data, bytesRead), bytesRead);
  }
  
  void BlobResourceHandle::notifyFail(int errorCode)
  {
      if (client())
          client()->didFail(this, ResourceError(webKitBlobResourceDomain, errorCode, firstRequest().url(), String()));
  }
  
  static void doNotifyFinish(void* context)
  {
      BlobResourceHandle* handle = static_cast<BlobResourceHandle*>(context);
      if (!handle->aborted() && handle->client())
          handle->client()->didFinishLoading(handle, 0);
  
      // Balance the ref() in BlobResourceHandle::notfyFinish().
      handle->deref();
  }
  
  void BlobResourceHandle::notifyFinish()
  {
      // Balanced in doNotifyFinish().
      ref();
  
      if (m_async) {
          // Schedule to notify the client from a standalone function because the client might dispose the handle immediately from the callback function
          // while we still have BlobResourceHandle calls in the stack.
          callOnMainThread(doNotifyFinish, this);
          return;
      }
  
      doNotifyFinish(this);
  }
  
  } // namespace WebCore
  
  #endif // ENABLE(BLOB)
  