| // 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/check.h" | |
| #include "base/compiler_specific.h" | |
| #include "base/containers/span.h" | |
| #include "base/files/file_util.h" | |
| #include "base/functional/bind.h" | |
| #include "base/logging.h" | |
| #include "base/memory/scoped_refptr.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 "base/types/pass_key.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) { | |
| auto stream = base::MakeRefCounted<DevToolsStreamFile>( | |
| base::PassKey<DevToolsStreamFile>(), binary); | |
| // Registering takes a reference to the stream, so it must happen after the | |
| // object has been adopted by `MakeRefCounted`, not during construction. | |
| stream->handle_ = stream->Register(context); | |
| return stream; | |
| } | |
| DevToolsStreamFile::DevToolsStreamFile(base::PassKey<DevToolsStreamFile>, | |
| bool binary) | |
| : DevToolsIOContext::Stream(impl_task_runner()), | |
| binary_(binary), | |
| task_runner_(impl_task_runner()) {} | |
| DevToolsStreamFile::~DevToolsStreamFile() { | |
| CHECK(task_runner_->RunsTasksInCurrentSequence(), base::NotFatalUntil::M159); | |
| } | |
| bool DevToolsStreamFile::InitOnFileSequenceIfNeeded() { | |
| CHECK(task_runner_->RunsTasksInCurrentSequence(), base::NotFatalUntil::M159); | |
| if (had_errors_) | |
| return false; | |
| if (file_.IsValid()) | |
| return true; | |
| base::FilePath temp_dir; | |
| if (!base::GetTempDir(&temp_dir)) { | |
| LOG(ERROR) << "Failed to get temporary directory"; | |
| had_errors_ = true; | |
| return false; | |
| } | |
| base::FilePath temp_path; | |
| file_ = base::CreateAndOpenTemporaryFileInDir( | |
| temp_dir, &temp_path, | |
| base::File::FLAG_WIN_TEMPORARY | base::File::FLAG_DELETE_ON_CLOSE); | |
| if (!file_.IsValid()) { | |
| LOG(ERROR) << "Failed to create temporary file: " | |
| << base::File::ErrorToString(file_.error_details()); | |
| had_errors_ = true; | |
| return false; | |
| } | |
| #if !BUILDFLAG(IS_WIN) | |
| base::DeleteFile(temp_path); | |
| #endif | |
| 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) { | |
| CHECK(task_runner_->RunsTasksInCurrentSequence(), base::NotFatalUntil::M159); | |
| CHECK(file_.IsValid(), base::NotFatalUntil::M159); | |
| 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 |