1
2
3
4
5 package zip
6
7 import (
8 "bufio"
9 "encoding/binary"
10 "errors"
11 "hash"
12 "hash/crc32"
13 "internal/godebug"
14 "io"
15 "io/fs"
16 "os"
17 "path"
18 "path/filepath"
19 "slices"
20 "strings"
21 "sync"
22 "time"
23 )
24
25 var zipinsecurepath = godebug.New("zipinsecurepath")
26
27 var (
28 ErrFormat = errors.New("zip: not a valid zip file")
29 ErrAlgorithm = errors.New("zip: unsupported compression algorithm")
30 ErrChecksum = errors.New("zip: checksum error")
31 ErrInsecurePath = errors.New("zip: insecure file path")
32 )
33
34
35 type Reader struct {
36 r io.ReaderAt
37 File []*File
38 Comment string
39 decompressors map[uint16]Decompressor
40
41
42
43 baseOffset int64
44
45
46
47 fileListOnce sync.Once
48 fileList []fileListEntry
49 }
50
51
52 type ReadCloser struct {
53 f *os.File
54 Reader
55 }
56
57
58
59
60 type File struct {
61 FileHeader
62 zip *Reader
63 zipr io.ReaderAt
64 headerOffset int64
65 zip64 bool
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 needUSize := f.UncompressedSize == ^uint32(0)
409 needCSize := f.CompressedSize == ^uint32(0)
410 needHeaderOffset := f.headerOffset == int64(^uint32(0))
411
412
413
414
415 var modified time.Time
416 parseExtras:
417 for extra := readBuf(f.Extra); len(extra) >= 4; {
418 fieldTag := extra.uint16()
419 fieldSize := int(extra.uint16())
420 if len(extra) < fieldSize {
421 break
422 }
423 fieldBuf := extra.sub(fieldSize)
424
425 switch fieldTag {
426 case zip64ExtraID:
427 f.zip64 = true
428
429
430
431
432
433 if needUSize {
434 needUSize = false
435 if len(fieldBuf) < 8 {
436 return ErrFormat
437 }
438 f.UncompressedSize64 = fieldBuf.uint64()
439 }
440 if needCSize {
441 needCSize = false
442 if len(fieldBuf) < 8 {
443 return ErrFormat
444 }
445 f.CompressedSize64 = fieldBuf.uint64()
446 }
447 if needHeaderOffset {
448 needHeaderOffset = false
449 if len(fieldBuf) < 8 {
450 return ErrFormat
451 }
452 f.headerOffset = int64(fieldBuf.uint64())
453 }
454 case ntfsExtraID:
455 if len(fieldBuf) < 4 {
456 continue parseExtras
457 }
458 fieldBuf.uint32()
459 for len(fieldBuf) >= 4 {
460 attrTag := fieldBuf.uint16()
461 attrSize := int(fieldBuf.uint16())
462 if len(fieldBuf) < attrSize {
463 continue parseExtras
464 }
465 attrBuf := fieldBuf.sub(attrSize)
466 if attrTag != 1 || attrSize != 24 {
467 continue
468 }
469
470 const ticksPerSecond = 1e7
471 ts := int64(attrBuf.uint64())
472 secs := ts / ticksPerSecond
473 nsecs := (1e9 / ticksPerSecond) * (ts % ticksPerSecond)
474 epoch := time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC)
475 modified = time.Unix(epoch.Unix()+secs, nsecs)
476 }
477 case unixExtraID, infoZipUnixExtraID:
478 if len(fieldBuf) < 8 {
479 continue parseExtras
480 }
481 fieldBuf.uint32()
482 ts := int64(fieldBuf.uint32())
483 modified = time.Unix(ts, 0)
484 case extTimeExtraID:
485 if len(fieldBuf) < 5 || fieldBuf.uint8()&1 == 0 {
486 continue parseExtras
487 }
488 ts := int64(fieldBuf.uint32())
489 modified = time.Unix(ts, 0)
490 }
491 }
492
493 msdosModified := msDosTimeToTime(f.ModifiedDate, f.ModifiedTime)
494 f.Modified = msdosModified
495 if !modified.IsZero() {
496 f.Modified = modified.UTC()
497
498
499
500
501
502
503
504
505
506 if f.ModifiedTime != 0 || f.ModifiedDate != 0 {
507 f.Modified = modified.In(timeZone(msdosModified.Sub(modified)))
508 }
509 }
510
511
512
513
514
515
516
517
518
519 _ = needUSize
520
521 if needCSize || needHeaderOffset {
522 return ErrFormat
523 }
524
525 return nil
526 }
527
528 func readDataDescriptor(r io.Reader, f *File) error {
529 var buf [dataDescriptorLen]byte
530
531
532
533
534
535
536
537
538
539
540 if _, err := io.ReadFull(r, buf[:4]); err != nil {
541 return err
542 }
543 off := 0
544 maybeSig := readBuf(buf[:4])
545 if maybeSig.uint32() != dataDescriptorSignature {
546
547
548 off += 4
549 }
550 if _, err := io.ReadFull(r, buf[off:12]); err != nil {
551 return err
552 }
553 b := readBuf(buf[:12])
554 if b.uint32() != f.CRC32 {
555 return ErrChecksum
556 }
557
558
559
560
561
562
563
564 return nil
565 }
566
567 func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, baseOffset int64, err error) {
568
569 var buf []byte
570 var directoryEndOffset int64
571 for i, bLen := range []int64{1024, 65 * 1024} {
572 if bLen > size {
573 bLen = size
574 }
575 buf = make([]byte, int(bLen))
576 if _, err := r.ReadAt(buf, size-bLen); err != nil && err != io.EOF {
577 return nil, 0, err
578 }
579 if p := findSignatureInBlock(buf); p >= 0 {
580 buf = buf[p:]
581 directoryEndOffset = size - bLen + int64(p)
582 break
583 }
584 if i == 1 || bLen == size {
585 return nil, 0, ErrFormat
586 }
587 }
588
589
590 b := readBuf(buf[4:])
591 d := &directoryEnd{
592 diskNbr: uint32(b.uint16()),
593 dirDiskNbr: uint32(b.uint16()),
594 dirRecordsThisDisk: uint64(b.uint16()),
595 directoryRecords: uint64(b.uint16()),
596 directorySize: uint64(b.uint32()),
597 directoryOffset: uint64(b.uint32()),
598 commentLen: b.uint16(),
599 }
600 l := int(d.commentLen)
601 if l > len(b) {
602 return nil, 0, errors.New("zip: invalid comment length")
603 }
604 d.comment = string(b[:l])
605
606
607 if d.directoryRecords == 0xffff || d.directorySize == 0xffff || d.directoryOffset == 0xffffffff {
608 p, err := findDirectory64End(r, directoryEndOffset)
609 if err == nil && p >= 0 {
610 directoryEndOffset = p
611 err = readDirectory64End(r, p, d)
612 }
613 if err != nil {
614 return nil, 0, err
615 }
616 }
617
618 maxInt64 := uint64(1<<63 - 1)
619 if d.directorySize > maxInt64 || d.directoryOffset > maxInt64 {
620 return nil, 0, ErrFormat
621 }
622
623 baseOffset = directoryEndOffset - int64(d.directorySize) - int64(d.directoryOffset)
624
625
626 if o := baseOffset + int64(d.directoryOffset); o < 0 || o >= size {
627 return nil, 0, ErrFormat
628 }
629
630
631
632
633
634
635 if baseOffset > 0 {
636 off := int64(d.directoryOffset)
637 rs := io.NewSectionReader(r, off, size-off)
638 if readDirectoryHeader(&File{}, rs) == nil {
639 baseOffset = 0
640 }
641 }
642
643 return d, baseOffset, nil
644 }
645
646
647
648
649 func findDirectory64End(r io.ReaderAt, directoryEndOffset int64) (int64, error) {
650 locOffset := directoryEndOffset - directory64LocLen
651 if locOffset < 0 {
652 return -1, nil
653 }
654 buf := make([]byte, directory64LocLen)
655 if _, err := r.ReadAt(buf, locOffset); err != nil {
656 return -1, err
657 }
658 b := readBuf(buf)
659 if sig := b.uint32(); sig != directory64LocSignature {
660 return -1, nil
661 }
662 if b.uint32() != 0 {
663 return -1, nil
664 }
665 p := b.uint64()
666 if b.uint32() != 1 {
667 return -1, nil
668 }
669 return int64(p), nil
670 }
671
672
673
674 func readDirectory64End(r io.ReaderAt, offset int64, d *directoryEnd) (err error) {
675 buf := make([]byte, directory64EndLen)
676 if _, err := r.ReadAt(buf, offset); err != nil {
677 return err
678 }
679
680 b := readBuf(buf)
681 if sig := b.uint32(); sig != directory64EndSignature {
682 return ErrFormat
683 }
684
685 b = b[12:]
686 d.diskNbr = b.uint32()
687 d.dirDiskNbr = b.uint32()
688 d.dirRecordsThisDisk = b.uint64()
689 d.directoryRecords = b.uint64()
690 d.directorySize = b.uint64()
691 d.directoryOffset = b.uint64()
692
693 return nil
694 }
695
696 func findSignatureInBlock(b []byte) int {
697 for i := len(b) - directoryEndLen; i >= 0; i-- {
698
699 if b[i] == 'P' && b[i+1] == 'K' && b[i+2] == 0x05 && b[i+3] == 0x06 {
700
701 n := int(b[i+directoryEndLen-2]) | int(b[i+directoryEndLen-1])<<8
702 if n+directoryEndLen+i > len(b) {
703
704
705
706 return -1
707 }
708 return i
709 }
710 }
711 return -1
712 }
713
714 type readBuf []byte
715
716 func (b *readBuf) uint8() uint8 {
717 v := (*b)[0]
718 *b = (*b)[1:]
719 return v
720 }
721
722 func (b *readBuf) uint16() uint16 {
723 v := binary.LittleEndian.Uint16(*b)
724 *b = (*b)[2:]
725 return v
726 }
727
728 func (b *readBuf) uint32() uint32 {
729 v := binary.LittleEndian.Uint32(*b)
730 *b = (*b)[4:]
731 return v
732 }
733
734 func (b *readBuf) uint64() uint64 {
735 v := binary.LittleEndian.Uint64(*b)
736 *b = (*b)[8:]
737 return v
738 }
739
740 func (b *readBuf) sub(n int) readBuf {
741 b2 := (*b)[:n]
742 *b = (*b)[n:]
743 return b2
744 }
745
746
747
748 type fileListEntry struct {
749 name string
750 file *File
751 isDir bool
752 isDup bool
753 }
754
755 type fileInfoDirEntry interface {
756 fs.FileInfo
757 fs.DirEntry
758 }
759
760 func (f *fileListEntry) stat() (fileInfoDirEntry, error) {
761 if f.isDup {
762 return nil, errors.New(f.name + ": duplicate entries in zip file")
763 }
764 if !f.isDir {
765 return headerFileInfo{&f.file.FileHeader}, nil
766 }
767 return f, nil
768 }
769
770
771 func (f *fileListEntry) Name() string { _, elem, _ := split(f.name); return elem }
772 func (f *fileListEntry) Size() int64 { return 0 }
773 func (f *fileListEntry) Mode() fs.FileMode { return fs.ModeDir | 0555 }
774 func (f *fileListEntry) Type() fs.FileMode { return fs.ModeDir }
775 func (f *fileListEntry) IsDir() bool { return true }
776 func (f *fileListEntry) Sys() any { return nil }
777
778 func (f *fileListEntry) ModTime() time.Time {
779 if f.file == nil {
780 return time.Time{}
781 }
782 return f.file.FileHeader.Modified.UTC()
783 }
784
785 func (f *fileListEntry) Info() (fs.FileInfo, error) { return f, nil }
786
787 func (f *fileListEntry) String() string {
788 return fs.FormatDirEntry(f)
789 }
790
791
792 func toValidName(name string) string {
793 name = strings.ReplaceAll(name, `\`, `/`)
794 p := path.Clean(name)
795
796 p = strings.TrimPrefix(p, "/")
797
798 for strings.HasPrefix(p, "../") {
799 p = p[len("../"):]
800 }
801
802 return p
803 }
804
805 func (r *Reader) initFileList() {
806 r.fileListOnce.Do(func() {
807
808
809
810 files := make(map[string]int)
811 knownDirs := make(map[string]int)
812
813
814
815 dirs := make(map[string]bool)
816
817 for _, file := range r.File {
818 isDir := len(file.Name) > 0 && file.Name[len(file.Name)-1] == '/'
819 name := toValidName(file.Name)
820 if name == "" {
821 continue
822 }
823
824 if idx, ok := files[name]; ok {
825 r.fileList[idx].isDup = true
826 continue
827 }
828 if idx, ok := knownDirs[name]; ok {
829 r.fileList[idx].isDup = true
830 continue
831 }
832
833 for dir := path.Dir(name); dir != "."; dir = path.Dir(dir) {
834 dirs[dir] = true
835 }
836
837 idx := len(r.fileList)
838 entry := fileListEntry{
839 name: name,
840 file: file,
841 isDir: isDir,
842 }
843 r.fileList = append(r.fileList, entry)
844 if isDir {
845 knownDirs[name] = idx
846 } else {
847 files[name] = idx
848 }
849 }
850 for dir := range dirs {
851 if _, ok := knownDirs[dir]; !ok {
852 if idx, ok := files[dir]; ok {
853 r.fileList[idx].isDup = true
854 } else {
855 entry := fileListEntry{
856 name: dir,
857 file: nil,
858 isDir: true,
859 }
860 r.fileList = append(r.fileList, entry)
861 }
862 }
863 }
864
865 slices.SortFunc(r.fileList, func(a, b fileListEntry) int {
866 return fileEntryCompare(a.name, b.name)
867 })
868 })
869 }
870
871 func fileEntryCompare(x, y string) int {
872 xdir, xelem, _ := split(x)
873 ydir, yelem, _ := split(y)
874 if xdir != ydir {
875 return strings.Compare(xdir, ydir)
876 }
877 return strings.Compare(xelem, yelem)
878 }
879
880
881
882
883
884 func (r *Reader) Open(name string) (fs.File, error) {
885 r.initFileList()
886
887 if !fs.ValidPath(name) {
888 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
889 }
890 e := r.openLookup(name)
891 if e == nil {
892 return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrNotExist}
893 }
894 if e.isDir {
895 return &openDir{e, r.openReadDir(name), 0}, nil
896 }
897 rc, err := e.file.Open()
898 if err != nil {
899 return nil, err
900 }
901 return rc.(fs.File), nil
902 }
903
904 func split(name string) (dir, elem string, isDir bool) {
905 name, isDir = strings.CutSuffix(name, "/")
906 i := strings.LastIndexByte(name, '/')
907 if i < 0 {
908 return ".", name, isDir
909 }
910 return name[:i], name[i+1:], isDir
911 }
912
913 var dotFile = &fileListEntry{name: "./", isDir: true}
914
915 func (r *Reader) openLookup(name string) *fileListEntry {
916 if name == "." {
917 return dotFile
918 }
919
920 dir, elem, _ := split(name)
921 files := r.fileList
922 i, _ := slices.BinarySearchFunc(files, dir, func(a fileListEntry, dir string) (ret int) {
923 idir, ielem, _ := split(a.name)
924 if dir != idir {
925 return strings.Compare(idir, dir)
926 }
927 return strings.Compare(ielem, elem)
928 })
929 if i < len(files) {
930 fname := files[i].name
931 if fname == name || len(fname) == len(name)+1 && fname[len(name)] == '/' && fname[:len(name)] == name {
932 return &files[i]
933 }
934 }
935 return nil
936 }
937
938 func (r *Reader) openReadDir(dir string) []fileListEntry {
939 files := r.fileList
940 i, _ := slices.BinarySearchFunc(files, dir, func(a fileListEntry, dir string) int {
941 idir, _, _ := split(a.name)
942 if dir != idir {
943 return strings.Compare(idir, dir)
944 }
945
946 return +1
947 })
948 j, _ := slices.BinarySearchFunc(files, dir, func(a fileListEntry, dir string) int {
949 jdir, _, _ := split(a.name)
950 if dir != jdir {
951 return strings.Compare(jdir, dir)
952 }
953
954 return -1
955 })
956 return files[i:j]
957 }
958
959 type openDir struct {
960 e *fileListEntry
961 files []fileListEntry
962 offset int
963 }
964
965 func (d *openDir) Close() error { return nil }
966 func (d *openDir) Stat() (fs.FileInfo, error) { return d.e.stat() }
967
968 func (d *openDir) Read([]byte) (int, error) {
969 return 0, &fs.PathError{Op: "read", Path: d.e.name, Err: errors.New("is a directory")}
970 }
971
972 func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
973 n := len(d.files) - d.offset
974 if count > 0 && n > count {
975 n = count
976 }
977 if n == 0 {
978 if count <= 0 {
979 return nil, nil
980 }
981 return nil, io.EOF
982 }
983 list := make([]fs.DirEntry, n)
984 for i := range list {
985 s, err := d.files[d.offset+i].stat()
986 if err != nil {
987 return nil, err
988 }
989 list[i] = s
990 }
991 d.offset += n
992 return list, nil
993 }
994
View as plain text