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 }