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 }