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