33//! the vendored backend refuses installers older than 2018, so both ask
44//! [`installed_major`] once per command.
55
6- use std:: ffi:: OsString ;
7- use std:: path:: { Path , PathBuf } ;
6+ use std:: path:: Path ;
87
98/// Pins the answer without spawning anything: CI images without pipenv on
109/// PATH, or a project installed with a different release than the machine's
@@ -39,38 +38,24 @@ fn parse_major(output: &str) -> Option<u32> {
3938 major. parse ( ) . ok ( )
4039}
4140
42- /// The `pipenv` executable, searched on ABSOLUTE `PATH` entries only: the
43- /// CLI runs inside the project being scanned, so a relative entry (`.`, an
44- /// empty component) would execute a `pipenv` planted in that repository. On Windows every `PATHEXT` extension is tried,
45- /// so `pipenv.exe` and the `pipenv.bat` / `pipenv.cmd` shims (pyenv-win,
46- /// hand-written wrappers) are both found. The rule lives in
47- /// [`crate::utils::process::resolve_tool_with`], shared with every other
48- /// tool the CLI spawns inside a project (`git`, `vlt`, `node`).
49- fn resolve_on_path ( var : & impl Fn ( & str ) -> Option < OsString > ) -> Option < PathBuf > {
50- crate :: utils:: process:: resolve_tool_with ( "pipenv" , var)
51- }
52-
5341/// The major of the `pipenv` on PATH (`11`, `2018`, `2026`, …), or `None`
5442/// when none is found, it does not answer within 10 s, exits non-zero or
5543/// prints something unrecognizable. [`MAJOR_OVERRIDE_ENV`] short-circuits
5644/// the probe.
57- pub async fn installed_major ( root : & Path ) -> Option < u32 > {
45+ pub async fn installed_major ( _root : & Path ) -> Option < u32 > {
5846 if let Some ( forced) = std:: env:: var ( MAJOR_OVERRIDE_ENV )
5947 . ok ( )
6048 . and_then ( |value| parse_override ( & value) )
6149 {
6250 return Some ( forced) ;
6351 }
64- let program = resolve_on_path ( & |name| std:: env:: var_os ( name) ) ?;
65- // The RESOLVED path is spawned; a Windows `.bat` / `.cmd` shim is run by
66- // `std` itself through cmd.exe with correct quoting (see the shared
67- // launcher's docs).
52+ // Absolute PATH entries only, so a repo-planted `pipenv` never runs.
53+ let program = crate :: utils:: process:: resolve_tool ( "pipenv" ) ?;
6854 let mut command = crate :: utils:: process:: command_for ( & program) ;
6955 // The version banner does not depend on a project, so the probe runs in a
7056 // NEUTRAL directory: with the scanned repository as cwd, Pipenv would read
7157 // its `.env`, `Pipfile` and `.venv` pointer — committed, attacker-shaped
7258 // inputs that must not influence (or slow down) a version check.
73- let _ = root;
7459 command
7560 . arg ( "--version" )
7661 . current_dir ( std:: env:: temp_dir ( ) )
@@ -121,68 +106,6 @@ mod tests {
121106 ) ;
122107 }
123108
124- #[ test]
125- fn resolve_on_path_skips_relative_entries_and_finds_absolute_ones ( ) {
126- let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
127- let bin = tmp. path ( ) . join ( "bin" ) ;
128- std:: fs:: create_dir_all ( & bin) . unwrap ( ) ;
129- let leaf = if cfg ! ( windows) {
130- "pipenv.exe"
131- } else {
132- "pipenv"
133- } ;
134- std:: fs:: write ( bin. join ( leaf) , b"" ) . unwrap ( ) ;
135- #[ cfg( unix) ]
136- {
137- use std:: os:: unix:: fs:: PermissionsExt ;
138- std:: fs:: set_permissions ( bin. join ( leaf) , std:: fs:: Permissions :: from_mode ( 0o755 ) )
139- . unwrap ( ) ;
140- }
141- // A repo-planted `pipenv` under a RELATIVE entry must never win.
142- let planted = tmp. path ( ) . join ( "planted" ) ;
143- std:: fs:: create_dir_all ( & planted) . unwrap ( ) ;
144- std:: fs:: write ( planted. join ( leaf) , b"" ) . unwrap ( ) ;
145- let joined = std:: env:: join_paths ( [
146- std:: path:: PathBuf :: from ( "." ) ,
147- std:: path:: PathBuf :: from ( "" ) ,
148- std:: path:: PathBuf :: from ( "planted" ) ,
149- bin. clone ( ) ,
150- ] )
151- . unwrap ( ) ;
152- let var = |name : & str | ( name == "PATH" ) . then ( || joined. clone ( ) ) ;
153- assert_eq ! ( resolve_on_path( & var) , Some ( bin. join( leaf) ) ) ;
154-
155- let only_relative = std:: env:: join_paths ( [
156- std:: path:: PathBuf :: from ( "." ) ,
157- std:: path:: PathBuf :: from ( "planted" ) ,
158- ] )
159- . unwrap ( ) ;
160- let var = |name : & str | ( name == "PATH" ) . then ( || only_relative. clone ( ) ) ;
161- assert_eq ! ( resolve_on_path( & var) , None ) ;
162- let none = |_: & str | None :: < OsString > ;
163- assert_eq ! ( resolve_on_path( & none) , None ) ;
164- }
165-
166- #[ cfg( unix) ]
167- #[ test]
168- fn resolve_on_path_skips_non_executable_files ( ) {
169- use std:: os:: unix:: fs:: PermissionsExt ;
170- let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
171- let data = tmp. path ( ) . join ( "data" ) ;
172- let bin = tmp. path ( ) . join ( "bin" ) ;
173- std:: fs:: create_dir_all ( & data) . unwrap ( ) ;
174- std:: fs:: create_dir_all ( & bin) . unwrap ( ) ;
175- std:: fs:: write ( data. join ( "pipenv" ) , b"not a program" ) . unwrap ( ) ;
176- std:: fs:: set_permissions ( data. join ( "pipenv" ) , std:: fs:: Permissions :: from_mode ( 0o644 ) )
177- . unwrap ( ) ;
178- std:: fs:: write ( bin. join ( "pipenv" ) , b"" ) . unwrap ( ) ;
179- std:: fs:: set_permissions ( bin. join ( "pipenv" ) , std:: fs:: Permissions :: from_mode ( 0o755 ) )
180- . unwrap ( ) ;
181- let joined = std:: env:: join_paths ( [ data. clone ( ) , bin. clone ( ) ] ) . unwrap ( ) ;
182- let var = |name : & str | ( name == "PATH" ) . then ( || joined. clone ( ) ) ;
183- assert_eq ! ( resolve_on_path( & var) , Some ( bin. join( "pipenv" ) ) ) ;
184- }
185-
186109 #[ test]
187110 fn override_accepts_a_major_or_a_full_release ( ) {
188111 assert_eq ! ( parse_override( "11" ) , Some ( 11 ) ) ;
@@ -193,32 +116,4 @@ mod tests {
193116 assert_eq ! ( parse_override( "" ) , None ) ;
194117 assert_eq ! ( parse_override( "-11" ) , None ) ;
195118 }
196-
197- #[ cfg( windows) ]
198- #[ test]
199- fn resolve_on_path_honours_pathext_shims ( ) {
200- let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
201- let bin = tmp. path ( ) . join ( "bin" ) ;
202- std:: fs:: create_dir_all ( & bin) . unwrap ( ) ;
203- std:: fs:: write ( bin. join ( "pipenv.bat" ) , b"@echo pipenv, version 2024.4.1" ) . unwrap ( ) ;
204- let joined = std:: env:: join_paths ( [ bin. clone ( ) ] ) . unwrap ( ) ;
205- let var = |name : & str | match name {
206- "PATH" => Some ( joined. clone ( ) ) ,
207- "PATHEXT" => Some ( OsString :: from ( ".COM;.EXE;.BAT;.CMD" ) ) ,
208- _ => None ,
209- } ;
210- let found = resolve_on_path ( & var) . unwrap ( ) ;
211- assert_eq ! ( found, bin. join( "pipenv.bat" ) ) ;
212- // Spawned directly: std runs the .bat through cmd.exe itself, and the
213- // banner parses like a real pipenv's.
214- let out = crate :: utils:: process:: command_for ( & found)
215- . arg ( "--version" )
216- . output ( )
217- . expect ( "std spawns a .bat shim" ) ;
218- assert ! ( out. status. success( ) , "{out:?}" ) ;
219- assert_eq ! (
220- parse_major( & String :: from_utf8_lossy( & out. stdout) ) ,
221- Some ( 2024 )
222- ) ;
223- }
224119}
0 commit comments