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