github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/format/cyclonedxjson/decoder.go (about) 1 package cyclonedxjson 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 8 "github.com/CycloneDX/cyclonedx-go" 9 10 "github.com/anchore/syft/internal/log" 11 "github.com/anchore/syft/syft/format/internal/cyclonedxutil" 12 "github.com/anchore/syft/syft/format/internal/cyclonedxutil/helpers" 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 decoder cyclonedxutil.Decoder 21 } 22 23 func NewFormatDecoder() sbom.FormatDecoder { 24 return decoder{ 25 decoder: cyclonedxutil.NewDecoder(cyclonedx.BOMFileFormatJSON), 26 } 27 } 28 29 func (d decoder) Decode(r io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) { 30 reader, err := stream.SeekableReader(r) 31 if err != nil { 32 return nil, "", "", err 33 } 34 35 id, version := d.Identify(reader) 36 if id != ID { 37 return nil, "", "", fmt.Errorf("not a cyclonedx json document") 38 } 39 if version == "" { 40 return nil, "", "", fmt.Errorf("unsupported cyclonedx json document version") 41 } 42 43 doc, err := d.decoder.Decode(reader) 44 if err != nil { 45 return nil, id, version, fmt.Errorf("unable to decode cyclonedx json document: %w", err) 46 } 47 48 s, err := helpers.ToSyftModel(doc) 49 if err != nil { 50 return nil, id, version, err 51 } 52 53 return s, id, version, nil 54 } 55 56 func (d decoder) Identify(r io.Reader) (sbom.FormatID, string) { 57 reader, err := stream.SeekableReader(r) 58 if err != nil { 59 return "", "" 60 } 61 62 if _, err := reader.Seek(0, io.SeekStart); err != nil { 63 log.Debugf("unable to seek to start of CycloneDX JSON SBOM: %+v", err) 64 return "", "" 65 } 66 67 type Document struct { 68 JSONSchema string `json:"$schema"` 69 BOMFormat string `json:"bomFormat"` 70 SpecVersion string `json:"specVersion"` 71 } 72 73 dec := json.NewDecoder(reader) 74 75 var doc Document 76 if err = dec.Decode(&doc); err != nil { 77 // maybe not json? maybe not valid? doesn't matter, we won't process it. 78 return "", "" 79 } 80 81 id, version := getFormatInfo(doc.BOMFormat, doc.SpecVersion) 82 if version == "" || id != ID { 83 // not a cyclonedx json document that we support 84 return "", "" 85 } 86 87 return id, version 88 } 89 90 func getFormatInfo(bomFormat string, specVersion any) (sbom.FormatID, string) { 91 if bomFormat != "CycloneDX" { 92 // not a cyclonedx json document 93 return "", "" 94 } 95 96 // by this point, it looks to be cyclonedx json, but we need to know the version 97 98 var ( 99 version string 100 spec cyclonedx.SpecVersion 101 err error 102 ) 103 switch s := specVersion.(type) { 104 case string: 105 version = s 106 spec, err = cyclonedxutil.SpecVersionFromString(version) 107 if err != nil { 108 // not a supported version, but is cyclonedx json 109 return ID, "" 110 } 111 case cyclonedx.SpecVersion: 112 spec = s 113 version = cyclonedxutil.VersionFromSpecVersion(spec) 114 if version == "" { 115 // not a supported version, but is cyclonedx json 116 return ID, "" 117 } 118 default: 119 // bad input provided for version info 120 return ID, "" 121 } 122 123 if spec < 0 { 124 // not a supported version, but is cyclonedx json 125 return ID, "" 126 } 127 128 return ID, version 129 }