BlobResourceHandle.cpp [plain text]
#include "config.h"
#include "BlobResourceHandle.h"
#include "AsyncFileStream.h"
#include "BlobData.h"
#include "FileStream.h"
#include "HTTPHeaderNames.h"
#include "HTTPParsers.h"
#include "ParsedContentRange.h"
#include "ResourceError.h"
#include "ResourceHandleClient.h"
#include "ResourceRequest.h"
#include "ResourceResponse.h"
#include "SharedBuffer.h"
#include <wtf/CompletionHandler.h>
#include <wtf/FileSystem.h>
#include <wtf/MainThread.h>
#include <wtf/Ref.h>
#include <wtf/URL.h>
namespace WebCore {
static const unsigned bufferSize = 512 * 1024;
static const int httpOK = 200;
static const int httpPartialContent = 206;
static const int httpNotAllowed = 403;
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* httpRequestedRangeNotSatisfiableText = "Requested Range Not Satisfiable";
static const char* httpInternalErrorText = "Internal Server Error";
static const char* const webKitBlobResourceDomain = "WebKitBlobResource";
namespace {
class BlobResourceSynchronousLoader : public ResourceHandleClient {
public:
BlobResourceSynchronousLoader(ResourceError&, ResourceResponse&, Vector<char>&);
void didReceiveResponseAsync(ResourceHandle*, ResourceResponse&&, CompletionHandler<void()>&&) final;
void didFail(ResourceHandle*, const ResourceError&) final;
void willSendRequestAsync(ResourceHandle*, ResourceRequest&&, ResourceResponse&&, CompletionHandler<void(ResourceRequest&&)>&&) final;
#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
void canAuthenticateAgainstProtectionSpaceAsync(ResourceHandle*, const ProtectionSpace&, CompletionHandler<void(bool)>&&) final;
#endif
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::willSendRequestAsync(ResourceHandle*, ResourceRequest&& request, ResourceResponse&&, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
{
ASSERT_NOT_REACHED();
completionHandler(WTFMove(request));
}
#if USE(PROTECTION_SPACE_AUTH_CALLBACK)
void BlobResourceSynchronousLoader::canAuthenticateAgainstProtectionSpaceAsync(ResourceHandle*, const ProtectionSpace&, CompletionHandler<void(bool)>&& completionHandler)
{
ASSERT_NOT_REACHED();
completionHandler(false);
}
#endif
void BlobResourceSynchronousLoader::didReceiveResponseAsync(ResourceHandle* handle, ResourceResponse&& response, CompletionHandler<void()>&& completionHandler)
{
if (response.expectedContentLength() > INT_MAX) {
m_error = ResourceError(webKitBlobResourceDomain, static_cast<int>(BlobResourceHandle::Error::NotReadableError), response.url(), "File is too large");
completionHandler();
return;
}
m_response = response;
m_data.resize(static_cast<size_t>(response.expectedContentLength()));
static_cast<BlobResourceHandle*>(handle)->readSync(m_data.data(), static_cast<int>(m_data.size()));
completionHandler();
}
void BlobResourceSynchronousLoader::didFail(ResourceHandle*, const ResourceError& error)
{
m_error = error;
}
}
Ref<BlobResourceHandle> BlobResourceHandle::createAsync(BlobData* blobData, const ResourceRequest& request, ResourceHandleClient* client)
{
return adoptRef(*new BlobResourceHandle(blobData, request, client, true));
}
void BlobResourceHandle::loadResourceSynchronously(BlobData* blobData, const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
{
if (!equalLettersIgnoringASCIICase(request.httpMethod(), "get")) {
error = ResourceError(webKitBlobResourceDomain, static_cast<int>(Error::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(BlobData* blobData, const ResourceRequest& request, ResourceHandleClient* client, bool async)
: ResourceHandle { nullptr, request, client, false , false , true }
, m_blobData { blobData }
, m_async { async }
{
if (m_async)
m_asyncStream = makeUnique<AsyncFileStream>(*this);
else
m_stream = makeUnique<FileStream>();
}
BlobResourceHandle::~BlobResourceHandle() = default;
void BlobResourceHandle::cancel()
{
m_asyncStream = nullptr;
m_fileOpened = false;
m_aborted = true;
ResourceHandle::cancel();
}
void BlobResourceHandle::start()
{
if (!m_async) {
doStart();
return;
}
callOnMainThread([protectedThis = makeRef(*this)]() mutable {
protectedThis->doStart();
});
}
void BlobResourceHandle::doStart()
{
ASSERT(isMainThread());
if (erroredOrAborted())
return;
if (!equalLettersIgnoringASCIICase(firstRequest().httpMethod(), "get")) {
notifyFail(Error::MethodNotAllowed);
return;
}
if (!m_blobData) {
notifyFail(Error::NotFoundError);
return;
}
String range = firstRequest().httpHeaderField(HTTPHeaderName::Range);
if (!range.isEmpty() && !parseRange(range, m_rangeOffset, m_rangeEnd, m_rangeSuffixLength)) {
m_errorCode = Error::RangeError;
notifyResponse();
return;
}
if (m_async)
getSizeForNext();
else {
Ref<BlobResourceHandle> protectedThis(*this); for (size_t i = 0; i < m_blobData->items().size() && !erroredOrAborted(); ++i)
getSizeForNext();
notifyResponse();
}
}
void BlobResourceHandle::getSizeForNext()
{
ASSERT(isMainThread());
if (m_sizeItemCount >= m_blobData->items().size()) {
seek();
if (m_async) {
Ref<BlobResourceHandle> protectedThis(*this);
notifyResponse();
}
return;
}
const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
switch (item.type()) {
case BlobDataItem::Type::Data:
didGetSize(item.length());
break;
case BlobDataItem::Type::File:
if (m_async)
m_asyncStream->getSize(item.file()->path(), item.file()->expectedModificationTime());
else
didGetSize(m_stream->getSize(item.file()->path(), item.file()->expectedModificationTime()));
break;
default:
ASSERT_NOT_REACHED();
}
}
void BlobResourceHandle::didGetSize(long long size)
{
ASSERT(isMainThread());
if (erroredOrAborted())
return;
if (size == -1) {
notifyFail(Error::NotFoundError);
return;
}
const BlobDataItem& item = m_blobData->items().at(m_sizeItemCount);
size = item.length();
m_itemLengthList.append(size);
m_totalSize += size;
m_totalRemainingSize += size;
m_sizeItemCount++;
getSizeForNext();
}
void BlobResourceHandle::seek()
{
ASSERT(isMainThread());
if (m_rangeSuffixLength != kPositionNotSpecified) {
m_rangeOffset = m_totalRemainingSize - m_rangeSuffixLength;
m_rangeEnd = m_rangeOffset + m_rangeSuffixLength - 1;
}
if (m_rangeOffset == kPositionNotSpecified)
return;
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];
m_currentItemReadSize = offset;
if (m_rangeEnd != kPositionNotSpecified) {
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(isMainThread());
ASSERT(!m_async);
Ref<BlobResourceHandle> protectedThis(*this);
int offset = 0;
int remaining = length;
while (remaining) {
if (erroredOrAborted())
break;
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::Type::Data)
bytesRead = readDataSync(item, buf + offset, remaining);
else if (item.type() == BlobDataItem::Type::File)
bytesRead = readFileSync(item, buf + offset, remaining);
else
ASSERT_NOT_REACHED();
if (bytesRead > 0) {
offset += bytesRead;
remaining -= bytesRead;
}
}
int result;
if (erroredOrAborted())
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(isMainThread());
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(isMainThread());
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.file()->path(), item.offset() + m_currentItemReadSize, bytesToRead);
m_currentItemReadSize = 0;
if (!success) {
m_errorCode = Error::NotReadableError;
return 0;
}
m_fileOpened = true;
}
int bytesRead = m_stream->read(buf, length);
if (bytesRead < 0) {
m_errorCode = Error::NotReadableError;
return 0;
}
if (!bytesRead) {
m_stream->close();
m_fileOpened = false;
m_readItemCount++;
} else
m_totalRemainingSize -= bytesRead;
return bytesRead;
}
void BlobResourceHandle::readAsync()
{
ASSERT(isMainThread());
if (erroredOrAborted())
return;
if (!m_totalRemainingSize || m_readItemCount >= m_blobData->items().size()) {
notifyFinish();
return;
}
const BlobDataItem& item = m_blobData->items().at(m_readItemCount);
if (item.type() == BlobDataItem::Type::Data)
readDataAsync(item);
else if (item.type() == BlobDataItem::Type::File)
readFileAsync(item);
else
ASSERT_NOT_REACHED();
}
void BlobResourceHandle::readDataAsync(const BlobDataItem& item)
{
ASSERT(isMainThread());
ASSERT(item.data().data());
Ref<BlobResourceHandle> protectedThis(*this);
long long bytesToRead = item.length() - m_currentItemReadSize;
ASSERT(bytesToRead >= 0);
if (bytesToRead > m_totalRemainingSize)
bytesToRead = m_totalRemainingSize;
auto* data = reinterpret_cast<const char*>(item.data().data()->data()) + item.offset() + m_currentItemReadSize;
m_currentItemReadSize = 0;
consumeData(data, static_cast<int>(bytesToRead));
}
void BlobResourceHandle::readFileAsync(const BlobDataItem& item)
{
ASSERT(isMainThread());
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.file()->path(), item.offset() + m_currentItemReadSize, bytesToRead);
m_fileOpened = true;
m_currentItemReadSize = 0;
}
void BlobResourceHandle::didOpen(bool success)
{
ASSERT(m_async);
if (!success) {
failed(Error::NotReadableError);
return;
}
readAsync();
}
void BlobResourceHandle::didRead(int bytesRead)
{
if (bytesRead < 0) {
failed(Error::NotReadableError);
return;
}
consumeData(m_buffer.data(), bytesRead);
}
void BlobResourceHandle::consumeData(const char* data, int bytesRead)
{
ASSERT(m_async);
Ref<BlobResourceHandle> protectedThis(*this);
m_totalRemainingSize -= bytesRead;
if (bytesRead)
notifyReceiveData(data, bytesRead);
if (m_fileOpened) {
if (!bytesRead) {
m_fileOpened = false;
m_asyncStream->close();
m_readItemCount++;
}
} else {
m_readItemCount++;
}
readAsync();
}
void BlobResourceHandle::failed(Error errorCode)
{
ASSERT(m_async);
Ref<BlobResourceHandle> protectedThis(*this);
notifyFail(errorCode);
if (m_fileOpened) {
m_fileOpened = false;
m_asyncStream->close();
}
}
void BlobResourceHandle::notifyResponse()
{
if (!client())
return;
if (m_errorCode != Error::NoError) {
Ref<BlobResourceHandle> protectedThis(*this);
notifyResponseOnError();
notifyFinish();
} else
notifyResponseOnSuccess();
}
void BlobResourceHandle::notifyResponseOnSuccess()
{
ASSERT(isMainThread());
bool isRangeRequest = m_rangeOffset != kPositionNotSpecified;
ResourceResponse response(firstRequest().url(), m_blobData->contentType(), m_totalRemainingSize, String());
response.setHTTPStatusCode(isRangeRequest ? httpPartialContent : httpOK);
response.setHTTPStatusText(isRangeRequest ? httpPartialContentText : httpOKText);
response.setHTTPHeaderField(HTTPHeaderName::ContentType, m_blobData->contentType());
response.setHTTPHeaderField(HTTPHeaderName::ContentLength, String::number(m_totalRemainingSize));
if (isRangeRequest)
response.setHTTPHeaderField(HTTPHeaderName::ContentRange, ParsedContentRange(m_rangeOffset, m_rangeEnd, m_totalSize).headerValue());
client()->didReceiveResponseAsync(this, WTFMove(response), [this, protectedThis = makeRef(*this)] {
m_buffer.resize(bufferSize);
readAsync();
});
}
void BlobResourceHandle::notifyResponseOnError()
{
ASSERT(m_errorCode != Error::NoError);
ResourceResponse response(firstRequest().url(), "text/plain", 0, String());
switch (m_errorCode) {
case Error::RangeError:
response.setHTTPStatusCode(httpRequestedRangeNotSatisfiable);
response.setHTTPStatusText(httpRequestedRangeNotSatisfiableText);
break;
case Error::SecurityError:
response.setHTTPStatusCode(httpNotAllowed);
response.setHTTPStatusText(httpNotAllowedText);
break;
default:
response.setHTTPStatusCode(httpInternalError);
response.setHTTPStatusText(httpInternalErrorText);
break;
}
client()->didReceiveResponseAsync(this, WTFMove(response), [this, protectedThis = makeRef(*this)] {
m_buffer.resize(bufferSize);
readAsync();
});
}
void BlobResourceHandle::notifyReceiveData(const char* data, int bytesRead)
{
if (client())
client()->didReceiveBuffer(this, SharedBuffer::create(reinterpret_cast<const uint8_t*>(data), bytesRead), bytesRead);
}
void BlobResourceHandle::notifyFail(Error errorCode)
{
if (client())
client()->didFail(this, ResourceError(webKitBlobResourceDomain, static_cast<int>(errorCode), firstRequest().url(), String()));
}
static void doNotifyFinish(BlobResourceHandle& handle)
{
if (handle.aborted())
return;
if (!handle.client())
return;
handle.client()->didFinishLoading(&handle);
}
void BlobResourceHandle::notifyFinish()
{
if (!m_async) {
doNotifyFinish(*this);
return;
}
callOnMainThread([protectedThis = makeRef(*this)]() mutable {
doNotifyFinish(protectedThis);
});
}
}