@@ -97,22 +97,24 @@ pub fn run_compare(opts: CompareOptions) {
9797 let fs_read = cap_root. grant :: < capsec_core:: permission:: FsRead > ( ) ;
9898 let spawn_cap = cap_root. grant :: < capsec_core:: permission:: Spawn > ( ) ;
9999
100- let left = parse_crate_spec_or_latest ( & opts. left ) ;
101- let right = parse_crate_spec_or_latest ( & opts. right ) ;
100+ let mut left = parse_crate_spec_or_latest ( & opts. left ) ;
101+ let mut right = parse_crate_spec_or_latest ( & opts. right ) ;
102102
103- eprintln ! ( "Fetching {} v{} ..." , left. name, left . version ) ;
103+ eprintln ! ( "Fetching {}..." , left. name) ;
104104 let left_dir = fetch_crate_source ( & left. name , & left. version , & spawn_cap, & fs_read)
105105 . unwrap_or_else ( |e| {
106106 eprintln ! ( "Error: {e}" ) ;
107107 std:: process:: exit ( 1 ) ;
108108 } ) ;
109+ resolve_version_from_path ( & mut left, & left_dir) ;
109110
110- eprintln ! ( "Fetching {} v{} ..." , right. name, right . version ) ;
111+ eprintln ! ( "Fetching {}..." , right. name) ;
111112 let right_dir = fetch_crate_source ( & right. name , & right. version , & spawn_cap, & fs_read)
112113 . unwrap_or_else ( |e| {
113114 eprintln ! ( "Error: {e}" ) ;
114115 std:: process:: exit ( 1 ) ;
115116 } ) ;
117+ resolve_version_from_path ( & mut right, & right_dir) ;
116118
117119 eprintln ! ( "Scanning...\n " ) ;
118120 let config = Config :: default ( ) ;
@@ -146,8 +148,13 @@ fn fetch_crate_source(
146148 let temp_dir = std:: env:: temp_dir ( ) . join ( format ! ( "capsec-fetch-{crate_name}-{version}" ) ) ;
147149 let _ = std:: fs:: create_dir_all ( & temp_dir) ;
148150
151+ let version_spec = if version == "*" {
152+ format ! ( "\" {version}\" " )
153+ } else {
154+ format ! ( "\" ={version}\" " )
155+ } ;
149156 let cargo_toml = format ! (
150- "[package]\n name = \" capsec-fetch-temp\" \n version = \" 0.0.1\" \n edition = \" 2021\" \n \n [dependencies]\n {crate_name} = \" ={version} \" \n "
157+ "[package]\n name = \" capsec-fetch-temp\" \n version = \" 0.0.1\" \n edition = \" 2021\" \n \n [dependencies]\n {crate_name} = {version_spec} \n "
151158 ) ;
152159 std:: fs:: write ( temp_dir. join ( "Cargo.toml" ) , cargo_toml)
153160 . map_err ( |e| format ! ( "Failed to write temp Cargo.toml: {e}" ) ) ?;
@@ -180,6 +187,7 @@ fn fetch_crate_source(
180187}
181188
182189/// Looks for a crate's source in ~/.cargo/registry/src/.
190+ /// When version is "*", finds the latest version available in the cache.
183191fn find_registry_source ( crate_name : & str , version : & str ) -> Option < PathBuf > {
184192 let home = std:: env:: var ( "CARGO_HOME" ) . unwrap_or_else ( |_| {
185193 std:: env:: var ( "HOME" )
@@ -192,17 +200,38 @@ fn find_registry_source(crate_name: &str, version: &str) -> Option<PathBuf> {
192200 return None ;
193201 }
194202
195- // Iterate index directories (index.crates.io-HASH/)
196203 let entries = std:: fs:: read_dir ( & registry_src) . ok ( ) ?;
197- for entry in entries. flatten ( ) {
198- let crate_dir = entry. path ( ) . join ( format ! ( "{crate_name}-{version}" ) ) ;
199- if crate_dir. exists ( ) {
200- // Some crates have src/ subdir, some don't
201- let src_dir = crate_dir. join ( "src" ) ;
202- if src_dir. exists ( ) {
203- return Some ( src_dir) ;
204+ for index_dir in entries. flatten ( ) {
205+ if version == "*" {
206+ // Find any version of this crate — pick the latest by name sort
207+ let prefix = format ! ( "{crate_name}-" ) ;
208+ if let Ok ( crate_dirs) = std:: fs:: read_dir ( index_dir. path ( ) ) {
209+ let mut matches: Vec < _ > = crate_dirs
210+ . flatten ( )
211+ . filter ( |e| {
212+ e. file_name ( )
213+ . to_str ( )
214+ . is_some_and ( |n| n. starts_with ( & prefix) )
215+ } )
216+ . collect ( ) ;
217+ matches. sort_by_key ( |b| std:: cmp:: Reverse ( b. file_name ( ) ) ) ;
218+ if let Some ( best) = matches. first ( ) {
219+ let src_dir = best. path ( ) . join ( "src" ) ;
220+ if src_dir. exists ( ) {
221+ return Some ( src_dir) ;
222+ }
223+ return Some ( best. path ( ) ) ;
224+ }
225+ }
226+ } else {
227+ let crate_dir = index_dir. path ( ) . join ( format ! ( "{crate_name}-{version}" ) ) ;
228+ if crate_dir. exists ( ) {
229+ let src_dir = crate_dir. join ( "src" ) ;
230+ if src_dir. exists ( ) {
231+ return Some ( src_dir) ;
232+ }
233+ return Some ( crate_dir) ;
204234 }
205- return Some ( crate_dir) ;
206235 }
207236 }
208237
@@ -250,6 +279,29 @@ fn diff_findings(old: &[Finding], new: &[Finding]) -> DiffResult {
250279
251280// ── Parsers ──
252281
282+ /// If version is "*", resolves it from the fetched directory path.
283+ /// e.g., path `.cargo/registry/src/.../ureq-2.12.1/src` → version "2.12.1"
284+ fn resolve_version_from_path ( spec : & mut CrateSpec , dir : & Path ) {
285+ if spec. version != "*" {
286+ return ;
287+ }
288+ // Walk up from src/ to the crate dir: ureq-2.12.1
289+ let crate_dir = if dir. ends_with ( "src" ) {
290+ dir. parent ( )
291+ } else {
292+ Some ( dir)
293+ } ;
294+ if let Some ( dir_name) = crate_dir
295+ . and_then ( |d| d. file_name ( ) )
296+ . and_then ( |n| n. to_str ( ) )
297+ {
298+ let prefix = format ! ( "{}-" , spec. name) ;
299+ if let Some ( ver) = dir_name. strip_prefix ( & prefix) {
300+ spec. version = ver. to_string ( ) ;
301+ }
302+ }
303+ }
304+
253305/// Parses "serde_json@1.0.133" into CrateSpec.
254306fn parse_crate_spec ( spec : & str ) -> Result < CrateSpec , String > {
255307 let parts: Vec < & str > = spec. splitn ( 2 , '@' ) . collect ( ) ;
0 commit comments