github.com/anchore/syft@v1.38.2/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 _, err = reader.Seek(0, io.SeekStart) 45 if err != nil { 46 return nil, id, version, fmt.Errorf("unable to seek to start of CycloneDX XML SBOM: %w", err) 47 } 48 doc, err := d.decoder.Decode(reader) 49 if err != nil { 50 return nil, id, version, fmt.Errorf("unable to decode cyclonedx xml document: %w", err) 51 } 52 53 s, err := helpers.ToSyftModel(doc) 54 if err != nil { 55 return nil, id, version, err 56 } 57 58 return s, id, version, nil 59 } 60 61 func (d decoder) Identify(r io.Reader) (sbom.FormatID, string) { 62 reader, err := stream.SeekableReader(r) 63 if err != nil { 64 return "", "" 65 } 66 67 if _, err := reader.Seek(0, io.SeekStart); err != nil { 68 log.Debugf("unable to seek to start of CycloneDX XML SBOM: %+v", err) 69 return "", "" 70 } 71 72 type Document struct { 73 XMLNS string `xml:"xmlns,attr"` 74 } 75 76 dec := xml.NewDecoder(reader) 77 78 var doc Document 79 if err = dec.Decode(&doc); err != nil { 80 // maybe not xml? maybe not valid? doesn't matter, we won't process it. 81 return "", "" 82 } 83 84 id, version := getFormatInfo(doc.XMLNS) 85 if version == "" || id != ID { 86 // not a cyclonedx xml document that we support 87 return "", "" 88 } 89 90 return id, version 91 } 92 93 func getFormatInfo(xmlns string) (sbom.FormatID, string) { 94 version := getVersionFromXMLNS(xmlns) 95 96 if !strings.Contains(xmlns, "cyclonedx.org/schema/bom") { 97 // not a cyclonedx xml document 98 return "", "" 99 } 100 101 spec, err := cyclonedxutil.SpecVersionFromString(version) 102 if spec < 0 || err != nil { 103 // not a supported version, but is cyclonedx xml 104 return ID, "" 105 } 106 return ID, version 107 } 108 109 func getVersionFromXMLNS(xmlns string) string { 110 fields := strings.Split(xmlns, "/") 111 return fields[len(fields)-1] 112 }