blob: 5ec080e221b21eab0bbad4c883d30a626803688e [file] [log] [blame]
// Copyright 2025 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/ash/crostini/baguette_download.h"
#include <memory>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_forward.h"
#include "base/location.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "crypto/hash.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace crostini {
const net::NetworkTrafficAnnotationTag kBaguetteTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("baguette_image_download",
R"(
semantics {
sender: "Baguette VM Image",
description: "Request sent to download VM image for a Baguette VM,"
"which allows the user to run the VM."
trigger: "User installing a Baguette VM"
internal {
contacts {
email: "clumptini+oncall@google.com"
}
}
user_data: {
type: ACCESS_TOKEN
}
data: "Request to download Baguette VM image."
destination: WEBSITE
last_reviewed: "2023-01-09"
}
policy {
cookies_allowed: NO
setting:
"This feature can not be disabled by settings."
policy_exception_justification:
"This feature is required to deliver core user experiences and "
"cannot be disabled by policy."
}
)");
namespace {
std::unique_ptr<base::ScopedTempDir> MakeTempDir() {
auto dir = std::make_unique<base::ScopedTempDir>();
CHECK(dir->CreateUniqueTempDir());
return dir;
}
std::string Sha256File(const base::FilePath& path) {
base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
return "";
}
crypto::hash::Hasher ctx(crypto::hash::kSha256);
std::array<uint8_t, 4096> buffer;
while (true) {
std::optional<size_t> read = file.ReadAtCurrentPos(buffer);
if (read.value_or(0) == 0) {
break;
}
ctx.Update(base::span(buffer).first(*read));
}
std::array<uint8_t, crypto::hash::kSha256Size> digest_bytes;
ctx.Finish(digest_bytes);
return base::HexEncode(digest_bytes);
}
} // namespace
std::string Sha256FileForTesting(const base::FilePath& path) {
return Sha256File(path);
}
SimpleURLLoaderDownload::SimpleURLLoaderDownload(PrefService& local_state)
: local_state_(local_state) {}
SimpleURLLoaderDownload::~SimpleURLLoaderDownload() {
auto seq = base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
seq->DeleteSoon(FROM_HERE, std::move(scoped_temp_dir_));
if (post_deletion_closure_for_testing_) {
seq->PostTask(FROM_HERE, std::move(post_deletion_closure_for_testing_));
}
}
void SimpleURLLoaderDownload::StartDownload(
Profile* profile,
GURL url,
base::OnceCallback<void(base::FilePath path, std::string sha256)>
callback) {
DCHECK(url_.is_empty()) << " each instance is single use.";
url_ = std::move(url);
callback_ = std::move(callback);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()}, base::BindOnce(&MakeTempDir),
base::BindOnce(&SimpleURLLoaderDownload::Download,
weak_ptr_factory_.GetWeakPtr(), profile));
}
void SimpleURLLoaderDownload::Download(
Profile* profile,
std::unique_ptr<base::ScopedTempDir> dir) {
scoped_temp_dir_ = std::move(dir);
auto path = scoped_temp_dir_->GetPath().Append("download");
auto req = std::make_unique<network::ResourceRequest>();
req->url = url_;
req->method = "GET";
req->load_flags = net::LOAD_DISABLE_CACHE;
req->credentials_mode = network::mojom::CredentialsMode::kOmit;
loader_ = network::SimpleURLLoader::Create(std::move(req),
kBaguetteTrafficAnnotation);
loader_->SetRetryOptions(
5, network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
loader_->DownloadToFile(g_browser_process->shared_url_loader_factory().get(),
base::BindOnce(&SimpleURLLoaderDownload::Finished,
weak_ptr_factory_.GetWeakPtr()),
std::move(path));
}
void SimpleURLLoaderDownload::Finished(base::FilePath path) {
if (path.empty()) {
LOG(ERROR) << "Download failed: " << net::ErrorToString(loader_->NetError())
<< "(" << loader_->NetError() << ")";
std::move(callback_).Run(path, "");
return;
}
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()}, base::BindOnce(&Sha256File, path),
base::BindOnce(std::move(callback_), path));
}
} // namespace crostini