github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/cyclonedxxml/decoder.go (about) 1 package cyclonedxxml 2 3 import ( 4 "encoding/xml" 5 "fmt" 6 "io" 7 "strings" 8 9 "github.com/CycloneDX/cyclonedx-go" 10 11 "github.com/anchore/syft/syft/format/common/cyclonedxhelpers" 12 "github.com/anchore/syft/syft/sbom" 13 "github.com/lineaje-labs/syft/internal/log" 14 "github.com/lineaje-labs/syft/syft/format/internal/cyclonedxutil" 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.BOMFileFormatXML), 26 } 27 } 28 29 func (d decoder) Decode(reader io.ReadSeeker) (*sbom.SBOM, sbom.FormatID, string, error) { 30 if reader == nil { 31 return nil, "", "", fmt.Errorf("no SBOM bytes provided") 32 } 33 34 id, version := d.Identify(reader) 35 if id != ID { 36 return nil, "", "", fmt.Errorf("not a cyclonedx xml document") 37 } 38 if version == "" { 39 return nil, "", "", fmt.Errorf("unsupported cyclonedx xml document version") 40 } 41 42 doc, err := d.decoder.Decode(reader) 43 if err != nil { 44 return nil, id, version, fmt.Errorf("unable to decode cyclonedx xml document: %w", err) 45 } 46 47 s, err := cyclonedxhelpers.ToSyftModel(doc) 48 if err != nil { 49 return nil, id, version, err 50 } 51 52 return s, id, version, nil 53 } 54 55 func (d decoder) Identify(reader io.ReadSeeker) (sbom.FormatID, string) { 56 if reader == nil { 57 return "", "" 58 } 59 60 if _, err := reader.Seek(0, io.SeekStart); err != nil { 61 log.Debugf("unable to seek to start of CycloneDX XML SBOM: %+v", err) 62 return "", "" 63 } 64 65 type Document struct { 66 XMLNS string `xml:"xmlns,attr"` 67 } 68 69 dec := xml.NewDecoder(reader) 70 71 var doc Document 72 err := dec.Decode(&doc) 73 if err != nil { 74 // maybe not xml? maybe not valid? doesn't matter, we won't process it. 75 return "", "" 76 } 77 78 id, version := getFormatInfo(doc.XMLNS) 79 if version == "" || id != ID { 80 // not a cyclonedx xml document that we support 81 return "", "" 82 } 83 84 return id, version 85 } 86 87 func getFormatInfo(xmlns string) (sbom.FormatID, string) { 88 version := getVersionFromXMLNS(xmlns) 89 90 if !strings.Contains(xmlns, "cyclonedx.org/schema/bom") { 91 // not a cyclonedx xml document 92 return "", "" 93 } 94 95 spec, err := cyclonedxutil.SpecVersionFromString(version) 96 if spec < 0 || err != nil { 97 // not a supported version, but is cyclonedx xml 98 return ID, "" 99 } 100 return ID, version 101 } 102 103 func getVersionFromXMLNS(xmlns string) string { 104 fields := strings.Split(xmlns, "/") 105 return fields[len(fields)-1] 106 }