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