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