github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/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/syft/file"
    11  	"github.com/anchore/syft/syft/license"
    12  	"github.com/anchore/syft/syft/pkg"
    13  	"github.com/lineaje-labs/syft/internal/log"
    14  	"github.com/lineaje-labs/syft/syft/internal/packagemetadata"
    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      []string        `json:"cpes"`
    36  	PURL      string          `json:"purl"`
    37  }
    38  
    39  type licenses []License
    40  
    41  type License struct {
    42  	Value          string          `json:"value"`
    43  	SPDXExpression string          `json:"spdxExpression"`
    44  	Type           license.Type    `json:"type"`
    45  	URLs           []string        `json:"urls"`
    46  	Locations      []file.Location `json:"locations"`
    47  }
    48  
    49  func newModelLicensesFromValues(licenses []string) (ml []License) {
    50  	for _, v := range licenses {
    51  		expression, err := license.ParseExpression(v)
    52  		if err != nil {
    53  			log.Trace("could not find valid spdx expression for %s: %w", v, err)
    54  		}
    55  		ml = append(ml, License{
    56  			Value:          v,
    57  			SPDXExpression: expression,
    58  			Type:           license.Declared,
    59  		})
    60  	}
    61  	return ml
    62  }
    63  
    64  func (f *licenses) UnmarshalJSON(b []byte) error {
    65  	var lics []License
    66  	if err := json.Unmarshal(b, &lics); err != nil {
    67  		var simpleLicense []string
    68  		if err := json.Unmarshal(b, &simpleLicense); err != nil {
    69  			return fmt.Errorf("unable to unmarshal license: %w", err)
    70  		}
    71  		lics = newModelLicensesFromValues(simpleLicense)
    72  	}
    73  	*f = lics
    74  	return nil
    75  }
    76  
    77  // PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
    78  type PackageCustomData struct {
    79  	MetadataType string `json:"metadataType,omitempty"`
    80  	Metadata     any    `json:"metadata,omitempty"`
    81  }
    82  
    83  // packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling.
    84  type packageMetadataUnpacker struct {
    85  	MetadataType string          `json:"metadataType"`
    86  	Metadata     json.RawMessage `json:"metadata"`
    87  }
    88  
    89  func (p *packageMetadataUnpacker) String() string {
    90  	return fmt.Sprintf("metadataType: %s, metadata: %s", p.MetadataType, string(p.Metadata))
    91  }
    92  
    93  // UnmarshalJSON is a custom unmarshaller for handling basic values and values with ambiguous types.
    94  func (p *Package) UnmarshalJSON(b []byte) error {
    95  	var basic PackageBasicData
    96  	if err := json.Unmarshal(b, &basic); err != nil {
    97  		return err
    98  	}
    99  	p.PackageBasicData = basic
   100  
   101  	var unpacker packageMetadataUnpacker
   102  	if err := json.Unmarshal(b, &unpacker); err != nil {
   103  		log.Warnf("failed to unmarshall into packageMetadataUnpacker: %v", err)
   104  		return err
   105  	}
   106  
   107  	err := unpackPkgMetadata(p, unpacker)
   108  	if errors.Is(err, errUnknownMetadataType) {
   109  		log.Warnf("unknown package metadata type=%q for packageID=%q", p.MetadataType, p.ID)
   110  		return nil
   111  	}
   112  
   113  	return err
   114  }
   115  
   116  func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error {
   117  	if unpacker.MetadataType == "" {
   118  		return nil
   119  	}
   120  
   121  	// check for legacy correction cases from schema v11 -> v12
   122  	ty := unpacker.MetadataType
   123  	switch unpacker.MetadataType {
   124  	case "HackageMetadataType":
   125  		for _, l := range p.Locations {
   126  			if strings.HasSuffix(l.RealPath, ".yaml.lock") {
   127  				ty = "haskell-hackage-stack-lock-entry"
   128  				break
   129  			} else if strings.HasSuffix(l.RealPath, ".yaml") {
   130  				ty = "haskell-hackage-stack-entry"
   131  				break
   132  			}
   133  		}
   134  	case "RpmMetadata":
   135  		for _, l := range p.Locations {
   136  			if strings.HasSuffix(l.RealPath, ".rpm") {
   137  				ty = "rpm-archive"
   138  				break
   139  			}
   140  		}
   141  	case "RustCargoPackageMetadata":
   142  		var found bool
   143  		for _, l := range p.Locations {
   144  			if strings.HasSuffix(strings.ToLower(l.RealPath), "cargo.lock") {
   145  				ty = "rust-cargo-lock-entry"
   146  				found = true
   147  				break
   148  			}
   149  		}
   150  		if !found {
   151  			ty = "rust-cargo-audit-entry"
   152  		}
   153  	}
   154  
   155  	typ := packagemetadata.ReflectTypeFromJSONName(ty)
   156  	if typ == nil {
   157  		// capture unknown metadata as a generic struct
   158  		if len(unpacker.Metadata) > 0 {
   159  			var val interface{}
   160  			if err := json.Unmarshal(unpacker.Metadata, &val); err != nil {
   161  				return err
   162  			}
   163  			p.Metadata = val
   164  		}
   165  
   166  		return errUnknownMetadataType
   167  	}
   168  
   169  	val := reflect.New(typ).Interface()
   170  	if len(unpacker.Metadata) > 0 {
   171  		if err := json.Unmarshal(unpacker.Metadata, val); err != nil {
   172  			return err
   173  		}
   174  	}
   175  	p.Metadata = reflect.ValueOf(val).Elem().Interface()
   176  	return nil
   177  }