@@ -33,6 +33,10 @@ type Reader struct {
33
33
Comment string
34
34
decompressors map [uint16 ]Decompressor
35
35
36
+ // Some JAR files are zip files with a prefix that is a bash script.
37
+ // The baseOffset field is the start of the zip file proper.
38
+ baseOffset int64
39
+
36
40
// fileList is a list of files sorted by ename,
37
41
// for use by the Open method.
38
42
fileListOnce sync.Once
@@ -52,9 +56,8 @@ type File struct {
52
56
FileHeader
53
57
zip * Reader
54
58
zipr io.ReaderAt
55
- headerOffset int64
59
+ headerOffset int64 // includes overall ZIP archive baseOffset
56
60
zip64 bool // zip64 extended information extra field presence
57
- descErr error // error reading the data descriptor during init
58
61
}
59
62
60
63
// OpenReader will open the Zip file specified by name and return a ReadCloser.
@@ -91,23 +94,24 @@ func NewReader(r io.ReaderAt, size int64) (*Reader, error) {
91
94
}
92
95
93
96
func (z * Reader ) init (r io.ReaderAt , size int64 ) error {
94
- end , err := readDirectoryEnd (r , size )
97
+ end , baseOffset , err := readDirectoryEnd (r , size )
95
98
if err != nil {
96
99
return err
97
100
}
98
101
z .r = r
102
+ z .baseOffset = baseOffset
99
103
// Since the number of directory records is not validated, it is not
100
104
// safe to preallocate z.File without first checking that the specified
101
105
// number of files is reasonable, since a malformed archive may
102
106
// indicate it contains up to 1 << 128 - 1 files. Since each file has a
103
107
// header which will be _at least_ 30 bytes we can safely preallocate
104
108
// if (data size / 30) >= end.directoryRecords.
105
- if (uint64 (size )- end .directorySize )/ 30 >= end .directoryRecords {
109
+ if end . directorySize < uint64 ( size ) && (uint64 (size )- end .directorySize )/ 30 >= end .directoryRecords {
106
110
z .File = make ([]* File , 0 , end .directoryRecords )
107
111
}
108
112
z .Comment = end .comment
109
113
rs := io .NewSectionReader (r , 0 , size )
110
- if _ , err = rs .Seek (int64 (end .directoryOffset ), io .SeekStart ); err != nil {
114
+ if _ , err = rs .Seek (z . baseOffset + int64 (end .directoryOffset ), io .SeekStart ); err != nil {
111
115
return err
112
116
}
113
117
buf := bufio .NewReader (rs )
@@ -119,12 +123,27 @@ func (z *Reader) init(r io.ReaderAt, size int64) error {
119
123
for {
120
124
f := & File {zip : z , zipr : r }
121
125
err = readDirectoryHeader (f , buf )
126
+
127
+ // For compatibility with other zip programs,
128
+ // if we have a non-zero base offset and can't read
129
+ // the first directory header, try again with a zero
130
+ // base offset.
131
+ if err == ErrFormat && z .baseOffset != 0 && len (z .File ) == 0 {
132
+ z .baseOffset = 0
133
+ if _ , err = rs .Seek (int64 (end .directoryOffset ), io .SeekStart ); err != nil {
134
+ return err
135
+ }
136
+ buf .Reset (rs )
137
+ continue
138
+ }
139
+
122
140
if err == ErrFormat || err == io .ErrUnexpectedEOF {
123
141
break
124
142
}
125
143
if err != nil {
126
144
return err
127
145
}
146
+ f .headerOffset += z .baseOffset
128
147
z .File = append (z .File , f )
129
148
}
130
149
if uint16 (len (z .File )) != uint16 (end .directoryRecords ) { // only compare 16 bits here
@@ -229,6 +248,9 @@ func (r *checksumReader) Read(b []byte) (n int, err error) {
229
248
n , err = r .rc .Read (b )
230
249
r .hash .Write (b [:n ])
231
250
r .nread += uint64 (n )
251
+ if r .nread > r .f .UncompressedSize64 {
252
+ return 0 , ErrFormat
253
+ }
232
254
if err == nil {
233
255
return
234
256
}
@@ -492,7 +514,7 @@ func readDataDescriptor(r io.Reader, f *File) error {
492
514
return nil
493
515
}
494
516
495
- func readDirectoryEnd (r io.ReaderAt , size int64 ) (dir * directoryEnd , err error ) {
517
+ func readDirectoryEnd (r io.ReaderAt , size int64 ) (dir * directoryEnd , baseOffset int64 , err error ) {
496
518
// look for directoryEndSignature in the last 1k, then in the last 65k
497
519
var buf []byte
498
520
var directoryEndOffset int64
@@ -502,15 +524,15 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error)
502
524
}
503
525
buf = make ([]byte , int (bLen ))
504
526
if _ , err := r .ReadAt (buf , size - bLen ); err != nil && err != io .EOF {
505
- return nil , err
527
+ return nil , 0 , err
506
528
}
507
529
if p := findSignatureInBlock (buf ); p >= 0 {
508
530
buf = buf [p :]
509
531
directoryEndOffset = size - bLen + int64 (p )
510
532
break
511
533
}
512
534
if i == 1 || bLen == size {
513
- return nil , ErrFormat
535
+ return nil , 0 , ErrFormat
514
536
}
515
537
}
516
538
@@ -527,25 +549,29 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error)
527
549
}
528
550
l := int (d .commentLen )
529
551
if l > len (b ) {
530
- return nil , errors .New ("zip: invalid comment length" )
552
+ return nil , 0 , errors .New ("zip: invalid comment length" )
531
553
}
532
554
d .comment = string (b [:l ])
533
555
534
556
// These values mean that the file can be a zip64 file
535
557
if d .directoryRecords == 0xffff || d .directorySize == 0xffff || d .directoryOffset == 0xffffffff {
536
558
p , err := findDirectory64End (r , directoryEndOffset )
537
559
if err == nil && p >= 0 {
560
+ directoryEndOffset = p
538
561
err = readDirectory64End (r , p , d )
539
562
}
540
563
if err != nil {
541
- return nil , err
564
+ return nil , 0 , err
542
565
}
543
566
}
567
+
568
+ baseOffset = directoryEndOffset - int64 (d .directorySize ) - int64 (d .directoryOffset )
569
+
544
570
// Make sure directoryOffset points to somewhere in our file.
545
- if o := int64 (d .directoryOffset ); o < 0 || o >= size {
546
- return nil , ErrFormat
571
+ if o := baseOffset + int64 (d .directoryOffset ); o < 0 || o >= size {
572
+ return nil , 0 , ErrFormat
547
573
}
548
- return d , nil
574
+ return d , baseOffset , nil
549
575
}
550
576
551
577
// findDirectory64End tries to read the zip64 locator just before the
@@ -650,18 +676,22 @@ type fileListEntry struct {
650
676
name string
651
677
file * File
652
678
isDir bool
679
+ isDup bool
653
680
}
654
681
655
682
type fileInfoDirEntry interface {
656
683
fs.FileInfo
657
684
fs.DirEntry
658
685
}
659
686
660
- func (e * fileListEntry ) stat () fileInfoDirEntry {
687
+ func (e * fileListEntry ) stat () (fileInfoDirEntry , error ) {
688
+ if e .isDup {
689
+ return nil , errors .New (e .name + ": duplicate entries in zip file" )
690
+ }
661
691
if ! e .isDir {
662
- return headerFileInfo {& e .file .FileHeader }
692
+ return headerFileInfo {& e .file .FileHeader }, nil
663
693
}
664
- return e
694
+ return e , nil
665
695
}
666
696
667
697
// Only used for directories.
@@ -696,32 +726,61 @@ func toValidName(name string) string {
696
726
697
727
func (r * Reader ) initFileList () {
698
728
r .fileListOnce .Do (func () {
729
+ // files and knownDirs map from a file/directory name
730
+ // to an index into the r.fileList entry that we are
731
+ // building. They are used to mark duplicate entries.
732
+ files := make (map [string ]int )
733
+ knownDirs := make (map [string ]int )
734
+
735
+ // dirs[name] is true if name is known to be a directory,
736
+ // because it appears as a prefix in a path.
699
737
dirs := make (map [string ]bool )
700
- knownDirs := make ( map [ string ] bool )
738
+
701
739
for _ , file := range r .File {
702
740
isDir := len (file .Name ) > 0 && file .Name [len (file .Name )- 1 ] == '/'
703
741
name := toValidName (file .Name )
742
+ if name == "" {
743
+ continue
744
+ }
745
+
746
+ if idx , ok := files [name ]; ok {
747
+ r .fileList [idx ].isDup = true
748
+ continue
749
+ }
750
+ if idx , ok := knownDirs [name ]; ok {
751
+ r .fileList [idx ].isDup = true
752
+ continue
753
+ }
754
+
704
755
for dir := path .Dir (name ); dir != "." ; dir = path .Dir (dir ) {
705
756
dirs [dir ] = true
706
757
}
758
+
759
+ idx := len (r .fileList )
707
760
entry := fileListEntry {
708
761
name : name ,
709
762
file : file ,
710
763
isDir : isDir ,
711
764
}
712
765
r .fileList = append (r .fileList , entry )
713
766
if isDir {
714
- knownDirs [name ] = true
767
+ knownDirs [name ] = idx
768
+ } else {
769
+ files [name ] = idx
715
770
}
716
771
}
717
772
for dir := range dirs {
718
- if ! knownDirs [dir ] {
719
- entry := fileListEntry {
720
- name : dir ,
721
- file : nil ,
722
- isDir : true ,
773
+ if _ , ok := knownDirs [dir ]; ! ok {
774
+ if idx , ok := files [dir ]; ok {
775
+ r .fileList [idx ].isDup = true
776
+ } else {
777
+ entry := fileListEntry {
778
+ name : dir ,
779
+ file : nil ,
780
+ isDir : true ,
781
+ }
782
+ r .fileList = append (r .fileList , entry )
723
783
}
724
- r .fileList = append (r .fileList , entry )
725
784
}
726
785
}
727
786
@@ -740,12 +799,11 @@ func fileEntryLess(x, y string) bool {
740
799
// paths are always slash separated, with no
741
800
// leading / or ../ elements.
742
801
func (r * Reader ) Open (name string ) (fs.File , error ) {
802
+ r .initFileList ()
803
+
743
804
if ! fs .ValidPath (name ) {
744
805
return nil , & fs.PathError {Op : "open" , Path : name , Err : fs .ErrInvalid }
745
806
}
746
-
747
- r .initFileList ()
748
-
749
807
e := r .openLookup (name )
750
808
if e == nil {
751
809
return nil , & fs.PathError {Op : "open" , Path : name , Err : fs .ErrNotExist }
@@ -761,7 +819,7 @@ func (r *Reader) Open(name string) (fs.File, error) {
761
819
}
762
820
763
821
func split (name string ) (dir , elem string , isDir bool ) {
764
- if name [len (name )- 1 ] == '/' {
822
+ if len ( name ) > 0 && name [len (name )- 1 ] == '/' {
765
823
isDir = true
766
824
name = name [:len (name )- 1 ]
767
825
}
@@ -817,7 +875,7 @@ type openDir struct {
817
875
}
818
876
819
877
func (d * openDir ) Close () error { return nil }
820
- func (d * openDir ) Stat () (fs.FileInfo , error ) { return d .e .stat (), nil }
878
+ func (d * openDir ) Stat () (fs.FileInfo , error ) { return d .e .stat () }
821
879
822
880
func (d * openDir ) Read ([]byte ) (int , error ) {
823
881
return 0 , & fs.PathError {Op : "read" , Path : d .e .name , Err : errors .New ("is a directory" )}
@@ -836,7 +894,11 @@ func (d *openDir) ReadDir(count int) ([]fs.DirEntry, error) {
836
894
}
837
895
list := make ([]fs.DirEntry , n )
838
896
for i := range list {
839
- list [i ] = d .files [d .offset + i ].stat ()
897
+ s , err := d .files [d .offset + i ].stat ()
898
+ if err != nil {
899
+ return nil , err
900
+ }
901
+ list [i ] = s
840
902
}
841
903
d .offset += n
842
904
return list , nil
0 commit comments