blob: 1ddf1a9414ce84ad9701fc3887962e1d1ff24f7b [file] [log] [blame]
// Copyright 2016 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/BlobBytesConsumer.h"
#include "core/fetch/FetchInitiatorTypeNames.h"
#include "core/loader/ThreadableLoader.h"
#include "modules/fetch/BytesConsumerForDataConsumerHandle.h"
#include "platform/blob/BlobData.h"
#include "platform/blob/BlobRegistry.h"
#include "platform/blob/BlobURL.h"
#include "platform/network/ResourceError.h"
#include "platform/network/ResourceRequest.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/SecurityOrigin.h"
namespace blink {
BlobBytesConsumer::BlobBytesConsumer(ExecutionContext* executionContext,
PassRefPtr<BlobDataHandle> blobDataHandle,
ThreadableLoader* loader)
: ContextLifecycleObserver(executionContext),
m_blobDataHandle(blobDataHandle),
m_loader(loader) {
if (!m_blobDataHandle) {
// Note that |m_loader| is non-null only in tests.
if (m_loader) {
m_loader->cancel();
m_loader = nullptr;
}
m_state = PublicState::Closed;
}
}
BlobBytesConsumer::BlobBytesConsumer(ExecutionContext* executionContext,
PassRefPtr<BlobDataHandle> blobDataHandle)
: BlobBytesConsumer(executionContext, std::move(blobDataHandle), nullptr) {}
BlobBytesConsumer::~BlobBytesConsumer() {}
BytesConsumer::Result BlobBytesConsumer::beginRead(const char** buffer,
size_t* available) {
*buffer = nullptr;
*available = 0;
if (m_state == PublicState::Closed) {
// It's possible that |cancel| has been called before the first
// |beginRead| call. That's why we need to check this condition
// before checking |isClean()|.
return Result::Done;
}
if (isClean()) {
KURL m_blobURL =
BlobURL::createPublicURL(getExecutionContext()->getSecurityOrigin());
if (m_blobURL.isEmpty()) {
error();
} else {
BlobRegistry::registerPublicBlobURL(
getExecutionContext()->getSecurityOrigin(), m_blobURL,
m_blobDataHandle);
// m_loader is non-null only in tests.
if (!m_loader)
m_loader = createLoader();
ResourceRequest request(m_blobURL);
request.setRequestContext(WebURLRequest::RequestContextInternal);
request.setUseStreamOnResponse(true);
// We intentionally skip
// 'setExternalRequestStateFromRequestorAddressSpace', as 'blob:'
// can never be external.
m_loader->start(request);
}
m_blobDataHandle = nullptr;
}
DCHECK_NE(m_state, PublicState::Closed);
if (m_state == PublicState::Errored)
return Result::Error;
if (!m_body) {
// The response has not arrived.
return Result::ShouldWait;
}
auto result = m_body->beginRead(buffer, available);
switch (result) {
case Result::Ok:
case Result::ShouldWait:
break;
case Result::Done:
m_hasSeenEndOfData = true;
if (m_hasFinishedLoading)
close();
return m_state == PublicState::Closed ? Result::Done : Result::ShouldWait;
case Result::Error:
error();
break;
}
return result;
}
BytesConsumer::Result BlobBytesConsumer::endRead(size_t read) {
DCHECK(m_body);
return m_body->endRead(read);
}
PassRefPtr<BlobDataHandle> BlobBytesConsumer::drainAsBlobDataHandle(
BlobSizePolicy policy) {
if (!isClean())
return nullptr;
DCHECK(m_blobDataHandle);
if (policy == BlobSizePolicy::DisallowBlobWithInvalidSize &&
m_blobDataHandle->size() == UINT64_MAX)
return nullptr;
close();
return m_blobDataHandle.release();
}
PassRefPtr<EncodedFormData> BlobBytesConsumer::drainAsFormData() {
RefPtr<BlobDataHandle> handle =
drainAsBlobDataHandle(BlobSizePolicy::AllowBlobWithInvalidSize);
if (!handle)
return nullptr;
RefPtr<EncodedFormData> formData = EncodedFormData::create();
formData->appendBlob(handle->uuid(), handle);
return formData.release();
}
void BlobBytesConsumer::setClient(BytesConsumer::Client* client) {
DCHECK(!m_client);
DCHECK(client);
m_client = client;
}
void BlobBytesConsumer::clearClient() {
m_client = nullptr;
}
void BlobBytesConsumer::cancel() {
if (m_state == PublicState::Closed || m_state == PublicState::Errored)
return;
close();
if (m_body) {
m_body->cancel();
m_body = nullptr;
}
if (!m_blobURL.isEmpty()) {
BlobRegistry::revokePublicBlobURL(m_blobURL);
m_blobURL = KURL();
}
m_blobDataHandle = nullptr;
}
BytesConsumer::Error BlobBytesConsumer::getError() const {
DCHECK_EQ(PublicState::Errored, m_state);
return Error("Failed to load a blob.");
}
BytesConsumer::PublicState BlobBytesConsumer::getPublicState() const {
return m_state;
}
void BlobBytesConsumer::contextDestroyed(ExecutionContext*) {
if (m_state != PublicState::ReadableOrWaiting)
return;
BytesConsumer::Client* client = m_client;
error();
if (client)
client->onStateChange();
}
void BlobBytesConsumer::onStateChange() {
if (m_state != PublicState::ReadableOrWaiting)
return;
DCHECK(m_body);
BytesConsumer::Client* client = m_client;
switch (m_body->getPublicState()) {
case PublicState::ReadableOrWaiting:
break;
case PublicState::Closed:
m_hasSeenEndOfData = true;
if (m_hasFinishedLoading)
close();
break;
case PublicState::Errored:
error();
break;
}
if (client)
client->onStateChange();
}
void BlobBytesConsumer::didReceiveResponse(
unsigned long identifier,
const ResourceResponse&,
std::unique_ptr<WebDataConsumerHandle> handle) {
DCHECK(handle);
DCHECK(!m_body);
DCHECK_EQ(PublicState::ReadableOrWaiting, m_state);
m_body = new BytesConsumerForDataConsumerHandle(getExecutionContext(),
std::move(handle));
m_body->setClient(this);
if (isClean()) {
// This function is called synchronously in ThreadableLoader::start.
return;
}
onStateChange();
}
void BlobBytesConsumer::didFinishLoading(unsigned long identifier,
double finishTime) {
DCHECK_EQ(PublicState::ReadableOrWaiting, m_state);
m_hasFinishedLoading = true;
m_loader = nullptr;
if (!m_hasSeenEndOfData)
return;
DCHECK(!isClean());
BytesConsumer::Client* client = m_client;
close();
if (client)
client->onStateChange();
}
void BlobBytesConsumer::didFail(const ResourceError& e) {
if (e.isCancellation()) {
if (m_state != PublicState::ReadableOrWaiting)
return;
}
DCHECK_EQ(PublicState::ReadableOrWaiting, m_state);
m_loader = nullptr;
BytesConsumer::Client* client = m_client;
error();
if (isClean()) {
// This function is called synchronously in ThreadableLoader::start.
return;
}
if (client) {
client->onStateChange();
client = nullptr;
}
}
void BlobBytesConsumer::didFailRedirectCheck() {
NOTREACHED();
}
DEFINE_TRACE(BlobBytesConsumer) {
visitor->trace(m_body);
visitor->trace(m_client);
visitor->trace(m_loader);
BytesConsumer::trace(visitor);
BytesConsumer::Client::trace(visitor);
ContextLifecycleObserver::trace(visitor);
}
BlobBytesConsumer* BlobBytesConsumer::createForTesting(
ExecutionContext* executionContext,
PassRefPtr<BlobDataHandle> blobDataHandle,
ThreadableLoader* loader) {
return new BlobBytesConsumer(executionContext, std::move(blobDataHandle),
loader);
}
ThreadableLoader* BlobBytesConsumer::createLoader() {
ThreadableLoaderOptions options;
options.preflightPolicy = ConsiderPreflight;
options.crossOriginRequestPolicy = DenyCrossOriginRequests;
options.contentSecurityPolicyEnforcement = DoNotEnforceContentSecurityPolicy;
options.initiator = FetchInitiatorTypeNames::internal;
ResourceLoaderOptions resourceLoaderOptions;
resourceLoaderOptions.dataBufferingPolicy = DoNotBufferData;
return ThreadableLoader::create(*getExecutionContext(), this, options,
resourceLoaderOptions);
}
void BlobBytesConsumer::close() {
DCHECK_EQ(m_state, PublicState::ReadableOrWaiting);
m_state = PublicState::Closed;
clear();
}
void BlobBytesConsumer::error() {
DCHECK_EQ(m_state, PublicState::ReadableOrWaiting);
m_state = PublicState::Errored;
clear();
}
void BlobBytesConsumer::clear() {
DCHECK_NE(m_state, PublicState::ReadableOrWaiting);
if (m_loader) {
m_loader->cancel();
m_loader = nullptr;
}
m_client = nullptr;
}
} // namespace blink