github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/formats/syftjson/model/package.go (about)

     1  package model
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  
     9  	"github.com/anchore/syft/internal/log"
    10  	"github.com/anchore/syft/syft/file"
    11  	"github.com/anchore/syft/syft/license"
    12  	"github.com/anchore/syft/syft/pkg"
    13  )
    14  
    15  var errUnknownMetadataType = errors.New("unknown metadata type")
    16  
    17  // Package represents a pkg.Package object specialized for JSON marshaling and unmarshalling.
    18  type Package struct {
    19  	PackageBasicData
    20  	PackageCustomData
    21  }
    22  
    23  // PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
    24  type PackageBasicData struct {
    25  	ID        string          `json:"id"`
    26  	Name      string          `json:"name"`
    27  	Version   string          `json:"version"`
    28  	Type      pkg.Type        `json:"type"`
    29  	FoundBy   string          `json:"foundBy"`
    30  	Locations []file.Location `json:"locations"`
    31  	Licenses  licenses        `json:"licenses"`
    32  	Language  pkg.Language    `json:"language"`
    33  	CPEs      []string        `json:"cpes"`
    34  	PURL      string          `json:"purl"`
    35  }
    36  
    37  type licenses []License
    38  
    39  type License struct {
    40  	Value          string          `json:"value"`
    41  	SPDXExpression string          `json:"spdxExpression"`
    42  	Type           license.Type    `json:"type"`
    43  	URLs           []string        `json:"urls"`
    44  	Locations      []file.Location `json:"locations"`
    45  }
    46  
    47  func newModelLicensesFromValues(licenses []string) (ml []License) {
    48  	for _, v := range licenses {
    49  		expression, err := license.ParseExpression(v)
    50  		if err != nil {
    51  			log.Trace("could not find valid spdx expression for %s: %w", v, err)
    52  		}
    53  		ml = append(ml, License{
    54  			Value:          v,
    55  			SPDXExpression: expression,
    56  			Type:           license.Declared,
    57  		})
    58  	}
    59  	return ml
    60  }
    61  
    62  func (f *licenses) UnmarshalJSON(b []byte) error {
    63  	var licenses []License
    64  	if err := json.Unmarshal(b, &licenses); err != nil {
    65  		var simpleLicense []string
    66  		if err := json.Unmarshal(b, &simpleLicense); err != nil {
    67  			return fmt.Errorf("unable to unmarshal license: %w", err)
    68  		}
    69  		licenses = newModelLicensesFromValues(simpleLicense)
    70  	}
    71  	*f = licenses
    72  	return nil
    73  }
    74  
    75  // PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
    76  type PackageCustomData struct {
    77  	MetadataType pkg.MetadataType `json:"metadataType,omitempty"`
    78  	Metadata     interface{}      `json:"metadata,omitempty"`
    79  }
    80  
    81  // packageMetadataUnpacker is all values needed from Package to disambiguate ambiguous fields during json unmarshaling.
    82  type packageMetadataUnpacker struct {
    83  	MetadataType pkg.MetadataType `json:"metadataType"`
    84  	Metadata     json.RawMessage  `json:"metadata"`
    85  }
    86  
    87  func (p *packageMetadataUnpacker) String() string {
    88  	return fmt.Sprintf("metadataType: %s, metadata: %s", p.MetadataType, string(p.Metadata))
    89  }
    90  
    91  // UnmarshalJSON is a custom unmarshaller for handling basic values and values with ambiguous types.
    92  func (p *Package) UnmarshalJSON(b []byte) error {
    93  	var basic PackageBasicData
    94  	if err := json.Unmarshal(b, &basic); err != nil {
    95  		return err
    96  	}
    97  	p.PackageBasicData = basic
    98  
    99  	var unpacker packageMetadataUnpacker
   100  	if err := json.Unmarshal(b, &unpacker); err != nil {
   101  		log.Warnf("failed to unmarshall into packageMetadataUnpacker: %v", err)
   102  		return err
   103  	}
   104  
   105  	err := unpackPkgMetadata(p, unpacker)
   106  	if errors.Is(err, errUnknownMetadataType) {
   107  		log.Warnf("unknown package metadata type=%q for packageID=%q", p.MetadataType, p.ID)
   108  		return nil
   109  	}
   110  
   111  	return err
   112  }
   113  
   114  func unpackPkgMetadata(p *Package, unpacker packageMetadataUnpacker) error {
   115  	p.MetadataType = pkg.CleanMetadataType(unpacker.MetadataType)
   116  
   117  	typ, ok := pkg.MetadataTypeByName[p.MetadataType]
   118  	if ok {
   119  		val := reflect.New(typ).Interface()
   120  		if len(unpacker.Metadata) > 0 {
   121  			if err := json.Unmarshal(unpacker.Metadata, val); err != nil {
   122  				return err
   123  			}
   124  		}
   125  		p.Metadata = reflect.ValueOf(val).Elem().Interface()
   126  		return nil
   127  	}
   128  
   129  	// capture unknown metadata as a generic struct
   130  	if len(unpacker.Metadata) > 0 {
   131  		var val interface{}
   132  		if err := json.Unmarshal(unpacker.Metadata, &val); err != nil {
   133  			return err
   134  		}
   135  		p.Metadata = val
   136  	}
   137  
   138  	if p.MetadataType != "" {
   139  		return errUnknownMetadataType
   140  	}
   141  
   142  	return nil
   143  }