github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/format/spdxjson/decoder.go (about)

     1  package spdxjson
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  
     9  	spdxJson "github.com/spdx/tools-golang/json"
    10  
    11  	"github.com/anchore/syft/syft/format/common/spdxhelpers"
    12  	"github.com/anchore/syft/syft/sbom"
    13  	"github.com/lineaje-labs/syft/internal/log"
    14  )
    15  
    16  var _ sbom.FormatDecoder = (*decoder)(nil)
    17  
    18  type decoder struct {
    19  }
    20  
    21  func NewFormatDecoder() sbom.FormatDecoder {
    22  	return decoder{}
    23  }
    24  
    25  func (d decoder) Decode(reader io.ReadSeeker) (*sbom.SBOM, sbom.FormatID, string, error) {
    26  	if reader == nil {
    27  		return nil, "", "", fmt.Errorf("no SBOM bytes provided")
    28  	}
    29  
    30  	// since spdx lib will always return the latest version of the document, we need to identify the version
    31  	// first and then decode into the appropriate document object. Otherwise if we get the version info from the
    32  	// decoded object we will always get the latest version (instead of the version we decoded from).
    33  	id, version := d.Identify(reader)
    34  	if id != ID {
    35  		return nil, "", "", fmt.Errorf("not a spdx json document")
    36  	}
    37  	if version == "" {
    38  		return nil, "", "", fmt.Errorf("unsupported spdx json document version")
    39  	}
    40  
    41  	if _, err := reader.Seek(0, io.SeekStart); err != nil {
    42  		return nil, "", "", fmt.Errorf("unable to seek to start of SPDX JSON SBOM: %+v", err)
    43  	}
    44  
    45  	doc, err := spdxJson.Read(reader)
    46  	if err != nil {
    47  		return nil, id, version, fmt.Errorf("unable to decode spdx json: %w", err)
    48  	}
    49  
    50  	s, err := spdxhelpers.ToSyftModel(doc)
    51  	if err != nil {
    52  		return nil, id, version, err
    53  	}
    54  	return s, id, version, nil
    55  }
    56  
    57  func (d decoder) Identify(reader io.ReadSeeker) (sbom.FormatID, string) {
    58  	if reader == nil {
    59  		return "", ""
    60  	}
    61  
    62  	if _, err := reader.Seek(0, io.SeekStart); err != nil {
    63  		log.Debugf("unable to seek to start of SPDX JSON SBOM: %+v", err)
    64  		return "", ""
    65  	}
    66  
    67  	// Example JSON document
    68  	// {
    69  	// "spdxVersion": "SPDX-2.3",
    70  	// ...
    71  	type Document struct {
    72  		SPDXVersion string `json:"spdxVersion"`
    73  	}
    74  
    75  	dec := json.NewDecoder(reader)
    76  
    77  	var doc Document
    78  	err := dec.Decode(&doc)
    79  	if err != nil {
    80  		// maybe not json? maybe not valid? doesn't matter, we won't process it.
    81  		return "", ""
    82  	}
    83  
    84  	id, version := getFormatInfo(doc.SPDXVersion)
    85  	if version == "" || id != ID {
    86  		// not a spdx json document that we support
    87  		return "", ""
    88  	}
    89  
    90  	return id, version
    91  }
    92  
    93  func getFormatInfo(spdxVersion string) (sbom.FormatID, string) {
    94  	// example input: SPDX-2.3
    95  	if !strings.HasPrefix(strings.ToLower(spdxVersion), "spdx-") {
    96  		return "", ""
    97  	}
    98  	fields := strings.Split(spdxVersion, "-")
    99  	if len(fields) != 2 {
   100  		return ID, ""
   101  	}
   102  
   103  	return ID, fields[1]
   104  }