Syft源码阅读4-14

syft/sbom

路径:D:\毕业设计\Project\syft-1.0.1\syft\sbom

syft/sbom/format.go

package sbom

import (
	"io"
)

type FormatID string

// String 方法可以将 FormatID 转换为普通字符串。
// String returns a string representation of the FormatID.
func (f FormatID) String() string {
	return string(f)
}

const AnyVersion = ""

// * `ID() FormatID`: 此方法应返回编码器的格式标识符 (`FormatID`)。
// * `Aliases() []string`: 此方法应返回格式的别名列表(可选)。
// * `Version() string`: 此方法应返回编码器支持的格式版本(可选)。
// * `Encode(io.Writer, SBOM) error`: 此方法接受一个 `io.Writer`(编码后的数据将写入其中)和一个 `SBOM` 对象(要编码的数据),并将 SBOM 数据编码为特定格式。
type FormatEncoder interface {
	ID() FormatID
	Aliases() []string
	Version() string
	Encode(io.Writer, SBOM) error
}

// * `Decode(io.Reader) (*SBOM, FormatID, string, error)`: 此方法接受一个 `io.Reader`(包含 SBOM 数据)并尝试对其进行解码。它返回指向 `SBOM` 对象(解码后的数据)的指针,格式标识符 (`FormatID`),格式版本(可选),以及如果解码失败则返回错误。
// * `Identify(io.Reader) (FormatID, string)`: 此方法接受一个 `io.Reader` 并尝试识别 SBOM 数据的格式和版本,而无需完全解码它。它返回格式标识符 (`FormatID`) 和格式版本(可选)。
type FormatDecoder interface {
	// Decode will return an SBOM from the given reader. If the bytes are not a valid SBOM for the given format
	// then an error will be returned.
	Decode(io.Reader) (*SBOM, FormatID, string, error)

	// Identify will return the format ID and version for the given reader. Note: this does not validate the
	// full SBOM, only pulls the minimal information necessary to identify the format.
	Identify(io.Reader) (FormatID, string)
}

syft/sbom/sbom.go

package sbom

import (
	"slices"
	"sort"

	"github.com/anchore/syft/internal/log"
	"github.com/anchore/syft/syft/artifact"
	"github.com/anchore/syft/syft/file"
	"github.com/anchore/syft/syft/linux"
	"github.com/anchore/syft/syft/pkg"
	"github.com/anchore/syft/syft/source"
)

// SBOM:此结构表示 SBOM 数据的核心。它包含以下字段:
// Artifacts:此字段存储有关软件工件的信息,例如包、文件及其属性。
// Relationships:此字段保存工件之间的关系列表,可能描述依赖关系或交互。
// Source:此字段存储有关 SBOM 数据来源的信息。
// Descriptor:此字段包含一个通用描述符,其中包含名称、版本和可选配置信息。
type SBOM struct {
	Artifacts     Artifacts
	Relationships []artifact.Relationship
	Source        source.Description
	Descriptor    Descriptor
}

// Artifacts:此嵌套结构包含有关 SBOM 中软件工件的详细信息。
// Packages:此字段指向包对象的集合(可能包含名称、版本、许可证等详细信息)。
// FileMetadata:此映射存储由坐标(可能是在软件内的路径)标识的单个文件的元数据(例如大小、权限)。
// FileDigests:此映射存储由坐标标识的文件的加密摘要(哈希)。
// FileContents:此映射存储一些由坐标标识的文件的实际内容(可选)。
// FileLicenses:此映射存储与由坐标标识的文件关联的许可证信息。
// Executables:此映射存储有关由坐标标识的可执行文件的信息。
// LinuxDistribution:此字段包含有关 SBOM 数据关联的 Linux 发行版的信息(如果适用)。
type Artifacts struct {
	Packages          *pkg.Collection
	FileMetadata      map[file.Coordinates]file.Metadata
	FileDigests       map[file.Coordinates][]file.Digest
	FileContents      map[file.Coordinates]string
	FileLicenses      map[file.Coordinates][]file.License
	Executables       map[file.Coordinates]file.Executable
	LinuxDistribution *linux.Release
}

// Descriptor:此结构提供了一种通用方式来描述 SBOM 本身。
// Name:此字段存储 SBOM 的名称。
// Version:此字段存储 SBOM 格式的版本。
// Configuration:此字段可以灵活地保存任何特定于 SBOM 的附加配置数据。
type Descriptor struct {
	Name          string
	Version       string
	Configuration interface{}
}

// RelationshipsSorted():此函数根据工件 ID 和关系类型对 SBOM 中的关系进行排序。
func (s SBOM) RelationshipsSorted() []artifact.Relationship {
	relationships := s.Relationships
	sort.SliceStable(relationships, func(i, j int) bool {
		if relationships[i].From.ID() == relationships[j].From.ID() {
			if relationships[i].To.ID() == relationships[j].To.ID() {
				return relationships[i].Type < relationships[j].Type
			}
			return relationships[i].To.ID() < relationships[j].To.ID()
		}
		return relationships[i].From.ID() < relationships[j].From.ID()
	})
	return relationships
}

// 此函数收集 SBOM 数据中跨越多个字段(元数据、摘要、内容等)提到的所有唯一文件坐标。
func (s SBOM) AllCoordinates() []file.Coordinates {
	set := file.NewCoordinateSet()
	for coordinates := range s.Artifacts.FileMetadata {
		set.Add(coordinates)
	}
	for coordinates := range s.Artifacts.FileContents {
		set.Add(coordinates)
	}
	for coordinates := range s.Artifacts.FileDigests {
		set.Add(coordinates)
	}
	for _, relationship := range s.Relationships {
		for _, coordinates := range extractCoordinates(relationship) {
			set.Add(coordinates)
		}
	}
	//根据一定的规则排序
	return set.ToSlice()
}

// 此函数将 SBOM 中的关系筛选为涉及特定包的关系,并可以选择按关系类型进行筛选。
// 输入packega和相关的关系类型,筛选出来对应的关系列表
// RelationshipsForPackage returns all relationships for the provided types.
// If no types are provided, all relationships for the package are returned.
func (s SBOM) RelationshipsForPackage(p pkg.Package, rt ...artifact.RelationshipType) []artifact.Relationship {
	if len(rt) == 0 {
		rt = artifact.AllRelationshipTypes()
	}

	var relationships []artifact.Relationship
	for _, relationship := range s.Relationships {
		if relationship.From == nil || relationship.To == nil {
			log.Debugf("relationship has nil edge, skipping: %#v", relationship)
			continue
		}
		if relationship.From.ID() != p.ID() {
			continue
		}
		// check if the relationship is one we're searching for; rt is inclusive
		if !slices.ContainsFunc(rt, func(r artifact.RelationshipType) bool { return relationship.Type == r }) {
			continue
		}
		relationships = append(relationships, relationship)
	}

	return relationships
}

// 此函数根据特定包在 SBOM 中的关系(可选地按关系类型过滤)检索所有关联的文件坐标
// CoordinatesForPackage returns all coordinates for the provided package for provided relationship types
// If no types are provided, all relationship types are considered.
// rt 是类型为 ...artifact.RelationshipType 的参数。这意味着该函数可以接收零个或多个类型为 artifact.RelationshipType 的参数。
func (s SBOM) CoordinatesForPackage(p pkg.Package, rt ...artifact.RelationshipType) []file.Coordinates {
	var coordinates []file.Coordinates
	for _, relationship := range s.RelationshipsForPackage(p, rt...) {
		cords := extractCoordinates(relationship)
		coordinates = append(coordinates, cords...)
	}
	return coordinates
}

// 此帮助函数从单个关系对象中提取文件坐标。
func extractCoordinates(relationship artifact.Relationship) (results []file.Coordinates) {
	if coordinates, exists := relationship.From.(file.Coordinates); exists {
		results = append(results, coordinates)
	}

	if coordinates, exists := relationship.To.(file.Coordinates); exists {
		results = append(results, coordinates)
	}

	return results
}

syft/sbom/writer.go

package sbom

// Writer an interface to write SBOMs to a destination
type Writer interface {
	Write(SBOM) error
}

统计

syft/format

路径:D:\毕业设计\Project\syft-1.0.1\syft\format

syft/format/decoders.go

package format

import (
	"io"

	"github.com/anchore/syft/syft/format/cyclonedxjson"
	"github.com/anchore/syft/syft/format/cyclonedxxml"
	"github.com/anchore/syft/syft/format/spdxjson"
	"github.com/anchore/syft/syft/format/spdxtagvalue"
	"github.com/anchore/syft/syft/format/syftjson"
	"github.com/anchore/syft/syft/sbom"
)

// 这是一个静态变量,用于存储一个实现了 sbom.FormatDecoder 接口的对象。
var staticDecoders sbom.FormatDecoder

// init 函数会在程序启动时自动执行,它将通过调用 NewDecoderCollection 函数并传入 Decoders 函数返回的解码器集合来初始化 staticDecoders 变量。
func init() {
	staticDecoders = NewDecoderCollection(Decoders()...)
}

// Decoders 函数:此函数返回一个切片,其中包含了各种 SBOM 格式解码器的实例。
// syftjson.NewFormatDecoder():可能用于解码 Syft JSON 格式的 SBOM。
// cyclonedxxml.NewFormatDecoder() 和 cyclonedxjson.NewFormatDecoder():可能分别用于解码 CycloneDX XML 和 JSON 格式的 SBOM。
// spdxtagvalue.NewFormatDecoder() 和 spdxjson.NewFormatDecoder():可能分别用于解码 SPDX tag-value 和 JSON 格式的 SBOM。
func Decoders() []sbom.FormatDecoder {
	return []sbom.FormatDecoder{
		syftjson.NewFormatDecoder(),
		cyclonedxxml.NewFormatDecoder(),
		cyclonedxjson.NewFormatDecoder(),
		spdxtagvalue.NewFormatDecoder(),
		spdxjson.NewFormatDecoder(),
	}
}

// 此函数尝试识别给定读取器 (reader) 中的 SBOM 数据的格式。它将数据传递给内部的 staticDecoders 对象,并调用其 Identify 方法进行格式识别。该方法会返回格式标识符 (sbom.FormatID) 和版本信息 (string)(可选)。
// Identify takes a set of bytes and attempts to identify the format of the SBOM.
func Identify(reader io.Reader) (sbom.FormatID, string) {
	return staticDecoders.Identify(reader)
}

// 此函数尝试解码给定读取器 (reader) 中的 SBOM 数据。它将数据传递给内部的 staticDecoders 对象,并调用其 Decode 方法进行解码。该方法会尝试将数据解码成 sbom.SBOM 对象,并返回解码后的 SBOM 对象、格式标识符 (sbom.FormatID)、版本信息 (string)(可选),以及解码过程中遇到的任何错误。
// Decode takes a set of bytes and attempts to decode it into an SBOM.
func Decode(reader io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) {
	return staticDecoders.Decode(reader)
}

syft/format/decoders_collection.go

package format

import (
	"fmt"
	"io"

	"github.com/anchore/syft/syft/sbom"
)

// 声明了一个空接口类型的变量 _ sbom.FormatDecoder
// 这是一种称为接口匿名的技术,用于确保 DecoderCollection 类型隐式地实现了 sbom.FormatDecoder 接口。
// sbom.FormatDecoder 接口可能定义了用于解码 SBOM 数据的方法(例如 Decode 和 Identify)。
var _ sbom.FormatDecoder = (*DecoderCollection)(nil)

// 该类型表示一个解码器集合,它包含了一个切片 decoders,用于存储各种 SBOM 格式解码器。
type DecoderCollection struct {
	decoders []sbom.FormatDecoder
}

// 此函数用于创建一个新的 DecoderCollection 实例。它接受零个或多个 sbom.FormatDecoder 接口类型的参数,并将它们存储在创建的 DecoderCollection 实例的 decoders 切片中。
func NewDecoderCollection(decoders ...sbom.FormatDecoder) sbom.FormatDecoder {
	return &DecoderCollection{
		decoders: decoders,
	}
}

// 此函数尝试解码给定读取器 (reader) 中的 SBOM 数据。
// 首先检查 reader 是否为空,如果不为空,则遍历 decoders 切片中的每个解码器。
// 对于每个解码器,它调用 d.Identify(reader) 方法尝试识别 SBOM 格式并获取格式标识符 (id) 和版本信息 (version)。
// 如果解码器无法识别格式,则会跳过该解码器并继续下一个。
// 如果找到可以识别的格式,并且解码器同时提供了格式标识符和版本信息,则会直接调用该解码器的 d.Decode(reader) 方法进行解码并返回结果。
// 如果遍历完所有解码器后都没有找到可以识别的格式,但识别到了格式标识符,则会返回错误信息,表明找到格式但版本不受支持。
// 如果遍历完所有解码器后都无法识别格式,则会返回错误信息,表明无法识别 SBOM 格式。
// Decode takes a set of bytes and attempts to decode it into an SBOM relative to the decoders in the collection.
func (c *DecoderCollection) Decode(reader io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) {
	if reader == nil {
		return nil, "", "", fmt.Errorf("no SBOM bytes provided")
	}
	var bestID sbom.FormatID
	for _, d := range c.decoders {
		id, version := d.Identify(reader)
		if id == "" || version == "" {
			if id != "" {
				bestID = id
			}
			continue
		}

		return d.Decode(reader)
	}

	if bestID != "" {
		return nil, bestID, "", fmt.Errorf("sbom format found to be %q but the version is not supported", bestID)
	}

	return nil, "", "", fmt.Errorf("sbom format not recognized")
}

// 此函数尝试识别给定读取器 (reader) 中的 SBOM 数据的格式。
// 类似于 Decode 函数,它会遍历 decoders 切片中的每个解码器,并调用它们的 Identify 方法尝试识别格式。
// 如果找到可以识别的格式,并且解码器同时提供了格式标识符 (id) 和版本信息 (version),则会直接返回这两个值。
// 如果遍历完所有解码器后都无法识别格式,则会返回空字符串。
// Identify takes a set of bytes and attempts to identify the format of the SBOM relative to the decoders in the collection.
func (c *DecoderCollection) Identify(reader io.Reader) (sbom.FormatID, string) {
	if reader == nil {
		return "", ""
	}
	for _, d := range c.decoders {
		id, version := d.Identify(reader)
		if id != "" && version != "" {
			return id, version
		}
	}
	return "", ""
}

syft/format/encoders.go

package format

import (
	"fmt"

	"github.com/hashicorp/go-multierror"

	"github.com/anchore/syft/syft/format/cyclonedxjson"
	"github.com/anchore/syft/syft/format/cyclonedxxml"
	"github.com/anchore/syft/syft/format/github"
	"github.com/anchore/syft/syft/format/spdxjson"
	"github.com/anchore/syft/syft/format/spdxtagvalue"
	"github.com/anchore/syft/syft/format/syftjson"
	"github.com/anchore/syft/syft/format/table"
	"github.com/anchore/syft/syft/format/template"
	"github.com/anchore/syft/syft/format/text"
	"github.com/anchore/syft/syft/sbom"
)

// AllVersions:这是一个常量字符串,代表支持所有版本的 SBOM 格式(例如,CycloneDX JSON/XML、SPDX JSON/tag-value)
const AllVersions = "all-versions"

// 此类型用于配置各种 SBOM 格式的编码器。它包含了多个嵌套的类型,分别用于配置模板、Syft JSON、SPDX JSON/tag-value、CycloneDX JSON/XML 等格式的编码器。
type EncodersConfig struct {
	Template      template.EncoderConfig
	SyftJSON      syftjson.EncoderConfig
	SPDXJSON      spdxjson.EncoderConfig
	SPDXTagValue  spdxtagvalue.EncoderConfig
	CyclonedxJSON cyclonedxjson.EncoderConfig
	CyclonedxXML  cyclonedxxml.EncoderConfig
}

// 此函数返回可用于编码 SBOM 数据的编码器列表。它首先调用 DefaultEncodersConfig 函数获取默认配置,然后调用该配置对象的 Encoders 方法来生成并返回编码器列表。
func Encoders() []sbom.FormatEncoder {
	encs, _ := DefaultEncodersConfig().Encoders()
	return encs
}

// DefaultEncodersConfig 函数:此函数返回一个默认的 EncodersConfig 配置对象。它为每个支持的格式设置了默认的配置,并特别将 SPDXJSON、SPDXTagValue、CyclonedxJSON 和 CyclonedxXML 的版本设置为 AllVersions,表示支持所有版本。
func DefaultEncodersConfig() EncodersConfig {
	cfg := EncodersConfig{
		Template:      template.DefaultEncoderConfig(),
		SyftJSON:      syftjson.DefaultEncoderConfig(),
		SPDXJSON:      spdxjson.DefaultEncoderConfig(),
		SPDXTagValue:  spdxtagvalue.DefaultEncoderConfig(),
		CyclonedxJSON: cyclonedxjson.DefaultEncoderConfig(),
		CyclonedxXML:  cyclonedxxml.DefaultEncoderConfig(),
	}

	// empty value means to support all versions
	cfg.SPDXJSON.Version = AllVersions
	cfg.SPDXTagValue.Version = AllVersions
	cfg.CyclonedxJSON.Version = AllVersions
	cfg.CyclonedxXML.Version = AllVersions

	return cfg
}

// (o EncodersConfig) Encoders() ([]sbom.FormatEncoder, error) 函数:此方法用于根据 EncodersConfig 配置生成编码器列表。它会遍历配置中的各个格式,并根据配置生成相应的编码器。例如,如果配置了模板路径,则会生成模板格式的编码器。
// 该方法使用了嵌套的 encodersList 类型来收集生成的编码器和遇到的错误。
// 对于支持多版本的格式(CycloneDX JSON/XML、SPDX JSON/tag-value),它会根据配置 (AllVersions 或指定版本) 循环创建每个版本的编码器。
// 在生成编码器过程中,如果遇到任何错误,则会使用 github.com/hashicorp/go-multierror 库将错误添加到 encodersList 的 err 字段中。
func (o EncodersConfig) Encoders() ([]sbom.FormatEncoder, error) {
	//encodersList 类型:这是一个辅助类型,用于在生成编码器列表的过程中收集生成的编码器 (encoders) 和遇到的错误 (err)。
	var l encodersList

	if o.Template.TemplatePath != "" {
		l.addWithErr(template.ID)(o.templateEncoders())
	}

	l.addWithErr(syftjson.ID)(o.syftJSONEncoders())
	l.add(table.ID)(table.NewFormatEncoder())
	l.add(text.ID)(text.NewFormatEncoder())
	l.add(github.ID)(github.NewFormatEncoder())
	l.addWithErr(cyclonedxxml.ID)(o.cyclonedxXMLEncoders())
	l.addWithErr(cyclonedxjson.ID)(o.cyclonedxJSONEncoders())
	l.addWithErr(spdxjson.ID)(o.spdxJSONEncoders())
	l.addWithErr(spdxtagvalue.ID)(o.spdxTagValueEncoders())

	return l.encoders, l.err
}

func (o EncodersConfig) templateEncoders() ([]sbom.FormatEncoder, error) {
	enc, err := template.NewFormatEncoder(o.Template)
	return []sbom.FormatEncoder{enc}, err
}

func (o EncodersConfig) syftJSONEncoders() ([]sbom.FormatEncoder, error) {
	enc, err := syftjson.NewFormatEncoderWithConfig(o.SyftJSON)
	return []sbom.FormatEncoder{enc}, err
}

func (o EncodersConfig) cyclonedxXMLEncoders() ([]sbom.FormatEncoder, error) {
	var (
		encs []sbom.FormatEncoder
		errs error
	)

	cfg := o.CyclonedxXML

	var versions []string
	if cfg.Version == AllVersions {
		versions = cyclonedxxml.SupportedVersions()
	} else {
		versions = []string{cfg.Version}
	}

	for _, v := range versions {
		cfg.Version = v
		enc, err := cyclonedxxml.NewFormatEncoderWithConfig(cfg)
		if err != nil {
			errs = multierror.Append(errs, err)
		} else {
			encs = append(encs, enc)
		}
	}
	return encs, errs
}

func (o EncodersConfig) cyclonedxJSONEncoders() ([]sbom.FormatEncoder, error) {
	var (
		encs []sbom.FormatEncoder
		errs error
	)

	cfg := o.CyclonedxJSON

	var versions []string
	if cfg.Version == AllVersions {
		versions = cyclonedxjson.SupportedVersions()
	} else {
		versions = []string{cfg.Version}
	}

	for _, v := range versions {
		cfg.Version = v
		enc, err := cyclonedxjson.NewFormatEncoderWithConfig(cfg)
		if err != nil {
			errs = multierror.Append(errs, err)
		} else {
			encs = append(encs, enc)
		}
	}
	return encs, errs
}

func (o EncodersConfig) spdxJSONEncoders() ([]sbom.FormatEncoder, error) {
	var (
		encs []sbom.FormatEncoder
		errs error
	)

	cfg := o.SPDXJSON

	var versions []string
	if cfg.Version == AllVersions {
		versions = spdxjson.SupportedVersions()
	} else {
		versions = []string{cfg.Version}
	}

	for _, v := range versions {
		cfg.Version = v
		enc, err := spdxjson.NewFormatEncoderWithConfig(cfg)
		if err != nil {
			errs = multierror.Append(errs, err)
		} else {
			encs = append(encs, enc)
		}
	}
	return encs, errs
}

func (o EncodersConfig) spdxTagValueEncoders() ([]sbom.FormatEncoder, error) {
	var (
		encs []sbom.FormatEncoder
		errs error
	)

	cfg := o.SPDXTagValue

	var versions []string
	if cfg.Version == AllVersions {
		versions = spdxtagvalue.SupportedVersions()
	} else {
		versions = []string{cfg.Version}
	}

	for _, v := range versions {
		cfg.Version = v
		enc, err := spdxtagvalue.NewFormatEncoderWithConfig(cfg)
		if err != nil {
			errs = multierror.Append(errs, err)
		} else {
			encs = append(encs, enc)
		}
	}
	return encs, errs
}

type encodersList struct {
	encoders []sbom.FormatEncoder
	err      error
}

func (l *encodersList) addWithErr(name sbom.FormatID) func([]sbom.FormatEncoder, error) {
	return func(encs []sbom.FormatEncoder, err error) {
		if err != nil {
			l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: %w", name, err))
			return
		}
		for _, enc := range encs {
			if enc == nil {
				l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: nil encoder returned", name))
				continue
			}
			l.encoders = append(l.encoders, enc)
		}
	}
}

func (l *encodersList) add(name sbom.FormatID) func(...sbom.FormatEncoder) {
	return func(encs ...sbom.FormatEncoder) {
		for _, enc := range encs {
			if enc == nil {
				l.err = multierror.Append(l.err, fmt.Errorf("unable to configure %q format encoder: nil encoder returned", name))
				continue
			}
			l.encoders = append(l.encoders, enc)
		}
	}
}

syft/format/encoders_collection.go

package format

import (
	"bytes"
	"fmt"
	"regexp"
	"sort"
	"strings"

	"github.com/scylladb/go-set/strset"

	"github.com/anchore/syft/internal/log"
	"github.com/anchore/syft/syft/sbom"
)

// 该类型表示一个 SBOM 编码器集合,它包含了一个切片 encoders 来存储各种格式的 SBOM 编码器
type EncoderCollection struct {
	encoders []sbom.FormatEncoder
}

// 此函数用于创建一个新的 EncoderCollection 实例。它接受零个或多个 sbom.FormatEncoder 接口类型的参数,并将它们存储在创建的 EncoderCollection 实例的 encoders 切片中。
func NewEncoderCollection(encoders ...sbom.FormatEncoder) *EncoderCollection {
	return &EncoderCollection{
		encoders: encoders,
	}
}

// IDs() []sbom.FormatID:此函数返回集合中所有支持的 SBOM 格式标识符 (sbom.FormatID) 列表。
// 它首先使用 strset.New() 创建一个字符串集合 (idSet)。
// 然后遍历 encoders 切片中的每个编码器,并将其格式标识符添加到集合中。
// 最后,它将集合转换为排序后的字符串列表 (idList),并将其转换为 sbom.FormatID 列表返回。
// IDs returns all format IDs represented in the collection.
func (e EncoderCollection) IDs() []sbom.FormatID {
	idSet := strset.New()
	for _, f := range e.encoders {
		idSet.Add(string(f.ID()))
	}

	idList := idSet.List()
	sort.Strings(idList)

	var ids []sbom.FormatID
	for _, id := range idList {
		ids = append(ids, sbom.FormatID(id))
	}

	return ids
}

// NameVersions() []string:此函数返回集合中所有支持的 SBOM 格式的名称和版本信息列表 (格式为 "name@version")。
// 它首先使用 strset.New() 创建一个字符串集合 (set)。
// 然后遍历 encoders 切片中的每个编码器,并根据其版本信息将格式添加到集合中。
// 如果版本为 sbom.AnyVersion (表示支持所有版本),则只添加格式名称。
// 否则,将格式名称和版本信息拼接成 "name@version" 格式并添加到集合中。
// 最后,它将集合转换为排序后的字符串列表并返回。
// NameVersions returns all formats that are supported by the collection as a list of "name@version" strings.
func (e EncoderCollection) NameVersions() []string {
	set := strset.New()
	for _, f := range e.encoders {
		if f.Version() == sbom.AnyVersion {
			set.Add(string(f.ID()))
		} else {
			set.Add(fmt.Sprintf("%s@%s", f.ID(), f.Version()))
		}
	}

	list := set.List()
	sort.Strings(list)

	return list
}

// Aliases() []string:此函数返回集合中所有支持的 SBOM 格式的别名列表。
// 它遍历 encoders 切片中的每个编码器,并将其所有的别名添加到一个 strset.New() 创建的字符串集合 (aliases) 中。
// 最后,将集合转换为排序后的字符串列表并返回。
// Aliases returns all format aliases represented in the collection (where an ID would be "spdx-tag-value" the alias would be "spdx").
func (e EncoderCollection) Aliases() []string {
	aliases := strset.New()
	for _, f := range e.encoders {
		aliases.Add(f.Aliases()...)
	}
	lst := aliases.List()
	sort.Strings(lst)
	return lst
}

// Get(name string, version string) sbom.FormatEncoder:此函数用于根据名称和版本信息获取集合中对应的 SBOM 编码器。
// 它首先对名称进行清理 (小写、去除特殊字符)。
// 然后遍历 encoders 切片中的每个编码器,并检查其名称 (包括别名) 和版本信息是否与给定参数匹配。
// 如果找到匹配的编码器,并且该编码器的版本比之前找到的更高级 (对于支持多版本的格式),则将其更新为最新的匹配编码器。
// 该函数使用日志记录功能 (log) 来跟踪匹配过程。
// 如果找到匹配的编码器,则返回该编码器,否则返回 nil。
// Get returns the contained encoder for a given format name and version.
func (e EncoderCollection) Get(name string, version string) sbom.FormatEncoder {
	log.WithFields("name", name, "version", version).Trace("looking for matching encoder")

	name = cleanFormatName(name)
	var mostRecentFormat sbom.FormatEncoder

	for _, f := range e.encoders {
		log.WithFields("name", f.ID(), "version", f.Version(), "aliases", f.Aliases()).Trace("considering format")
		names := []string{string(f.ID())}
		names = append(names, f.Aliases()...)
		for _, n := range names {
			if cleanFormatName(n) == name && versionMatches(f.Version(), version) {
				if mostRecentFormat == nil || f.Version() > mostRecentFormat.Version() {
					mostRecentFormat = f
				}
			}
		}
	}

	if mostRecentFormat != nil {
		log.WithFields("name", mostRecentFormat.ID(), "version", mostRecentFormat.Version()).Trace("found matching encoder")
	} else {
		log.WithFields("search-name", name, "search-version", version).Trace("no matching encoder found")
	}

	return mostRecentFormat
}

// GetByString accepts a name@version string, such as:
//   - json
//   - spdx-json@2.1
//   - cdx@1.5
//
// 此函数用于根据格式字符串 (例如 "json" 或 "spdx-json@2.1") 获取对应的 SBOM 编码器。
// 它首先将格式字符串按照 "@" 符号分割成名称和版本两部分。
// 然后调用 Get 函数来根据解析出的名称和版本信息获取对应的编码器。
func (e EncoderCollection) GetByString(s string) sbom.FormatEncoder {
	parts := strings.SplitN(s, "@", 2)
	version := sbom.AnyVersion
	if len(parts) > 1 {
		version = parts[1]
	}
	return e.Get(parts[0], version)
}

// 此函数用于检查给定版本号 (version) 是否与匹配字符串 (match) 匹配。
// 它支持通配符 (*) 和版本范围匹配。
// 该函数使用正则表达式来实现匹配功能。
func versionMatches(version string, match string) bool {
	if version == sbom.AnyVersion || match == sbom.AnyVersion {
		return true
	}

	match = strings.ReplaceAll(match, ".", "\\.")
	match = strings.ReplaceAll(match, "*", ".*")
	match = fmt.Sprintf("^%s(\\..*)*$", match)
	matcher, err := regexp.Compile(match)
	if err != nil {
		return false
	}
	return matcher.MatchString(version)
}

// 此函数用于清理格式名称,将其转换为小写并去除特殊字符。
func cleanFormatName(name string) string {
	r := strings.NewReplacer("-", "", "_", "")
	return strings.ToLower(r.Replace(name))
}

// 此函数用于使用指定的 SBOM 编码器 (f) 将 SBOM 数据 (s) 编码成字节数组。
// 它首先创建一个缓冲区 (buff)。
// 然后调用编码器 f.Encode(&buff, s) 方法将 SBOM 数据编码并写入缓冲区。
// 如果编码过程中遇到错误,则会返回错误信息。
// 最后,将缓冲区中的内容转换为字节数组并返回。
// Encode takes all SBOM elements and a format option and encodes an SBOM document.
func Encode(s sbom.SBOM, f sbom.FormatEncoder) ([]byte, error) {
	buff := bytes.Buffer{}

	if err := f.Encode(&buff, s); err != nil {
		return nil, fmt.Errorf("unable to encode sbom: %w", err)
	}

	return buff.Bytes(), nil
}

syft/format/spdxjson

syft/format/spdxjson/decoder.go

package spdxjson

import (
	"encoding/json"
	"fmt"
	"io"
	"strings"

	spdxJson "github.com/spdx/tools-golang/json"

	"github.com/anchore/syft/internal/log"
	"github.com/anchore/syft/syft/format/common/spdxhelpers"
	"github.com/anchore/syft/syft/format/internal/stream"
	"github.com/anchore/syft/syft/sbom"
)

// 这行代码的作用是进行类型断言。它断言 decoder 类型实现了 sbom.FormatDecoder 接口。这是一种常用的方式来确保实现了接口的方法。
var _ sbom.FormatDecoder = (*decoder)(nil)

type decoder struct {
}

// 用于创建一个新的 SPDX JSON 格式解码器实例。
func NewFormatDecoder() sbom.FormatDecoder {
	return decoder{}
}

// 该函数用于从给定的可读流 (io.Reader) 中读取 SPDX JSON 数据,并将其解码成 sbom.SBOM 对象。
// 首先确保流可以定位 (以便重复读取数据)。
// 然后调用 Identify 函数识别 SPDX JSON 文档的格式和版本信息。
// 如果识别到格式为 SPDX JSON,则重新定位流到开头并使用 spdx/tools-golang/json 库读取文档内容。
// 最后,将读取到的 SPDX JSON 文档转换为 syft.SBOM 对象并返回。
func (d decoder) Decode(r io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) {
	reader, err := stream.SeekableReader(r)
	if err != nil {
		return nil, "", "", err
	}
	//这段注释解释了为什么解码器需要在解码之前识别 SPDX JSON 文档的版本。这是因为使用的第三方库 (spdx/tools-golang/json)总是返回最新版本的文档,如果不提前识别版本,就无法解码成对应版本的具体对象。
	// since spdx lib will always return the latest version of the document, we need to identify the version
	// first and then decode into the appropriate document object. Otherwise if we get the version info from the
	// decoded object we will always get the latest version (instead of the version we decoded from).
	id, version := d.Identify(reader)
	if id != ID {
		return nil, "", "", fmt.Errorf("not a spdx json document")
	}
	if version == "" {
		return nil, "", "", fmt.Errorf("unsupported spdx json document version")
	}

	if _, err := reader.Seek(0, io.SeekStart); err != nil {
		return nil, "", "", fmt.Errorf("unable to seek to start of SPDX JSON SBOM: %+v", err)
	}

	doc, err := spdxJson.Read(reader)
	if err != nil {
		return nil, id, version, fmt.Errorf("unable to decode spdx json: %w", err)
	}

	s, err := spdxhelpers.ToSyftModel(doc)
	if err != nil {
		return nil, id, version, err
	}
	return s, id, version, nil
}

func (d decoder) Identify(r io.Reader) (sbom.FormatID, string) {
	reader, err := stream.SeekableReader(r)
	if err != nil {
		return "", ""
	}

	if _, err := reader.Seek(0, io.SeekStart); err != nil {
		log.Debugf("unable to seek to start of SPDX JSON SBOM: %+v", err)
		return "", ""
	}

	// Example JSON document
	// {
	// "spdxVersion": "SPDX-2.3",
	// ...
	type Document struct {
		SPDXVersion string `json:"spdxVersion"`
	}

	dec := json.NewDecoder(reader)

	var doc Document
	if err = dec.Decode(&doc); err != nil {
		//这段注释解释了当无法解码文档头部 (可能不是有效的 JSON 格式或不支持的版本) 时,解码器会跳过该文件。
		// maybe not json? maybe not valid? doesn't matter, we won't process it.
		return "", ""
	}

	id, version := getFormatInfo(doc.SPDXVersion)
	if version == "" || id != ID {
		// not a spdx json document that we support
		return "", ""
	}

	return id, version
}

// 输入SPDX-2.3,返回SPDX,2.3
func getFormatInfo(spdxVersion string) (sbom.FormatID, string) {
	// example input: SPDX-2.3
	if !strings.HasPrefix(strings.ToLower(spdxVersion), "spdx-") {
		return "", ""
	}
	fields := strings.Split(spdxVersion, "-")
	if len(fields) != 2 {
		return ID, ""
	}

	return ID, fields[1]
}

syft/format/spdxjson/encoder.go

package spdxjson

import (
	"encoding/json"
	"fmt"
	"io"

	"github.com/spdx/tools-golang/convert"
	"github.com/spdx/tools-golang/spdx/v2/v2_1"
	"github.com/spdx/tools-golang/spdx/v2/v2_2"
	"github.com/spdx/tools-golang/spdx/v2/v2_3"

	"github.com/anchore/syft/syft/format/common/spdxhelpers"
	"github.com/anchore/syft/syft/format/internal/spdxutil"
	"github.com/anchore/syft/syft/sbom"
)

const ID = spdxutil.JSONFormatID

// 返回该编码器支持的 SPDX JSON 文档版本列表 (字符串数组)。
func SupportedVersions() []string {
	return spdxutil.SupportedVersions(ID)
}

// 用于配置 SPDX JSON 编码器,包含版本 (Version) 和格式化 (Pretty) 选项。
type EncoderConfig struct {
	Version string
	Pretty  bool // don't include spaces and newlines; same as jq -c
}

type encoder struct {
	cfg EncoderConfig
}

// 根据给定的配置 (EncoderConfig) 创建一个新的 SPDX JSON 编码器实例。
func NewFormatEncoderWithConfig(cfg EncoderConfig) (sbom.FormatEncoder, error) {
	return encoder{
		cfg: cfg,
	}, nil
}

func DefaultEncoderConfig() EncoderConfig {
	return EncoderConfig{
		Version: spdxutil.DefaultVersion,
		Pretty:  false,
	}
}

// 返回编码器的格式标识符 (sbom.FormatID),为 spdxutil.JSONFormatID。
func (e encoder) ID() sbom.FormatID {
	return ID
}

// 返回编码器的别名列表 (空列表)。
func (e encoder) Aliases() []string {
	return []string{}
}

// 返回编码器配置的版本信息。
func (e encoder) Version() string {
	return e.cfg.Version
}

// 将 SBOM 数据 (s) 编码成 SPDX JSON 格式并写入指定的输出流 (writer)。
// 首先将 SBOM 数据转换为内部使用的 SPDX 文档模型 (latestDoc)。
// 然后根据编码器配置的版本 (Version),使用相应的 spdx/tools-golang/spdx/v2/vX.X 库将内部文档模型转换为特定的 SPDX JSON 文档格式 (encodeDoc)。
// 最后,使用 encoding/json 库将编码后的 SPDX JSON 文档写入输出流。
func (e encoder) Encode(writer io.Writer, s sbom.SBOM) error {
	latestDoc := spdxhelpers.ToFormatModel(s)
	if latestDoc == nil {
		return fmt.Errorf("unable to convert SBOM to SPDX document")
	}

	var err error
	var encodeDoc any
	switch e.cfg.Version {
	case "2.1":
		doc := v2_1.Document{}
		err = convert.Document(latestDoc, &doc)
		encodeDoc = doc
	case "2.2":
		doc := v2_2.Document{}
		err = convert.Document(latestDoc, &doc)
		encodeDoc = doc

	case "2.3":
		doc := v2_3.Document{}
		err = convert.Document(latestDoc, &doc)
		encodeDoc = doc
	default:
		return fmt.Errorf("unsupported SPDX version %q", e.cfg.Version)
	}

	if err != nil {
		return fmt.Errorf("unable to convert SBOM to SPDX document: %w", err)
	}

	enc := json.NewEncoder(writer)

	enc.SetEscapeHTML(false)

	if e.cfg.Pretty {
		enc.SetIndent("", " ")
	}

	return enc.Encode(encodeDoc)
}

统计

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值