@@ -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
69376937fn hash_matches ( expected_hash : Option < & str > , bytes : & [ u8 ] ) -> bool {
@@ -6942,13 +6942,34 @@ fn hash_matches(expected_hash: Option<&str>, bytes: &[u8]) -> bool {
69426942
69436943fn 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+
69526973fn 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) ]
76127633mod 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