1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6 "bufio"
7 "bytes"
8 "compress/flate"
9 "encoding/binary"
10 "errors"
11 "hash"
12 "hash/adler32"
13 "io"
14 "math"
15 "sync"
16)
17
18// buffer is a pooled byte slice. Pooling the pointer keeps Put free of
19// allocations.
20type buffer struct{ b []byte }
21
22// maxPooledBuffer is the largest buffer that goes back to the pool, so a
23// single big blob does not pin memory.
24const maxPooledBuffer = 4 << 20
25
26var bufPool = sync.Pool{New: func() any { return &buffer{b: make([]byte, 0, 4096)} }}
27
28func getBuf() *buffer { return bufPool.Get().(*buffer) }
29
30func putBuf(b *buffer) {
31 if b == nil || cap(b.b) > maxPooledBuffer {
32 return
33 }
34 b.b = b.b[:0]
35 bufPool.Put(b)
36}
37
38// grow returns b with length n, reusing its capacity when possible. The
39// content is not preserved.
40func grow(b []byte, n int) []byte {
41 if cap(b) >= n {
42 return b[:n]
43 }
44 // Spare room for the next, slightly bigger object; n+n/4 may overflow
45 // an int on 32-bit systems, where the size is capped at MaxInt.
46 c := n
47 if n <= math.MaxInt-n/4 {
48 c = n + n/4
49 }
50 return make([]byte, n, c)
51}
52
53// Errors of the zlib wrapper. Callers wrap them in ErrCorrupt.
54var (
55 errZlibHeader = errors.New("zlib: invalid header")
56 errZlibDict = errors.New("zlib: preset dictionary")
57 errZlibChecksum = errors.New("zlib: invalid checksum")
58)
59
60// zlibReader inflates a zlib stream (RFC 1950). It is compress/zlib's reader
61// without its allocation per Reset: Go's zlib.Reader.Reset calls
62// adler32.New() each time, while here the checksum state and the flate
63// decompressor are both created once and reset. The adler32 trailer is
64// verified when the deflate stream ends; it is the only check of the
65// inflated content, since neither the pack trailer nor the idx CRCs are
66// verified.
67//
68// A zlibReader is not safe for concurrent use.
69type zlibReader struct {
70 src flate.Reader
71 fl io.ReadCloser // *flate decompressor, created at the first Reset
72 rs flate.Resetter
73 sum hash.Hash32
74 err error // sticky; io.EOF once the stream and its checksum are verified
75 tail [4]byte
76}
77
78// Reset starts reading the zlib stream at the current position of src. It
79// reads the 2-byte header: deflate with a window of at most 32 KiB, a valid
80// check value, no preset dictionary (Git never writes one).
81func (z *zlibReader) Reset(src flate.Reader) error {
82 z.src, z.err = src, nil
83 cmf, err := src.ReadByte()
84 if err == nil {
85 var flg byte
86 if flg, err = src.ReadByte(); err == nil {
87 switch {
88 case cmf&0x0f != 8 || cmf>>4 > 7 || (uint(cmf)<<8|uint(flg))%31 != 0:
89 err = errZlibHeader
90 case flg&0x20 != 0:
91 err = errZlibDict
92 }
93 }
94 }
95 if err != nil {
96 if errors.Is(err, io.EOF) {
97 err = io.ErrUnexpectedEOF
98 }
99 z.err = err
100 return err
101 }
102 if z.fl == nil {
103 z.fl = flate.NewReader(src)
104 z.rs = z.fl.(flate.Resetter)
105 z.sum = adler32.New()
106 } else {
107 _ = z.rs.Reset(src, nil) // never fails without a dictionary
108 z.sum.Reset()
109 }
110 return nil
111}
112
113func (z *zlibReader) Read(p []byte) (int, error) {
114 if z.err != nil {
115 return 0, z.err
116 }
117 n, err := z.fl.Read(p)
118 _, _ = z.sum.Write(p[:n]) // never fails
119 if err != io.EOF {
120 z.err = err
121 return n, err
122 }
123 // End of the deflate stream: the big-endian adler32 of the content.
124 if _, err := io.ReadFull(z.src, z.tail[:]); err != nil {
125 if errors.Is(err, io.EOF) {
126 err = io.ErrUnexpectedEOF
127 }
128 z.err = err
129 return n, err
130 }
131 if binary.BigEndian.Uint32(z.tail[:]) != z.sum.Sum32() {
132 z.err = errZlibChecksum
133 return n, z.err
134 }
135 z.err = io.EOF
136 return n, io.EOF
137}
138
139// memInflater inflates a zlib stream held in memory.
140type memInflater struct {
141 zr zlibReader
142 br bytes.Reader
143 scratch [maxLooseHeader]byte // for reads through the io.Reader interface, which escape
144}
145
146var memPool = sync.Pool{New: func() any { return new(memInflater) }}
147
148// getMem returns an inflater positioned at the start of src.
149func getMem(src []byte) (*memInflater, error) {
150 m := memPool.Get().(*memInflater)
151 m.br.Reset(src)
152 if err := m.zr.Reset(&m.br); err != nil {
153 putMem(m)
154 return nil, corrupt("zlib header: %v", err)
155 }
156 return m, nil
157}
158
159func putMem(m *memInflater) {
160 m.br.Reset(nil)
161 memPool.Put(m)
162}
163
164// fileInflater inflates a zlib stream read from a file.
165type fileInflater struct {
166 zr zlibReader
167 br *bufio.Reader
168 scratch [maxLooseHeader]byte
169}
170
171var filePool = sync.Pool{New: func() any { return &fileInflater{br: bufio.NewReaderSize(nil, 8192)} }}
172
173func getFile(r io.Reader) (*fileInflater, error) {
174 f := filePool.Get().(*fileInflater)
175 f.br.Reset(r)
176 if err := f.zr.Reset(f.br); err != nil {
177 putFile(f)
178 return nil, corrupt("zlib header: %v", err)
179 }
180 return f, nil
181}
182
183func putFile(f *fileInflater) {
184 f.br.Reset(nil)
185 filePool.Put(f)
186}
187
188// inflateExact fills dst from zr and checks that the stream ends right
189// after, so a stream shorter or longer than declared is corrupt.
190func inflateExact(zr io.Reader, dst []byte, probe []byte) error {
191 if _, err := io.ReadFull(zr, dst); err != nil {
192 return corrupt("inflate: %v", err)
193 }
194 n, err := zr.Read(probe[:1])
195 if n != 0 {
196 return corrupt("inflate: object longer than its declared size")
197 }
198 if !errors.Is(err, io.EOF) {
199 return corrupt("inflate: %v", err)
200 }
201 return nil
202}