summaryrefslogtreecommitdiffstats
path: root/WebCore/platform/network/BlobResourceHandle.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/platform/network/BlobResourceHandle.cpp')
-rw-r--r--WebCore/platform/network/BlobResourceHandle.cpp589
1 files changed, 589 insertions, 0 deletions
diff --git a/WebCore/platform/network/BlobResourceHandle.cpp b/WebCore/platform/network/BlobResourceHandle.cpp
new file mode 100644
index 0000000..63335f6
--- /dev/null
+++ b/WebCore/platform/network/BlobResourceHandle.cpp
@@ -0,0 +1,589 @@
+/*
+ * 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 "BlobRegistryImpl.h"
+#include "FileStream.h"
+#include "FileSystem.h"
+#include "HTTPParsers.h"
+#include "KURL.h"
+#include "ResourceError.h"
+#include "ResourceLoader.h"
+#include "ResourceRequest.h"
+#include "ResourceResponse.h"
+
+namespace WebCore {
+
+static const unsigned bufferSize = 1024;
+static const int maxVectorLength = 0x7fffffff;
+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 int notFoundError = 1;
+static const int securityError = 2;
+static const int rangeError = 3;
+static const int notReadableError = 4;
+
+///////////////////////////////////////////////////////////////////////////////
+// BlobResourceSynchronousLoader
+
+namespace {
+
+class BlobResourceSynchronousLoader : public ResourceHandleClient {
+public:
+ BlobResourceSynchronousLoader(ResourceError&, ResourceResponse&, Vector<char>&);
+
+ virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&);
+ virtual void didReceiveData(ResourceHandle*, const char*, int, int /*lengthReceived*/);
+ virtual void didFinishLoading(ResourceHandle*);
+ virtual void didFail(ResourceHandle*, const ResourceError&);
+
+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.
+ const int intMaxForLength = 0x7fffffff;
+ if (response.expectedContentLength() > intMaxForLength) {
+ m_error = ResourceError(String(), notReadableError, response.url(), String());
+ 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*)
+{
+}
+
+void BlobResourceSynchronousLoader::didFail(ResourceHandle*, const ResourceError& error)
+{
+ m_error = error;
+}
+
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// BlobResourceHandle
+
+// static
+void BlobResourceHandle::loadResourceSynchronously(PassRefPtr<BlobStorageData> blobData, const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
+{
+ BlobResourceSynchronousLoader loader(error, response, data);
+ RefPtr<BlobResourceHandle> handle = BlobResourceHandle::create(blobData, request, &loader, false);
+ handle->start();
+}
+
+static void delayedStart(void* context)
+{
+ static_cast<BlobResourceHandle*>(context)->start();
+}
+
+BlobResourceHandle::BlobResourceHandle(PassRefPtr<BlobStorageData> blobData, const ResourceRequest& request, ResourceHandleClient* client, bool async)
+ : ResourceHandle(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 = adoptRef(client->createAsyncFileStream(this));
+ callOnMainThread(delayedStart, 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;
+}
+
+void BlobResourceHandle::start()
+{
+ // 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 {
+ 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) {
+ 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);
+
+ 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;
+
+ 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);
+
+ 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)
+{
+ consumeData(m_buffer.data(), bytesRead);
+}
+
+void BlobResourceHandle::consumeData(const char* data, int bytesRead)
+{
+ ASSERT(m_async);
+
+ 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);
+
+ // 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) {
+ 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());
+ client()->didReceiveResponse(this, response);
+}
+
+void BlobResourceHandle::notifyResponseOnError()
+{
+ ASSERT(m_errorCode);
+
+ ResourceResponse response(firstRequest().url(), String(), 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;
+ }
+ client()->didReceiveResponse(this, response);
+}
+
+void BlobResourceHandle::notifyReceiveData(const char* data, int bytesRead)
+{
+ if (client())
+ client()->didReceiveData(this, data, bytesRead, bytesRead);
+}
+
+void BlobResourceHandle::notifyFail(int errorCode)
+{
+ if (client())
+ client()->didFail(this, ResourceError(String(), errorCode, firstRequest().url(), String()));
+}
+
+void BlobResourceHandle::notifyFinish()
+{
+ if (client())
+ client()->didFinishLoading(this);
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(BLOB)
+