use super::checksum_error::ProtoChecksumError;
use starbase_utils::fs;
use starbase_utils::hash::{self, HashError};
use std::fmt::Debug;
use std::io::{BufRead, BufReader};
use std::path::Path;
use tracing::{instrument, trace};
#[instrument]
pub fn hash_file_contents_sha256<P: AsRef<Path> + Debug>(path: P) -> Result<String, HashError> {
let path = path.as_ref();
trace!(file = ?path, "Calculating SHA256 checksum");
let hash = hash::sha256::from_file(path)?;
trace!(file = ?path, hash, "Calculated hash");
Ok(hash)
}
#[instrument]
pub fn hash_file_contents_sha512<P: AsRef<Path> + Debug>(path: P) -> Result<String, HashError> {
let path = path.as_ref();
trace!(file = ?path, "Calculating SHA512 checksum");
let hash = hash::sha512::from_file(path)?;
trace!(file = ?path, hash, "Calculated hash");
Ok(hash)
}
#[instrument(name = "verify_sha_checksum")]
pub fn verify_checksum(
download_file: &Path,
checksum_file: &Path,
checksum_hash: &str,
) -> Result<bool, ProtoChecksumError> {
let download_file_name = fs::file_name(download_file);
for line in BufReader::new(fs::open_file(checksum_file)?)
.lines()
.map_while(Result::ok)
{
if line.is_empty() {
continue;
}
let mut parts = line.split_whitespace();
let file_hash = parts.next().unwrap_or_default();
let has_file_name = parts.next().is_some();
let hash_matches = file_hash.eq_ignore_ascii_case(checksum_hash);
let file_name_matches_if_present = !has_file_name || line.ends_with(&download_file_name);
if hash_matches && file_name_matches_if_present {
return Ok(true);
}
if line.starts_with("Hash")
&& let Some((_, hash)) = line.split_once(':')
{
return Ok(hash.trim().to_lowercase() == checksum_hash);
}
}
Ok(false)
}
#[cfg(test)]
mod tests {
use super::*;
use starbase_sandbox::create_empty_sandbox;
const HASH: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
fn verify(checksum_contents: &str) -> bool {
let sandbox = create_empty_sandbox();
sandbox.create_file("checksum.sha256", checksum_contents);
verify_checksum(
Path::new("tool.tar.gz"),
&sandbox.path().join("checksum.sha256"),
HASH,
)
.unwrap()
}
#[test]
fn matches_bare_hash() {
assert!(verify(HASH));
}
#[test]
fn matches_hash_and_file_name() {
assert!(verify(&format!("{HASH} tool.tar.gz")));
}
#[test]
fn matches_hash_and_binary_file_name() {
assert!(verify(&format!("{HASH} *tool.tar.gz")));
}
#[test]
fn matches_uppercase_bare_hash() {
assert!(verify(&HASH.to_uppercase()));
}
#[test]
fn matches_uppercase_hash_and_file_name() {
assert!(verify(&format!("{} tool.tar.gz", HASH.to_uppercase())));
}
#[test]
fn matches_windows_get_file_hash_format() {
assert!(verify(&format!("Hash : {}", HASH.to_uppercase())));
}
#[test]
fn rejects_non_matching_hash() {
assert!(!verify(&"a".repeat(64)));
}
#[test]
fn rejects_matching_hash_listed_for_another_file() {
assert!(!verify(&format!("{HASH} other.tar.gz")));
}
#[test]
fn finds_the_right_line_among_many() {
assert!(verify(&format!(
"{} other.tar.gz\n{HASH} tool.tar.gz\n",
"b".repeat(64)
)));
}
}