package util import ( "bytes" "errors" "io" "strings" ) // PeekedReadCloser is a ReadCloser that allows peeking into a stream and buffering it in memory. // It can be instantiated using the Peek function. After a stream has been peeked, it can still be fully // read by reading the PeekedReadCloser. It first drained from the memory buffer, and then from the remaining // underlying reader. type PeekedReadCloser struct { PeekedBytes []byte LimitReached bool peeked io.Reader underlying io.ReadCloser closed bool } // Peek reads the underlying ReadCloser into memory up until the limit and returns a PeekedReadCloser. // It does not return an error if limit is reached. Instead, LimitReached will be set to true. func Peek(underlying io.ReadCloser, limit int) (*PeekedReadCloser, error) { if underlying == nil { underlying = io.NopCloser(strings.NewReader("")) } peeked := make([]byte, limit) read, err := io.ReadFull(underlying, peeked) if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) && err != io.EOF { return nil, err } return &PeekedReadCloser{ PeekedBytes: peeked[:read], LimitReached: read == limit, underlying: underlying, peeked: bytes.NewReader(peeked[:read]), closed: false, }, nil } // Read reads from the peeked bytes and then from the underlying stream func (r *PeekedReadCloser) Read(p []byte) (n int, err error) { if r.closed { return 0, io.EOF } n, err = r.peeked.Read(p) if errors.Is(err, io.EOF) { return r.underlying.Read(p) } else if err != nil { return 0, err } return } // Close closes the underlying stream func (r *PeekedReadCloser) Close() error { if r.closed { return io.EOF } r.closed = true return r.underlying.Close() }