1// SPDX-License-Identifier: MIT
2
3package git
4
5import (
6 "bytes"
7 "errors"
8 "io"
9)
10
11// OpenBlob opens the blob id for reading and returns its size. Undeltified
12// blobs stream from the memory-mapped pack or the loose file, inflated as they
13// are read, so a large file is never held in memory. A deltified blob is
14// rebuilt in a pooled buffer first and is bounded by WithMaxObjectSize
15// (ErrTooLarge). The caller must close the reader, which also releases the
16// pack it reads from. Reads detect truncated or oversized data and return
17// ErrCorrupt. The checksum of the stream is verified when the last byte is
18// read: that Read returns (0, ErrCorrupt) instead of the final chunk, so
19// io.ReadFull and io.CopyN of exactly size bytes report a corrupt trailer too.
20// Close returns ErrCorrupt if all size bytes were consumed (or the blob is
21// empty) but the end of the stream was not checked. A reader closed before
22// size bytes were read cannot verify the checksum.
23func (r *Repo) OpenBlob(id ObjectID) (int64, io.ReadCloser, error) {
24 f, err := r.finder()
25 if err != nil {
26 return 0, nil, err
27 }
28 defer f.close()
29 for {
30 p, off, err := f.pack(&id)
31 if err != nil {
32 return 0, nil, err
33 }
34 if p != nil {
35 size, rc, err := r.openPackedBlob(p, off, id)
36 if err != nil {
37 return 0, nil, wrapPack(p.name, err)
38 }
39 return size, rc, nil
40 }
41 size, rc, err := r.openLooseBlob(&id)
42 if err == nil {
43 return size, rc, nil
44 }
45 if !errors.Is(err, ErrNotFound) {
46 return 0, nil, err
47 }
48 if again, err := f.again(); err != nil {
49 return 0, nil, err
50 } else if !again {
51 return 0, nil, notFound("object %s", id)
52 }
53 }
54}
55
56func (r *Repo) openPackedBlob(p *pack, off uint64, id ObjectID) (int64, io.ReadCloser, error) {
57 h, err := parseEntryHeader(p.data, off)
58 if err != nil {
59 return 0, nil, err
60 }
61 if h.isDelta() {
62 b := getBuf()
63 typ, data, err := r.readPacked(p, off, b.b)
64 if err != nil {
65 putBuf(b)
66 return 0, nil, err
67 }
68 b.b = data
69 if typ != TypeBlob {
70 putBuf(b)
71 return 0, nil, wrongType(id, typ, TypeBlob)
72 }
73 return int64(len(data)), &memBlob{buf: b, r: bytes.NewReader(data)}, nil
74 }
75 if Type(h.typ) != TypeBlob {
76 return 0, nil, wrongType(id, Type(h.typ), TypeBlob)
77 }
78 if h.size > 1<<62 || h.size/1032 > uint64(len(p.stream(&h))) {
79 return 0, nil, corrupt("blob of %d bytes at %d", h.size, off)
80 }
81 m, err := getMem(p.stream(&h))
82 if err != nil {
83 return 0, nil, err
84 }
85 p.retain()
86 return int64(h.size), &streamBlob{src: m, pack: p, left: int64(h.size), zr: &m.zr}, nil
87}
88
89func (r *Repo) openLooseBlob(id *ObjectID) (int64, io.ReadCloser, error) {
90 l, err := r.openLoose(id)
91 if err != nil {
92 return 0, nil, err
93 }
94 if l.typ != TypeBlob {
95 l.close()
96 return 0, nil, wrongType(*id, l.typ, TypeBlob)
97 }
98 if l.size > 1<<62 || uint64(l.nlea) > l.size {
99 l.close()
100 return 0, nil, corrupt("loose object %s: size %d", id.String(), l.size)
101 }
102 return int64(l.size), &streamBlob{loose: l, left: int64(l.size), zr: &l.fi.zr, lead: l.lead[:l.nlea]}, nil
103}
104
105// memBlob serves a blob rebuilt in memory.
106type memBlob struct {
107 buf *buffer
108 r *bytes.Reader
109}
110
111func (m *memBlob) Read(p []byte) (int, error) {
112 if m.r == nil {
113 return 0, io.ErrClosedPipe
114 }
115 return m.r.Read(p)
116}
117
118func (m *memBlob) WriteTo(w io.Writer) (int64, error) {
119 if m.r == nil {
120 return 0, io.ErrClosedPipe
121 }
122 return m.r.WriteTo(w)
123}
124
125func (m *memBlob) Close() error {
126 if m.r != nil {
127 m.r = nil
128 putBuf(m.buf)
129 m.buf = nil
130 }
131 return nil
132}
133
134// streamBlob inflates a blob while it is read.
135type streamBlob struct {
136 src *memInflater // packed
137 pack *pack
138 loose *looseObject // loose
139 zr io.Reader
140 lead []byte // loose: content already read with the header
141 left int64 // bytes still to return
142 done bool // end of stream verified
143 err error
144 probe [1]byte
145}
146
147func (s *streamBlob) Read(p []byte) (int, error) {
148 if s.zr == nil {
149 return 0, io.ErrClosedPipe
150 }
151 if s.err != nil {
152 return 0, s.err
153 }
154 if len(p) == 0 {
155 return 0, nil
156 }
157 if s.left == 0 {
158 return 0, s.finish()
159 }
160 if int64(len(p)) > s.left {
161 p = p[:s.left]
162 }
163 if len(s.lead) > 0 {
164 n := copy(p, s.lead)
165 s.lead = s.lead[n:]
166 s.left -= int64(n)
167 return s.served(n)
168 }
169 n, err := s.zr.Read(p)
170 s.left -= int64(n)
171 if err != nil && !errors.Is(err, io.EOF) {
172 s.err = corrupt("blob: %v", err)
173 return 0, s.err
174 }
175 if errors.Is(err, io.EOF) && s.left > 0 {
176 s.err = corrupt("blob shorter than its declared size")
177 return 0, s.err
178 }
179 if n > 0 {
180 return s.served(n)
181 }
182 if s.left == 0 {
183 return 0, s.finish()
184 }
185 return 0, nil
186}
187
188// served returns the n > 0 bytes just read. When they are the last ones, it
189// verifies the end of the stream and its checksum before returning them, so a
190// caller that stops at the declared size (io.ReadFull, io.CopyN) still sees a
191// corrupt trailer. On failure the chunk is withheld, (0, err), because
192// io.ReadFull drops an error that comes with a full buffer.
193func (s *streamBlob) served(n int) (int, error) {
194 if s.left == 0 {
195 if err := s.finish(); !errors.Is(err, io.EOF) {
196 return 0, err
197 }
198 }
199 return n, nil
200}
201
202// finish checks that the stream ends exactly here and that its checksum is
203// right, then reports EOF.
204func (s *streamBlob) finish() error {
205 if !s.done {
206 n, err := s.zr.Read(s.probe[:])
207 switch {
208 case n != 0:
209 s.err = corrupt("blob longer than its declared size")
210 case !errors.Is(err, io.EOF):
211 s.err = corrupt("blob: %v", err)
212 }
213 s.done = true
214 }
215 if s.err != nil {
216 return s.err
217 }
218 return io.EOF
219}
220
221func (s *streamBlob) Close() error {
222 if s.zr == nil {
223 return nil
224 }
225 var err error
226 if s.left == 0 && !s.done {
227 // Nothing was read yet (an empty blob): verify the stream now.
228 if err = s.finish(); errors.Is(err, io.EOF) {
229 err = nil
230 }
231 }
232 s.zr = nil
233 if s.src != nil {
234 putMem(s.src)
235 s.pack.release()
236 }
237 if s.loose != nil {
238 s.loose.close()
239 }
240 return err
241}