/* * 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&); virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); virtual void didReceiveData(ResourceHandle*, const char*, int, int /*encodedDataLength*/); virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/); virtual void didFail(ResourceHandle*, const ResourceError&); private: ResourceError& m_error; ResourceResponse& m_response; Vector& m_data; }; BlobResourceSynchronousLoader::BlobResourceSynchronousLoader(ResourceError& error, ResourceResponse& response, Vector& 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(response.expectedContentLength())); static_cast(handle)->readSync(m_data.data(), static_cast(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 // static void BlobResourceHandle::loadResourceSynchronously(PassRefPtr blobData, const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector& data) { BlobResourceSynchronousLoader loader(error, response, data); RefPtr handle = BlobResourceHandle::create(blobData, request, &loader, false); handle->start(); } BlobResourceHandle::BlobResourceHandle(PassRefPtr 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 = client->createAsyncFileStream(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 handle = adoptRef(static_cast(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 { 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(remaining) : length; if (bytesToRead > m_totalRemainingSize) bytesToRead = static_cast(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(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(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())); } static void doNotifyFinish(void* context) { BlobResourceHandle* handle = static_cast(context); if (handle->client()) handle->client()->didFinishLoading(handle, 0); } void BlobResourceHandle::notifyFinish() { 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)