#include "components/services/unzip/public/cpp/unzip.h"
#include <string>
#include <utility>
#include "base/check.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/synchronization/atomic_flag.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "components/services/storage/public/cpp/filesystem/filesystem_impl.h"
#include "components/services/unzip/public/mojom/unzipper.mojom.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
namespace unzip {
class UnzipParams : public base::RefCounted<UnzipParams>,
public unzip::mojom::UnzipFilter,
public unzip::mojom::UnzipListener {
public:
UnzipParams(mojo::PendingRemote<mojom::Unzipper> unzipper,
UnzipCallback callback)
: unzipper_(std::move(unzipper)), callback_(std::move(callback)) {}
mojo::Remote<mojom::Unzipper>& unzipper() { return unzipper_; }
void InvokeCallback(bool result) {
if (callback_)
std::move(callback_).Run(result);
unzipper_.reset();
}
void SetFilter(
mojo::PendingReceiver<unzip::mojom::UnzipFilter> filter_receiver,
UnzipFilterCallback filter_callback) {
DCHECK(filter_callback);
filter_receiver_.Bind(std::move(filter_receiver));
filter_callback_ = std::move(filter_callback);
}
void SetListener(UnzipListenerCallback listener_callback) {
DCHECK(listener_callback);
listener_callback_ = std::move(listener_callback);
}
mojo::Receiver<unzip::mojom::UnzipListener>* GetListenerReceiver() {
return &listener_receiver_;
}
base::AtomicFlag clean_up_is_done;
private:
friend class base::RefCounted<UnzipParams>;
~UnzipParams() override = default;
void ShouldUnzipFile(const base::FilePath& path,
ShouldUnzipFileCallback callback) override {
DCHECK(filter_callback_);
std::move(callback).Run(filter_callback_.Run(path));
}
void OnProgress(uint64_t bytes) override {
DCHECK(listener_callback_);
listener_callback_.Run(bytes);
}
mojo::Remote<mojom::Unzipper> unzipper_;
mojo::Receiver<unzip::mojom::UnzipFilter> filter_receiver_{this};
UnzipFilterCallback filter_callback_;
mojo::Receiver<unzip::mojom::UnzipListener> listener_receiver_{this};
UnzipListenerCallback listener_callback_;
UnzipCallback callback_;
};
namespace {
std::string Redact(const base::FilePath& path) {
return LOG_IS_ON(INFO) ? "'" + path.AsUTF8Unsafe() + "'" : "(redacted)";
}
class DetectEncodingParams : public base::RefCounted<DetectEncodingParams> {
public:
DetectEncodingParams(mojo::PendingRemote<mojom::Unzipper> unzipper,
DetectEncodingCallback callback)
: unzipper_(std::move(unzipper)), callback_(std::move(callback)) {}
mojo::Remote<mojom::Unzipper>& unzipper() { return unzipper_; }
void InvokeCallback(int encoding) {
if (callback_) {
base::UmaHistogramEnumeration("Unzipper.DetectEncoding.Result",
static_cast<Encoding>(encoding),
Encoding::NUM_ENCODINGS);
base::UmaHistogramTimes("Unzipper.DetectEncoding.Time",
base::TimeTicks::Now() - start_time_);
std::move(callback_).Run(static_cast<Encoding>(encoding));
}
unzipper_.reset();
}
private:
friend class base::RefCounted<DetectEncodingParams>;
~DetectEncodingParams() = default;
mojo::Remote<mojom::Unzipper> unzipper_;
DetectEncodingCallback callback_;
const base::TimeTicks start_time_ = base::TimeTicks::Now();
};
class GetExtractedInfoParams : public base::RefCounted<GetExtractedInfoParams> {
public:
GetExtractedInfoParams(mojo::PendingRemote<mojom::Unzipper> unzipper,
GetExtractedInfoCallback callback)
: unzipper_(std::move(unzipper)), callback_(std::move(callback)) {}
mojo::Remote<mojom::Unzipper>& unzipper() { return unzipper_; }
void InvokeCallback(mojom::InfoPtr info) {
if (callback_) {
std::move(callback_).Run(std::move(info));
}
unzipper_.reset();
}
private:
friend class base::RefCounted<GetExtractedInfoParams>;
~GetExtractedInfoParams() = default;
mojo::Remote<mojom::Unzipper> unzipper_;
GetExtractedInfoCallback callback_;
};
bool CheckZipFileValid(const base::FilePath& zip_path,
const base::File& zip_file) {
if (!zip_file.IsValid()) {
LOG(ERROR) << "Cannot open ZIP archive " << Redact(zip_path) << ": "
<< base::File::ErrorToString(zip_file.error_details());
return false;
}
return true;
}
void PrepareUnzipParams(
scoped_refptr<UnzipParams> unzip_params,
const base::FilePath& output_dir,
UnzipFilterCallback filter_callback,
UnzipListenerCallback listener_callback,
mojo::PendingRemote<storage::mojom::Directory>& directory_remote,
mojo::PendingRemote<unzip::mojom::UnzipFilter>& filter_remote,
mojo::PendingRemote<unzip::mojom::UnzipListener>& listener_remote) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<storage::FilesystemImpl>(
output_dir, storage::FilesystemImpl::ClientType::kTrusted),
directory_remote.InitWithNewPipeAndPassReceiver());
unzip_params->unzipper().set_disconnect_handler(
base::BindOnce(&UnzipParams::InvokeCallback, unzip_params, false));
if (filter_callback) {
unzip_params->SetFilter(filter_remote.InitWithNewPipeAndPassReceiver(),
std::move(filter_callback));
}
if (listener_callback) {
mojo::Receiver<unzip::mojom::UnzipListener>* listener =
unzip_params->GetListenerReceiver();
unzip_params->SetListener(std::move(listener_callback));
listener_remote = listener->BindNewPipeAndPassRemote();
}
}
void DoUnzip(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_path,
const base::FilePath& output_dir,
UnzipFilterCallback filter_callback,
UnzipListenerCallback listener_callback,
mojom::UnzipOptionsPtr options,
UnzipCallback result_callback) {
base::File zip_file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!CheckZipFileValid(zip_path, zip_file)) {
std::move(result_callback).Run(false);
return;
}
auto unzip_params = base::MakeRefCounted<UnzipParams>(
std::move(unzipper), std::move(result_callback));
mojo::PendingRemote<storage::mojom::Directory> directory_remote;
mojo::PendingRemote<unzip::mojom::UnzipFilter> filter_remote;
mojo::PendingRemote<unzip::mojom::UnzipListener> listener_remote;
PrepareUnzipParams(unzip_params, output_dir, filter_callback,
listener_callback, directory_remote, filter_remote,
listener_remote);
unzip_params->unzipper()->Unzip(
std::move(zip_file), std::move(directory_remote), std::move(options),
std::move(filter_remote), std::move(listener_remote),
base::BindOnce(&UnzipParams::InvokeCallback, unzip_params));
}
void DoDetectEncoding(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_path,
DetectEncodingCallback result_callback) {
base::File zip_file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!zip_file.IsValid()) {
LOG(ERROR) << "Cannot open ZIP archive " << Redact(zip_path) << ": "
<< base::File::ErrorToString(zip_file.error_details());
std::move(result_callback).Run(UNKNOWN_ENCODING);
return;
}
auto params = base::MakeRefCounted<DetectEncodingParams>(
std::move(unzipper), std::move(result_callback));
params->unzipper().set_disconnect_handler(base::BindOnce(
&DetectEncodingParams::InvokeCallback, params, UNKNOWN_ENCODING));
params->unzipper()->DetectEncoding(
std::move(zip_file),
base::BindOnce(&DetectEncodingParams::InvokeCallback, params));
}
void DoGetExtractedInfo(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_path,
GetExtractedInfoCallback result_callback) {
base::File zip_file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
unzip::mojom::InfoPtr info = unzip::mojom::Info::New(false, 0, false, false);
if (!zip_file.IsValid()) {
LOG(ERROR) << "Cannot open ZIP archive " << Redact(zip_path) << ": "
<< base::File::ErrorToString(zip_file.error_details());
std::move(result_callback).Run(std::move(info));
return;
}
auto params = base::MakeRefCounted<GetExtractedInfoParams>(
std::move(unzipper), std::move(result_callback));
params->unzipper().set_disconnect_handler(base::BindOnce(
&GetExtractedInfoParams::InvokeCallback, params, std::move(info)));
params->unzipper()->GetExtractedInfo(
std::move(zip_file),
base::BindOnce(&GetExtractedInfoParams::InvokeCallback, params));
}
}
void Unzip(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_path,
const base::FilePath& output_dir,
UnzipCallback callback) {
UnzipWithFilter(std::move(unzipper), zip_path, output_dir,
UnzipFilterCallback(), std::move(callback));
}
void Unzip(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_file,
const base::FilePath& output_dir,
mojom::UnzipOptionsPtr options,
UnzipListenerCallback listener_callback,
UnzipCallback result_callback) {
DCHECK(!result_callback.is_null());
const scoped_refptr<base::SequencedTaskRunner> runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
runner->PostTask(
FROM_HERE,
base::BindOnce(
&DoUnzip, std::move(unzipper), zip_file, output_dir,
UnzipFilterCallback(),
base::BindPostTaskToCurrentDefault(std::move(listener_callback)),
std::move(options),
base::BindPostTaskToCurrentDefault(std::move(result_callback))));
}
void UnzipWithFilter(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_path,
const base::FilePath& output_dir,
UnzipFilterCallback filter_callback,
UnzipCallback result_callback) {
DCHECK(!result_callback.is_null());
const scoped_refptr<base::SequencedTaskRunner> runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
runner->PostTask(
FROM_HERE,
base::BindOnce(
&DoUnzip, std::move(unzipper), zip_path, output_dir, filter_callback,
UnzipListenerCallback(), unzip::mojom::UnzipOptions::New(),
base::BindPostTaskToCurrentDefault(std::move(result_callback))));
}
void DetectEncoding(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_path,
DetectEncodingCallback result_callback) {
const scoped_refptr<base::SequencedTaskRunner> runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
runner->PostTask(FROM_HERE, base::BindOnce(&DoDetectEncoding,
std::move(unzipper), zip_path,
base::BindPostTaskToCurrentDefault(
std::move(result_callback))));
}
void GetExtractedInfo(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_path,
GetExtractedInfoCallback result_callback) {
const scoped_refptr<base::SequencedTaskRunner> runner =
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
runner->PostTask(FROM_HERE, base::BindOnce(&DoGetExtractedInfo,
std::move(unzipper), zip_path,
base::BindPostTaskToCurrentDefault(
std::move(result_callback))));
}
ZipFileUnpacker::ZipFileUnpacker() = default;
ZipFileUnpacker::~ZipFileUnpacker() = default;
void DoUnzipWithParams(mojo::PendingRemote<mojom::Unzipper> unzipper,
scoped_refptr<UnzipParams>& unzip_params,
const base::FilePath& zip_path,
const base::FilePath& output_dir,
UnzipFilterCallback filter_callback,
UnzipListenerCallback listener_callback,
mojom::UnzipOptionsPtr options,
UnzipCallback result_callback) {
base::File zip_file(zip_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!CheckZipFileValid(zip_path, zip_file)) {
std::move(result_callback).Run(false);
return;
}
unzip_params = base::MakeRefCounted<UnzipParams>(std::move(unzipper),
std::move(result_callback));
mojo::PendingRemote<storage::mojom::Directory> directory_remote;
mojo::PendingRemote<unzip::mojom::UnzipFilter> filter_remote;
mojo::PendingRemote<unzip::mojom::UnzipListener> listener_remote;
PrepareUnzipParams(unzip_params, output_dir, filter_callback,
listener_callback, directory_remote, filter_remote,
listener_remote);
unzip_params->unzipper()->Unzip(
std::move(zip_file), std::move(directory_remote), std::move(options),
std::move(filter_remote), std::move(listener_remote),
base::BindOnce(&UnzipParams::InvokeCallback, unzip_params));
}
void ZipFileUnpacker::Unpack(mojo::PendingRemote<mojom::Unzipper> unzipper,
const base::FilePath& zip_file,
const base::FilePath& output_dir,
mojom::UnzipOptionsPtr options,
UnzipListenerCallback listener_callback,
UnzipCallback result_callback) {
DCHECK(!result_callback.is_null());
runner_->PostTask(
FROM_HERE,
base::BindOnce(
&::unzip::DoUnzipWithParams, std::move(unzipper), std::ref(params_),
zip_file, output_dir, UnzipFilterCallback(),
base::BindPostTaskToCurrentDefault(std::move(listener_callback)),
std::move(options),
base::BindPostTaskToCurrentDefault(std::move(result_callback))));
}
void ReleaseParams(scoped_refptr<UnzipParams>& unzip_params) {
if (unzip_params) {
unzip_params->clean_up_is_done.Set();
unzip_params.reset();
}
}
void EndUnpack(scoped_refptr<UnzipParams>& unzip_params) {
if (unzip_params) {
unzip_params->InvokeCallback(false);
ReleaseParams(unzip_params);
}
}
void ZipFileUnpacker::Stop() {
runner_->PostTask(FROM_HERE, base::BindOnce(&EndUnpack, std::ref(params_)));
}
bool ZipFileUnpacker::CleanUpDone() {
if (params_) {
return params_->clean_up_is_done.IsSet();
}
return true;
}
void ZipFileUnpacker::CleanUp() {
runner_->PostTask(FROM_HERE,
base::BindOnce(&ReleaseParams, std::ref(params_)));
}
}