| // 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_task_runner.h" |
| #include "base/task/post_task.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/fileapi/file_system_context.h" |
| |
| namespace content { |
| |
| scoped_refptr<base::SequencedTaskRunner> impl_task_runner() { |
| constexpr base::TaskTraits kBlockingTraits = { |
| base::MayBlock(), base::TaskPriority::BEST_EFFORT}; |
| static base::LazySequencedTaskRunner s_sequenced_task_unner = |
| LAZY_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()), |
| 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; |
| DeleteFile(temp_path, false); |
| 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) { |
| DCHECK(task_runner_->RunsTasksInCurrentSequence()); |
| Status status = StatusFailure; |
| std::unique_ptr<std::string> 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; |
| } |
| base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, |
| base::BindOnce(std::move(callback), std::move(data), |
| base64_encoded, status)); |
| } |
| |
| void DevToolsStreamFile::AppendOnFileSequence( |
| std::unique_ptr<std::string> data) { |
| if (!InitOnFileSequenceIfNeeded()) |
| return; |
| int size_written = file_.WriteAtCurrentPos(&*data->begin(), data->length()); |
| if (size_written != static_cast<int>(data->length())) { |
| LOG(ERROR) << "Failed to write temporary file"; |
| had_errors_ = true; |
| file_.Close(); |
| } |
| } |
| |
| } // namespace content |