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