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