blob: 27135840da1e6cf903ba883f082c472efc824325 [file] [log] [blame]
// Copyright 2018 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 "content/browser/devtools/devtools_stream_blob.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/strings/string_piece.h"
#include "base/task/post_task.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/io_buffer.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_reader.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/common/blob_storage/blob_storage_constants.h"
namespace content {
using storage::BlobReader;
DevToolsStreamBlob::ReadRequest::ReadRequest(off_t position,
size_t max_size,
ReadCallback callback)
: position(position), max_size(max_size), callback(std::move(callback)) {}
DevToolsStreamBlob::ReadRequest::~ReadRequest() = default;
DevToolsStreamBlob::DevToolsStreamBlob()
: DevToolsIOContext::Stream(
base::CreateSingleThreadTaskRunnerWithTraits({BrowserThread::IO})),
last_read_pos_(0),
failed_(false),
is_binary_(false) {}
DevToolsStreamBlob::~DevToolsStreamBlob() {
if (blob_reader_)
blob_reader_->Kill();
}
namespace {
void UnregisterIfOpenFailed(base::WeakPtr<DevToolsIOContext> context,
const std::string& handle,
bool success) {
if (!success && context)
context->Close(handle);
}
} // namespace
// static
scoped_refptr<DevToolsIOContext::Stream> DevToolsStreamBlob::Create(
DevToolsIOContext* io_context,
ChromeBlobStorageContext* blob_context,
StoragePartition* partition,
const std::string& handle,
const std::string& uuid) {
scoped_refptr<DevToolsStreamBlob> result = new DevToolsStreamBlob();
result->Register(io_context, handle);
result->Open(
blob_context, partition, uuid,
base::BindOnce(&UnregisterIfOpenFailed, io_context->AsWeakPtr(), handle));
return std::move(result);
}
void DevToolsStreamBlob::ReadRequest::Fail() {
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
base::BindOnce(std::move(callback), nullptr, false,
Stream::StatusFailure));
}
void DevToolsStreamBlob::Open(scoped_refptr<ChromeBlobStorageContext> context,
StoragePartition* partition,
const std::string& handle,
OpenCallback callback) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&DevToolsStreamBlob::OpenOnIO, this, context, handle,
std::move(callback)));
}
void DevToolsStreamBlob::Read(off_t position,
size_t max_size,
ReadCallback callback) {
std::unique_ptr<ReadRequest> request(
new ReadRequest(position, max_size, std::move(callback)));
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&DevToolsStreamBlob::ReadOnIO, this, std::move(request)));
}
void DevToolsStreamBlob::OpenOnIO(
scoped_refptr<ChromeBlobStorageContext> blob_context,
const std::string& uuid,
OpenCallback callback) {
DCHECK(!blob_handle_);
storage::BlobStorageContext* bsc = blob_context->context();
blob_handle_ = bsc->GetBlobDataFromUUID(uuid);
if (!blob_handle_) {
LOG(ERROR) << "No blob with uuid: " << uuid;
FailOnIO(std::move(callback));
return;
}
is_binary_ = !DevToolsIOContext::IsTextMimeType(blob_handle_->content_type());
open_callback_ = std::move(callback);
blob_handle_->RunOnConstructionComplete(
base::BindOnce(&DevToolsStreamBlob::OnBlobConstructionComplete, this));
}
void DevToolsStreamBlob::OnBlobConstructionComplete(
storage::BlobStatus status) {
DCHECK(!BlobStatusIsPending(status));
if (BlobStatusIsError(status)) {
LOG(ERROR) << "Blob building failed: " << static_cast<int>(status);
FailOnIO(std::move(open_callback_));
return;
}
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
base::BindOnce(std::move(open_callback_), true));
if (!pending_reads_.empty())
StartReadRequest();
}
void DevToolsStreamBlob::ReadOnIO(std::unique_ptr<ReadRequest> request) {
if (failed_) {
request->Fail();
return;
}
pending_reads_.push(std::move(request));
if (pending_reads_.size() > 1 || open_callback_)
return;
StartReadRequest();
}
void DevToolsStreamBlob::FailOnIO() {
failed_ = true;
while (!pending_reads_.empty()) {
pending_reads_.front()->Fail();
pending_reads_.pop();
}
}
void DevToolsStreamBlob::FailOnIO(OpenCallback callback) {
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
base::BindOnce(std::move(callback), false));
FailOnIO();
}
void DevToolsStreamBlob::StartReadRequest() {
DCHECK_GE(pending_reads_.size(), 1UL);
DCHECK(blob_handle_);
DCHECK(!failed_);
ReadRequest& request = *pending_reads_.front();
if (request.position < 0)
request.position = last_read_pos_;
if (request.position != last_read_pos_)
blob_reader_.reset();
if (!blob_reader_)
CreateReader();
else
BeginRead();
}
void DevToolsStreamBlob::BeginRead() {
DCHECK_GE(pending_reads_.size(), 1UL);
ReadRequest& request = *pending_reads_.front();
if (!io_buf_ || static_cast<size_t>(io_buf_->size()) < request.max_size)
io_buf_ = base::MakeRefCounted<net::IOBufferWithSize>(request.max_size);
int bytes_read;
BlobReader::Status status = blob_reader_->Read(
io_buf_.get(), request.max_size, &bytes_read,
base::BindOnce(&DevToolsStreamBlob::OnReadComplete, this));
if (status == BlobReader::Status::IO_PENDING)
return;
// This is for uniformity with the asynchronous case.
if (status == BlobReader::Status::NET_ERROR) {
bytes_read = blob_reader_->net_error();
DCHECK_LT(0, bytes_read);
}
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&DevToolsStreamBlob::OnReadComplete, this, bytes_read));
}
void DevToolsStreamBlob::OnReadComplete(int bytes_read) {
std::unique_ptr<ReadRequest> request = std::move(pending_reads_.front());
pending_reads_.pop();
Status status;
std::unique_ptr<std::string> data(new std::string());
bool base64_encoded = false;
if (bytes_read < 0) {
status = StatusFailure;
LOG(ERROR) << "Error reading blob: " << net::ErrorToString(bytes_read);
} else if (!bytes_read) {
status = StatusEOF;
} else {
last_read_pos_ += bytes_read;
status = blob_reader_->remaining_bytes() ? StatusSuccess : StatusEOF;
if (is_binary_) {
base64_encoded = true;
Base64Encode(base::StringPiece(io_buf_->data(), bytes_read), data.get());
} else {
// TODO(caseq): truncate at UTF8 boundary.
*data = std::string(io_buf_->data(), bytes_read);
}
}
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(std::move(request->callback), std::move(data),
base64_encoded, status));
if (!pending_reads_.empty())
StartReadRequest();
}
void DevToolsStreamBlob::CreateReader() {
DCHECK(!blob_reader_);
blob_reader_ = blob_handle_->CreateReader();
BlobReader::Status status = blob_reader_->CalculateSize(
base::BindOnce(&DevToolsStreamBlob::OnCalculateSizeComplete, this));
if (status != BlobReader::Status::IO_PENDING) {
OnCalculateSizeComplete(status == BlobReader::Status::NET_ERROR
? blob_reader_->net_error()
: net::OK);
}
}
void DevToolsStreamBlob::OnCalculateSizeComplete(int net_error) {
if (net_error != net::OK) {
FailOnIO();
return;
}
off_t seek_to = pending_reads_.front()->position;
if (seek_to != 0UL) {
if (seek_to >= static_cast<off_t>(blob_reader_->total_size())) {
OnReadComplete(0);
return;
}
BlobReader::Status status = blob_reader_->SetReadRange(
seek_to, blob_reader_->total_size() - seek_to);
if (status != BlobReader::Status::DONE) {
FailOnIO();
return;
}
}
BeginRead();
}
} // namespace content