package objfile import ( "errors" "io" "strconv" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/format/packfile" "github.com/go-git/go-git/v5/utils/sync" ) var ( ErrClosed = errors.New("objfile: already closed") ErrHeader = errors.New("objfile: invalid header") ErrNegativeSize = errors.New("objfile: negative object size") ) // Reader reads and decodes compressed objfile data from a provided io.Reader. // Reader implements io.ReadCloser. Close should be called when finished with // the Reader. Close will not close the underlying io.Reader. type Reader struct { multi io.Reader zlib io.Reader zlibref sync.ZLibReader hasher plumbing.Hasher } // NewReader returns a new Reader reading from r. func NewReader(r io.Reader) (*Reader, error) { zlib, err := sync.GetZlibReader(r) if err != nil { return nil, packfile.ErrZLib.AddDetails(err.Error()) } return &Reader{ zlib: zlib.Reader, zlibref: zlib, }, nil } // Header reads the type and the size of object, and prepares the reader for read func (r *Reader) Header() (t plumbing.ObjectType, size int64, err error) { var raw []byte raw, err = r.readUntil(' ') if err != nil { return } t, err = plumbing.ParseObjectType(string(raw)) if err != nil { return } raw, err = r.readUntil(0) if err != nil { return } size, err = strconv.ParseInt(string(raw), 10, 64) if err != nil { err = ErrHeader return } defer r.prepareForRead(t, size) return } // readSlice reads one byte at a time from r until it encounters delim or an // error. func (r *Reader) readUntil(delim byte) ([]byte, error) { var buf [1]byte value := make([]byte, 0, 16) for { if n, err := r.zlib.Read(buf[:]); err != nil && (err != io.EOF || n == 0) { if err == io.EOF { return nil, ErrHeader } return nil, err } if buf[0] == delim { return value, nil } value = append(value, buf[0]) } } func (r *Reader) prepareForRead(t plumbing.ObjectType, size int64) { r.hasher = plumbing.NewHasher(t, size) r.multi = io.TeeReader(r.zlib, r.hasher) } // Read reads len(p) bytes into p from the object data stream. It returns // the number of bytes read (0 <= n <= len(p)) and any error encountered. Even // if Read returns n < len(p), it may use all of p as scratch space during the // call. // // If Read encounters the end of the data stream it will return err == io.EOF, // either in the current call if n > 0 or in a subsequent call. func (r *Reader) Read(p []byte) (n int, err error) { return r.multi.Read(p) } // Hash returns the hash of the object data stream that has been read so far. func (r *Reader) Hash() plumbing.Hash { return r.hasher.Sum() } // Close releases any resources consumed by the Reader. Calling Close does not // close the wrapped io.Reader originally passed to NewReader. func (r *Reader) Close() error { sync.PutZlibReader(r.zlibref) return nil }