github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/common/cyclonedxhelpers/external_references.go (about) 1 package cyclonedxhelpers 2 3 import ( 4 "fmt" 5 "net/url" 6 "strings" 7 8 "github.com/CycloneDX/cyclonedx-go" 9 10 syftFile "github.com/anchore/syft/syft/file" 11 "github.com/anchore/syft/syft/pkg" 12 "github.com/lineaje-labs/syft/internal/file" 13 ) 14 15 //nolint:gocognit 16 func encodeExternalReferences(p pkg.Package) *[]cyclonedx.ExternalReference { 17 var refs []cyclonedx.ExternalReference 18 if hasMetadata(p) { 19 // Skip adding extracted URL and Homepage metadata 20 // as "external_reference" if the metadata isn't IRI-compliant 21 switch metadata := p.Metadata.(type) { 22 case pkg.ApkDBEntry: 23 if metadata.URL != "" && isValidExternalRef(metadata.URL) { 24 refs = append(refs, cyclonedx.ExternalReference{ 25 URL: metadata.URL, 26 Type: cyclonedx.ERTypeDistribution, 27 }) 28 } 29 case pkg.RustCargoLockEntry: 30 if metadata.Source != "" { 31 refs = append(refs, cyclonedx.ExternalReference{ 32 URL: metadata.Source, 33 Type: cyclonedx.ERTypeDistribution, 34 }) 35 } 36 case pkg.NpmPackage: 37 if metadata.URL != "" && isValidExternalRef(metadata.URL) { 38 refs = append(refs, cyclonedx.ExternalReference{ 39 URL: metadata.URL, 40 Type: cyclonedx.ERTypeDistribution, 41 }) 42 } 43 if metadata.Homepage != "" && isValidExternalRef(metadata.Homepage) { 44 refs = append(refs, cyclonedx.ExternalReference{ 45 URL: metadata.Homepage, 46 Type: cyclonedx.ERTypeWebsite, 47 }) 48 } 49 case pkg.RubyGemspec: 50 if metadata.Homepage != "" && isValidExternalRef(metadata.Homepage) { 51 refs = append(refs, cyclonedx.ExternalReference{ 52 URL: metadata.Homepage, 53 Type: cyclonedx.ERTypeWebsite, 54 }) 55 } 56 case pkg.JavaArchive: 57 if len(metadata.ArchiveDigests) > 0 { 58 for _, digest := range metadata.ArchiveDigests { 59 refs = append(refs, cyclonedx.ExternalReference{ 60 URL: "", 61 Type: cyclonedx.ERTypeBuildMeta, 62 Hashes: &[]cyclonedx.Hash{{ 63 Algorithm: toCycloneDXAlgorithm(digest.Algorithm), 64 Value: digest.Value, 65 }}, 66 }) 67 } 68 } 69 case pkg.PythonPackage: 70 if metadata.DirectURLOrigin != nil && metadata.DirectURLOrigin.URL != "" { 71 ref := cyclonedx.ExternalReference{ 72 URL: metadata.DirectURLOrigin.URL, 73 Type: cyclonedx.ERTypeVCS, 74 } 75 if metadata.DirectURLOrigin.CommitID != "" { 76 ref.Comment = fmt.Sprintf("commit: %s", metadata.DirectURLOrigin.CommitID) 77 } 78 refs = append(refs, ref) 79 } 80 } 81 } 82 if len(refs) > 0 { 83 return &refs 84 } 85 return nil 86 } 87 88 // supported algorithm in cycloneDX as of 1.4 89 // "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512", 90 // "SHA3-256", "SHA3-384", "SHA3-512", "BLAKE2b-256", "BLAKE2b-384", "BLAKE2b-512", "BLAKE3" 91 // syft supported digests: cmd/syft/cli/eventloop/tasks.go 92 // MD5, SHA1, SHA256 93 func toCycloneDXAlgorithm(algorithm string) cyclonedx.HashAlgorithm { 94 validMap := map[string]cyclonedx.HashAlgorithm{ 95 "sha1": cyclonedx.HashAlgorithm("SHA-1"), 96 "md5": cyclonedx.HashAlgorithm("MD5"), 97 "sha256": cyclonedx.HashAlgorithm("SHA-256"), 98 } 99 100 return validMap[strings.ToLower(algorithm)] 101 } 102 103 func decodeExternalReferences(c *cyclonedx.Component, metadata interface{}) { 104 if c.ExternalReferences == nil { 105 return 106 } 107 switch meta := metadata.(type) { 108 case *pkg.ApkDBEntry: 109 meta.URL = refURL(c, cyclonedx.ERTypeDistribution) 110 case *pkg.RustCargoLockEntry: 111 meta.Source = refURL(c, cyclonedx.ERTypeDistribution) 112 case *pkg.NpmPackage: 113 meta.URL = refURL(c, cyclonedx.ERTypeDistribution) 114 meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite) 115 case *pkg.RubyGemspec: 116 meta.Homepage = refURL(c, cyclonedx.ERTypeWebsite) 117 case *pkg.JavaArchive: 118 var digests []syftFile.Digest 119 if ref := findExternalRef(c, cyclonedx.ERTypeBuildMeta); ref != nil { 120 if ref.Hashes != nil { 121 for _, hash := range *ref.Hashes { 122 digests = append(digests, syftFile.Digest{ 123 Algorithm: file.CleanDigestAlgorithmName(string(hash.Algorithm)), 124 Value: hash.Value, 125 }) 126 } 127 } 128 } 129 130 meta.ArchiveDigests = digests 131 case *pkg.PythonPackage: 132 if meta.DirectURLOrigin == nil { 133 meta.DirectURLOrigin = &pkg.PythonDirectURLOriginInfo{} 134 } 135 meta.DirectURLOrigin.URL = refURL(c, cyclonedx.ERTypeVCS) 136 meta.DirectURLOrigin.CommitID = strings.TrimPrefix(refComment(c, cyclonedx.ERTypeVCS), "commit: ") 137 } 138 } 139 140 func findExternalRef(c *cyclonedx.Component, typ cyclonedx.ExternalReferenceType) *cyclonedx.ExternalReference { 141 if c.ExternalReferences != nil { 142 for _, r := range *c.ExternalReferences { 143 if r.Type == typ { 144 return &r 145 } 146 } 147 } 148 return nil 149 } 150 151 func refURL(c *cyclonedx.Component, typ cyclonedx.ExternalReferenceType) string { 152 if r := findExternalRef(c, typ); r != nil { 153 return r.URL 154 } 155 return "" 156 } 157 158 func refComment(c *cyclonedx.Component, typ cyclonedx.ExternalReferenceType) string { 159 if r := findExternalRef(c, typ); r != nil { 160 return r.Comment 161 } 162 return "" 163 } 164 165 // isValidExternalRef checks for IRI-comppliance for input string to be added into "external_reference" 166 func isValidExternalRef(s string) bool { 167 parsed, err := url.Parse(s) 168 return err == nil && parsed != nil && parsed.Host != "" 169 }