blob: ab08ead7d2e655ec0765fa6d6131641a6d5070ad [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/fetch/FetchBlobDataConsumerHandle.h"
#include "core/dom/ExecutionContext.h"
#include "core/fetch/FetchInitiatorTypeNames.h"
#include "core/loader/ThreadableLoaderClient.h"
#include "modules/fetch/CompositeDataConsumerHandle.h"
#include "modules/fetch/CrossThreadHolder.h"
#include "modules/fetch/DataConsumerHandleUtil.h"
#include "platform/blob/BlobRegistry.h"
#include "platform/blob/BlobURL.h"
#include "platform/network/ResourceRequest.h"
#include "wtf/OwnPtr.h"
namespace blink {
using Result = FetchBlobDataConsumerHandle::Result;
namespace {
// Object graph:
// +-------------+
// |ReaderContext|
// +-------------+ +-----------+ +---+ | |
// |LoaderContext|<-|CTH::Bridge|<->|CTH|<-| |
// +-------------+ +-----------+ +---+ +-------------+
// |
// +--> ThreadableLoader
//
// When the loader thread is stopped, CrossThreadHolder::Bridge and
// LoaderContext (and thus ThreadableLoader) is destructed:
// +-------------+
// |ReaderContext|
// +---+ | |
// |CTH|<-| |
// +---+ +-------------+
// and the rest will be destructed when ReaderContext is destructed.
//
// When ReaderContext is destructed, CrossThreadHolder is destructed:
//
// +-------------+ +-----------+
// |LoaderContext|<-|CTH::Bridge|
// +-------------+ +-----------+
// |
// +--> ThreadableLoader
// and the rest will be shortly destructed when CrossThreadHolder::Bridge
// is garbage collected.
// LoaderContext is created and destructed on the same thread
// (call this thread loader thread).
// All methods must be called on the loader thread.
class LoaderContext {
public:
virtual ~LoaderContext() { }
virtual void start(ExecutionContext*) = 0;
};
// All methods must be called on the loader thread.
class BlobLoaderContext final
: public LoaderContext
, public ThreadableLoaderClient {
public:
BlobLoaderContext(CompositeDataConsumerHandle::Updater* updater, PassRefPtr<BlobDataHandle> blobDataHandle, FetchBlobDataConsumerHandle::LoaderFactory* loaderFactory)
: m_updater(updater)
, m_blobDataHandle(blobDataHandle)
, m_loaderFactory(loaderFactory)
, m_receivedResponse(false) { }
~BlobLoaderContext() override
{
if (m_loader && !m_receivedResponse)
m_updater->update(createUnexpectedErrorDataConsumerHandle());
if (m_loader) {
m_loader->cancel();
m_loader.clear();
}
}
void start(ExecutionContext* executionContext) override
{
ASSERT(executionContext->isContextThread());
ASSERT(!m_loader);
KURL url = BlobURL::createPublicURL(executionContext->getSecurityOrigin());
if (url.isEmpty()) {
m_updater->update(createUnexpectedErrorDataConsumerHandle());
return;
}
BlobRegistry::registerPublicBlobURL(executionContext->getSecurityOrigin(), url, m_blobDataHandle);
m_loader = createLoader(executionContext, this);
ASSERT(m_loader);
ResourceRequest request(url);
request.setRequestContext(WebURLRequest::RequestContextInternal);
request.setUseStreamOnResponse(true);
// We intentionally skip 'setExternalRequestStateFromRequestorAddressSpace', as 'data:' can never be external.
m_loader->start(request);
}
private:
PassOwnPtr<ThreadableLoader> createLoader(ExecutionContext* executionContext, ThreadableLoaderClient* client) const
{
ThreadableLoaderOptions options;
options.preflightPolicy = ConsiderPreflight;
options.crossOriginRequestPolicy = DenyCrossOriginRequests;
options.contentSecurityPolicyEnforcement = DoNotEnforceContentSecurityPolicy;
options.initiator = FetchInitiatorTypeNames::internal;
ResourceLoaderOptions resourceLoaderOptions;
resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData;
return m_loaderFactory->create(*executionContext, client, options, resourceLoaderOptions);
}
// ThreadableLoaderClient
void didReceiveResponse(unsigned long, const ResourceResponse&, PassOwnPtr<WebDataConsumerHandle> handle) override
{
ASSERT(!m_receivedResponse);
m_receivedResponse = true;
if (!handle) {
// Here we assume WebURLLoader must return the response body as
// |WebDataConsumerHandle| since we call
// request.setUseStreamOnResponse().
m_updater->update(createUnexpectedErrorDataConsumerHandle());
return;
}
m_updater->update(handle);
}
void didFinishLoading(unsigned long, double) override
{
m_loader.clear();
}
void didFail(const ResourceError&) override
{
if (!m_receivedResponse)
m_updater->update(createUnexpectedErrorDataConsumerHandle());
m_loader.clear();
}
void didFailRedirectCheck() override
{
// We don't expect redirects for Blob loading.
ASSERT_NOT_REACHED();
}
Persistent<CompositeDataConsumerHandle::Updater> m_updater;
RefPtr<BlobDataHandle> m_blobDataHandle;
Persistent<FetchBlobDataConsumerHandle::LoaderFactory> m_loaderFactory;
OwnPtr<ThreadableLoader> m_loader;
bool m_receivedResponse;
};
class DefaultLoaderFactory final : public FetchBlobDataConsumerHandle::LoaderFactory {
public:
PassOwnPtr<ThreadableLoader> create(
ExecutionContext& executionContext,
ThreadableLoaderClient* client,
const ThreadableLoaderOptions& options,
const ResourceLoaderOptions& resourceLoaderOptions) override
{
return ThreadableLoader::create(executionContext, client, options, resourceLoaderOptions);
}
};
} // namespace
// ReaderContext is referenced from FetchBlobDataConsumerHandle and
// ReaderContext::ReaderImpl.
// All functions/members must be called/accessed only on the reader thread,
// except for constructor, destructor, and obtainReader().
class FetchBlobDataConsumerHandle::ReaderContext final : public ThreadSafeRefCounted<ReaderContext> {
public:
class ReaderImpl : public FetchDataConsumerHandle::Reader {
public:
ReaderImpl(Client* client, PassRefPtr<ReaderContext> readerContext, PassOwnPtr<WebDataConsumerHandle::Reader> reader)
: m_readerContext(readerContext)
, m_reader(reader)
, m_notifier(client) { }
~ReaderImpl() override { }
Result read(void* data, size_t size, Flags flags, size_t* readSize) override
{
if (m_readerContext->drained())
return Done;
m_readerContext->ensureStartLoader();
Result r = m_reader->read(data, size, flags, readSize);
if (!(size == 0 && (r == Ok || r == ShouldWait))) {
// We read non-empty data, so we cannot use the blob data
// handle which represents the whole data.
m_readerContext->clearBlobDataHandleForDrain();
}
return r;
}
Result beginRead(const void** buffer, Flags flags, size_t* available) override
{
if (m_readerContext->drained())
return Done;
m_readerContext->ensureStartLoader();
m_readerContext->clearBlobDataHandleForDrain();
return m_reader->beginRead(buffer, flags, available);
}
Result endRead(size_t readSize) override
{
return m_reader->endRead(readSize);
}
PassRefPtr<BlobDataHandle> drainAsBlobDataHandle(BlobSizePolicy blobSizePolicy) override
{
if (!m_readerContext->m_blobDataHandleForDrain)
return nullptr;
if (blobSizePolicy == DisallowBlobWithInvalidSize && m_readerContext->m_blobDataHandleForDrain->size() == UINT64_MAX)
return nullptr;
RefPtr<BlobDataHandle> blobDataHandle = m_readerContext->m_blobDataHandleForDrain;
m_readerContext->setDrained();
m_readerContext->clearBlobDataHandleForDrain();
return blobDataHandle.release();
}
PassRefPtr<EncodedFormData> drainAsFormData() override
{
RefPtr<BlobDataHandle> handle = drainAsBlobDataHandle(AllowBlobWithInvalidSize);
if (!handle)
return nullptr;
RefPtr<EncodedFormData> formData = EncodedFormData::create();
formData->appendBlob(handle->uuid(), handle);
return formData.release();
}
private:
RefPtr<ReaderContext> m_readerContext;
OwnPtr<WebDataConsumerHandle::Reader> m_reader;
NotifyOnReaderCreationHelper m_notifier;
};
ReaderContext(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobDataHandle, FetchBlobDataConsumerHandle::LoaderFactory* loaderFactory)
: m_blobDataHandleForDrain(blobDataHandle)
, m_loaderStarted(false)
, m_drained(false)
{
CompositeDataConsumerHandle::Updater* updater = nullptr;
m_handle = CompositeDataConsumerHandle::create(createWaitingDataConsumerHandle(), &updater);
m_loaderContextHolder = CrossThreadHolder<LoaderContext>::create(executionContext, adoptPtr(new BlobLoaderContext(updater, m_blobDataHandleForDrain, loaderFactory)));
}
PassOwnPtr<FetchDataConsumerHandle::Reader> obtainReader(WebDataConsumerHandle::Client* client)
{
return adoptPtr(new ReaderImpl(client, this, m_handle->obtainReader(client)));
}
private:
void ensureStartLoader()
{
if (m_loaderStarted)
return;
m_loaderStarted = true;
m_loaderContextHolder->postTask(threadSafeBind<LoaderContext*, ExecutionContext*>(&LoaderContext::start));
}
void clearBlobDataHandleForDrain()
{
m_blobDataHandleForDrain.clear();
}
bool drained() const { return m_drained; }
void setDrained() { m_drained = true; }
OwnPtr<WebDataConsumerHandle> m_handle;
RefPtr<BlobDataHandle> m_blobDataHandleForDrain;
OwnPtr<CrossThreadHolder<LoaderContext>> m_loaderContextHolder;
bool m_loaderStarted;
bool m_drained;
};
FetchBlobDataConsumerHandle::FetchBlobDataConsumerHandle(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobDataHandle, LoaderFactory* loaderFactory)
: m_readerContext(adoptRef(new ReaderContext(executionContext, blobDataHandle, loaderFactory)))
{
}
FetchBlobDataConsumerHandle::~FetchBlobDataConsumerHandle()
{
}
PassOwnPtr<FetchDataConsumerHandle> FetchBlobDataConsumerHandle::create(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobDataHandle, LoaderFactory* loaderFactory)
{
if (!blobDataHandle)
return createFetchDataConsumerHandleFromWebHandle(createDoneDataConsumerHandle());
return adoptPtr(new FetchBlobDataConsumerHandle(executionContext, blobDataHandle, loaderFactory));
}
PassOwnPtr<FetchDataConsumerHandle> FetchBlobDataConsumerHandle::create(ExecutionContext* executionContext, PassRefPtr<BlobDataHandle> blobDataHandle)
{
if (!blobDataHandle)
return createFetchDataConsumerHandleFromWebHandle(createDoneDataConsumerHandle());
return adoptPtr(new FetchBlobDataConsumerHandle(executionContext, blobDataHandle, new DefaultLoaderFactory));
}
FetchDataConsumerHandle::Reader* FetchBlobDataConsumerHandle::obtainReaderInternal(Client* client)
{
return m_readerContext->obtainReader(client).leakPtr();
}
} // namespace blink