diff --git a/rust/vm-package-manager/src/links/cargo.rs b/rust/vm-package-manager/src/links/cargo.rs index 0fdd441a..0b273001 100644 --- a/rust/vm-package-manager/src/links/cargo.rs +++ b/rust/vm-package-manager/src/links/cargo.rs @@ -1,10 +1,7 @@ use rayon::prelude::*; -use regex::Regex; use std::collections::HashSet; use std::process::Command; -use std::sync::OnceLock; use vm_core::error::{Result, VmError}; -use vm_core::vm_error; pub fn detect_cargo_packages(packages: &[String]) -> Result> { let package_set: HashSet<&String> = packages.iter().collect(); @@ -43,34 +40,56 @@ pub fn detect_cargo_packages(packages: &[String]) -> Result Result> { let mut installations = Vec::new(); - // Regex to match cargo install list format: - // package_name v1.0.0 (/path/to/source): - static RE: OnceLock = OnceLock::new(); - let re = RE.get_or_init(|| { - Regex::new(r"^([a-zA-Z0-9_-]+)\s+[^\(]*\(([^)]+)\):$").expect("Failed to compile regex") - }); - for line in output.lines() { - if let Some(captures) = re.captures(line) { - let pkg_name = match captures.get(1) { - Some(m) => m.as_str().to_string(), - None => { - vm_error!("Malformed cargo metadata line: missing package name"); - continue; - } - }; - let pkg_path = match captures.get(2) { - Some(m) => m.as_str(), - None => { - vm_error!("Malformed cargo metadata line: missing package path"); - continue; - } - }; + // Parse: package_name v1.0.0 (/path/to/source): + // Regex equivalent: ^([a-zA-Z0-9_-]+)\s+[^\(]*\(([^)]+)\):$ - // Only include path-based installs (not registry installs) - if pkg_path.contains('/') && !pkg_path.starts_with("registry+") { - installations.push((pkg_name, pkg_path.to_string())); - } + // 1. Extract package name (chars until first whitespace) + let Some(first_space_idx) = line.find(char::is_whitespace) else { + continue; + }; + + let pkg_name_str = &line[..first_space_idx]; + + // Validation: [a-zA-Z0-9_-]+ + if pkg_name_str.is_empty() + || !pkg_name_str + .chars() + .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-') + { + continue; + } + + // 2. Check line ending + if !line.ends_with("):") { + continue; + } + + // 3. Find path start + // Skip package name, search for first '(' + let Some(paren_relative_idx) = line[first_space_idx..].find('(') else { + continue; + }; + let paren_start_idx = first_space_idx + paren_relative_idx; + + // Path is between '(' and final '):' + let path_start = paren_start_idx + 1; + let path_end = line.len() - 2; + + if path_start >= path_end { + continue; + } + + let pkg_path = &line[path_start..path_end]; + + // Regex ([^)]+) implies path cannot contain ')' + if pkg_path.contains(')') { + continue; + } + + // Only include path-based installs (not registry installs) + if pkg_path.contains('/') && !pkg_path.starts_with("registry+") { + installations.push((pkg_name_str.to_string(), pkg_path.to_string())); } }