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