1818
1919import static io .github .guacsec .trustifyda .impl .ExhortApi .debugLoggingIsNeeded ;
2020
21- import com .fasterxml .jackson .annotation .JsonIgnoreProperties ;
22- import com .fasterxml .jackson .annotation .JsonProperty ;
2321import com .fasterxml .jackson .databind .ObjectMapper ;
2422import com .github .packageurl .PackageURL ;
2523import io .github .guacsec .trustifyda .Api ;
2624import io .github .guacsec .trustifyda .Provider ;
2725import io .github .guacsec .trustifyda .logging .LoggersFactory ;
26+ import io .github .guacsec .trustifyda .providers .rust .model .CargoDep ;
27+ import io .github .guacsec .trustifyda .providers .rust .model .CargoDepKind ;
28+ import io .github .guacsec .trustifyda .providers .rust .model .CargoMetadata ;
29+ import io .github .guacsec .trustifyda .providers .rust .model .CargoNode ;
30+ import io .github .guacsec .trustifyda .providers .rust .model .DependencyInfo ;
31+ import io .github .guacsec .trustifyda .providers .rust .model .ProjectInfo ;
2832import io .github .guacsec .trustifyda .sbom .Sbom ;
2933import io .github .guacsec .trustifyda .sbom .SbomFactory ;
3034import io .github .guacsec .trustifyda .tools .Ecosystem .Type ;
3842import java .util .HashMap ;
3943import java .util .HashSet ;
4044import java .util .LinkedHashMap ;
41- import java .util .List ;
4245import java .util .Map ;
4346import java .util .Set ;
4447import java .util .concurrent .TimeUnit ;
5053 * Concrete implementation of the {@link Provider} used for converting dependency trees for Rust
5154 * projects (Cargo.toml) into a SBOM content for Component analysis or Stack analysis.
5255 */
53- public final class RustProvider extends Provider {
56+ public final class CargoProvider extends Provider {
5457
5558 private static final ObjectMapper MAPPER = new ObjectMapper ();
56- private static final Logger log = LoggersFactory .getLogger (RustProvider .class .getName ());
59+ private static final Logger log = LoggersFactory .getLogger (CargoProvider .class .getName ());
5760 private static final String PACKAGE_NAME = "package.name" ;
5861 private static final String PACKAGE_VERSION = "package.version" ;
5962 private static final String PACKAGE_VERSION_WORKSPACE = "package.version.workspace" ;
@@ -62,79 +65,6 @@ public final class RustProvider extends Provider {
6265 Long .parseLong (System .getProperty ("trustify.cargo.timeout.seconds" , "5" ));
6366 private final String cargoExecutable ;
6467
65- private record ProjectInfo (String name , String version ) {
66- private ProjectInfo (String name , String version ) {
67- this .name = name != null ? name : "unknown-rust-project" ;
68- this .version = version != null ? version : "0.0.0" ;
69- }
70- }
71-
72- private record DependencyInfo (String name , String version ) {}
73-
74- private enum AnalysisType {
75- STACK ,
76- COMPONENT
77- }
78-
79- // cargo-metadata output format https://doc.rust-lang.org/cargo/commands/cargo-metadata.html
80- // JSON model classes for cargo metadata parsing
81-
82- /** Root cargo metadata structure - minimal for dependency analysis */
83- @ JsonIgnoreProperties (ignoreUnknown = true )
84- private record CargoMetadata (
85- @ JsonProperty ("packages" ) List <CargoPackage > packages ,
86- @ JsonProperty ("resolve" ) CargoResolve resolve ,
87- @ JsonProperty ("workspace_members" ) List <String > workspaceMembers ,
88- @ JsonProperty ("workspace_root" ) String workspaceRoot ) {}
89-
90- /** Package information - only dependency analysis fields */
91- @ JsonIgnoreProperties (ignoreUnknown = true )
92- private record CargoPackage (
93- @ JsonProperty ("name" ) String name ,
94- @ JsonProperty ("version" ) String version ,
95- @ JsonProperty ("id" ) String id ,
96- @ JsonProperty ("dependencies" ) List <CargoDependency > dependencies ) {}
97-
98- /** Dependency declaration - core fields for dependency analysis */
99- @ JsonIgnoreProperties (ignoreUnknown = true )
100- private record CargoDependency (
101- @ JsonProperty ("name" ) String name ,
102- @ JsonProperty ("req" ) String req ,
103- @ JsonProperty ("kind" ) String kind ,
104- @ JsonProperty ("optional" ) Boolean optional ) {}
105-
106- /** Dependency resolution graph (contains actual resolved versions) */
107- @ JsonIgnoreProperties (ignoreUnknown = true )
108- private record CargoResolve (
109- @ JsonProperty ("nodes" ) List <CargoNode > nodes , @ JsonProperty ("root" ) String root ) {}
110-
111- /** Resolved dependency node - essential fields for dependency resolution */
112- @ JsonIgnoreProperties (ignoreUnknown = true )
113- private record CargoNode (
114- @ JsonProperty ("id" ) String id ,
115- @ JsonProperty ("dependencies" ) List <String > dependencies ,
116- @ JsonProperty ("deps" ) List <CargoDep > deps ) {}
117-
118- /** Detailed dependency information with resolved package reference */
119- @ JsonIgnoreProperties (ignoreUnknown = true )
120- private record CargoDep (
121- @ JsonProperty ("name" ) String name ,
122- @ JsonProperty ("pkg" ) String pkg ,
123- @ JsonProperty ("dep_kinds" ) List <CargoDepKind > depKinds ) {}
124-
125- /** Dependency kind information (normal, dev, build) */
126- @ JsonIgnoreProperties (ignoreUnknown = true )
127- private record CargoDepKind (
128- @ JsonProperty ("kind" ) String kind , @ JsonProperty ("target" ) String target ) {}
129-
130- private void addStackDependencies (Sbom sbom , PackageURL root , Set <String > ignoredDeps ) {
131- addDependencies (sbom , root , ignoredDeps , AnalysisType .STACK );
132- }
133-
134- private void addComponentDependencies (Sbom sbom , PackageURL root , Set <String > ignoredDeps ) {
135- addDependencies (sbom , root , ignoredDeps , AnalysisType .COMPONENT );
136- }
137-
13868 private void addDependencies (
13969 Sbom sbom , PackageURL root , Set <String > ignoredDeps , AnalysisType analysisType ) {
14070 try {
@@ -385,7 +315,7 @@ private void addResolvedDependencyToSbom(DependencyInfo childInfo, Sbom sbom, Pa
385315 // Use EXACT resolved version from resolve graph
386316 PackageURL packageUrl =
387317 new PackageURL (
388- Type .RUST .getType (), null , childInfo .name (), childInfo .version (), null , null );
318+ Type .CARGO .getType (), null , childInfo .name (), childInfo .version (), null , null );
389319 sbom .addDependency (root , packageUrl , null );
390320 if (debugLoggingIsNeeded ()) {
391321 log .info (
@@ -459,7 +389,7 @@ private void processDependencyNode(
459389 try {
460390 PackageURL childUrl =
461391 new PackageURL (
462- Type .RUST .getType (), null , childInfo .name (), childInfo .version (), null , null );
392+ Type .CARGO .getType (), null , childInfo .name (), childInfo .version (), null , null );
463393
464394 // Create unique key for deduplication using stable identifiers
465395 String relationshipKey = parent .getCoordinates () + "->" + childUrl .getCoordinates ();
@@ -541,7 +471,7 @@ private DependencyInfo parseSimplePackageId(String packageId) {
541471 }
542472
543473 // Just a package name without version - validate it looks like a valid package name
544- if (!packageId .isEmpty () && isValidPackageName ( packageId ) ) {
474+ if (!packageId .isEmpty ()) {
545475 if (debugLoggingIsNeeded ()) {
546476 log .info ("Parsed simple package ID (name only): " + packageId + " -> " + packageId );
547477 }
@@ -593,12 +523,6 @@ private DependencyInfo parseUrlPackageId(String packageId) {
593523 }
594524 }
595525
596- // Fragment should be just a version - validate it's not malformed
597- if (isMalformedPackageVersion (fragment )) {
598- log .fine ("Fragment appears to be malformed package-version string: " + fragment );
599- return null ;
600- }
601-
602526 // Fragment should not start or end with separators (indicates malformed format)
603527 if (fragment .startsWith ("@" )
604528 || fragment .startsWith (":" )
@@ -623,45 +547,6 @@ private DependencyInfo parseUrlPackageId(String packageId) {
623547 return null ;
624548 }
625549
626- /** Validate if string looks like a valid Rust package name */
627- private boolean isValidPackageName (String name ) {
628- if (name == null || name .isEmpty ()) {
629- return false ;
630- }
631- // Must start with a letter
632- if (!Character .isLetter (name .charAt (0 ))) {
633- return false ;
634- }
635- // Can only contain letters, numbers, hyphens, and underscores
636- if (!name .matches ("^[a-zA-Z][a-zA-Z0-9_-]*$" )) {
637- return false ;
638- }
639- // Cannot end with hyphen
640- if (name .endsWith ("-" )) {
641- return false ;
642- }
643- // Reject overly long or obviously invalid names
644- if (name .length () > 64 || name .contains ("this-is-not" )) {
645- return false ;
646- }
647- return true ;
648- }
649-
650- private boolean isMalformedPackageVersion (String fragment ) {
651- // Check for patterns like "package-1.0.0" or "package_1.0.0"
652- // where it should be "package@1.0.0" or "package:1.0.0"
653-
654- // Look for package-name followed by dash and version-like pattern
655- if (fragment .matches ("^[a-zA-Z][a-zA-Z0-9_-]*-\\ d+\\ ..*" )) {
656- return true ;
657- }
658- // Look for package-name followed by underscore and version-like pattern
659- if (fragment .matches ("^[a-zA-Z][a-zA-Z0-9_-]*_\\ d+\\ ..*" )) {
660- return true ;
661- }
662- return false ;
663- }
664-
665550 /** Extract package name from URL path */
666551 private String extractNameFromUrl (String url ) {
667552 try {
@@ -700,8 +585,8 @@ private String extractNameFromUrl(String url) {
700585 }
701586 }
702587
703- public RustProvider (Path manifest ) {
704- super (Type .RUST , manifest );
588+ public CargoProvider (Path manifest ) {
589+ super (Type .CARGO , manifest );
705590 this .cargoExecutable = Operations .getExecutable ("cargo" , "--version" );
706591
707592 if (cargoExecutable != null ) {
@@ -714,17 +599,17 @@ public RustProvider(Path manifest) {
714599
715600 @ Override
716601 public Content provideComponent () throws IOException {
717- Sbom sbom = createRustSbom (false );
602+ Sbom sbom = createSbom (false );
718603 return new Content (sbom .getAsJsonString ().getBytes (), Api .CYCLONEDX_MEDIA_TYPE );
719604 }
720605
721606 @ Override
722607 public Content provideStack () throws IOException {
723- Sbom sbom = createRustSbom (true );
608+ Sbom sbom = createSbom (true );
724609 return new Content (sbom .getAsJsonString ().getBytes (), Api .CYCLONEDX_MEDIA_TYPE );
725610 }
726611
727- private Sbom createRustSbom (boolean includeTransitiveDependencies ) throws IOException {
612+ private Sbom createSbom (boolean includeTransitiveDependencies ) throws IOException {
728613 if (!Files .exists (manifest ) || !Files .isRegularFile (manifest )) {
729614 throw new IOException ("Cargo.toml not found: " + manifest );
730615 }
@@ -741,16 +626,16 @@ private Sbom createRustSbom(boolean includeTransitiveDependencies) throws IOExce
741626 try {
742627 var root =
743628 new PackageURL (
744- Type .RUST .getType (), null , projectInfo .name , projectInfo .version , null , null );
629+ Type .CARGO .getType (), null , projectInfo .name () , projectInfo .version () , null , null );
745630 sbom .addRoot (root );
746631
747632 String cargoContent = Files .readString (manifest , StandardCharsets .UTF_8 );
748633 Set <String > ignoredDeps = getIgnoredDependencies (tomlResult , cargoContent );
749634
750635 if (includeTransitiveDependencies ) {
751- addStackDependencies (sbom , root , ignoredDeps );
636+ addDependencies (sbom , root , ignoredDeps , AnalysisType . STACK );
752637 } else {
753- addComponentDependencies (sbom , root , ignoredDeps );
638+ addDependencies (sbom , root , ignoredDeps , AnalysisType . COMPONENT );
754639 }
755640 return sbom ;
756641 } catch (Exception e ) {
@@ -833,8 +718,6 @@ private Set<String> getIgnoredDependencies(TomlParseResult result, String conten
833718 private Set <String > collectAllDependencies (TomlParseResult result ) {
834719 Set <String > allDeps = new HashSet <>();
835720 addDependenciesFromSection (result , "dependencies" , allDeps );
836- addDependenciesFromSection (result , "dev-dependencies" , allDeps );
837- addDependenciesFromSection (result , "build-dependencies" , allDeps );
838721 addDependenciesFromSection (result , "workspace.dependencies" , allDeps );
839722 addDependenciesFromSection (result , "workspace.build-dependencies" , allDeps );
840723 return allDeps ;
0 commit comments