github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/format/spdxjson/decoder.go (about) 1 package spdxjson 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "strings" 8 9 spdxJson "github.com/spdx/tools-golang/json" 10 11 "github.com/anchore/syft/internal/log" 12 "github.com/anchore/syft/syft/format/common/spdxhelpers" 13 "github.com/anchore/syft/syft/format/internal/stream" 14 "github.com/anchore/syft/syft/sbom" 15 ) 16 17 var _ sbom.FormatDecoder = (*decoder)(nil) 18 19 type decoder struct { 20 } 21 22 func NewFormatDecoder() sbom.FormatDecoder { 23 return decoder{} 24 } 25 26 func (d decoder) Decode(r io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) { 27 reader, err := stream.SeekableReader(r) 28 if err != nil { 29 return nil, "", "", err 30 } 31 32 // since spdx lib will always return the latest version of the document, we need to identify the version 33 // first and then decode into the appropriate document object. Otherwise if we get the version info from the 34 // decoded object we will always get the latest version (instead of the version we decoded from). 35 id, version := d.Identify(reader) 36 if id != ID { 37 return nil, "", "", fmt.Errorf("not a spdx json document") 38 } 39 if version == "" { 40 return nil, "", "", fmt.Errorf("unsupported spdx json document version") 41 } 42 43 if _, err := reader.Seek(0, io.SeekStart); err != nil { 44 return nil, "", "", fmt.Errorf("unable to seek to start of SPDX JSON SBOM: %+v", err) 45 } 46 47 doc, err := spdxJson.Read(reader) 48 if err != nil { 49 return nil, id, version, fmt.Errorf("unable to decode spdx json: %w", err) 50 } 51 52 s, err := spdxhelpers.ToSyftModel(doc) 53 if err != nil { 54 return nil, id, version, err 55 } 56 return s, id, version, nil 57 } 58 59 func (d decoder) Identify(r io.Reader) (sbom.FormatID, string) { 60 reader, err := stream.SeekableReader(r) 61 if err != nil { 62 return "", "" 63 } 64 65 if _, err := reader.Seek(0, io.SeekStart); err != nil { 66 log.Debugf("unable to seek to start of SPDX JSON SBOM: %+v", err) 67 return "", "" 68 } 69 70 // Example JSON document 71 // { 72 // "spdxVersion": "SPDX-2.3", 73 // ... 74 type Document struct { 75 SPDXVersion string `json:"spdxVersion"` 76 } 77 78 dec := json.NewDecoder(reader) 79 80 var doc Document 81 if err = dec.Decode(&doc); err != nil { 82 // maybe not json? maybe not valid? doesn't matter, we won't process it. 83 return "", "" 84 } 85 86 id, version := getFormatInfo(doc.SPDXVersion) 87 if version == "" || id != ID { 88 // not a spdx json document that we support 89 return "", "" 90 } 91 92 return id, version 93 } 94 95 func getFormatInfo(spdxVersion string) (sbom.FormatID, string) { 96 // example input: SPDX-2.3 97 if !strings.HasPrefix(strings.ToLower(spdxVersion), "spdx-") { 98 return "", "" 99 } 100 fields := strings.Split(spdxVersion, "-") 101 if len(fields) != 2 { 102 return ID, "" 103 } 104 105 return ID, fields[1] 106 }