blob: d035055199273915d7c79f639fe150a6c1902c62 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/plugin_vm/plugin_vm_image_manager.h"
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/guid.h"
#include "base/optional.h"
#include "base/strings/string_util.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "chrome/browser/chromeos/plugin_vm/plugin_vm_pref_names.h"
#include "chrome/browser/download/download_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/download/public/background_service/download_service.h"
#include "components/prefs/pref_service.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "third_party/zlib/google/zip.h"
namespace plugin_vm {
bool PluginVmImageManager::IsProcessingImage() {
return processing_image_;
}
void PluginVmImageManager::StartDownload() {
if (IsProcessingImage()) {
LOG(ERROR) << "Download of a PluginVm image couldn't be started as"
<< " another PluginVm image is currently being processed";
OnDownloadFailed();
return;
}
processing_image_ = true;
GURL url = GetPluginVmImageDownloadUrl();
if (url.is_empty()) {
OnDownloadFailed();
return;
}
download_service_->StartDownload(GetDownloadParams(url));
}
void PluginVmImageManager::CancelDownload() {
download_service_->CancelDownload(current_download_guid_);
}
void PluginVmImageManager::StartUnzipping() {
if (IsDownloading()) {
LOG(ERROR) << "Unzipping of PluginVm image couldn't be proceeded "
<< "as image is still being downloaded";
OnUnzipped(false);
return;
}
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
base::BindOnce(&PluginVmImageManager::UnzipDownloadedPluginVmImageArchive,
base::Unretained(this)),
base::BindOnce(&PluginVmImageManager::OnUnzipped,
weak_ptr_factory_.GetWeakPtr()));
}
void PluginVmImageManager::CancelUnzipping() {
unzipping_cancelled_ = true;
}
void PluginVmImageManager::SetObserver(Observer* observer) {
observer_ = observer;
}
void PluginVmImageManager::RemoveObserver() {
observer_ = nullptr;
}
void PluginVmImageManager::OnDownloadStarted() {
if (observer_)
observer_->OnDownloadStarted();
}
void PluginVmImageManager::OnProgressUpdated(
base::Optional<double> fraction_complete) {
if (observer_)
observer_->OnProgressUpdated(fraction_complete);
}
void PluginVmImageManager::OnDownloadCompleted(base::FilePath file_path) {
// TODO(https://crbug.com/928816): Call OnDownloadFailed() in case download
// verification using hashes fails.
downloaded_plugin_vm_image_archive_ = file_path;
current_download_guid_.clear();
if (observer_)
observer_->OnDownloadCompleted();
}
void PluginVmImageManager::OnDownloadCancelled() {
RemoveTemporaryPluginVmImageArchiveIfExists();
current_download_guid_.clear();
processing_image_ = false;
if (observer_)
observer_->OnDownloadCancelled();
}
void PluginVmImageManager::OnDownloadFailed() {
RemoveTemporaryPluginVmImageArchiveIfExists();
current_download_guid_.clear();
processing_image_ = false;
if (observer_)
observer_->OnDownloadFailed();
}
void PluginVmImageManager::SetDownloadServiceForTesting(
download::DownloadService* download_service) {
download_service_ = download_service;
}
void PluginVmImageManager::SetDownloadedPluginVmImageArchiveForTesting(
base::FilePath downloaded_plugin_vm_image_archive) {
downloaded_plugin_vm_image_archive_ = downloaded_plugin_vm_image_archive;
}
std::string PluginVmImageManager::GetCurrentDownloadGuidForTesting() {
return current_download_guid_;
}
PluginVmImageManager::PluginVmImageManager(Profile* profile)
: profile_(profile),
download_service_(DownloadServiceFactory::GetForBrowserContext(profile)),
weak_ptr_factory_(this) {}
PluginVmImageManager::~PluginVmImageManager() = default;
GURL PluginVmImageManager::GetPluginVmImageDownloadUrl() {
const base::Value* url_ptr =
profile_->GetPrefs()
->GetDictionary(plugin_vm::prefs::kPluginVmImage)
->FindKey("url");
if (!url_ptr) {
LOG(ERROR) << "Url to PluginVm image is not specified";
return GURL();
}
return GURL(url_ptr->GetString());
}
download::DownloadParams PluginVmImageManager::GetDownloadParams(
const GURL& url) {
download::DownloadParams params;
// DownloadParams
params.client = download::DownloadClient::PLUGIN_VM_IMAGE;
params.guid = base::GenerateGUID();
params.callback = base::BindRepeating(&PluginVmImageManager::OnStartDownload,
weak_ptr_factory_.GetWeakPtr());
// TODO(okalitova): Create annotation.
params.traffic_annotation =
net::MutableNetworkTrafficAnnotationTag(NO_TRAFFIC_ANNOTATION_YET);
// RequestParams
params.request_params.url = url;
params.request_params.method = "GET";
// SchedulingParams
// User initiates download by clicking on PluginVm icon so priorities should
// be the highest.
params.scheduling_params.priority = download::SchedulingParams::Priority::UI;
params.scheduling_params.battery_requirements =
download::SchedulingParams::BatteryRequirements::BATTERY_INSENSITIVE;
params.scheduling_params.network_requirements =
download::SchedulingParams::NetworkRequirements::NONE;
return params;
}
void PluginVmImageManager::OnStartDownload(
const std::string& download_guid,
download::DownloadParams::StartResult start_result) {
if (start_result == download::DownloadParams::ACCEPTED)
current_download_guid_ = download_guid;
else
OnDownloadFailed();
}
bool PluginVmImageManager::IsDownloading() {
return !current_download_guid_.empty();
}
bool PluginVmImageManager::VerifyDownload(std::string downloaded_archive_hash) {
const base::Value* plugin_vm_image_hash_ptr =
profile_->GetPrefs()
->GetDictionary(plugin_vm::prefs::kPluginVmImage)
->FindKey("hash");
if (!plugin_vm_image_hash_ptr) {
LOG(ERROR) << "Hash of PluginVm image is not specified";
return false;
}
std::string plugin_vm_image_hash = plugin_vm_image_hash_ptr->GetString();
return base::EqualsCaseInsensitiveASCII(plugin_vm_image_hash,
downloaded_archive_hash);
}
bool PluginVmImageManager::UnzipDownloadedPluginVmImageArchive() {
if (!EnsureDirectoryForPluginVmImageIsPresent() ||
!EnsureDownloadedPluginVmImageArchiveIsPresent()) {
LOG(ERROR) << "Unzipping of PluginVm image couldn't be proceeded";
return false;
}
base::File file(downloaded_plugin_vm_image_archive_,
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
LOG(ERROR) << "Failed to open "
<< downloaded_plugin_vm_image_archive_.value();
return false;
}
bool success = zip::UnzipWithFilterAndWriters(
file.GetPlatformFile(),
base::BindRepeating(
&PluginVmImageManager::CreatePluginVmImageWriterDelegate,
base::Unretained(this)),
base::BindRepeating(&PluginVmImageManager::CreateDirectory,
base::Unretained(this)),
base::BindRepeating(
&PluginVmImageManager::FilterFilesInPluginVmImageArchive,
base::Unretained(this)),
true /* log_skipped_files */);
return success;
}
bool PluginVmImageManager::IsUnzippingCancelled() {
return unzipping_cancelled_;
}
PluginVmImageManager::PluginVmImageWriterDelegate::PluginVmImageWriterDelegate(
PluginVmImageManager* manager,
const base::FilePath& output_file_path)
: manager_(manager), output_file_path_(output_file_path) {}
bool PluginVmImageManager::PluginVmImageWriterDelegate::PrepareOutput() {
// We can't rely on parent directory entries being specified in the
// zip, so we make sure they are created.
if (!base::CreateDirectory(output_file_path_.DirName()))
return false;
output_file_.Initialize(output_file_path_, base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_WRITE);
return output_file_.IsValid();
}
bool PluginVmImageManager::PluginVmImageWriterDelegate::WriteBytes(
const char* data,
int num_bytes) {
return !manager_->IsUnzippingCancelled() &&
num_bytes == output_file_.WriteAtCurrentPos(data, num_bytes);
}
void PluginVmImageManager::PluginVmImageWriterDelegate::SetTimeModified(
const base::Time& time) {
output_file_.Close();
base::TouchFile(output_file_path_, base::Time::Now(), time);
}
std::unique_ptr<zip::WriterDelegate>
PluginVmImageManager::CreatePluginVmImageWriterDelegate(
const base::FilePath& entry_path) {
return std::make_unique<PluginVmImageWriterDelegate>(
this, plugin_vm_image_dir_.Append(entry_path));
}
bool PluginVmImageManager::CreateDirectory(const base::FilePath& entry_path) {
return base::CreateDirectory(plugin_vm_image_dir_.Append(entry_path));
}
bool PluginVmImageManager::FilterFilesInPluginVmImageArchive(
const base::FilePath& file) {
return true;
}
void PluginVmImageManager::OnUnzipped(bool success) {
unzipping_cancelled_ = false;
RemoveTemporaryPluginVmImageArchiveIfExists();
if (!success) {
if (observer_)
observer_->OnUnzippingFailed();
RemovePluginVmImageDirectoryIfExists();
processing_image_ = false;
return;
}
if (observer_)
observer_->OnUnzipped();
processing_image_ = false;
// TODO(okalitova): Populate necessary preferences with information about
// PluginVm image that has been successfully downloaded and extracted.
}
bool PluginVmImageManager::EnsureDownloadedPluginVmImageArchiveIsPresent() {
return !downloaded_plugin_vm_image_archive_.empty();
}
bool PluginVmImageManager::EnsureDirectoryForPluginVmImageIsPresent() {
plugin_vm_image_dir_ = profile_->GetPath()
.AppendASCII(kCrosvmDir)
.AppendASCII(kPvmDir)
.AppendASCII(kPluginVmImageDir);
if (!base::CreateDirectory(plugin_vm_image_dir_)) {
LOG(ERROR) << "Directory " << plugin_vm_image_dir_.value()
<< " failed to be created";
plugin_vm_image_dir_.clear();
return false;
}
return true;
}
void PluginVmImageManager::RemoveTemporaryPluginVmImageArchiveIfExists() {
if (!downloaded_plugin_vm_image_archive_.empty()) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
base::BindOnce(&base::DeleteFile, downloaded_plugin_vm_image_archive_,
false /* recursive */),
base::BindOnce(
&PluginVmImageManager::OnTemporaryPluginVmImageArchiveRemoved,
weak_ptr_factory_.GetWeakPtr()));
}
}
void PluginVmImageManager::OnTemporaryPluginVmImageArchiveRemoved(
bool success) {
if (!success) {
LOG(ERROR) << "Downloaded PluginVm image archive located in "
<< downloaded_plugin_vm_image_archive_.value()
<< " failed to be deleted";
return;
}
downloaded_plugin_vm_image_archive_.clear();
}
void PluginVmImageManager::RemovePluginVmImageDirectoryIfExists() {
if (!plugin_vm_image_dir_.empty()) {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_VISIBLE, base::MayBlock()},
base::BindOnce(&base::DeleteFile, plugin_vm_image_dir_,
true /* recursive */),
base::BindOnce(&PluginVmImageManager::OnPluginVmImageDirectoryRemoved,
weak_ptr_factory_.GetWeakPtr()));
}
}
void PluginVmImageManager::OnPluginVmImageDirectoryRemoved(bool success) {
if (!success) {
LOG(ERROR) << "Directory with PluginVm image "
<< plugin_vm_image_dir_.value() << " failed to be deleted";
return;
}
plugin_vm_image_dir_.clear();
}
} // namespace plugin_vm