chromium/components/download/internal/common/in_memory_download_file.cc

// Copyright 2024 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 "components/download/internal/common/in_memory_download_file.h"

#include "base/android/jni_string.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/process/process_handle.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "components/download/public/common/download_destination_observer.h"
#include "crypto/secure_hash.h"

// Must come after all headers that specialize FromJniType() / ToJniType().
#include "components/download/internal/common/jni_headers/InMemoryDownloadFile_jni.h"

namespace download {
namespace {
const char kDefaultFileName[] = "download";

void OnRenameComplete(const base::FilePath& file_path,
                      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
                      DownloadFile::RenameCompletionCallback callback) {
  task_runner->PostTask(
      FROM_HERE, base::BindOnce(std::move(callback),
                                DOWNLOAD_INTERRUPT_REASON_NONE, file_path));
}
}  // namespace

InMemoryDownloadFile::InMemoryDownloadFile(
    std::unique_ptr<DownloadSaveInfo> save_info,
    std::unique_ptr<InputStream> stream,
    base::WeakPtr<DownloadDestinationObserver> observer)
    : save_info_(std::move(save_info)),
      input_stream_(std::move(stream)),
      main_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
      observer_(observer) {}

InMemoryDownloadFile::~InMemoryDownloadFile() {
  Cancel();
}

void InMemoryDownloadFile::Initialize(
    InitializeCallback initialize_callback,
    CancelRequestCallback cancel_request_callback,
    const DownloadItem::ReceivedSlices& received_slices) {
  JNIEnv* env = base::android::AttachCurrentThread();
  base::FilePath filename = save_info_->file_path.BaseName();

  java_ref_ = Java_InMemoryDownloadFile_createFile(
      env, filename.empty() ? kDefaultFileName : filename.value());
  if (!java_ref_) {
    main_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(std::move(initialize_callback),
                                  DOWNLOAD_INTERRUPT_REASON_FILE_FAILED,
                                  /*bytes_wasted=*/0));
    return;
  }

  int fd = Java_InMemoryDownloadFile_getFd(env, java_ref_);
  memory_file_path_ = base::FilePath(base::StringPrintf(
      "/proc/%d/fd/%d", static_cast<int>(base::GetCurrentProcId()), fd));

  SendUpdate();

  input_stream_->Initialize();

  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(std::move(initialize_callback),
                     DOWNLOAD_INTERRUPT_REASON_NONE, /*bytes_wasted=*/0));

  StreamActive(MOJO_RESULT_OK);
}

void InMemoryDownloadFile::AddInputStream(std::unique_ptr<InputStream> stream,
                                          int64_t offset) {
  DCHECK(false) << "In memory file shouldn't have more than 1 input stream";
}

void InMemoryDownloadFile::RenameAndUniquify(
    const base::FilePath& full_path,
    RenameCompletionCallback callback) {
  OnRenameComplete(memory_file_path_, main_task_runner_, std::move(callback));
}

void InMemoryDownloadFile::RenameAndAnnotate(
    const base::FilePath& full_path,
    const std::string& client_guid,
    const GURL& source_url,
    const GURL& referrer_url,
    mojo::PendingRemote<quarantine::mojom::Quarantine> remote_quarantine,
    RenameCompletionCallback callback) {
  OnRenameComplete(memory_file_path_, main_task_runner_, std::move(callback));
}

void InMemoryDownloadFile::Detach() {}

void InMemoryDownloadFile::Cancel() {
  JNIEnv* env = base::android::AttachCurrentThread();
  Java_InMemoryDownloadFile_destroy(env, java_ref_);
}

void InMemoryDownloadFile::SetPotentialFileLength(int64_t length) {}

const base::FilePath& InMemoryDownloadFile::FullPath() const {
  return memory_file_path_;
}

bool InMemoryDownloadFile::InProgress() const {
  return true;
}

void InMemoryDownloadFile::Pause() {}

void InMemoryDownloadFile::Resume() {}

void InMemoryDownloadFile::PublishDownload(RenameCompletionCallback callback) {
  // This shouldn't get called.
  DCHECK(false);
}

void InMemoryDownloadFile::StreamActive(MojoResult result) {
  JNIEnv* env = base::android::AttachCurrentThread();
  scoped_refptr<net::IOBuffer> incoming_data;
  size_t incoming_data_size = 0;
  InputStream::StreamState state(InputStream::EMPTY);
  DownloadInterruptReason reason = DOWNLOAD_INTERRUPT_REASON_NONE;
  // Take care of any file local activity required.
  do {
    state = input_stream_->Read(&incoming_data, &incoming_data_size);
    switch (state) {
      case InputStream::EMPTY:
        break;
      case InputStream::HAS_DATA:
        Java_InMemoryDownloadFile_writeData(
            env, java_ref_,
            std::vector<unsigned char>(
                incoming_data->data(),
                incoming_data->data() + incoming_data_size));
        total_bytes_ += incoming_data_size;
        rate_estimator_.Increment(incoming_data_size);
        break;
      case InputStream::WAIT_FOR_COMPLETION:
        input_stream_->RegisterCompletionCallback(
            base::BindOnce(&InMemoryDownloadFile::OnStreamCompleted,
                           weak_factory_.GetWeakPtr()));
        break;
      case InputStream::COMPLETE:
        break;
    }
  } while (state == InputStream::HAS_DATA &&
           reason == DOWNLOAD_INTERRUPT_REASON_NONE);

  if (state == InputStream::EMPTY) {
    input_stream_->RegisterDataReadyCallback(base::BindRepeating(
        &InMemoryDownloadFile::StreamActive, weak_factory_.GetWeakPtr()));
  }

  if (state == InputStream::COMPLETE) {
    OnStreamCompleted();
  } else if (reason != DOWNLOAD_INTERRUPT_REASON_NONE) {
    NotifyObserver(reason, state);
  }
}

void InMemoryDownloadFile::SendUpdate() {
  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&DownloadDestinationObserver::DestinationUpdate, observer_,
                     total_bytes_, rate_estimator_.GetCountPerSecond(),
                     std::vector<DownloadItem::ReceivedSlice>()));
}

void InMemoryDownloadFile::OnStreamCompleted() {
  SendUpdate();
  input_stream_->ClearDataReadyCallback();
  weak_factory_.InvalidateWeakPtrs();

  JNIEnv* env = base::android::AttachCurrentThread();
  Java_InMemoryDownloadFile_finish(env, java_ref_);
  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&DownloadDestinationObserver::DestinationCompleted,
                     observer_, total_bytes_,
                     crypto::SecureHash::Create(crypto::SecureHash::SHA256)));
}

void InMemoryDownloadFile::NotifyObserver(
    DownloadInterruptReason reason,
    InputStream::StreamState stream_state) {
  input_stream_->ClearDataReadyCallback();
  SendUpdate();  // Make info up to date before error.
  Cancel();
  // Error case for both upstream source and file write.
  // Shut down processing and signal an error to our observer.
  // Our observer will clean us up.
  weak_factory_.InvalidateWeakPtrs();
  main_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&DownloadDestinationObserver::DestinationError, observer_,
                     reason, total_bytes_,
                     crypto::SecureHash::Create(crypto::SecureHash::SHA256)));
}

bool InMemoryDownloadFile::IsMemoryFile() {
  return true;
}

}  //  namespace download