blob: 749e8a60488b5af67292559b27427043febbd359 [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 "core/fetch/BlobBytesConsumer.h"
#include "core/fetch/BytesConsumerForDataConsumerHandle.h"
#include "core/loader/ThreadableLoader.h"
#include "platform/blob/BlobData.h"
#include "platform/blob/BlobRegistry.h"
#include "platform/blob/BlobURL.h"
#include "platform/loader/fetch/ResourceError.h"
#include "platform/loader/fetch/ResourceLoaderOptions.h"
#include "platform/loader/fetch/ResourceRequest.h"
#include "platform/loader/fetch/fetch_initiator_type_names.h"
#include "platform/weborigin/KURL.h"
#include "platform/weborigin/SecurityOrigin.h"
namespace blink {
BlobBytesConsumer::BlobBytesConsumer(
ExecutionContext* execution_context,
scoped_refptr<BlobDataHandle> blob_data_handle,
ThreadableLoader* loader)
: ContextLifecycleObserver(execution_context),
blob_data_handle_(std::move(blob_data_handle)),
loader_(loader) {
if (!blob_data_handle_) {
// Note that |m_loader| is non-null only in tests.
if (loader_) {
loader_->Cancel();
loader_ = nullptr;
}
state_ = PublicState::kClosed;
}
}
BlobBytesConsumer::BlobBytesConsumer(
ExecutionContext* execution_context,
scoped_refptr<BlobDataHandle> blob_data_handle)
: BlobBytesConsumer(execution_context,
std::move(blob_data_handle),
nullptr) {}
BlobBytesConsumer::~BlobBytesConsumer() {
if (!blob_url_.IsEmpty())
BlobRegistry::RevokePublicBlobURL(blob_url_);
}
BytesConsumer::Result BlobBytesConsumer::BeginRead(const char** buffer,
size_t* available) {
*buffer = nullptr;
*available = 0;
if (state_ == PublicState::kClosed) {
// 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::kDone;
}
if (IsClean()) {
DCHECK(blob_url_.IsEmpty());
blob_url_ =
BlobURL::CreatePublicURL(GetExecutionContext()->GetSecurityOrigin());
if (blob_url_.IsEmpty()) {
GetError();
} else {
BlobRegistry::RegisterPublicBlobURL(
GetExecutionContext()->GetMutableSecurityOrigin(), blob_url_,
blob_data_handle_);
// m_loader is non-null only in tests.
if (!loader_)
loader_ = CreateLoader();
ResourceRequest request(blob_url_);
request.SetRequestContext(WebURLRequest::kRequestContextInternal);
request.SetFetchRequestMode(
network::mojom::FetchRequestMode::kSameOrigin);
request.SetFetchCredentialsMode(
network::mojom::FetchCredentialsMode::kOmit);
request.SetUseStreamOnResponse(true);
// We intentionally skip
// 'setExternalRequestStateFromRequestorAddressSpace', as 'blob:'
// can never be external.
loader_->Start(request);
}
blob_data_handle_ = nullptr;
}
DCHECK_NE(state_, PublicState::kClosed);
if (state_ == PublicState::kErrored)
return Result::kError;
if (!body_) {
// The response has not arrived.
return Result::kShouldWait;
}
auto result = body_->BeginRead(buffer, available);
switch (result) {
case Result::kOk:
case Result::kShouldWait:
break;
case Result::kDone:
has_seen_end_of_data_ = true;
if (has_finished_loading_)
Close();
return state_ == PublicState::kClosed ? Result::kDone
: Result::kShouldWait;
case Result::kError:
GetError();
break;
}
return result;
}
BytesConsumer::Result BlobBytesConsumer::EndRead(size_t read) {
DCHECK(body_);
return body_->EndRead(read);
}
scoped_refptr<BlobDataHandle> BlobBytesConsumer::DrainAsBlobDataHandle(
BlobSizePolicy policy) {
if (!IsClean())
return nullptr;
DCHECK(blob_data_handle_);
if (policy == BlobSizePolicy::kDisallowBlobWithInvalidSize &&
blob_data_handle_->size() == UINT64_MAX)
return nullptr;
Close();
return std::move(blob_data_handle_);
}
scoped_refptr<EncodedFormData> BlobBytesConsumer::DrainAsFormData() {
scoped_refptr<BlobDataHandle> handle =
DrainAsBlobDataHandle(BlobSizePolicy::kAllowBlobWithInvalidSize);
if (!handle)
return nullptr;
scoped_refptr<EncodedFormData> form_data = EncodedFormData::Create();
form_data->AppendBlob(handle->Uuid(), handle);
return form_data;
}
void BlobBytesConsumer::SetClient(BytesConsumer::Client* client) {
DCHECK(!client_);
DCHECK(client);
client_ = client;
}
void BlobBytesConsumer::ClearClient() {
client_ = nullptr;
}
void BlobBytesConsumer::Cancel() {
if (state_ == PublicState::kClosed || state_ == PublicState::kErrored)
return;
Close();
if (body_) {
body_->Cancel();
body_ = nullptr;
}
if (!blob_url_.IsEmpty()) {
BlobRegistry::RevokePublicBlobURL(blob_url_);
blob_url_ = KURL();
}
blob_data_handle_ = nullptr;
}
BytesConsumer::Error BlobBytesConsumer::GetError() const {
DCHECK_EQ(PublicState::kErrored, state_);
return Error("Failed to load a blob.");
}
BytesConsumer::PublicState BlobBytesConsumer::GetPublicState() const {
return state_;
}
void BlobBytesConsumer::ContextDestroyed(ExecutionContext*) {
if (state_ != PublicState::kReadableOrWaiting)
return;
BytesConsumer::Client* client = client_;
GetError();
if (client)
client->OnStateChange();
}
void BlobBytesConsumer::OnStateChange() {
if (state_ != PublicState::kReadableOrWaiting)
return;
DCHECK(body_);
BytesConsumer::Client* client = client_;
switch (body_->GetPublicState()) {
case PublicState::kReadableOrWaiting:
break;
case PublicState::kClosed:
has_seen_end_of_data_ = true;
if (has_finished_loading_)
Close();
break;
case PublicState::kErrored:
GetError();
break;
}
if (client)
client->OnStateChange();
}
void BlobBytesConsumer::DidReceiveResponse(
unsigned long identifier,
const ResourceResponse&,
std::unique_ptr<WebDataConsumerHandle> handle) {
DCHECK(handle);
DCHECK(!body_);
DCHECK_EQ(PublicState::kReadableOrWaiting, state_);
body_ = new BytesConsumerForDataConsumerHandle(GetExecutionContext(),
std::move(handle));
body_->SetClient(this);
if (IsClean()) {
// This function is called synchronously in ThreadableLoader::start.
return;
}
OnStateChange();
}
void BlobBytesConsumer::DidFinishLoading(unsigned long identifier,
double finish_time) {
DCHECK_EQ(PublicState::kReadableOrWaiting, state_);
has_finished_loading_ = true;
loader_ = nullptr;
if (!has_seen_end_of_data_)
return;
DCHECK(!IsClean());
BytesConsumer::Client* client = client_;
Close();
if (client)
client->OnStateChange();
}
void BlobBytesConsumer::DidFail(const ResourceError& e) {
if (e.IsCancellation()) {
if (state_ != PublicState::kReadableOrWaiting)
return;
}
DCHECK_EQ(PublicState::kReadableOrWaiting, state_);
loader_ = nullptr;
BytesConsumer::Client* client = client_;
GetError();
if (IsClean()) {
// This function is called synchronously in ThreadableLoader::start.
return;
}
if (client) {
client->OnStateChange();
client = nullptr;
}
}
void BlobBytesConsumer::DidFailRedirectCheck() {
NOTREACHED();
}
void BlobBytesConsumer::Trace(blink::Visitor* visitor) {
visitor->Trace(body_);
visitor->Trace(client_);
visitor->Trace(loader_);
BytesConsumer::Trace(visitor);
BytesConsumer::Client::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
}
BlobBytesConsumer* BlobBytesConsumer::CreateForTesting(
ExecutionContext* execution_context,
scoped_refptr<BlobDataHandle> blob_data_handle,
ThreadableLoader* loader) {
return new BlobBytesConsumer(execution_context, std::move(blob_data_handle),
loader);
}
ThreadableLoader* BlobBytesConsumer::CreateLoader() {
ThreadableLoaderOptions options;
ResourceLoaderOptions resource_loader_options;
resource_loader_options.data_buffering_policy = kDoNotBufferData;
resource_loader_options.initiator_info.name =
FetchInitiatorTypeNames::internal;
return ThreadableLoader::Create(*GetExecutionContext(), this, options,
resource_loader_options);
}
void BlobBytesConsumer::Close() {
DCHECK_EQ(state_, PublicState::kReadableOrWaiting);
state_ = PublicState::kClosed;
Clear();
}
void BlobBytesConsumer::GetError() {
DCHECK_EQ(state_, PublicState::kReadableOrWaiting);
state_ = PublicState::kErrored;
Clear();
}
void BlobBytesConsumer::Clear() {
DCHECK_NE(state_, PublicState::kReadableOrWaiting);
if (loader_) {
loader_->Cancel();
loader_ = nullptr;
}
client_ = nullptr;
}
} // namespace blink