diff --git a/Cargo.lock b/Cargo.lock index 439bcde..27bf7fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,9 +450,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] @@ -1579,9 +1579,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.36.2" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 9c07d83..adbac9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ uuid = { version = "1.7.0", features = ["v4"] } which = "6.0.0" zip = "2.3.0" tempfile = "3.12.0" -quick-xml = "0.36.1" +quick-xml = "0.41" ignore = "0.4" globset = "0.4" termcolor = "1.1" diff --git a/src/scanners/fortify.rs b/src/scanners/fortify.rs index 077988e..c6f92a3 100644 --- a/src/scanners/fortify.rs +++ b/src/scanners/fortify.rs @@ -2,6 +2,7 @@ use crate::scan::upload_scan; use crate::Config; use quick_xml::events::Event; use quick_xml::reader::Reader; +use quick_xml::XmlVersion; use std::fs::File; use std::io; use std::io::{BufReader, Read}; @@ -92,7 +93,9 @@ fn extract_file_path(scan_file: PathBuf) -> (String, Vec) { Ok(attr) => { let attr_key = attr.key.as_ref(); if attr_key == b"path" { - if let Ok(value) = attr.unescape_value() { + if let Ok(value) = + attr.normalized_value(XmlVersion::Implicit1_0) + { let path_str = value.to_string(); if !paths.contains(&path_str) { paths.push(path_str); @@ -115,7 +118,9 @@ fn extract_file_path(scan_file: PathBuf) -> (String, Vec) { Ok(attr) => { let attr_key = attr.key.as_ref(); if attr_key == b"path" { - if let Ok(value) = attr.unescape_value() { + if let Ok(value) = + attr.normalized_value(XmlVersion::Implicit1_0) + { let path_str = value.to_string(); if !paths.contains(&path_str) { paths.push(path_str); @@ -145,3 +150,52 @@ fn extract_file_path(scan_file: PathBuf) -> (String, Vec) { (contents, paths) } + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + + /// Exercises the FVDL path extraction end to end on a representative + /// report: both the `Start` and `Empty` `SourceLocation` arms, XML entity + /// unescaping, in-`Vulnerability` scoping, and de-duplication. `fortify.rs` + /// had no coverage; this pins the behavior across the quick-xml 0.36 -> 0.41 + /// bump (the migration replaced the deprecated `unescape_value` with + /// `normalized_value(XmlVersion::Implicit1_0)`, which must still resolve + /// `&` -> `&` for source paths). + #[test] + fn extract_file_path_pulls_scoped_source_locations() { + let fvdl = r#" + + + + + + + + + + + +"#; + + let mut tmp = tempfile::NamedTempFile::new().expect("create temp fvdl"); + tmp.write_all(fvdl.as_bytes()).expect("write fvdl"); + tmp.flush().expect("flush fvdl"); + + let (contents, paths) = extract_file_path(tmp.path().to_path_buf()); + + assert_eq!(contents, fvdl, "returns the raw scan contents unchanged"); + assert_eq!( + paths, + vec![ + // Start arm, with `&` unescaped by normalized_value. + "src/start/a&b.java".to_string(), + // Empty arm, de-duplicated across the two Vulnerability blocks. + "src/empty/App.java".to_string(), + ], + "extracts in-Vulnerability SourceLocation paths, unescapes entities, \ + ignores the out-of-scope SourceLocation, and de-duplicates" + ); + } +} diff --git a/src/verify_deps/registry.rs b/src/verify_deps/registry.rs index 8168fc5..2339ca1 100644 --- a/src/verify_deps/registry.rs +++ b/src/verify_deps/registry.rs @@ -532,11 +532,10 @@ fn split_pep440_prerelease(v: &str) -> Option<(&str, Option)> { (1, "a", r) } else if let Some(r) = suffix.strip_prefix('b') { (2, "b", r) - } else if let Some(r) = suffix.strip_prefix('c') { + } else { // PEP 440 spells release-candidate `c` and `rc` interchangeably. + let r = suffix.strip_prefix('c')?; (3, "rc", r) - } else { - return None; }; let num_str = rest.trim_start_matches(['.', '-', '_']); // Reject anything we didn't fully consume (combined `a1.dev2`, local