github.com/anchore/syft@v1.38.2/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 _, err = reader.Seek(0, io.SeekStart) 44 if err != nil { 45 return nil, id, version, fmt.Errorf("unable to seek to start of CycloneDX JSON SBOM: %+v", err) 46 } 47 doc, err := d.decoder.Decode(reader) 48 if err != nil { 49 return nil, id, version, fmt.Errorf("unable to decode cyclonedx json document: %w", err) 50 } 51 52 s, err := helpers.ToSyftModel(doc) 53 if err != nil { 54 return nil, id, version, err 55 } 56 57 return s, id, version, nil 58 } 59 60 func (d decoder) Identify(r io.Reader) (sbom.FormatID, string) { 61 reader, err := stream.SeekableReader(r) 62 if err != nil { 63 return "", "" 64 } 65 66 if _, err := reader.Seek(0, io.SeekStart); err != nil { 67 log.Debugf("unable to seek to start of CycloneDX JSON SBOM: %+v", err) 68 return "", "" 69 } 70 71 type Document struct { 72 JSONSchema string `json:"$schema"` 73 BOMFormat string `json:"bomFormat"` 74 SpecVersion string `json:"specVersion"` 75 } 76 77 dec := json.NewDecoder(reader) 78 79 var doc Document 80 if err = dec.Decode(&doc); err != nil { 81 // maybe not json? maybe not valid? doesn't matter, we won't process it. 82 return "", "" 83 } 84 85 id, version := getFormatInfo(doc.BOMFormat, doc.SpecVersion) 86 if version == "" || id != ID { 87 // not a cyclonedx json document that we support 88 return "", "" 89 } 90 91 return id, version 92 } 93 94 func getFormatInfo(bomFormat string, specVersion any) (sbom.FormatID, string) { 95 if bomFormat != "CycloneDX" { 96 // not a cyclonedx json document 97 return "", "" 98 } 99 100 // by this point, it looks to be cyclonedx json, but we need to know the version 101 102 var ( 103 version string 104 spec cyclonedx.SpecVersion 105 err error 106 ) 107 switch s := specVersion.(type) { 108 case string: 109 version = s 110 spec, err = cyclonedxutil.SpecVersionFromString(version) 111 if err != nil { 112 // not a supported version, but is cyclonedx json 113 return ID, "" 114 } 115 case cyclonedx.SpecVersion: 116 spec = s 117 version = cyclonedxutil.VersionFromSpecVersion(spec) 118 if version == "" { 119 // not a supported version, but is cyclonedx json 120 return ID, "" 121 } 122 default: 123 // bad input provided for version info 124 return ID, "" 125 } 126 127 if spec < 0 { 128 // not a supported version, but is cyclonedx json 129 return ID, "" 130 } 131 132 return ID, version 133 }