From 1bec1fc5d397d7f31db29378158f55da71c56747 Mon Sep 17 00:00:00 2001 From: Russell Haering Date: Thu, 16 May 2024 12:17:11 -0700 Subject: [PATCH] fix: DecoderCollection discarding input from non-seekable Readers (#2878) Signed-off-by: Russell Haering --- syft/format/decoders_collection.go | 23 +++++++++++++++++++---- syft/format/decoders_collection_test.go | 13 +++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/syft/format/decoders_collection.go b/syft/format/decoders_collection.go index 16227510b..5a0e39a6b 100644 --- a/syft/format/decoders_collection.go +++ b/syft/format/decoders_collection.go @@ -4,6 +4,8 @@ import ( "fmt" "io" + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/format/internal/stream" "github.com/anchore/syft/syft/sbom" ) @@ -20,10 +22,16 @@ func NewDecoderCollection(decoders ...sbom.FormatDecoder) sbom.FormatDecoder { } // 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 { +func (c *DecoderCollection) Decode(r io.Reader) (*sbom.SBOM, sbom.FormatID, string, error) { + if r == nil { return nil, "", "", fmt.Errorf("no SBOM bytes provided") } + + reader, err := stream.SeekableReader(r) + if err != nil { + return nil, "", "", fmt.Errorf("unable to create a seekable reader: %w", err) + } + var bestID sbom.FormatID for _, d := range c.decoders { id, version := d.Identify(reader) @@ -45,10 +53,17 @@ func (c *DecoderCollection) Decode(reader io.Reader) (*sbom.SBOM, sbom.FormatID, } // 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 { +func (c *DecoderCollection) Identify(r io.Reader) (sbom.FormatID, string) { + if r == nil { return "", "" } + + reader, err := stream.SeekableReader(r) + if err != nil { + log.Debugf("unable to create a seekable reader: %v", err) + return "", "" + } + for _, d := range c.decoders { id, version := d.Identify(reader) if id != "" && version != "" { diff --git a/syft/format/decoders_collection_test.go b/syft/format/decoders_collection_test.go index 9064bb3b2..3db3dd897 100644 --- a/syft/format/decoders_collection_test.go +++ b/syft/format/decoders_collection_test.go @@ -2,12 +2,14 @@ package format import ( "fmt" + "io" "os" "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/anchore/syft/syft/format/spdxjson" "github.com/anchore/syft/syft/format/syftjson" "github.com/anchore/syft/syft/sbom" ) @@ -37,6 +39,17 @@ func TestIdentify(t *testing.T) { } } +func TestDecodeUnseekable(t *testing.T) { + reader, err := os.Open("spdxjson/test-fixtures/spdx/example7-go-module.spdx.json") + assert.NoError(t, err) + + // io.NopCloser wraps the reader in a non-seekable type + unseekableReader := io.NopCloser(reader) + _, formatID, _, err := Decode(unseekableReader) + assert.NoError(t, err) + assert.Equal(t, spdxjson.ID, formatID) +} + func TestFormats_EmptyInput(t *testing.T) { for _, format := range Decoders() { name := strings.Split(fmt.Sprintf("%#v", format), "{")[0]