1
2
3
4
5 package zip
6
7 import (
8 "bufio"
9 "encoding/binary"
10 "errors"
11 "fmt"
12 "hash"
13 "hash/crc32"
14 "internal/godebug"
15 "io"
16 "io/fs"
17 "os"
18 "path"
19 "path/filepath"
20 "slices"
21 "strings"
22 "sync"
23 "time"
24 )
25
26 var zipinsecurepath = godebug.New("zipinsecurepath")
27
28 var (
29 ErrFormat = errors.New("zip: not a valid zip file")
30 ErrAlgorithm = errors.New("zip: unsupported compression algorithm")
31 ErrChecksum = errors.New("zip: checksum error")
32 ErrInsecurePath = errors.New("zip: insecure file path")
33 )
34
35
36 type Reader struct {
37 r io.ReaderAt
38 File []*File
39 Comment string
40 decompressors map[uint16]Decompressor
41
42
43
44 baseOffset int64
45
46
47
48 fileListOnce sync.Once
49 fileList []fileListEntry
50 }
51
52
53 type ReadCloser struct {
54 f *os.File
55 Reader
56 }
57
58
59
60
61 type File struct {
62 FileHeader
63 zip *Reader
64 zipr io.ReaderAt
65 headerOffset int64
66 zip64 bool
67 }
68
69
70
71
72
73
74
75
76
77
78 func OpenReader(name string) (*ReadCloser, error) {
79 f, err := os.Open(name)
80 if err != nil {
81 return nil, err
82 }
83 fi, err := f.Stat()
84 if err != nil {
85 f.Close()
86 return nil, err
87 }
88 r := new(ReadCloser)
89 if err = r.init(f, fi.Size()); err != nil && err != ErrInsecurePath {
90 f.Close()
91 return nil, err
92 }
93 r.f = f
94 return r, err
95 }
96
97
98
99
100
101
102
103
104
105
106
107 func NewReader(r io.ReaderAt, size int64) (*Reader, error) {
108 if size < 0 {
109 return nil, errors.New("zip: size cannot be negative")
110 }
111 zr := new(Reader)
112 var err error
113 if err = zr.init(r, size); err != nil && err != ErrInsecurePath {
114 return nil, err
115 }
116 return zr, err
117 }
118
119 func (r *Reader) init(rdr io.ReaderAt, size int64) error {
120 end, baseOffset, err := readDirectoryEnd(rdr, size)
121 if err != nil {
122 return err
123 }
124 r.r = rdr
125 r.baseOffset = baseOffset
126
127
128
129
130
131
132 if end.directorySize < uint64(size) && (uint64(size)-end.directorySize)/30 >= end.directoryRecords {
133 r.File = make([]*File, 0, end.directoryRecords)
134 }
135 r.Comment = end.comment
136 rs := io.NewSectionReader(rdr, 0, size)
137 if _, err = rs.Seek(r.baseOffset+int64(end.directoryOffset), io.SeekStart); err != nil {
138 return err
139 }
140 buf := bufio.NewReader(rs)
141
142
143
144
145
146 for {
147 f := &File{zip: r, zipr: rdr}
148 err = readDirectoryHeader(f, buf)
149 if err == ErrFormat || err == io.ErrUnexpectedEOF {
150 break
151 }
152 if err != nil {
153 return err
154 }
155 f.headerOffset += r.baseOffset
156 r.File = append(r.File, f)
157 }
158 if uint16(len(r.File)) != uint16(end.directoryRecords) {
159
160
161 return err
162 }
163 if zipinsecurepath.Value() == "0" {
164 for _, f := range r.File {
165 if f.Name == "" {
166
167 continue
168 }
169
170
171 if !filepath.IsLocal(f.Name) || strings.Contains(f.Name, `\`) {
172 zipinsecurepath.IncNonDefault()
173 return ErrInsecurePath
174 }
175 }
176 }
177 return nil
178 }
179
180
181
182
183 func (r *Reader) RegisterDecompressor(method uint16, dcomp Decompressor) {
184 if r.decompressors == nil {
185 r.decompressors = make(map[uint16]Decompressor)
186 }
187 r.decompressors[method] = dcomp
188 }
189
190 func (r *Reader) decompressor(method uint16) Decompressor {
191 dcomp := r.decompressors[method]
192 if dcomp == nil {
193 dcomp = decompressor(method)
194 }
195 return dcomp
196 }
197
198
199 func (rc *ReadCloser) Close() error {
200 return rc.f.Close()
201 }
202
203
204
205
206
207
208 func (f *File) DataOffset() (offset int64, err error) {
209 bodyOffset, err := f.findBodyOffset()
210 if err != nil {
211 return
212 }
213 return f.headerOffset + bodyOffset, nil
214 }
215
216
217
218 func (f *File) Open() (io.ReadCloser, error) {
219 bodyOffset, err := f.findBodyOffset()
220 if err != nil {
221 return nil, err
222 }
223 if strings.HasSuffix(f.Name, "/") {
224
225
226
227
228
229
230
231
232
233 if f.UncompressedSize64 != 0 {
234 return &dirReader{ErrFormat}, nil
235 } else {
236 return &dirReader{io.EOF}, nil
237 }
238 }
239 size := int64(f.CompressedSize64)
240 r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size)
241 dcomp := f.zip.decompressor(f.Method)
242 if dcomp == nil {
243 return nil, ErrAlgorithm
244 }
245 var rc io.ReadCloser = dcomp(r)
246 var desr io.Reader
247 if f.hasDataDescriptor() {
248 desr = io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset+size, dataDescriptorLen)
249 }
250 rc = &checksumReader{
251 rc: rc,
252 hash: crc32.NewIEEE(),
253 f: f,
254 desr: desr,
255 }
256 return rc, nil
257 }
258
259
260
261 func (f *File) OpenRaw() (io.Reader, error) {
262 bodyOffset, err := f.findBodyOffset()
263 if err != nil {
264 return nil, err
265 }
266 r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, int64(f.CompressedSize64))
267 return r, nil
268 }
269
270 type dirReader struct {
271 err error
272 }
273
274 func (r *dirReader) Read([]byte) (int, error) {
275 return 0, r.err
276 }
277
278 func (r *dirReader) Close() error {
279 return nil
280 }
281
282 type checksumReader struct {
283 rc io.ReadCloser
284 hash hash.Hash32
285 nread uint64
286 f *File
287 desr io.Reader
288 err error
289 }
290
291 func (r *checksumReader) Stat() (fs.FileInfo, error) {
292 return headerFileInfo{&r.f.FileHeader}, nil
293 }
294
295 func (r *checksumReader) Read(b []byte) (n int, err error) {
296 if r.err != nil {
297 return 0, r.err
298 }
299 n, err = r.rc.Read(b)
300 r.hash.Write(b[:n])
301 r.nread += uint64(n)
302 if r.nread > r.f.UncompressedSize64 {
303 return 0, ErrFormat
304 }
305 if err == nil {
306 return
307 }
308 if err == io.EOF {
309 if r.nread != r.f.UncompressedSize64 {
310 return 0, io.ErrUnexpectedEOF
311 }
312 if r.desr != nil {
313 if err1 := readDataDescriptor(r.desr, r.f); err1 != nil {
314 if err1 == io.EOF {
315 err = io.ErrUnexpectedEOF
316 } else {
317 err = err1
318 }
319 } else if r.hash.Sum32() != r.f.CRC32 {
320 err = ErrChecksum
321 }
322 } else {
323
324
325
326 if r.f.CRC32 != 0 && r.hash.Sum32() != r.f.CRC32 {
327 err = ErrChecksum
328 }
329 }
330 }
331 r.err = err
332 return
333 }
334
335 func (r *checksumReader) Close() error { return r.rc.Close() }
336
337
338
339 func (f *File) findBodyOffset() (int64, error) {
340 var buf [fileHeaderLen]byte
341 if _, err := f.zipr.ReadAt(buf[:], f.headerOffset); err != nil {
342 return 0, err
343 }
344 b := readBuf(buf[:])
345 if sig := b.uint32(); sig != fileHeaderSignature {
346 return 0, ErrFormat
347 }
348 b = b[22:]
349 filenameLen := int(b.uint16())
350 extraLen := int(b.uint16())
351 return int64(fileHeaderLen + filenameLen + extraLen), nil
352 }
353
354
355
356
357 func readDirectoryHeader(f *File, r io.Reader) error {
358 var buf [directoryHeaderLen]byte
359 if _, err := io.ReadFull(r, buf[:]); err != nil {
360 return err
361 }
362 b := readBuf(buf[:])
363 if sig := b.uint32(); sig != directoryHeaderSignature {
364 return ErrFormat
365 }
366 f.CreatorVersion = b.uint16()
367 f.ReaderVersion = b.uint16()
368 f.Flags = b.uint16()
369 f.Method = b.uint16()
370 f.ModifiedTime = b.uint16()
371 f.ModifiedDate = b.uint16()
372 f.CRC32 = b.uint32()
373 f.CompressedSize = b.uint32()
374 f.UncompressedSize = b.uint32()
375 f.CompressedSize64 = uint64(f.CompressedSize)
376 f.UncompressedSize64 = uint64(f.UncompressedSize)
377 filenameLen := int(b.uint16())
378 extraLen := int(b.uint16())
379 commentLen := int(b.uint16())
380 b = b[4:]
381 f.ExternalAttrs = b.uint32()
382 f.headerOffset = int64(b.uint32())
383 d := make([]byte, filenameLen+extraLen+commentLen)
384 if _, err := io.ReadFull(r, d); err != nil {
385 return err
386 }
387 f.Name = string(d[:filenameLen])
388 f.Extra = d[filenameLen : filenameLen+extraLen]
389 f.Comment = string(d[filenameLen+extraLen:])
390
391
392 utf8Valid1, utf8Require1 := detectUTF8(f.Name)
393 utf8Valid2, utf8Require2 := detectUTF8(f.Comment)
394 switch {
395 case !utf8Valid1 || !utf8Valid2:
396
397 f.NonUTF8 = true
398 case !utf8Require1 && !utf8Require2:
399
400 f.NonUTF8 = false
401 default:
402
403
404
405
406 f.NonUTF8 = f.Flags&0x800 == 0
407 }
408
409 needUSize := f.UncompressedSize == ^uint32(0)
410 needCSize := f.CompressedSize == ^uint32(0)
411 needHeaderOffset := f.headerOffset == int64(^uint32(0))
412
413
414
415
416 var modified time.Time
417 parseExtras:
418 for extra := readBuf(f.Extra); len(extra) >= 4; {
419 fieldTag := extra.uint16()
420 fieldSize := int(extra.uint16())
421 if len(extra) < fieldSize {
422 break
423 }
424 fieldBuf := extra.sub(fieldSize)
425
426 switch fieldTag {
427 case zip64ExtraID:
428 f.zip64 = true
429
430
431
432
433
434 if needUSize {
435 needUSize = false
436 if len(fieldBuf) < 8 {
437 return ErrFormat
438 }
439 f.UncompressedSize64 = fieldBuf.uint64()
440 }
441 if needCSize {
442 needCSize = false
443 if len(fieldBuf) < 8 {
444 return ErrFormat
445 }
446 f.CompressedSize64 = fieldBuf.uint64()
447 }
448 if needHeaderOffset {
449 needHeaderOffset = false
450 if len(fieldBuf) < 8 {
451 return ErrFormat
452 }
453 f.headerOffset = int64(fieldBuf.uint64())
454 }
455 case ntfsExtraID:
456 if len(fieldBuf) < 4 {
457 continue parseExtras
458 }
459 fieldBuf.uint32()
460 for len(fieldBuf) >= 4 {
461 attrTag := fieldBuf.uint16()
462 attrSize := int(fieldBuf.uint16())
463 if len(fieldBuf) < attrSize {
464 continue parseExtras
465 }
466 attrBuf := fieldBuf.sub(attrSize)
467 if attrTag != 1 || attrSize != 24 {
468 continue
469 }
470
471 const ticksPerSecond = 1e7
472 ts := int64(attrBuf.uint64())
473 secs := ts / ticksPerSecond
474 nsecs := (1e9 / ticksPerSecond) * (ts % ticksPerSecond)
475 epoch := time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC)
476 modified = time.Unix(epoch.Unix()+secs, nsecs)
477 }
478 case unixExtraID, infoZipUnixExtraID:
479 if len(fieldBuf) < 8 {
480 continue parseExtras
481 }
482 fieldBuf.uint32()
483 ts := int64(fieldBuf.uint32())
484 modified = time.Unix(ts, 0)
485 case extTimeExtraID:
486 if len(fieldBuf) < 5 || fieldBuf.uint8()&1 == 0 {
487 continue parseExtras
488 }
489 ts := int64(fieldBuf.uint32())
490 modified = time.Unix(ts, 0)
491 }
492 }
493
494 msdosModified := msDosTimeToTime(f.ModifiedDate, f.ModifiedTime)
495 f.Modified = msdosModified
496 if !modified.IsZero() {
497 f.Modified = modified.UTC()
498
499
500
501
502
503
504
505
506
507 if f.ModifiedTime != 0 || f.ModifiedDate != 0 {
508 f.Modified = modified.In(timeZone(msdosModified.Sub(modified)))
509 }
510 }
511
512
513
514
515
516
517
518
519
520 _ = needUSize
521
522 if needCSize || needHeaderOffset {
523 return ErrFormat
524 }
525
526 return nil
527 }
528
529 func readDataDescriptor(r io.Reader, f *File) error {
530 var buf [dataDescriptorLen]byte
531
532
533
534
535
536
537
538
539
540
541 if _, err := io.ReadFull(r, buf[:4]); err != nil {
542 return err
543 }
544 off := 0
545 maybeSig := readBuf(buf[:4])
546 if maybeSig.uint32() != dataDescriptorSignature {
547
548
549 off += 4
550 }
551 if _, err := io.ReadFull(r, buf[off:12]); err != nil {
552 return err
553 }
554 b := readBuf(buf[:12])
555 if b.uint32() != f.CRC32 {
556 return ErrChecksum
557 }
558
559
560
561
562
563
564
565 return nil
566 }
567
568 func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, baseOffset int64, err error) {
569
570 var buf []byte
571 var directoryEndOffset int64
572 for i, bLen := range []int64{1024, 65 * 1024} {
573 if bLen > size {
574 bLen = size
575 }
576 buf = make([]byte, int(bLen))
577 if _, err := r.ReadAt(buf, size-bLen); err != nil && err != io.EOF {
578 return nil, 0, err
579 }
580 if p := findSignatureInBlock(buf); p >= 0 {
581 buf = buf[p:]
582 directoryEndOffset = size - bLen + int64(p)
583 break
584 }
585 if i == 1 || bLen == size {
586 return nil, 0, ErrFormat
587 }
588 }
589
590
591 b := readBuf(buf[4:])
592 d := &directoryEnd{
593 diskNbr: uint32(b.uint16()),
594 dirDiskNbr: uint32(b.uint16()),
595 dirRecordsThisDisk: uint64(b.uint16()),
596 directoryRecords: uint64(b.uint16()),
597 directorySize: uint64(b.uint32()),
598 directoryOffset: uint64(b.uint32()),
599 commentLen: b.uint16(),
600 }
601 l := int(d.commentLen)
602 if l > len(b) {
603 return nil, 0, errors.New("zip: invalid comment length")
604 }
605 d.comment = string(b[:l])
606
607
608 if d.directoryRecords == 0xffff || d.directorySize == 0xffff || d.directoryOffset == 0xffffffff {
609 p, err := findDirectory64End(r, directoryEndOffset)
610 if err == nil && p >= 0 {
611 directoryEndOffset = p
612 err = readDirectory64End(r, p, d)
613 }
614 if err != nil {
615 return nil, 0, err
616 }
617 }
618
619 maxInt64 := uint64(1<<63 - 1)
620 if d.directorySize > maxInt64 || d.directoryOffset > maxInt64 {
621 return nil, 0, ErrFormat
622 }
623
624 baseOffset = directoryEndOffset - int64(d.directorySize) - int64(d.directoryOffset)
625
626
627 if o := baseOffset + int64(d.directoryOffset); o < 0 || o >= size {
628 return nil, 0, ErrFormat
629 }
630
631
632
633
634
635
636 if baseOffset > 0 {
637 off := int64(d.directoryOffset)
638 rs := io.NewSectionReader(r, off, size-off)
639 if readDirectoryHeader(&File{}, rs) == nil {
640 baseOffset = 0
641 }
642 }
643
644 return d, baseOffset, nil
645 }
646
647
648
649
650 func findDirectory64End(r io.ReaderAt, directoryEndOffset int64) (int64, error) {
651 locOffset := directoryEndOffset - directory64LocLen
652 if locOffset < 0 {
653 return -1, nil
654 }
655 buf := make([]byte, directory64LocLen)
656 if _, err := r.ReadAt(buf, locOffset); err != nil {
657 return -1, err
658 }
659 b := readBuf(buf)
660 if sig := b.uint32(); sig != directory64LocSignature {
661 return -1, nil
662 }
663 if b.uint32() != 0 {
664 return -1, nil
665 }
666 p := b.uint64()
667 if b.uint32() != 1 {
668 return -1, nil
669 }
670 return int64(p), nil
671 }
672
673
674
675 func readDirectory64End(r io.ReaderAt, offset int64, d *directoryEnd) (err error) {
676 buf := make([]byte, directory64EndLen)
677 if _, err := r.ReadAt(buf, offset); err != nil {
678 return err
679 }
680
681 b := readBuf(buf)
682 if sig := b.uint32(); sig != directory64EndSignature {
683 return ErrFormat
684 }
685
686 b = b[12:]
687 d.diskNbr = b.uint32()
688 d.dirDiskNbr = b.uint32()
689 d.dirRecordsThisDisk = b.uint64()
690 d.directoryRecords = b.uint64()
691 d.directorySize = b.uint64()
692 d.directoryOffset = b.uint64()
693
694 return nil
695 }
696
697 func findSignatureInBlock(b []byte) int {
698 for i := len(b) - directoryEndLen; i >= 0; i-- {
699
700 if b[i] == 'P' && b[i+1] == 'K' && b[i+2] == 0x05 && b[i+3] == 0x06 {
701
702 n := int(b[i+directoryEndLen-2]) | int(b[i+directoryEndLen-1])<<8
703 if n+directoryEndLen+i > len(b) {
704
705
706
707 return -1
708 }
709 return i
710 }
711 }
712 return -1
713 }
714
715 type readBuf []byte
716
717 func (b *readBuf) uint8() uint8 {
718 v := (*b)[0]
719 *b = (*b)[1:]
720 return v
721 }
722
723 func (b *readBuf) uint16() uint16 {
724 v := binary.LittleEndian.Uint16(*b)
725 *b = (*b)[2:]
726 return v
727 }
728
729 func (b *readBuf) uint32() uint32 {
730 v := binary.LittleEndian.Uint32(*b)
731 *b = (*b)[4:]
732 return v
733 }
734
735 func (b *readBuf) uint64() uint64 {
736 v := binary.LittleEndian.Uint64(*b)
737 *b = (*b)[8:]
738 return v
739 }
740
741 func (b *readBuf) sub(n int) readBuf {
742 b2 := (*b)[:n]
743 *b = (*b)[n:]
744 return b2
745 }
746
747
748
749 type fileListEntry struct {
750 name string
751 file *File
752 isDir bool
753 isDup bool
754 }
755
756 type fileInfoDirEntry interface {
757 fs.FileInfo
758 fs.DirEntry
759 }
760
761 func (f *fileListEntry) stat() (fileInfoDirEntry, error) {
762 if f.isDup {
763 return nil, errors.New(f.name + ": duplicate entries in zip file")
764 }
765 if !f.isDir {
766 return headerFileInfo{&f.file.FileHeader}, nil
767 }
768 return f, nil
769 }
770
771
772 func (f *fileListEntry) Name() string { _, elem, _ := split(f.name); return elem }
773 func (f *fileListEntry) Size() int64 { return 0 }
774 func (f *fileListEntry) Mode() fs.FileMode { return fs.ModeDir | 0555 }
775 func (f *fileListEntry) Type() fs.FileMode { return fs.ModeDir }
776 func (f *fileListEntry) IsDir() bool { return true }
777 func (f *fileListEntry) Sys() any { return nil }
778
779 func (f *fileListEntry) ModTime() time.Time {
780 if f.file == nil {
781 return time.Time{}
782 }
783 return f.file.FileHeader.Modified.UTC()
784 }
785
786 func (f *fileListEntry) Info() (fs.FileInfo, error) { return f, nil }
787
788 func (f *fileListEntry) String() string {
789 return fs.FormatDirEntry(f)
790 }
791
792
793 func toValidName(name string) string {
794 name = strings.ReplaceAll(name, `\`, `/`)
795 p := path.Clean(name)
796
797 p = strings.TrimPrefix(p, "/")
798
799 for strings.HasPrefix(p, "../") {
800 p = p[len("../"):]
801 }
802
803 return p
804 }
805
806 func (r *Reader) initFileList() {
807 r.fileListOnce.Do(func() {
808
809
810 r.fileList = make([]fileListEntry, 0, len(r.File))
811
812
813
814 files := make(map[string]int)
815 knownDirs := make(map[string]int)
816
817
818
819 dirs := make(map[string]bool)
820
821 for _, file := range r.File {
822 isDir := len(file.Name) > 0 && file.Name[len(file.Name)-1] == '/'
823 name := toValidName(file.Name)
824 if name == "" {
825 continue
826 }
827
828 if idx, ok := files[name]; ok {
829 r.fileList[idx].isDup = true
830 continue
831 }
832 if idx, ok := knownDirs[name]; ok {
833 r.fileList[idx].isDup = true
834 continue
835 }
836
837 for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) {
838 dirs[dir] = true
839 }
840
841 idx := len(r.fileList)
842 entry := fileListEntry{
843 name: name,
844 file: file,
845 isDir: isDir,
846 }
847 r.fileList = append(r.fileList, entry)
848 if isDir {
849 knownDirs[name] = idx
850 } else {
851 files[name] = idx
852 }
853 }
854 for dir := range dirs {
855 if _, ok := knownDirs[dir]; !ok {
856 if idx, ok := files[dir]; ok {
857 r.fileList[idx].isDup = true
858 } else {
859 entry := fileListEntry{
860 name: dir,
861 file: nil,
862 isDir: true,
863 }
864 r.fileList = append(r.fileList, entry)
865 }
866 }
867 }
868
869 slices.SortFunc(r.fileList, func(a, b fileListEntry) int {
870 return fileEntryCompare(a.name, b.name)
871 })
872 })
873 }
874
875 func fileEntryCompare(x, y string) int {
876 xdir, xelem, _ := split(x)
877 ydir, yelem, _ := split(y)
878 if xdir != ydir {
879 return strings.Compare(xdir, ydir)
880 }
881 return strings.Compare(xelem, yelem)
882 }
883
884
885
886
887
888 func (r *Reader) Open(name string) (fs.File, error) {
889 r.initFileList()
890
891 if !fs.ValidPath(name) {
892 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
893 }
894 e := r.openLookup(name)
895 if e == nil {
896 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
897 }
898 if e.isDir {
899 return &openDir{e, r.openReadDir(name), 0}, nil
900 }
901 rc, err := e.file.Open()
902 if err != nil {
903 return nil, err
904 }
905 return rc.(fs.File), nil
906 }
907
908 func split(name string) (dir, elem string, isDir bool) {
909 name, isDir = strings.CutSuffix(name, "/")
910 i := strings.LastIndexByte(name, '/')
911 if i < 0 {
912 return ".", name, isDir
913 }
914 return name[:i], name[i+1:], isDir
915 }
916
917 var dotFile = &fileListEntry{name: "./", isDir: true}
918
919 func (r *Reader) openLookup(name string) *fileListEntry {
920 if name == "." {
921 return dotFile
922 }
923
924 dir, elem, _ := split(name)
925 files := r.fileList
926 i, _ := slices.BinarySearchFunc(files, dir, func(a fileListEntry, dir string) (ret int) {
927 idir, ielem, _ := split(a.name)
928 if dir != idir {
929 return strings.Compare(idir, dir)
930 }
931 return strings.Compare(ielem, elem)
932 })
933 if i < len(files) {
934 fname := files[i].name
935 if fname == name || len(fname) == len(name)+1 && fname[len(name)] == '/' && fname[:len(name)] == name {
936 return &files[i]
937 }
938 }
939 return nil
940 }
941
942 func (r *Reader) openReadDir(dir string) []fileListEntry {
943 files := r.fileList
944 i, _ := slices.BinarySearchFunc(files, dir, func(a fileListEntry, dir string) int {
945 idir, _, _ := split(a.name)
946 if dir != idir {
947 return strings.Compare(idir, dir)
948 }
949
950 return +1
951 })
952 j, _ := slices.BinarySearchFunc(files, dir, func(a fileListEntry, dir string) int {
953 jdir, _, _ := split(a.name)
954 if dir != jdir {
955 return strings.Compare(jdir, dir)
956 }
957
958 return -1
959 })
960 return files[i:j]
961 }
962
963 type openDir struct {
964 e *fileListEntry
965 files []fileListEntry
966 offset int
967 }
968
969 func (d *openDir) Close() error { return nil }
970 func (d *openDir) Stat() (fs.FileInfo, error) { return d.e.stat() }
971
972 func (d *openDir) Read([]byte) (int, error) {
973 return 0, &fs.PathError{Op: "read", Path: d.e.name, Err: errors.New("is a directory")}
974 }
975
976 func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
977 n := len(d.files) - d.offset
978 if count > 0 && n > count {
979 n = count
980 }
981 if n == 0 {
982 if count <= 0 {
983 return nil, nil
984 }
985 return nil, io.EOF
986 }
987 list := make([]fs.DirEntry, n)
988 for i := range list {
989 s, err := d.files[d.offset+i].stat()
990 if err != nil {
991 return nil, err
992 } else if s.Name() == "." || !fs.ValidPath(s.Name()) {
993 return nil, &fs.PathError{
994 Op: "readdir",
995 Path: d.e.name,
996 Err: fmt.Errorf("invalid file name: %v", d.files[d.offset+i].name),
997 }
998 }
999 list[i] = s
1000 }
1001 d.offset += n
1002 return list, nil
1003 }
1004
View as plain text