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)
}