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

     1  package spdxtagvalue
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  
     9  	"github.com/spdx/tools-golang/tagvalue"
    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 tag-value document")
    36  	}
    37  	if version == "" {
    38  		return nil, "", "", fmt.Errorf("unsupported spdx tag-value 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 Tag-Value SBOM: %+v", err)
    43  	}
    44  
    45  	doc, err := tagvalue.Read(reader)
    46  	if err != nil {
    47  		return nil, id, version, fmt.Errorf("unable to decode spdx tag-value: %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 Tag-Value SBOM: %+v", err)
    64  		return "", ""
    65  	}
    66  
    67  	// Example document
    68  	// SPDXVersion: SPDX-2.3
    69  	// DataLicense: CC0-1.0
    70  	// SPDXID: SPDXRef-DOCUMENT
    71  
    72  	scanner := bufio.NewScanner(reader)
    73  	scanner.Split(bufio.ScanLines)
    74  
    75  	var id sbom.FormatID
    76  	var version string
    77  	for i := 0; scanner.Scan() && i < 3; i++ {
    78  		line := scanner.Text()
    79  		if strings.HasPrefix(line, "SPDXVersion:") {
    80  			id, version = getFormatInfo(line)
    81  			break
    82  		}
    83  	}
    84  
    85  	if version == "" || id != ID {
    86  		// not a spdx tag-value document
    87  		return "", ""
    88  	}
    89  
    90  	return id, version
    91  }
    92  
    93  func getFormatInfo(line string) (sbom.FormatID, string) {
    94  	// example input: SPDXVersion: SPDX-2.3
    95  	fields := strings.SplitN(line, ":", 2)
    96  	if len(fields) != 2 {
    97  		return "", ""
    98  	}
    99  	spdxVersion := fields[1]
   100  	if !strings.HasPrefix(strings.TrimSpace(strings.ToLower(spdxVersion)), "spdx-") {
   101  		return "", ""
   102  	}
   103  	fields = strings.Split(spdxVersion, "-")
   104  	if len(fields) != 2 {
   105  		return ID, ""
   106  	}
   107  
   108  	return ID, fields[1]
   109  }