blob: 999c44e314a433eb1b68183a543a1947823f9481 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/image_writer_private/operation.h"
#include <string_view>
#include <utility>
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/api/image_writer_private/error_constants.h"
#include "chrome/browser/extensions/api/image_writer_private/extraction_properties.h"
#include "chrome/browser/extensions/api/image_writer_private/image_writer_utility_client.h"
#include "chrome/browser/extensions/api/image_writer_private/operation_manager.h"
#include "chrome/browser/extensions/api/image_writer_private/tar_extractor.h"
#include "chrome/browser/extensions/api/image_writer_private/xz_extractor.h"
#include "chrome/browser/extensions/api/image_writer_private/zip_extractor.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
namespace extensions {
namespace image_writer {
crypto::obsolete::Md5 MakeMd5HasherForImageWriter() {
return crypto::obsolete::Md5();
}
namespace {
// Returns true if the file at |image_path| is an archived image.
bool IsArchive(const base::FilePath& image_path) {
return ZipExtractor::IsZipFile(image_path) ||
TarExtractor::IsTarFile(image_path) ||
XzExtractor::IsXzFile(image_path);
}
// Extracts the archive at |image_path| using to |temp_dir_path| using a proper
// extractor.
void ExtractArchive(ExtractionProperties properties) {
if (ZipExtractor::IsZipFile(properties.image_path)) {
ZipExtractor::Extract(std::move(properties));
} else if (TarExtractor::IsTarFile(properties.image_path)) {
TarExtractor::Extract(std::move(properties));
} else if (XzExtractor::IsXzFile(properties.image_path)) {
XzExtractor::Extract(std::move(properties));
} else {
NOTREACHED();
}
}
} // namespace
Operation::Operation(base::WeakPtr<OperationManager> manager,
const ExtensionId& extension_id,
const std::string& device_path,
const base::FilePath& download_folder)
: manager_(manager),
extension_id_(extension_id),
#if BUILDFLAG(IS_WIN)
device_path_(base::FilePath::FromUTF8Unsafe(device_path)),
#else
device_path_(device_path),
#endif
temp_dir_(std::make_unique<base::ScopedTempDir>()),
stage_(image_writer_api::Stage::kUnknown),
progress_(0),
download_folder_(download_folder),
task_runner_(
base::ThreadPool::CreateSequencedTaskRunner(blocking_task_traits())) {
}
Operation::~Operation() {
// base::ScopedTempDir must be destroyed on a thread that allows blocking IO
// because it will try delete the directory if a call to Delete() hasn't been
// made or was unsuccessful.
task_runner_->DeleteSoon(FROM_HERE, std::move(temp_dir_));
}
void Operation::Cancel() {
DCHECK(IsRunningInCorrectSequence());
stage_ = image_writer_api::Stage::kNone;
CleanUp();
}
void Operation::Abort() {
DCHECK(IsRunningInCorrectSequence());
Error(error::kAborted);
}
int Operation::GetProgress() {
return progress_;
}
image_writer_api::Stage Operation::GetStage() {
return stage_;
}
void Operation::PostTask(base::OnceClosure task) {
task_runner_->PostTask(FROM_HERE, std::move(task));
}
void Operation::Start() {
DCHECK(IsRunningInCorrectSequence());
#if BUILDFLAG(IS_CHROMEOS)
if (download_folder_.empty() ||
!temp_dir_->CreateUniqueTempDirUnderPath(download_folder_)) {
#else
if (!temp_dir_->CreateUniqueTempDir()) {
#endif
Error(error::kTempDirError);
return;
}
AddCleanUpFunction(
base::BindOnce(base::IgnoreResult(&base::ScopedTempDir::Delete),
base::Unretained(temp_dir_.get())));
StartImpl();
}
void Operation::OnExtractOpenComplete(const base::FilePath& image_path) {
DCHECK(IsRunningInCorrectSequence());
image_path_ = image_path;
}
void Operation::Extract(base::OnceClosure continuation) {
DCHECK(IsRunningInCorrectSequence());
if (IsCancelled()) {
return;
}
if (IsArchive(image_path_)) {
SetStage(image_writer_api::Stage::kUnzip);
ExtractionProperties properties;
properties.image_path = image_path_;
properties.temp_dir_path = temp_dir_->GetPath();
properties.open_callback =
base::BindOnce(&Operation::OnExtractOpenComplete, this);
properties.complete_callback = base::BindOnce(
&Operation::CompleteAndContinue, this, std::move(continuation));
properties.failure_callback =
base::BindOnce(&Operation::OnExtractFailure, this);
properties.progress_callback =
base::BindRepeating(&Operation::OnExtractProgress, this);
ExtractArchive(std::move(properties));
} else {
PostTask(std::move(continuation));
}
}
void Operation::Finish() {
DCHECK(IsRunningInCorrectSequence());
CleanUp();
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&OperationManager::OnComplete, manager_, extension_id_));
}
void Operation::Error(const std::string& error_message) {
DCHECK(IsRunningInCorrectSequence());
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&OperationManager::OnError, manager_, extension_id_,
stage_, progress_, error_message));
CleanUp();
}
void Operation::SetProgress(int progress) {
DCHECK(IsRunningInCorrectSequence());
if (progress <= progress_) {
return;
}
if (IsCancelled()) {
return;
}
progress_ = progress;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&OperationManager::OnProgress, manager_,
extension_id_, stage_, progress_));
}
void Operation::SetStage(image_writer_api::Stage stage) {
DCHECK(IsRunningInCorrectSequence());
if (IsCancelled())
return;
stage_ = stage;
progress_ = 0;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&OperationManager::OnProgress, manager_,
extension_id_, stage_, progress_));
}
bool Operation::IsCancelled() {
DCHECK(IsRunningInCorrectSequence());
return stage_ == image_writer_api::Stage::kNone;
}
void Operation::AddCleanUpFunction(base::OnceClosure callback) {
DCHECK(IsRunningInCorrectSequence());
cleanup_functions_.push_back(std::move(callback));
}
void Operation::CompleteAndContinue(base::OnceClosure continuation) {
DCHECK(IsRunningInCorrectSequence());
SetProgress(kProgressComplete);
PostTask(std::move(continuation));
}
#if !BUILDFLAG(IS_CHROMEOS)
void Operation::StartUtilityClient() {
DCHECK(IsRunningInCorrectSequence());
if (!image_writer_client_.get()) {
image_writer_client_ = ImageWriterUtilityClient::Create(task_runner_);
AddCleanUpFunction(base::BindOnce(&Operation::StopUtilityClient, this));
}
}
void Operation::StopUtilityClient() {
DCHECK(IsRunningInCorrectSequence());
image_writer_client_->Shutdown();
}
void Operation::WriteImageProgress(int64_t total_bytes, int64_t curr_bytes) {
DCHECK(IsRunningInCorrectSequence());
if (IsCancelled()) {
return;
}
int progress = kProgressComplete * curr_bytes / total_bytes;
if (progress > GetProgress()) {
SetProgress(progress);
}
}
#endif
void Operation::GetMD5SumOfFile(
const base::FilePath& file_path,
base::OnceCallback<void(const std::string&)> callback) {
DCHECK(IsRunningInCorrectSequence());
if (IsCancelled()) {
return;
}
base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
Error(error::kImageOpenError);
return;
}
int64_t file_size = file.GetLength();
if (file_size < 0) {
Error(error::kImageOpenError);
return;
}
PostTask(base::BindOnce(&Operation::MD5Chunk, this, std::move(file),
MakeMd5HasherForImageWriter(), 0,
base::checked_cast<size_t>(file_size),
std::move(callback)));
}
bool Operation::IsRunningInCorrectSequence() const {
return task_runner_->RunsTasksInCurrentSequence();
}
void Operation::MD5Chunk(
base::File file,
crypto::obsolete::Md5 md5,
size_t bytes_processed,
size_t bytes_total,
base::OnceCallback<void(const std::string&)> callback) {
DCHECK(IsRunningInCorrectSequence());
if (IsCancelled())
return;
CHECK_LE(bytes_processed, bytes_total);
std::array<uint8_t, 1024> buffer;
size_t read_size = std::min(bytes_total - bytes_processed, buffer.size());
if (read_size == 0) {
// Nothing to read, we are done.
std::move(callback).Run(base::ToLowerASCII(base::HexEncode(md5.Finish())));
} else {
int64_t offset = base::checked_cast<int64_t>(bytes_processed);
auto target = base::span(buffer).first(read_size);
if (file.ReadAndCheck(offset, target)) {
// Process data.
md5.Update(target);
bytes_processed += read_size;
int percent_curr = (bytes_processed * kProgressComplete) / bytes_total;
SetProgress(percent_curr);
PostTask(base::BindOnce(&Operation::MD5Chunk, this, std::move(file),
std::move(md5), bytes_processed, bytes_total,
std::move(callback)));
// Skip closing the file.
return;
} else {
// We didn't read the bytes we expected.
Error(error::kHashReadError);
}
}
}
void Operation::OnExtractFailure(const std::string& error) {
DCHECK(IsRunningInCorrectSequence());
Error(error);
}
void Operation::OnExtractProgress(int64_t total_bytes, int64_t progress_bytes) {
DCHECK(IsRunningInCorrectSequence());
int progress_percent = kProgressComplete * progress_bytes / total_bytes;
SetProgress(progress_percent);
}
void Operation::CleanUp() {
DCHECK(IsRunningInCorrectSequence());
for (base::OnceClosure& cleanup_function : cleanup_functions_)
std::move(cleanup_function).Run();
cleanup_functions_.clear();
}
} // namespace image_writer
} // namespace extensions