blob: 0d319f83d8e3fee25aaf18f399d09eb1de25a188 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// 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_file.h"
#include "base/base64.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/strings/string_util.h"
#include "base/task/lazy_thread_pool_task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "base/third_party/icu/icu_utf.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "storage/browser/file_system/file_system_context.h"
namespace content {
scoped_refptr<base::SequencedTaskRunner> impl_task_runner() {
constexpr base::TaskTraits kBlockingTraits = {
base::MayBlock(), base::TaskPriority::USER_VISIBLE};
static base::LazyThreadPoolSequencedTaskRunner s_sequenced_task_unner =
LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(kBlockingTraits);
return s_sequenced_task_unner.Get();
}
scoped_refptr<DevToolsStreamFile> DevToolsStreamFile::Create(
DevToolsIOContext* context,
bool binary) {
return new DevToolsStreamFile(context, binary);
}
DevToolsStreamFile::DevToolsStreamFile(DevToolsIOContext* context, bool binary)
: DevToolsIOContext::Stream(impl_task_runner()),
handle_(Register(context)),
binary_(binary),
task_runner_(impl_task_runner()) {}
DevToolsStreamFile::~DevToolsStreamFile() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
}
bool DevToolsStreamFile::InitOnFileSequenceIfNeeded() {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
if (had_errors_)
return false;
if (file_.IsValid())
return true;
base::FilePath temp_path;
if (!base::CreateTemporaryFile(&temp_path)) {
LOG(ERROR) << "Failed to create temporary file";
had_errors_ = true;
return false;
}
const unsigned flags = base::File::FLAG_OPEN_TRUNCATED |
base::File::FLAG_WRITE | base::File::FLAG_READ |
base::File::FLAG_DELETE_ON_CLOSE;
file_.Initialize(temp_path, flags);
if (!file_.IsValid()) {
LOG(ERROR) << "Failed to open temporary file: " << temp_path.value() << ", "
<< base::File::ErrorToString(file_.error_details());
had_errors_ = true;
base::DeleteFile(temp_path);
return false;
}
return true;
}
void DevToolsStreamFile::Read(off_t position,
size_t max_size,
ReadCallback callback) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DevToolsStreamFile::ReadOnFileSequence, this,
position, max_size, std::move(callback)));
}
void DevToolsStreamFile::Append(std::unique_ptr<std::string> data) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DevToolsStreamFile::AppendOnFileSequence, this,
std::move(data)));
}
void DevToolsStreamFile::ReadOnFileSequence(off_t position,
size_t max_size,
ReadCallback callback) {
auto data = std::make_unique<std::string>();
Status status;
if (!file_.IsValid()) {
status = StatusFailure;
} else {
status = InnerReadOnFileSequence(position, max_size, *data);
if (status == StatusFailure) {
had_errors_ = true;
file_.Close();
}
}
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), std::move(data), binary_, status));
}
DevToolsIOContext::Stream::Status DevToolsStreamFile::InnerReadOnFileSequence(
off_t position,
size_t max_size,
std::string& buffer) {
DCHECK(task_runner_->RunsTasksInCurrentSequence());
DCHECK(file_.IsValid());
if (position < 0) {
position = last_read_pos_;
}
if (position >= last_written_pos_) {
return StatusEOF;
}
max_size =
std::min(max_size, static_cast<size_t>(last_written_pos_ - position));
buffer.resize(max_size);
std::optional<size_t> size_got =
file_.ReadNoBestEffort(position, base::as_writable_byte_span(buffer));
if (!size_got.has_value()) {
LOG(ERROR) << "Failed to read temporary file";
return StatusFailure;
}
// Provided client has requested sufficient large block, make their
// life easier by not truncating in the middle of a UTF-8 character.
if (size_got.value() > 6 && !CBU8_IS_SINGLE(buffer[size_got.value() - 1])) {
std::string truncated;
base::TruncateUTF8ToByteSize(buffer, size_got.value(), &truncated);
// If the above failed, we're dealing with binary files, so
// don't mess with them.
if (truncated.size()) {
buffer = std::move(truncated);
size_got = buffer.size();
}
}
buffer.resize(size_got.value());
last_read_pos_ = position + size_got.value();
if (binary_) {
buffer = base::Base64Encode(buffer);
}
return size_got ? StatusSuccess : StatusEOF;
}
void DevToolsStreamFile::AppendOnFileSequence(
std::unique_ptr<std::string> data) {
if (!InitOnFileSequenceIfNeeded()) {
return;
}
if (!file_.WriteAtCurrentPosAndCheck(base::as_byte_span(*data))) {
LOG(ERROR) << "Failed to write temporary file";
had_errors_ = true;
file_.Close();
return;
}
last_written_pos_ += data->size();
}
} // namespace content