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  }