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)