blob: e47b879831488ebd566171aebfa640c9d8a109fc [file] [log] [blame]
/*
* 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 <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,
};
///////////////////////////////////////////////////////////////////////////////
// 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 /*encodedDataLength*/);
virtual void didFinishLoading(ResourceHandle*, double /*finishTime*/);
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.
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
// 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();
}
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 = 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<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;
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());
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(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)