chromium/chrome/browser/webshare/store_file_task.cc

// 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/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