github.com/anchore/syft@v1.38.2/syft/format/internal/stream/seekable_reader.go (about)

     1  package stream
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  )
     8  
     9  // SeekableReader takes an io.Reader and returns an io.ReadSeeker relative to the current position of the reader.
    10  // Users of this function expect to be able to reset the reader to the current position, not potentially reset the
    11  // reader prior to the location when this reader is provided. An example is a reader with multiple JSON
    12  // documents separated by newlines (JSONL). After reading the first document, if a call is made to decode
    13  // the second and Seek(0, SeekStart) is called it would reset the overall reader back to the first document.
    14  func SeekableReader(reader io.Reader) (io.ReadSeeker, error) {
    15  	if reader == nil {
    16  		return nil, fmt.Errorf("no bytes provided")
    17  	}
    18  
    19  	if r, ok := reader.(io.ReadSeeker); ok {
    20  		return getOffsetReadSeeker(r)
    21  	}
    22  
    23  	content, err := io.ReadAll(reader)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  
    28  	return bytes.NewReader(content), nil
    29  }
    30  
    31  type offsetReadSeeker struct {
    32  	rdr    io.ReadSeeker
    33  	offset int64
    34  }
    35  
    36  // getOffsetReadSeeker returns a new io.ReadSeeker that may wrap another io.ReadSeeker with the current offset, so
    37  // seek calls will be relative to the _current_ position, rather than relative to the reader itself
    38  func getOffsetReadSeeker(r io.ReadSeeker) (io.ReadSeeker, error) {
    39  	if r == nil {
    40  		return nil, fmt.Errorf("no reader provided")
    41  	}
    42  	pos, err := r.Seek(0, io.SeekCurrent)
    43  	if pos == 0 {
    44  		// if the ReadSeeker is currently at 0, we don't need to track an offset
    45  		return r, nil
    46  	}
    47  	return &offsetReadSeeker{
    48  		rdr:    r,
    49  		offset: pos,
    50  	}, err
    51  }
    52  
    53  func (o *offsetReadSeeker) Read(p []byte) (n int, err error) {
    54  	return o.rdr.Read(p)
    55  }
    56  
    57  func (o *offsetReadSeeker) Seek(offset int64, whence int) (int64, error) {
    58  	switch whence {
    59  	case io.SeekStart:
    60  		if offset < 0 {
    61  			return 0, fmt.Errorf("cannot seek < 0")
    62  		}
    63  		newOffset, err := o.rdr.Seek(o.offset+offset, io.SeekStart)
    64  		return newOffset - o.offset, err
    65  	case io.SeekCurrent:
    66  		currentOffset, err := o.rdr.Seek(0, io.SeekCurrent)
    67  		if err != nil {
    68  			return 0, fmt.Errorf("cannot seek current: %w", err)
    69  		}
    70  		if currentOffset-o.offset+offset < 0 {
    71  			return 0, fmt.Errorf("cannot seek < 0")
    72  		}
    73  		newOffset, err := o.rdr.Seek(offset, io.SeekCurrent)
    74  		return newOffset - o.offset, err
    75  	}
    76  	return 0, fmt.Errorf("only SeekStart and SeekCurrent supported")
    77  }
    78  
    79  var _ io.ReadSeeker = (*offsetReadSeeker)(nil)