| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #include "chrome/browser/webshare/store_file_task.h" |
| |
| #include "base/containers/span.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/string_view_util.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/base/net_errors.h" |
| |
| namespace { |
| bool g_skip_copying_for_testing = false; |
| } // namespace |
| |
| namespace webshare { |
| |
| StoreFileTask::StoreFileTask(base::FilePath filename, |
| blink::mojom::SharedFilePtr file, |
| uint64_t& available_space, |
| blink::mojom::ShareService::ShareCallback callback) |
| : filename_(std::move(filename)), |
| file_(std::move(file)), |
| available_space_(available_space), |
| callback_(std::move(callback)), |
| read_pipe_watcher_(FROM_HERE, |
| mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) { |
| // |filename| is generated by GenerateFileName(). |
| DCHECK(!filename_.ReferencesParent()); |
| DCHECK(filename_.BaseName().MaybeAsASCII().find("share") >= 0); |
| } |
| |
| StoreFileTask::~StoreFileTask() = default; |
| |
| void StoreFileTask::Start() { |
| { |
| base::ScopedBlockingCall scoped_blocking_call( |
| FROM_HERE, base::BlockingType::WILL_BLOCK); |
| output_file_.Initialize( |
| filename_, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| } |
| |
| if (g_skip_copying_for_testing) { |
| std::move(callback_).Run(blink::mojom::ShareError::OK); |
| return; |
| } |
| |
| StartRead(); |
| } |
| |
| void StoreFileTask::StartRead() { |
| constexpr size_t kDataPipeCapacity = 65536; |
| |
| MojoCreateDataPipeOptions options; |
| options.struct_size = sizeof(MojoCreateDataPipeOptions); |
| options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE; |
| options.element_num_bytes = 1; |
| options.capacity_num_bytes = kDataPipeCapacity; |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| MojoResult rv = CreateDataPipe(&options, producer_handle, consumer_handle_); |
| if (rv != MOJO_RESULT_OK) { |
| std::move(callback_).Run(blink::mojom::ShareError::INTERNAL_ERROR); |
| return; |
| } |
| |
| mojo::Remote<blink::mojom::Blob> blob(std::move(file_->blob->blob)); |
| blob->ReadAll(std::move(producer_handle), |
| receiver_.BindNewPipeAndPassRemote()); |
| } |
| |
| // static |
| void StoreFileTask::SkipCopyingForTesting() { |
| g_skip_copying_for_testing = true; |
| } |
| |
| void StoreFileTask::OnDataPipeReadable(MojoResult result) { |
| if (result != MOJO_RESULT_OK) { |
| if (!received_all_data_) { |
| std::move(callback_).Run(blink::mojom::ShareError::INTERNAL_ERROR); |
| } |
| return; |
| } |
| |
| while (true) { |
| base::span<const uint8_t> buffer; |
| MojoResult pipe_result = |
| consumer_handle_->BeginReadData(MOJO_READ_DATA_FLAG_NONE, buffer); |
| if (pipe_result == MOJO_RESULT_SHOULD_WAIT) |
| return; |
| |
| if (pipe_result == MOJO_RESULT_FAILED_PRECONDITION) { |
| // Pipe closed. |
| if (!received_all_data_) { |
| std::move(callback_).Run(blink::mojom::ShareError::INTERNAL_ERROR); |
| } |
| return; |
| } |
| if (pipe_result != MOJO_RESULT_OK) { |
| std::move(callback_).Run(blink::mojom::ShareError::INTERNAL_ERROR); |
| return; |
| } |
| |
| // Defend against compromised renderer process sending too much data. |
| std::string_view chars = base::as_string_view(buffer); |
| int chars_size_int = base::saturated_cast<int>(chars.size()); |
| if (buffer.size() > total_bytes_ - bytes_received_ || |
| output_file_.WriteAtCurrentPos(chars.data(), chars_size_int) != |
| chars_size_int) { |
| std::move(callback_).Run(blink::mojom::ShareError::INTERNAL_ERROR); |
| return; |
| } |
| bytes_received_ += buffer.size(); |
| DCHECK_LE(bytes_received_, total_bytes_); |
| |
| consumer_handle_->EndReadData(buffer.size()); |
| if (bytes_received_ == total_bytes_) { |
| received_all_data_ = true; |
| if (received_on_complete_) |
| OnSuccess(); |
| return; |
| } |
| } |
| } |
| |
| void StoreFileTask::OnSuccess() { |
| DCHECK_EQ(bytes_received_, total_bytes_); |
| |
| std::move(callback_).Run(blink::mojom::ShareError::OK); |
| } |
| |
| void StoreFileTask::OnCalculatedSize(uint64_t total_size, |
| uint64_t expected_content_size) { |
| DCHECK_EQ(total_size, expected_content_size); |
| |
| if (expected_content_size > *available_space_ || |
| expected_content_size != file_->blob->size) { |
| VLOG(1) << "Share too large: " << expected_content_size << " bytes"; |
| std::move(callback_).Run(blink::mojom::ShareError::PERMISSION_DENIED); |
| return; |
| } |
| |
| *available_space_ -= expected_content_size; |
| total_bytes_ = expected_content_size; |
| |
| if (expected_content_size == 0) { |
| received_all_data_ = true; |
| return; |
| } |
| |
| read_pipe_watcher_.Watch( |
| consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE, |
| base::BindRepeating(&StoreFileTask::OnDataPipeReadable, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void StoreFileTask::OnComplete(int32_t status, uint64_t data_length) { |
| if (status != net::OK || data_length != total_bytes_) { |
| std::move(callback_).Run(blink::mojom::ShareError::INTERNAL_ERROR); |
| return; |
| } |
| |
| received_on_complete_ = true; |
| if (received_all_data_) |
| OnSuccess(); |
| } |
| |
| } // namespace webshare |