mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
134 lines
3.1 KiB
Go
134 lines
3.1 KiB
Go
package cyclonedxjson
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/CycloneDX/cyclonedx-go"
|
|
|
|
"github.com/anchore/syft/internal/log"
|
|
"github.com/anchore/syft/syft/format/internal/cyclonedxutil"
|
|
"github.com/anchore/syft/syft/format/internal/cyclonedxutil/helpers"
|
|
"github.com/anchore/syft/syft/format/internal/stream"
|
|
"github.com/anchore/syft/syft/sbom"
|
|
)
|
|
|
|
var _ sbom.FormatDecoder = (*decoder)(nil)
|
|
|
|
type decoder struct {
|
|
decoder cyclonedxutil.Decoder
|
|
}
|
|
|
|
func NewFormatDecoder() sbom.FormatDecoder {
|
|
return decoder{
|
|
decoder: cyclonedxutil.NewDecoder(cyclonedx.BOMFileFormatJSON),
|
|
}
|
|
}
|
|
|
|
func (d decoder) Decode(r io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) {
|
|
reader, err := stream.SeekableReader(r)
|
|
if err != nil {
|
|
return nil, "", "", err
|
|
}
|
|
|
|
id, version := d.Identify(reader)
|
|
if id != ID {
|
|
return nil, "", "", fmt.Errorf("not a cyclonedx json document")
|
|
}
|
|
if version == "" {
|
|
return nil, "", "", fmt.Errorf("unsupported cyclonedx json document version")
|
|
}
|
|
|
|
_, err = reader.Seek(0, io.SeekStart)
|
|
if err != nil {
|
|
return nil, id, version, fmt.Errorf("unable to seek to start of CycloneDX JSON SBOM: %+v", err)
|
|
}
|
|
doc, err := d.decoder.Decode(reader)
|
|
if err != nil {
|
|
return nil, id, version, fmt.Errorf("unable to decode cyclonedx json document: %w", err)
|
|
}
|
|
|
|
s, err := helpers.ToSyftModel(doc)
|
|
if err != nil {
|
|
return nil, id, version, err
|
|
}
|
|
|
|
return s, id, version, nil
|
|
}
|
|
|
|
func (d decoder) Identify(r io.Reader) (sbom.FormatID, string) {
|
|
reader, err := stream.SeekableReader(r)
|
|
if err != nil {
|
|
return "", ""
|
|
}
|
|
|
|
if _, err := reader.Seek(0, io.SeekStart); err != nil {
|
|
log.Debugf("unable to seek to start of CycloneDX JSON SBOM: %+v", err)
|
|
return "", ""
|
|
}
|
|
|
|
type Document struct {
|
|
JSONSchema string `json:"$schema"`
|
|
BOMFormat string `json:"bomFormat"`
|
|
SpecVersion string `json:"specVersion"`
|
|
}
|
|
|
|
dec := json.NewDecoder(reader)
|
|
|
|
var doc Document
|
|
if err = dec.Decode(&doc); err != nil {
|
|
// maybe not json? maybe not valid? doesn't matter, we won't process it.
|
|
return "", ""
|
|
}
|
|
|
|
id, version := getFormatInfo(doc.BOMFormat, doc.SpecVersion)
|
|
if version == "" || id != ID {
|
|
// not a cyclonedx json document that we support
|
|
return "", ""
|
|
}
|
|
|
|
return id, version
|
|
}
|
|
|
|
func getFormatInfo(bomFormat string, specVersion any) (sbom.FormatID, string) {
|
|
if bomFormat != "CycloneDX" {
|
|
// not a cyclonedx json document
|
|
return "", ""
|
|
}
|
|
|
|
// by this point, it looks to be cyclonedx json, but we need to know the version
|
|
|
|
var (
|
|
version string
|
|
spec cyclonedx.SpecVersion
|
|
err error
|
|
)
|
|
switch s := specVersion.(type) {
|
|
case string:
|
|
version = s
|
|
spec, err = cyclonedxutil.SpecVersionFromString(version)
|
|
if err != nil {
|
|
// not a supported version, but is cyclonedx json
|
|
return ID, ""
|
|
}
|
|
case cyclonedx.SpecVersion:
|
|
spec = s
|
|
version = cyclonedxutil.VersionFromSpecVersion(spec)
|
|
if version == "" {
|
|
// not a supported version, but is cyclonedx json
|
|
return ID, ""
|
|
}
|
|
default:
|
|
// bad input provided for version info
|
|
return ID, ""
|
|
}
|
|
|
|
if spec < 0 {
|
|
// not a supported version, but is cyclonedx json
|
|
return ID, ""
|
|
}
|
|
|
|
return ID, version
|
|
}
|