// 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_file.h" #include "base/base64.h" #include "base/bind.h" #include "base/files/file_util.h" #include "base/sequenced_task_runner.h" #include "base/strings/string_util.h" #include "base/task/lazy_thread_pool_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 impl_task_runner() { constexpr base::TaskTraits kBlockingTraits = { base::MayBlock(), base::TaskPriority::BEST_EFFORT}; static base::LazyThreadPoolSequencedTaskRunner s_sequenced_task_unner = LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(kBlockingTraits); return s_sequenced_task_unner.Get(); } scoped_refptr 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()), had_errors_(false), last_read_pos_(0) {} 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 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) { DCHECK(task_runner_->RunsTasksInCurrentSequence()); Status status = StatusFailure; std::unique_ptr data; bool base64_encoded = false; if (file_.IsValid()) { std::string buffer; buffer.resize(max_size); if (position < 0) position = last_read_pos_; int size_got = file_.ReadNoBestEffort(position, &*buffer.begin(), max_size); if (size_got < 0) { LOG(ERROR) << "Failed to read temporary file"; had_errors_ = true; file_.Close(); } else { // 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 > 6 && !CBU8_IS_SINGLE(buffer[size_got - 1])) { base::TruncateUTF8ToByteSize(buffer, size_got, &buffer); size_got = buffer.size(); } else { buffer.resize(size_got); } data.reset(new std::string(std::move(buffer))); status = size_got ? StatusSuccess : StatusEOF; last_read_pos_ = position + size_got; } } if (binary_) { std::string raw_data(std::move(*data)); base::Base64Encode(raw_data, data.get()); base64_encoded = true; } GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(std::move(callback), std::move(data), base64_encoded, status)); } void DevToolsStreamFile::AppendOnFileSequence( std::unique_ptr data) { if (!InitOnFileSequenceIfNeeded()) return; int size_written = file_.WriteAtCurrentPos(&*data->begin(), data->length()); if (size_written != static_cast(data->length())) { LOG(ERROR) << "Failed to write temporary file"; had_errors_ = true; file_.Close(); } } } // namespace content