Skip to content

Commit 5e8fba5

Browse files
refactor: stream cached installer hash checks
Agent-Logs-Url: https://github.com/Devolutions/pinget/sessions/62049e36-b058-4656-b88e-46248ae307d9 Co-authored-by: mamoreau-devolutions <242016029+mamoreau-devolutions@users.noreply.github.com>
1 parent e8e1d61 commit 5e8fba5

2 files changed

Lines changed: 75 additions & 26 deletions

File tree

‎dotnet/src/Devolutions.Pinget.Core/Repository.cs‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3042,7 +3042,14 @@ private static bool CanReuseInstallerDownload(string installerPath, string? expe
30423042
if (expectedHash is null)
30433043
return true;
30443044

3045-
return Sha256Hex(File.ReadAllBytes(installerPath)).Equals(expectedHash, StringComparison.OrdinalIgnoreCase);
3045+
using var stream = File.OpenRead(installerPath);
3046+
return Sha256Hex(stream).Equals(expectedHash, StringComparison.OrdinalIgnoreCase);
3047+
}
3048+
3049+
private static string Sha256Hex(Stream data)
3050+
{
3051+
var hash = SHA256.HashData(data);
3052+
return Convert.ToHexString(hash).ToLowerInvariant();
30463053
}
30473054

30483055
private class VersionComparer : IComparer<string>

‎rust/crates/pinget-core/src/lib.rs‎

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6930,8 +6930,8 @@ fn can_reuse_installer_download(path: &Path, expected_hash: Option<&str>) -> Res
69306930
return Ok(true);
69316931
}
69326932

6933-
let cached = fs::read(path).context("failed to read cached installer")?;
6934-
Ok(hash_matches(expected_hash, &cached))
6933+
let cached = fs::File::open(path).context("failed to open cached installer")?;
6934+
Ok(sha256_hex_reader(cached)?.eq_ignore_ascii_case(expected_hash.unwrap_or_default()))
69356935
}
69366936

69376937
fn hash_matches(expected_hash: Option<&str>, bytes: &[u8]) -> bool {
@@ -6942,13 +6942,34 @@ fn hash_matches(expected_hash: Option<&str>, bytes: &[u8]) -> bool {
69426942

69436943
fn sha256_hex(bytes: &[u8]) -> String {
69446944
let digest = Sha256::digest(bytes);
6945-
let mut output = String::with_capacity(digest.len() * 2);
6946-
for byte in digest {
6945+
hex_encode(&digest)
6946+
}
6947+
6948+
fn hex_encode(bytes: &[u8]) -> String {
6949+
let mut output = String::with_capacity(bytes.len() * 2);
6950+
for byte in bytes {
69476951
output.push_str(&format!("{byte:02X}"));
69486952
}
69496953
output
69506954
}
69516955

6956+
fn sha256_hex_reader(mut reader: impl Read) -> Result<String> {
6957+
let mut hasher = Sha256::new();
6958+
let mut buffer = [0u8; 8192];
6959+
loop {
6960+
let read = reader
6961+
.read(&mut buffer)
6962+
.context("failed to read bytes while hashing installer")?;
6963+
if read == 0 {
6964+
break;
6965+
}
6966+
hasher.update(&buffer[..read]);
6967+
}
6968+
6969+
let digest = hasher.finalize();
6970+
Ok(hex_encode(&digest))
6971+
}
6972+
69526973
fn pins_db_path(app_root: &Path) -> PathBuf {
69536974
if uses_packaged_layout(app_root) {
69546975
app_root.join("pinning.db")
@@ -7610,6 +7631,7 @@ fn uninstall_package(_installed: &ListMatch, _request: &UninstallRequest) -> Res
76107631

76117632
#[cfg(test)]
76127633
mod tests {
7634+
use std::ffi::OsString;
76137635
use std::io::Write;
76147636
use std::path::{Path, PathBuf};
76157637

@@ -7625,14 +7647,43 @@ mod tests {
76257647
))
76267648
}
76277649

7628-
fn set_process_env(key: &str, value: impl AsRef<std::ffi::OsStr>) {
7629-
// SAFETY: These tests change process-global environment variables in a narrow scope and restore them before returning.
7630-
unsafe { std::env::set_var(key, value) };
7650+
struct EnvVarGuard {
7651+
key: &'static str,
7652+
prior: Option<OsString>,
7653+
}
7654+
7655+
impl EnvVarGuard {
7656+
fn new(key: &'static str) -> Self {
7657+
Self {
7658+
key,
7659+
prior: std::env::var_os(key),
7660+
}
7661+
}
7662+
7663+
fn set(&self, value: impl AsRef<std::ffi::OsStr>) {
7664+
// SAFETY: Tests use these helpers in a narrow scope and Drop restores the prior value before the test exits.
7665+
unsafe { std::env::set_var(self.key, value) };
7666+
}
7667+
7668+
fn remove(&self) {
7669+
// SAFETY: Tests use these helpers in a narrow scope and Drop restores the prior value before the test exits.
7670+
unsafe { std::env::remove_var(self.key) };
7671+
}
76317672
}
76327673

7633-
fn remove_process_env(key: &str) {
7634-
// SAFETY: These tests change process-global environment variables in a narrow scope and restore them before returning.
7635-
unsafe { std::env::remove_var(key) };
7674+
impl Drop for EnvVarGuard {
7675+
fn drop(&mut self) {
7676+
match self.prior.as_ref() {
7677+
Some(value) => {
7678+
// SAFETY: This restores the exact prior process environment value captured at guard creation.
7679+
unsafe { std::env::set_var(self.key, value) };
7680+
}
7681+
None => {
7682+
// SAFETY: This restores the prior unset state captured at guard creation.
7683+
unsafe { std::env::remove_var(self.key) };
7684+
}
7685+
}
7686+
}
76367687
}
76377688

76387689
#[test]
@@ -7655,35 +7706,26 @@ mod tests {
76557706
let explicit = PathBuf::from(r"C:\temp\explicit-downloads");
76567707
let env_path = PathBuf::from(r"C:\temp\env-downloads");
76577708
let legacy_env_path = PathBuf::from(r"C:\temp\legacy-downloads");
7658-
let prior_primary = std::env::var_os("PINGET_DOWNLOAD_CACHE_DIR");
7659-
let prior_legacy = std::env::var_os("PINGET_DOWNLOAD_CACHE");
7709+
let primary_guard = EnvVarGuard::new("PINGET_DOWNLOAD_CACHE_DIR");
7710+
let legacy_guard = EnvVarGuard::new("PINGET_DOWNLOAD_CACHE");
76607711

7661-
set_process_env("PINGET_DOWNLOAD_CACHE_DIR", &env_path);
7662-
set_process_env("PINGET_DOWNLOAD_CACHE", &legacy_env_path);
7712+
primary_guard.set(&env_path);
7713+
legacy_guard.set(&legacy_env_path);
76637714

76647715
assert_eq!(
76657716
resolve_installer_download_cache_root(&app_root, Some(explicit.as_path())),
76667717
explicit
76677718
);
76687719
assert_eq!(resolve_installer_download_cache_root(&app_root, None), env_path);
76697720

7670-
remove_process_env("PINGET_DOWNLOAD_CACHE_DIR");
7721+
primary_guard.remove();
76717722
assert_eq!(resolve_installer_download_cache_root(&app_root, None), legacy_env_path);
76727723

7673-
remove_process_env("PINGET_DOWNLOAD_CACHE");
7724+
legacy_guard.remove();
76747725
assert_eq!(
76757726
resolve_installer_download_cache_root(&app_root, None),
76767727
app_root.join("downloads")
76777728
);
7678-
7679-
match prior_primary {
7680-
Some(value) => set_process_env("PINGET_DOWNLOAD_CACHE_DIR", value),
7681-
None => remove_process_env("PINGET_DOWNLOAD_CACHE_DIR"),
7682-
}
7683-
match prior_legacy {
7684-
Some(value) => set_process_env("PINGET_DOWNLOAD_CACHE", value),
7685-
None => remove_process_env("PINGET_DOWNLOAD_CACHE"),
7686-
}
76877729
}
76887730

76897731
#[test]

0 commit comments

Comments
 (0)