44 "bufio"
55 "bytes"
66 "errors"
7+ "fmt"
78 "io"
8-
99 "strconv"
1010 "time"
1111
@@ -26,12 +26,14 @@ var (
2626 ErrInvalidChecksum = errors .New ("invalid checksum" )
2727 // ErrUnknownExtension is returned when an index extension is encountered that is considered mandatory
2828 ErrUnknownExtension = errors .New ("unknown extension" )
29+ // ErrMalformedIndexFile is returned when the index file contents are
30+ // structurally invalid.
31+ ErrMalformedIndexFile = errors .New ("index decoder: malformed index file" )
2932)
3033
3134const (
3235 entryHeaderLength = 62
3336 entryExtended = 0x4000
34- entryValid = 0x8000
3537 nameMask = 0xfff
3638 intentToAddMask = 1 << 13
3739 skipWorkTreeMask = 1 << 14
@@ -140,33 +142,55 @@ func (d *Decoder) readEntry(idx *Index) (*Entry, error) {
140142 e .SkipWorktree = extended & skipWorkTreeMask != 0
141143 }
142144
143- if err := d .readEntryName (idx , e , flags ); err != nil {
145+ nameConsumed , err := d .readEntryName (idx , e , flags )
146+ if err != nil {
144147 return nil , err
145148 }
146149
147- return e , d .padEntry (idx , e , read )
150+ return e , d .padEntry (idx , e , read , nameConsumed )
148151}
149152
150- func (d * Decoder ) readEntryName (idx * Index , e * Entry , flags uint16 ) error {
151- var name string
152- var err error
153-
153+ // readEntryName reads the entry path and sets e.Name. It returns the
154+ // number of bytes consumed from the stream for the name portion.
155+ func (d * Decoder ) readEntryName (idx * Index , e * Entry , flags uint16 ) (int , error ) {
154156 switch idx .Version {
155157 case 2 , 3 :
156- len := flags & nameMask
157- name , err = d .doReadEntryName (len )
158+ nameLen := flags & nameMask
159+ name , consumed , err := d .doReadEntryName (nameLen )
160+ if err != nil {
161+ return 0 , err
162+ }
163+ e .Name = name
164+ return consumed , nil
158165 case 4 :
159- name , err = d .doReadEntryNameV4 ()
166+ name , err := d .doReadEntryNameV4 ()
167+ if err != nil {
168+ return 0 , err
169+ }
170+ e .Name = name
171+ return 0 , nil // V4 has no padding; consumed count unused
160172 default :
161- return ErrUnsupportedVersion
173+ return 0 , ErrUnsupportedVersion
162174 }
175+ }
163176
164- if err != nil {
165- return err
177+ // doReadEntryName reads the entry path for V2/V3 indexes. It returns the
178+ // name, the number of bytes consumed from the stream, and any error.
179+ // When nameLen equals nameMask (0xFFF), the name was too long to fit in
180+ // the 12-bit field and the real length is found by scanning for the NUL
181+ // terminator — matching C Git's strlen(name) fallback in create_from_disk.
182+ func (d * Decoder ) doReadEntryName (nameLen uint16 ) (string , int , error ) {
183+ if nameLen == nameMask {
184+ name , err := binary .ReadUntil (d .r , '\x00' )
185+ if err != nil {
186+ return "" , 0 , err
187+ }
188+ return string (name ), len (name ) + 1 , nil // +1 for the consumed NUL delimiter
166189 }
167190
168- e .Name = name
169- return nil
191+ name := make ([]byte , nameLen )
192+ _ , err := io .ReadFull (d .r , name )
193+ return string (name ), int (nameLen ), err
170194}
171195
172196func (d * Decoder ) doReadEntryNameV4 () (string , error ) {
@@ -177,7 +201,14 @@ func (d *Decoder) doReadEntryNameV4() (string, error) {
177201
178202 var base string
179203 if d .lastEntry != nil {
204+ if l < 0 || int (l ) > len (d .lastEntry .Name ) {
205+ return "" , fmt .Errorf ("%w: invalid V4 entry name strip length %d (previous name length: %d)" ,
206+ ErrMalformedIndexFile , l , len (d .lastEntry .Name ))
207+ }
180208 base = d .lastEntry .Name [:len (d .lastEntry .Name )- int (l )]
209+ } else if l > 0 {
210+ return "" , fmt .Errorf ("%w: non-zero strip length %d on first V4 entry" ,
211+ ErrMalformedIndexFile , l )
181212 }
182213
183214 name , err := binary .ReadUntil (d .r , '\x00' )
@@ -188,24 +219,23 @@ func (d *Decoder) doReadEntryNameV4() (string, error) {
188219 return base + string (name ), nil
189220}
190221
191- func (d * Decoder ) doReadEntryName (len uint16 ) (string , error ) {
192- name := make ([]byte , len )
193- _ , err := io .ReadFull (d .r , name )
194-
195- return string (name ), err
196- }
197-
198- // Index entries are padded out to the next 8 byte alignment
199- // for historical reasons related to how C Git read the files.
200- func (d * Decoder ) padEntry (idx * Index , e * Entry , read int ) error {
222+ // padEntry discards NUL padding bytes that follow each V2/V3 entry on
223+ // disk. nameConsumed is the number of stream bytes consumed while reading
224+ // the entry name (which may exceed len(e.Name) when a NUL terminator was
225+ // consumed for long names where the 12-bit length field overflowed).
226+ func (d * Decoder ) padEntry (idx * Index , e * Entry , read , nameConsumed int ) error {
201227 if idx .Version == 4 {
202228 return nil
203229 }
204230
205231 entrySize := read + len (e .Name )
206232 padLen := 8 - entrySize % 8
207- _ , err := io .CopyN (io .Discard , d .r , int64 (padLen ))
208- return err
233+ padLen -= nameConsumed - len (e .Name )
234+ if padLen > 0 {
235+ _ , err := io .CopyN (io .Discard , d .r , int64 (padLen ))
236+ return err
237+ }
238+ return nil
209239}
210240
211241func (d * Decoder ) readExtensions (idx * Index ) error {
@@ -312,7 +342,7 @@ func (d *Decoder) readChecksum(expected []byte) error {
312342}
313343
314344func validateHeader (r io.Reader ) (version uint32 , err error ) {
315- var s = make ([]byte , 4 )
345+ s : = make ([]byte , 4 )
316346 if _ , err := io .ReadFull (r , s ); err != nil {
317347 return 0 , err
318348 }
@@ -376,24 +406,26 @@ func (d *treeExtensionDecoder) readEntry() (*TreeEntry, error) {
376406 return nil , err
377407 }
378408
379- // An entry can be in an invalidated state and is represented by having a
380- // negative number in the entry_count field.
381- if i == - 1 {
382- return nil , nil
383- }
384-
385409 e .Entries = i
386410 trees , err := binary .ReadUntil (d .r , '\n' )
387411 if err != nil {
388412 return nil , err
389413 }
390414
391- i , err = strconv .Atoi (string (trees ))
415+ subtrees , err : = strconv .Atoi (string (trees ))
392416 if err != nil {
393417 return nil , err
394418 }
395419
396- e .Trees = i
420+ e .Trees = subtrees
421+
422+ // An entry can be in an invalidated state and is represented by having a
423+ // negative number in the entry_count field. In this case, there is no
424+ // object name and the next entry starts immediately after the newline.
425+ if i < 0 {
426+ return nil , nil
427+ }
428+
397429 _ , err = io .ReadFull (d .r , e .Hash [:])
398430 if err != nil {
399431 return nil , err
0 commit comments