github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/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/internal/log" 12 "github.com/anchore/syft/syft/format/internal/cyclonedxutil" 13 "github.com/anchore/syft/syft/format/internal/cyclonedxutil/helpers" 14 "github.com/anchore/syft/syft/format/internal/stream" 15 "github.com/anchore/syft/syft/sbom" 16 ) 17 18 var _ sbom.FormatDecoder = (*decoder)(nil) 19 20 type decoder struct { 21 decoder cyclonedxutil.Decoder 22 } 23 24 func NewFormatDecoder() sbom.FormatDecoder { 25 return decoder{ 26 decoder: cyclonedxutil.NewDecoder(cyclonedx.BOMFileFormatXML), 27 } 28 } 29 30 func (d decoder) Decode(r io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) { 31 reader, err := stream.SeekableReader(r) 32 if err != nil { 33 return nil, "", "", err 34 } 35 36 id, version := d.Identify(reader) 37 if id != ID { 38 return nil, "", "", fmt.Errorf("not a cyclonedx xml document") 39 } 40 if version == "" { 41 return nil, "", "", fmt.Errorf("unsupported cyclonedx xml document version") 42 } 43 44 doc, err := d.decoder.Decode(reader) 45 if err != nil { 46 return nil, id, version, fmt.Errorf("unable to decode cyclonedx xml document: %w", err) 47 } 48 49 s, err := helpers.ToSyftModel(doc) 50 if err != nil { 51 return nil, id, version, err 52 } 53 54 return s, id, version, nil 55 } 56 57 func (d decoder) Identify(r io.Reader) (sbom.FormatID, string) { 58 reader, err := stream.SeekableReader(r) 59 if err != nil { 60 return "", "" 61 } 62 63 if _, err := reader.Seek(0, io.SeekStart); err != nil { 64 log.Debugf("unable to seek to start of CycloneDX XML SBOM: %+v", err) 65 return "", "" 66 } 67 68 type Document struct { 69 XMLNS string `xml:"xmlns,attr"` 70 } 71 72 dec := xml.NewDecoder(reader) 73 74 var doc Document 75 if err = dec.Decode(&doc); err != nil { 76 // maybe not xml? maybe not valid? doesn't matter, we won't process it. 77 return "", "" 78 } 79 80 id, version := getFormatInfo(doc.XMLNS) 81 if version == "" || id != ID { 82 // not a cyclonedx xml document that we support 83 return "", "" 84 } 85 86 return id, version 87 } 88 89 func getFormatInfo(xmlns string) (sbom.FormatID, string) { 90 version := getVersionFromXMLNS(xmlns) 91 92 if !strings.Contains(xmlns, "cyclonedx.org/schema/bom") { 93 // not a cyclonedx xml document 94 return "", "" 95 } 96 97 spec, err := cyclonedxutil.SpecVersionFromString(version) 98 if spec < 0 || err != nil { 99 // not a supported version, but is cyclonedx xml 100 return ID, "" 101 } 102 return ID, version 103 } 104 105 func getVersionFromXMLNS(xmlns string) string { 106 fields := strings.Split(xmlns, "/") 107 return fields[len(fields)-1] 108 }