chromium/chrome/browser/ash/bruschetta/bruschetta_download.cc

// Copyright 2023 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 "chrome/browser/ash/bruschetta/bruschetta_download.h"

#include "base/files/scoped_temp_dir.h"
#include "base/memory/ptr_util.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/bruschetta/bruschetta_network_context.h"
#include "chrome/browser/extensions/cws_info_service.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/storage_partition.h"
#include "crypto/secure_hash.h"
#include "crypto/sha2.h"
#include "net/base/net_errors.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"

namespace bruschetta {

const net::NetworkTrafficAnnotationTag kBruschettaTrafficAnnotation =
    net::DefineNetworkTrafficAnnotation("bruschetta_installer_download",
                                        R"(
      semantics {
        sender: "Bruschetta VM Installer",
        description: "Request sent to download firmware and VM image for "
          "a Bruschetta VM, which allows the user to run the VM."
        trigger: "User installing a Bruschetta VM"
        internal {
          contacts {
            email: "[email protected]"
          }
        }
        user_data: {
          type: ACCESS_TOKEN
        }
        data: "Request to download Bruschetta firmware and VM image. "
          "Sends cookies associated with the source to authenticate the user."
        destination: WEBSITE
        last_reviewed: "2023-01-09"
      }
      policy {
        cookies_allowed: YES
        cookies_store: "user"
        chrome_policy {
          BruschettaVMConfiguration {
            BruschettaVMConfiguration: "{}"
          }
        }
      }
    )");

namespace {

std::unique_ptr<base::ScopedTempDir> MakeTempDir() {
  auto dir = std::make_unique<base::ScopedTempDir>();
  CHECK(dir->CreateUniqueTempDir());
  return dir;
}

// Calculates the sha256 hash of the file at `path` incrementally i.e. without
// loading the entire thing into memory at once. Blocking.
std::string Sha256File(const base::FilePath& path) {
  base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ);
  if (!file.IsValid()) {
    return "";
  }

  std::unique_ptr<crypto::SecureHash> ctx(
      crypto::SecureHash::Create(crypto::SecureHash::SHA256));
  const size_t kReadBufferSize = 4096;
  char buffer[kReadBufferSize];
  while (true) {
    int count = file.ReadAtCurrentPos(buffer, kReadBufferSize);

    // Treat EOF the same as any other error, stop reading and return the hash
    // of what we read. If there was a disk error or something we'll end up with
    // an invalid hash, same as if the file were truncated.
    if (count <= 0) {
      break;
    }
    ctx->Update(buffer, count);
  }

  uint8_t digest_bytes[crypto::kSHA256Length];
  ctx->Finish(digest_bytes, crypto::kSHA256Length);
  return base::HexEncode(digest_bytes);
}

}  // namespace

std::string Sha256FileForTesting(const base::FilePath& path) {
  return Sha256File(path);
}

SimpleURLLoaderDownload::SimpleURLLoaderDownload() = default;

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);
  // We're owned (through a few levels of owning class) by the installer view
  // which won't outlive the profile, so it's safe to pass around the raw
  // pointer.
  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_;
  loader_ = network::SimpleURLLoader::Create(std::move(req),
                                             kBruschettaTrafficAnnotation);
  network_context_ = std::make_unique<BruschettaNetworkContext>(profile);
  loader_->DownloadToFile(network_context_->GetURLLoaderFactory(),
                          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 bruschetta