github.com/anchore/syft@v1.38.2/syft/format/syftjson/model/package.go (about)

     1  package model
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  	"strings"
     9  
    10  	"github.com/anchore/syft/internal/log"
    11  	"github.com/anchore/syft/internal/packagemetadata"
    12  	"github.com/anchore/syft/syft/file"
    13  	"github.com/anchore/syft/syft/license"
    14  	"github.com/anchore/syft/syft/pkg"
    15  )
    16  
    17  var errUnknownMetadataType = errors.New("unknown metadata type")
    18  
    19  // Package represents a pkg.Package object specialized for JSON marshaling and unmarshalling.
    20  type Package struct {
    21  	PackageBasicData
    22  	PackageCustomData
    23  }
    24  
    25  // PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
    26  type PackageBasicData struct {
    27  	ID        string          `json:"id"`
    28  	Name      string          `json:"name"`
    29  	Version   string          `json:"version"`
    30  	Type      pkg.Type        `json:"type"`
    31  	FoundBy   string          `json:"foundBy"`
    32  	Locations []file.Location `json:"locations"`
    33  	Licenses  licenses        `json:"licenses"`
    34  	Language  pkg.Language    `json:"language"`
    35  	CPEs      cpes            `json:"cpes"`
    36  	PURL      string          `json:"purl"`
    37  }
    38  
    39  // cpes is a collection of Common Platform Enumeration identifiers for a package.
    40  type cpes []CPE
    41  
    42  // CPE represents a Common Platform Enumeration identifier used for matching packages to known vulnerabilities in security databases.
    43  type CPE struct {
    44  	// Value is the CPE string identifier.
    45  	Value string `json:"cpe"`
    46  
    47  	// Source is the source where this CPE was obtained or generated from.
    48  	Source string `json:"source,omitempty"`
    49  }
    50  
    51  // licenses is a collection of license findings associated with a package.
    52  type licenses []License
    53  
    54  // License represents software license information discovered for a package, including SPDX expressions and supporting evidence locations.
    55  type License struct {
    56  	// Value is the raw license identifier or expression as found.
    57  	Value string `json:"value"`
    58  
    59  	// SPDXExpression is the parsed SPDX license expression.
    60  	SPDXExpression string `json:"spdxExpression"`
    61  
    62  	// Type is the license type classification (e.g., declared, concluded, discovered).
    63  	Type license.Type `json:"type"`
    64  
    65  	// URLs are URLs where license text or information can be found.
    66  	URLs []string `json:"urls"`
    67  
    68  	// Locations are file locations where this license was discovered.
    69  	Locations []file.Location `json:"locations"`
    70  
    71  	// Contents is the full license text content.
    72  	Contents string `json:"contents,omitempty"`
    73  }
    74  
    75  func newModelLicensesFromValues(licenses []string) (ml []License) {
    76  	for _, v := range licenses {
    77  		expression, err := license.ParseExpression(v)
    78  		if err != nil {
    79  			log.Tracef("could not find valid spdx expression for %s: %w", v, err)
    80  		}
    81  		ml = append(ml, License{
    82  			Value:          v,
    83  			SPDXExpression: expression,
    84  			Type:           license.Declared,
    85  		})
    86  	}
    87  	return ml
    88  }
    89  
    90  func (f *licenses) UnmarshalJSON(b []byte) error {
    91  	var lics []License
    92  	if err := json.Unmarshal(b, &lics); err != nil {
    93  		var simpleLicense []string
    94  		if err := json.Unmarshal(b, &simpleLicense); err != nil {
    95  			return fmt.Errorf("unable to unmarshal license: %w", err)
    96  		}
    97  		lics = newModelLicensesFromValues(simpleLicense)
    98  	}
    99  	*f = lics
   100  	return nil
   101  }
   102  
   103  func sourcedCPESfromSimpleCPEs(simpleCPEs []string) []CPE {
   104  	var result []CPE
   105  	for _, s := range simpleCPEs {
   106  		result = append(result, CPE{
   107  			Value: s,
   108  		})
   109  	}
   110  	return result
   111  }
   112  
   113  func (c *cpes) UnmarshalJSON(b []byte) error {
   114  	var cs []CPE
   115  	if err := json.Unmarshal(b, &cs); err != nil {
   116  		var simpleCPEs []string
   117  		if err := json.Unmarshal(b, &simpleCPEs); err != nil {
   118  			return fmt.Errorf("unable to unmarshal cpes: %w", err)
   119  		}
   120  		cs = sourcedCPESfromSimpleCPEs(simpleCPEs)
   121  	}
   122  	*c = cs
   123  	return nil
   124  }
   125  
   126  // PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
   127  type PackageCustomData struct {
   128  	MetadataType string `json:"metadataType,omitempty"`
   129  	Metadata     any    `json:"metadata,omitempty"`
   130  }
   131  
   132  // packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling.
   133  type packageMetadataUnpacker struct {
   134  	MetadataType string          `json:"metadataType"`
   135  	Metadata     json.RawMessage `json:"metadata"`
   136  }
   137  
   138  func (p *packageMetadataUnpacker) String() string {
   139  	return fmt.Sprintf("metadataType: %s, metadata: %s", p.MetadataType, string(p.Metadata))
   140  }
   141  
   142  // UnmarshalJSON is a custom unmarshaller for handling basic values and values with ambiguous types.
   143  func (p *Package) UnmarshalJSON(b []byte) error {
   144  	var basic PackageBasicData
   145  	if err := json.Unmarshal(b, &basic); err != nil {
   146  		return err
   147  	}
   148  	p.PackageBasicData = basic
   149  
   150  	var unpacker packageMetadataUnpacker
   151  	if err := json.Unmarshal(b, &unpacker); err != nil {
   152  		log.Warnf("failed to unmarshall into packageMetadataUnpacker: %v", err)
   153  		return err
   154  	}
   155  
   156  	err := unpackPkgMetadata(p, unpacker)
   157  	if errors.Is(err, errUnknownMetadataType) {
   158  		log.Warnf("unknown package metadata type=%q for packageID=%q", p.MetadataType, p.ID)
   159  		return nil
   160  	}
   161  
   162  	return err
   163  }
   164  
   165  func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error {
   166  	if unpacker.MetadataType == "" {
   167  		return nil
   168  	}
   169  
   170  	// check for legacy correction cases from schema v11 -> v12
   171  	ty := unpacker.MetadataType
   172  	switch unpacker.MetadataType {
   173  	case "HackageMetadataType":
   174  		for _, l := range p.Locations {
   175  			if strings.HasSuffix(l.RealPath, ".yaml.lock") {
   176  				ty = "haskell-hackage-stack-lock-entry"
   177  				break
   178  			} else if strings.HasSuffix(l.RealPath, ".yaml") {
   179  				ty = "haskell-hackage-stack-entry"
   180  				break
   181  			}
   182  		}
   183  	case "RpmMetadata":
   184  		for _, l := range p.Locations {
   185  			if strings.HasSuffix(l.RealPath, ".rpm") {
   186  				ty = "rpm-archive"
   187  				break
   188  			}
   189  		}
   190  	case "RustCargoPackageMetadata":
   191  		var found bool
   192  		for _, l := range p.Locations {
   193  			if strings.HasSuffix(strings.ToLower(l.RealPath), "cargo.lock") {
   194  				ty = "rust-cargo-lock-entry"
   195  				found = true
   196  				break
   197  			}
   198  		}
   199  		if !found {
   200  			ty = "rust-cargo-audit-entry"
   201  		}
   202  	}
   203  
   204  	typ := packagemetadata.ReflectTypeFromJSONName(ty)
   205  	if typ == nil {
   206  		// capture unknown metadata as a generic struct
   207  		if len(unpacker.Metadata) > 0 {
   208  			var val interface{}
   209  			if err := json.Unmarshal(unpacker.Metadata, &val); err != nil {
   210  				return err
   211  			}
   212  			p.Metadata = val
   213  		}
   214  
   215  		return errUnknownMetadataType
   216  	}
   217  
   218  	val := reflect.New(typ).Interface()
   219  	if len(unpacker.Metadata) > 0 {
   220  		if err := json.Unmarshal(unpacker.Metadata, val); err != nil {
   221  			return err
   222  		}
   223  	}
   224  	p.Metadata = reflect.ValueOf(val).Elem().Interface()
   225  	p.MetadataType = ty
   226  	return nil
   227  }