diff --git a/cargo-cyclonedx/src/generator.rs b/cargo-cyclonedx/src/generator.rs index 33b3707d..31ffe10a 100644 --- a/cargo-cyclonedx/src/generator.rs +++ b/cargo-cyclonedx/src/generator.rs @@ -399,6 +399,34 @@ impl SbomGenerator { e ), } + } else { + if let Some(source) = &package.source { + if !source.is_crates_io() { + match source.repr.split_once('+') { + Some(("git", git_path)) => { + let repo_url = git_path + .split_once('#') + .map_or(git_path, |(first, _)| first); + match Uri::try_from(repo_url.to_string()) { + Ok(uri) => references + .push(ExternalReference::new(ExternalReferenceType::Vcs, uri)), + Err(e) => log::warn!( + "Package {} has an invalid repository URI ({}): {} ", + package.name, + source, + e + ), + } + } + Some((source, _path)) => log::warn!("Unknown source kind {}", source), + None => { + log::warn!( + "No '+' separator found in source field from `cargo metadata`" + ) + } + } + } + } } if !references.is_empty() { @@ -1041,6 +1069,8 @@ impl From for SbomWriterError { mod test { use super::*; + const GIT_PACKAGE_JSON: &str = include_str!("../tests/fixtures/git_package.json"); + #[test] fn it_should_parse_author_and_email() { let actual = SbomGenerator::parse_author("First Last ") @@ -1079,4 +1109,72 @@ mod test { assert_eq!(actual, expected); } + + #[test] + fn it_should_get_external_references_from_source() { + let mut git_package: Package = serde_json::from_str(GIT_PACKAGE_JSON).unwrap(); + git_package.repository = None; + + let actual = SbomGenerator::get_external_references(&git_package) + .expect("Failed to parse external reference"); + + let mut expected = Vec::new(); + expected.push(ExternalReference::new( + ExternalReferenceType::Vcs, + Uri::new("https://github.com/rust-secure-code/cargo-auditable.git"), + )); + + assert_eq!(actual, ExternalReferences(expected)); + } + + #[test] + fn it_should_get_external_references_from_source_without_digest() { + let mut git_package: Package = serde_json::from_str(GIT_PACKAGE_JSON).unwrap(); + git_package.repository = None; + git_package.source = Some(cargo_metadata::Source { + repr: "git+https://github.com/rust-secure-code/cargo-auditable.git".to_string(), + }); + + let actual = SbomGenerator::get_external_references(&git_package) + .expect("Failed to parse external reference"); + + let mut expected = Vec::new(); + expected.push(ExternalReference::new( + ExternalReferenceType::Vcs, + Uri::new("https://github.com/rust-secure-code/cargo-auditable.git"), + )); + + assert_eq!(actual, ExternalReferences(expected)); + } + + #[test] + fn it_should_not_get_external_references_from_unidentifiable_source() { + let mut git_package: Package = serde_json::from_str(GIT_PACKAGE_JSON).unwrap(); + git_package.repository = None; + git_package.source = Some(cargo_metadata::Source { + repr: "https://github.com/rust-secure-code/cargo-auditable.git".to_string(), + }); + + assert_eq!(SbomGenerator::get_external_references(&git_package), None); + } + + #[test] + fn it_should_not_get_external_references_from_non_uri_source() { + let mut git_package: Package = serde_json::from_str(GIT_PACKAGE_JSON).unwrap(); + git_package.repository = None; + git_package.source = Some(cargo_metadata::Source { + repr: "not a uri".to_string(), + }); + + assert_eq!(SbomGenerator::get_external_references(&git_package), None); + } + + #[test] + fn it_should_not_get_external_reference_from_invalid_source_uri() { + let mut git_package: Package = serde_json::from_str(GIT_PACKAGE_JSON).unwrap(); + git_package.repository = None; + git_package.source = None; + + assert_eq!(SbomGenerator::get_external_references(&git_package), None); + } }