src/archive/zip/reader_test.go GO 1,958 lines View on github.com → Search inside
1// Copyright 2010 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package zip67import (8	"bytes"9	"encoding/binary"10	"encoding/hex"11	"errors"12	"fmt"13	"internal/obscuretestdata"14	"io"15	"io/fs"16	"os"17	"path/filepath"18	"regexp"19	"slices"20	"strings"21	"testing"22	"testing/fstest"23	"time"24)2526type ZipTest struct {27	Name     string28	Source   func() (r io.ReaderAt, size int64) // if non-nil, used instead of testdata/<Name> file29	Comment  string30	File     []ZipTestFile31	Obscured bool  // needed for Apple notarization (golang.org/issue/34986)32	Error    error // the error that Opening this file should return33}3435type ZipTestFile struct {36	Name     string37	Mode     fs.FileMode38	NonUTF8  bool39	ModTime  time.Time40	Modified time.Time4142	// Information describing expected zip file content.43	// First, reading the entire content should produce the error ContentErr.44	// Second, if ContentErr==nil, the content should match Content.45	// If content is large, an alternative to setting Content is to set File,46	// which names a file in the testdata/ directory containing the47	// uncompressed expected content.48	// If content is very large, an alternative to setting Content or File49	// is to set Size, which will then be checked against the header-reported size50	// but will bypass the decompressing of the actual data.51	// This last option is used for testing very large (multi-GB) compressed files.52	ContentErr error53	Content    []byte54	File       string55	Size       uint6456}5758var tests = []ZipTest{59	{60		Name:    "test.zip",61		Comment: "This is a zipfile comment.",62		File: []ZipTestFile{63			{64				Name:     "test.txt",65				Content:  []byte("This is a test text file.\n"),66				Modified: time.Date(2010, 9, 5, 12, 12, 1, 0, timeZone(+10*time.Hour)),67				Mode:     0644,68			},69			{70				Name:     "gophercolor16x16.png",71				File:     "gophercolor16x16.png",72				Modified: time.Date(2010, 9, 5, 15, 52, 58, 0, timeZone(+10*time.Hour)),73				Mode:     0644,74			},75		},76	},77	{78		Name:    "test-trailing-junk.zip",79		Comment: "This is a zipfile comment.",80		File: []ZipTestFile{81			{82				Name:     "test.txt",83				Content:  []byte("This is a test text file.\n"),84				Modified: time.Date(2010, 9, 5, 12, 12, 1, 0, timeZone(+10*time.Hour)),85				Mode:     0644,86			},87			{88				Name:     "gophercolor16x16.png",89				File:     "gophercolor16x16.png",90				Modified: time.Date(2010, 9, 5, 15, 52, 58, 0, timeZone(+10*time.Hour)),91				Mode:     0644,92			},93		},94	},95	{96		Name:    "test-prefix.zip",97		Comment: "This is a zipfile comment.",98		File: []ZipTestFile{99			{100				Name:     "test.txt",101				Content:  []byte("This is a test text file.\n"),102				Modified: time.Date(2010, 9, 5, 12, 12, 1, 0, timeZone(+10*time.Hour)),103				Mode:     0644,104			},105			{106				Name:     "gophercolor16x16.png",107				File:     "gophercolor16x16.png",108				Modified: time.Date(2010, 9, 5, 15, 52, 58, 0, timeZone(+10*time.Hour)),109				Mode:     0644,110			},111		},112	},113	{114		Name:    "test-baddirsz.zip",115		Comment: "This is a zipfile comment.",116		File: []ZipTestFile{117			{118				Name:     "test.txt",119				Content:  []byte("This is a test text file.\n"),120				Modified: time.Date(2010, 9, 5, 12, 12, 1, 0, timeZone(+10*time.Hour)),121				Mode:     0644,122			},123			{124				Name:     "gophercolor16x16.png",125				File:     "gophercolor16x16.png",126				Modified: time.Date(2010, 9, 5, 15, 52, 58, 0, timeZone(+10*time.Hour)),127				Mode:     0644,128			},129		},130	},131	{132		Name:    "test-badbase.zip",133		Comment: "This is a zipfile comment.",134		File: []ZipTestFile{135			{136				Name:     "test.txt",137				Content:  []byte("This is a test text file.\n"),138				Modified: time.Date(2010, 9, 5, 12, 12, 1, 0, timeZone(+10*time.Hour)),139				Mode:     0644,140			},141			{142				Name:     "gophercolor16x16.png",143				File:     "gophercolor16x16.png",144				Modified: time.Date(2010, 9, 5, 15, 52, 58, 0, timeZone(+10*time.Hour)),145				Mode:     0644,146			},147		},148	},149	{150		Name:   "r.zip",151		Source: returnRecursiveZip,152		File: []ZipTestFile{153			{154				Name:     "r/r.zip",155				Content:  rZipBytes(),156				Modified: time.Date(2010, 3, 4, 0, 24, 16, 0, time.UTC),157				Mode:     0666,158			},159		},160	},161	{162		Name: "symlink.zip",163		File: []ZipTestFile{164			{165				Name:     "symlink",166				Content:  []byte("../target"),167				Modified: time.Date(2012, 2, 3, 19, 56, 48, 0, timeZone(-2*time.Hour)),168				Mode:     0777 | fs.ModeSymlink,169			},170		},171	},172	{173		Name: "readme.zip",174	},175	{176		Name:  "readme.notzip",177		Error: ErrFormat,178	},179	{180		Name: "dd.zip",181		File: []ZipTestFile{182			{183				Name:     "filename",184				Content:  []byte("This is a test textfile.\n"),185				Modified: time.Date(2011, 2, 2, 13, 6, 20, 0, time.UTC),186				Mode:     0666,187			},188		},189	},190	{191		// created in windows XP file manager.192		Name: "winxp.zip",193		File: []ZipTestFile{194			{195				Name:     "hello",196				Content:  []byte("world \r\n"),197				Modified: time.Date(2011, 12, 8, 10, 4, 24, 0, time.UTC),198				Mode:     0666,199			},200			{201				Name:     "dir/bar",202				Content:  []byte("foo \r\n"),203				Modified: time.Date(2011, 12, 8, 10, 4, 50, 0, time.UTC),204				Mode:     0666,205			},206			{207				Name:     "dir/empty/",208				Content:  []byte{},209				Modified: time.Date(2011, 12, 8, 10, 8, 6, 0, time.UTC),210				Mode:     fs.ModeDir | 0777,211			},212			{213				Name:     "readonly",214				Content:  []byte("important \r\n"),215				Modified: time.Date(2011, 12, 8, 10, 6, 8, 0, time.UTC),216				Mode:     0444,217			},218		},219	},220	{221		// created by Zip 3.0 under Linux222		Name: "unix.zip",223		File: []ZipTestFile{224			{225				Name:     "hello",226				Content:  []byte("world \r\n"),227				Modified: time.Date(2011, 12, 8, 10, 4, 24, 0, timeZone(0)),228				Mode:     0666,229			},230			{231				Name:     "dir/bar",232				Content:  []byte("foo \r\n"),233				Modified: time.Date(2011, 12, 8, 10, 4, 50, 0, timeZone(0)),234				Mode:     0666,235			},236			{237				Name:     "dir/empty/",238				Content:  []byte{},239				Modified: time.Date(2011, 12, 8, 10, 8, 6, 0, timeZone(0)),240				Mode:     fs.ModeDir | 0777,241			},242			{243				Name:     "readonly",244				Content:  []byte("important \r\n"),245				Modified: time.Date(2011, 12, 8, 10, 6, 8, 0, timeZone(0)),246				Mode:     0444,247			},248		},249	},250	{251		// created by Go, before we wrote the "optional" data252		// descriptor signatures (which are required by macOS).253		// Use obscured file to avoid Apple’s notarization service254		// rejecting the toolchain due to an inability to unzip this archive.255		// See golang.org/issue/34986256		Name:     "go-no-datadesc-sig.zip.base64",257		Obscured: true,258		File: []ZipTestFile{259			{260				Name:     "foo.txt",261				Content:  []byte("foo\n"),262				Modified: time.Date(2012, 3, 8, 16, 59, 10, 0, timeZone(-8*time.Hour)),263				Mode:     0644,264			},265			{266				Name:     "bar.txt",267				Content:  []byte("bar\n"),268				Modified: time.Date(2012, 3, 8, 16, 59, 12, 0, timeZone(-8*time.Hour)),269				Mode:     0644,270			},271		},272	},273	{274		// created by Go, after we wrote the "optional" data275		// descriptor signatures (which are required by macOS)276		Name: "go-with-datadesc-sig.zip",277		File: []ZipTestFile{278			{279				Name:     "foo.txt",280				Content:  []byte("foo\n"),281				Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC),282				Mode:     0666,283			},284			{285				Name:     "bar.txt",286				Content:  []byte("bar\n"),287				Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC),288				Mode:     0666,289			},290		},291	},292	{293		Name:   "Bad-CRC32-in-data-descriptor",294		Source: returnCorruptCRC32Zip,295		File: []ZipTestFile{296			{297				Name:       "foo.txt",298				Content:    []byte("foo\n"),299				Modified:   time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC),300				Mode:       0666,301				ContentErr: ErrChecksum,302			},303			{304				Name:     "bar.txt",305				Content:  []byte("bar\n"),306				Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC),307				Mode:     0666,308			},309		},310	},311	// Tests that we verify (and accept valid) crc32s on files312	// with crc32s in their file header (not in data descriptors)313	{314		Name: "crc32-not-streamed.zip",315		File: []ZipTestFile{316			{317				Name:     "foo.txt",318				Content:  []byte("foo\n"),319				Modified: time.Date(2012, 3, 8, 16, 59, 10, 0, timeZone(-8*time.Hour)),320				Mode:     0644,321			},322			{323				Name:     "bar.txt",324				Content:  []byte("bar\n"),325				Modified: time.Date(2012, 3, 8, 16, 59, 12, 0, timeZone(-8*time.Hour)),326				Mode:     0644,327			},328		},329	},330	// Tests that we verify (and reject invalid) crc32s on files331	// with crc32s in their file header (not in data descriptors)332	{333		Name:   "crc32-not-streamed.zip",334		Source: returnCorruptNotStreamedZip,335		File: []ZipTestFile{336			{337				Name:       "foo.txt",338				Content:    []byte("foo\n"),339				Modified:   time.Date(2012, 3, 8, 16, 59, 10, 0, timeZone(-8*time.Hour)),340				Mode:       0644,341				ContentErr: ErrChecksum,342			},343			{344				Name:     "bar.txt",345				Content:  []byte("bar\n"),346				Modified: time.Date(2012, 3, 8, 16, 59, 12, 0, timeZone(-8*time.Hour)),347				Mode:     0644,348			},349		},350	},351	{352		Name: "zip64.zip",353		File: []ZipTestFile{354			{355				Name:     "README",356				Content:  []byte("This small file is in ZIP64 format.\n"),357				Modified: time.Date(2012, 8, 10, 14, 33, 32, 0, time.UTC),358				Mode:     0644,359			},360		},361	},362	// Another zip64 file with different Extras fields. (golang.org/issue/7069)363	{364		Name: "zip64-2.zip",365		File: []ZipTestFile{366			{367				Name:     "README",368				Content:  []byte("This small file is in ZIP64 format.\n"),369				Modified: time.Date(2012, 8, 10, 14, 33, 32, 0, timeZone(-4*time.Hour)),370				Mode:     0644,371			},372		},373	},374	// Largest possible non-zip64 file, with no zip64 header.375	{376		Name:   "big.zip",377		Source: returnBigZipBytes,378		File: []ZipTestFile{379			{380				Name:     "big.file",381				Content:  nil,382				Size:     1<<32 - 1,383				Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC),384				Mode:     0666,385			},386		},387	},388	{389		Name: "utf8-7zip.zip",390		File: []ZipTestFile{391			{392				Name:     "世界",393				Content:  []byte{},394				Mode:     0666,395				Modified: time.Date(2017, 11, 6, 13, 9, 27, 867862500, timeZone(-8*time.Hour)),396			},397		},398	},399	{400		Name: "utf8-infozip.zip",401		File: []ZipTestFile{402			{403				Name:    "世界",404				Content: []byte{},405				Mode:    0644,406				// Name is valid UTF-8, but format does not have UTF-8 flag set.407				// We don't do UTF-8 detection for multi-byte runes due to408				// false-positives with other encodings (e.g., Shift-JIS).409				// Format says encoding is not UTF-8, so we trust it.410				NonUTF8:  true,411				Modified: time.Date(2017, 11, 6, 13, 9, 27, 0, timeZone(-8*time.Hour)),412			},413		},414	},415	{416		Name: "utf8-osx.zip",417		File: []ZipTestFile{418			{419				Name:    "世界",420				Content: []byte{},421				Mode:    0644,422				// Name is valid UTF-8, but format does not have UTF-8 set.423				NonUTF8:  true,424				Modified: time.Date(2017, 11, 6, 13, 9, 27, 0, timeZone(-8*time.Hour)),425			},426		},427	},428	{429		Name: "utf8-winrar.zip",430		File: []ZipTestFile{431			{432				Name:     "世界",433				Content:  []byte{},434				Mode:     0666,435				Modified: time.Date(2017, 11, 6, 13, 9, 27, 867862500, timeZone(-8*time.Hour)),436			},437		},438	},439	{440		Name: "utf8-winzip.zip",441		File: []ZipTestFile{442			{443				Name:     "世界",444				Content:  []byte{},445				Mode:     0666,446				Modified: time.Date(2017, 11, 6, 13, 9, 27, 867000000, timeZone(-8*time.Hour)),447			},448		},449	},450	{451		Name: "time-7zip.zip",452		File: []ZipTestFile{453			{454				Name:     "test.txt",455				Content:  []byte{},456				Size:     1<<32 - 1,457				Modified: time.Date(2017, 10, 31, 21, 11, 57, 244817900, timeZone(-7*time.Hour)),458				Mode:     0666,459			},460		},461	},462	{463		Name: "time-infozip.zip",464		File: []ZipTestFile{465			{466				Name:     "test.txt",467				Content:  []byte{},468				Size:     1<<32 - 1,469				Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)),470				Mode:     0644,471			},472		},473	},474	{475		Name: "time-osx.zip",476		File: []ZipTestFile{477			{478				Name:     "test.txt",479				Content:  []byte{},480				Size:     1<<32 - 1,481				Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)),482				Mode:     0644,483			},484		},485	},486	{487		Name: "time-win7.zip",488		File: []ZipTestFile{489			{490				Name:     "test.txt",491				Content:  []byte{},492				Size:     1<<32 - 1,493				Modified: time.Date(2017, 10, 31, 21, 11, 58, 0, time.UTC),494				Mode:     0666,495			},496		},497	},498	{499		Name: "time-winrar.zip",500		File: []ZipTestFile{501			{502				Name:     "test.txt",503				Content:  []byte{},504				Size:     1<<32 - 1,505				Modified: time.Date(2017, 10, 31, 21, 11, 57, 244817900, timeZone(-7*time.Hour)),506				Mode:     0666,507			},508		},509	},510	{511		Name: "time-winzip.zip",512		File: []ZipTestFile{513			{514				Name:     "test.txt",515				Content:  []byte{},516				Size:     1<<32 - 1,517				Modified: time.Date(2017, 10, 31, 21, 11, 57, 244000000, timeZone(-7*time.Hour)),518				Mode:     0666,519			},520		},521	},522	{523		Name: "time-go.zip",524		File: []ZipTestFile{525			{526				Name:     "test.txt",527				Content:  []byte{},528				Size:     1<<32 - 1,529				Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)),530				Mode:     0666,531			},532		},533	},534	{535		Name: "time-22738.zip",536		File: []ZipTestFile{537			{538				Name:     "file",539				Content:  []byte{},540				Mode:     0666,541				Modified: time.Date(1999, 12, 31, 19, 0, 0, 0, timeZone(-5*time.Hour)),542				ModTime:  time.Date(1999, 12, 31, 19, 0, 0, 0, time.UTC),543			},544		},545	},546	{547		Name: "dupdir.zip",548		File: []ZipTestFile{549			{550				Name:     "a/",551				Content:  []byte{},552				Mode:     fs.ModeDir | 0666,553				Modified: time.Date(2021, 12, 29, 0, 0, 0, 0, timeZone(0)),554			},555			{556				Name:     "a/b",557				Content:  []byte{},558				Mode:     0666,559				Modified: time.Date(2021, 12, 29, 0, 0, 0, 0, timeZone(0)),560			},561			{562				Name:     "a/b/",563				Content:  []byte{},564				Mode:     fs.ModeDir | 0666,565				Modified: time.Date(2021, 12, 29, 0, 0, 0, 0, timeZone(0)),566			},567			{568				Name:     "a/b/c",569				Content:  []byte{},570				Mode:     0666,571				Modified: time.Date(2021, 12, 29, 0, 0, 0, 0, timeZone(0)),572			},573		},574	},575	// Issue 66869: Don't skip over an EOCDR with a truncated comment.576	// The test file sneakily hides a second EOCDR before the first one;577	// previously we would extract one file ("file") from this archive,578	// while most other tools would reject the file or extract a different one ("FILE").579	{580		Name:  "comment-truncated.zip",581		Error: ErrFormat,582	},583}584585func TestReader(t *testing.T) {586	for _, zt := range tests {587		t.Run(zt.Name, func(t *testing.T) {588			readTestZip(t, zt)589		})590	}591}592593func readTestZip(t *testing.T, zt ZipTest) {594	var z *Reader595	var err error596	var raw []byte597	if zt.Source != nil {598		rat, size := zt.Source()599		z, err = NewReader(rat, size)600		raw = make([]byte, size)601		if _, err := rat.ReadAt(raw, 0); err != nil {602			t.Errorf("ReadAt error=%v", err)603			return604		}605	} else {606		path := filepath.Join("testdata", zt.Name)607		if zt.Obscured {608			tf, err := obscuretestdata.DecodeToTempFile(path)609			if err != nil {610				t.Errorf("obscuretestdata.DecodeToTempFile(%s): %v", path, err)611				return612			}613			defer os.Remove(tf)614			path = tf615		}616		var rc *ReadCloser617		rc, err = OpenReader(path)618		if err == nil {619			defer rc.Close()620			z = &rc.Reader621		}622		var err2 error623		raw, err2 = os.ReadFile(path)624		if err2 != nil {625			t.Errorf("ReadFile(%s) error=%v", path, err2)626			return627		}628	}629	if err != zt.Error {630		t.Errorf("error=%v, want %v", err, zt.Error)631		return632	}633634	// bail if file is not zip635	if err == ErrFormat {636		return637	}638639	// bail here if no Files expected to be tested640	// (there may actually be files in the zip, but we don't care)641	if zt.File == nil {642		return643	}644645	if z.Comment != zt.Comment {646		t.Errorf("comment=%q, want %q", z.Comment, zt.Comment)647	}648	if len(z.File) != len(zt.File) {649		t.Fatalf("file count=%d, want %d", len(z.File), len(zt.File))650	}651652	// test read of each file653	for i, ft := range zt.File {654		readTestFile(t, zt, ft, z.File[i], raw)655	}656	if t.Failed() {657		return658	}659660	// test simultaneous reads661	n := 0662	done := make(chan bool)663	for i := 0; i < 5; i++ {664		for j, ft := range zt.File {665			go func(j int, ft ZipTestFile) {666				readTestFile(t, zt, ft, z.File[j], raw)667				done <- true668			}(j, ft)669			n++670		}671	}672	for ; n > 0; n-- {673		<-done674	}675}676677func equalTimeAndZone(t1, t2 time.Time) bool {678	name1, offset1 := t1.Zone()679	name2, offset2 := t2.Zone()680	return t1.Equal(t2) && name1 == name2 && offset1 == offset2681}682683func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File, raw []byte) {684	if f.Name != ft.Name {685		t.Errorf("name=%q, want %q", f.Name, ft.Name)686	}687	if !ft.Modified.IsZero() && !equalTimeAndZone(f.Modified, ft.Modified) {688		t.Errorf("%s: Modified=%s, want %s", f.Name, f.Modified, ft.Modified)689	}690	if !ft.ModTime.IsZero() && !equalTimeAndZone(f.ModTime(), ft.ModTime) {691		t.Errorf("%s: ModTime=%s, want %s", f.Name, f.ModTime(), ft.ModTime)692	}693694	testFileMode(t, f, ft.Mode)695696	size := uint64(f.UncompressedSize)697	if size == uint32max {698		size = f.UncompressedSize64699	} else if size != f.UncompressedSize64 {700		t.Errorf("%v: UncompressedSize=%#x does not match UncompressedSize64=%#x", f.Name, size, f.UncompressedSize64)701	}702703	// Check that OpenRaw returns the correct byte segment704	rw, err := f.OpenRaw()705	if err != nil {706		t.Errorf("%v: OpenRaw error=%v", f.Name, err)707		return708	}709	start, err := f.DataOffset()710	if err != nil {711		t.Errorf("%v: DataOffset error=%v", f.Name, err)712		return713	}714	got, err := io.ReadAll(rw)715	if err != nil {716		t.Errorf("%v: OpenRaw ReadAll error=%v", f.Name, err)717		return718	}719	end := uint64(start) + f.CompressedSize64720	want := raw[start:end]721	if !bytes.Equal(got, want) {722		t.Logf("got %q", got)723		t.Logf("want %q", want)724		t.Errorf("%v: OpenRaw returned unexpected bytes", f.Name)725		return726	}727728	r, err := f.Open()729	if err != nil {730		t.Errorf("%v", err)731		return732	}733734	// For very large files, just check that the size is correct.735	// The content is expected to be all zeros.736	// Don't bother uncompressing: too big.737	if ft.Content == nil && ft.File == "" && ft.Size > 0 {738		if size != ft.Size {739			t.Errorf("%v: uncompressed size %#x, want %#x", ft.Name, size, ft.Size)740		}741		r.Close()742		return743	}744745	var b bytes.Buffer746	_, err = io.Copy(&b, r)747	if err != ft.ContentErr {748		t.Errorf("copying contents: %v (want %v)", err, ft.ContentErr)749	}750	if err != nil {751		return752	}753	r.Close()754755	if g := uint64(b.Len()); g != size {756		t.Errorf("%v: read %v bytes but f.UncompressedSize == %v", f.Name, g, size)757	}758759	var c []byte760	if ft.Content != nil {761		c = ft.Content762	} else if c, err = os.ReadFile("testdata/" + ft.File); err != nil {763		t.Error(err)764		return765	}766767	if b.Len() != len(c) {768		t.Errorf("%s: len=%d, want %d", f.Name, b.Len(), len(c))769		return770	}771772	for i, b := range b.Bytes() {773		if b != c[i] {774			t.Errorf("%s: content[%d]=%q want %q", f.Name, i, b, c[i])775			return776		}777	}778}779780func testFileMode(t *testing.T, f *File, want fs.FileMode) {781	mode := f.Mode()782	if want == 0 {783		t.Errorf("%s mode: got %v, want none", f.Name, mode)784	} else if mode != want {785		t.Errorf("%s mode: want %v, got %v", f.Name, want, mode)786	}787}788789func TestInvalidFiles(t *testing.T) {790	const size = 1024 * 70 // 70kb791	b := make([]byte, size)792793	// zeroes794	_, err := NewReader(bytes.NewReader(b), size)795	if err != ErrFormat {796		t.Errorf("zeroes: error=%v, want %v", err, ErrFormat)797	}798799	// repeated directoryEndSignatures800	sig := make([]byte, 4)801	binary.LittleEndian.PutUint32(sig, directoryEndSignature)802	for i := 0; i < size-4; i += 4 {803		copy(b[i:i+4], sig)804	}805	_, err = NewReader(bytes.NewReader(b), size)806	if err != ErrFormat {807		t.Errorf("sigs: error=%v, want %v", err, ErrFormat)808	}809810	// negative size811	_, err = NewReader(bytes.NewReader([]byte("foobar")), -1)812	if err == nil {813		t.Errorf("archive/zip.NewReader: expected error when negative size is passed")814	}815}816817func messWith(fileName string, corrupter func(b []byte)) (r io.ReaderAt, size int64) {818	data, err := os.ReadFile(filepath.Join("testdata", fileName))819	if err != nil {820		panic("Error reading " + fileName + ": " + err.Error())821	}822	corrupter(data)823	return bytes.NewReader(data), int64(len(data))824}825826func returnCorruptCRC32Zip() (r io.ReaderAt, size int64) {827	return messWith("go-with-datadesc-sig.zip", func(b []byte) {828		// Corrupt one of the CRC32s in the data descriptor:829		b[0x2d]++830	})831}832833func returnCorruptNotStreamedZip() (r io.ReaderAt, size int64) {834	return messWith("crc32-not-streamed.zip", func(b []byte) {835		// Corrupt foo.txt's final crc32 byte, in both836		// the file header and TOC. (0x7e -> 0x7f)837		b[0x11]++838		b[0x9d]++839840		// TODO(bradfitz): add a new test that only corrupts841		// one of these values, and verify that that's also an842		// error. Currently, the reader code doesn't verify the843		// fileheader and TOC's crc32 match if they're both844		// non-zero and only the second line above, the TOC,845		// is what matters.846	})847}848849// rZipBytes returns the bytes of a recursive zip file, without850// putting it on disk and triggering certain virus scanners.851func rZipBytes() []byte {852	s := `8530000000 50 4b 03 04 14 00 00 00 08 00 08 03 64 3c f9 f48540000010 89 64 48 01 00 00 b8 01 00 00 07 00 00 00 72 2f8550000020 72 2e 7a 69 70 00 25 00 da ff 50 4b 03 04 14 008560000030 00 00 08 00 08 03 64 3c f9 f4 89 64 48 01 00 008570000040 b8 01 00 00 07 00 00 00 72 2f 72 2e 7a 69 70 008580000050 2f 00 d0 ff 00 25 00 da ff 50 4b 03 04 14 00 008590000060 00 08 00 08 03 64 3c f9 f4 89 64 48 01 00 00 b88600000070 01 00 00 07 00 00 00 72 2f 72 2e 7a 69 70 00 2f8610000080 00 d0 ff c2 54 8e 57 39 00 05 00 fa ff c2 54 8e8620000090 57 39 00 05 00 fa ff 00 05 00 fa ff 00 14 00 eb86300000a0 ff c2 54 8e 57 39 00 05 00 fa ff 00 05 00 fa ff86400000b0 00 14 00 eb ff 42 88 21 c4 00 00 14 00 eb ff 4286500000c0 88 21 c4 00 00 14 00 eb ff 42 88 21 c4 00 00 1486600000d0 00 eb ff 42 88 21 c4 00 00 14 00 eb ff 42 88 2186700000e0 c4 00 00 00 00 ff ff 00 00 00 ff ff 00 34 00 cb86800000f0 ff 42 88 21 c4 00 00 00 00 ff ff 00 00 00 ff ff8690000100 00 34 00 cb ff 42 e8 21 5e 0f 00 00 00 ff ff 0a8700000110 f0 66 64 12 61 c0 15 dc e8 a0 48 bf 48 af 2a b38710000120 20 c0 9b 95 0d c4 67 04 42 53 06 06 06 40 00 068720000130 00 f9 ff 6d 01 00 00 00 00 42 e8 21 5e 0f 00 008730000140 00 ff ff 0a f0 66 64 12 61 c0 15 dc e8 a0 48 bf8740000150 48 af 2a b3 20 c0 9b 95 0d c4 67 04 42 53 06 068750000160 06 40 00 06 00 f9 ff 6d 01 00 00 00 00 50 4b 018760000170 02 14 00 14 00 00 00 08 00 08 03 64 3c f9 f4 898770000180 64 48 01 00 00 b8 01 00 00 07 00 00 00 00 00 008780000190 00 00 00 00 00 00 00 00 00 00 00 72 2f 72 2e 7a87900001a0 69 70 50 4b 05 06 00 00 00 00 01 00 01 00 35 0088000001b0 00 00 6d 01 00 00 00 00`881	s = regexp.MustCompile(`[0-9a-f]{7}`).ReplaceAllString(s, "")882	s = regexp.MustCompile(`\s+`).ReplaceAllString(s, "")883	b, err := hex.DecodeString(s)884	if err != nil {885		panic(err)886	}887	return b888}889890func returnRecursiveZip() (r io.ReaderAt, size int64) {891	b := rZipBytes()892	return bytes.NewReader(b), int64(len(b))893}894895// biggestZipBytes returns the bytes of a zip file biggest.zip896// that contains a zip file bigger.zip that contains a zip file897// big.zip that contains big.file, which contains 2³²-1 zeros.898// The big.zip file is interesting because it has no zip64 header,899// much like the innermost zip files in the well-known 42.zip.900//901// biggest.zip was generated by changing isZip64 to use > uint32max902// instead of >= uint32max and then running this program:903//904//	package main905//906//	import (907//		"archive/zip"908//		"bytes"909//		"io"910//		"log"911//		"os"912//	)913//914//	type zeros struct{}915//916//	func (zeros) Read(b []byte) (int, error) {917//		clear(b)918//		return len(b), nil919//	}920//921//	func main() {922//		bigZip := makeZip("big.file", io.LimitReader(zeros{}, 1<<32-1))923//		if err := os.WriteFile("/tmp/big.zip", bigZip, 0666); err != nil {924//			log.Fatal(err)925//		}926//927//		biggerZip := makeZip("big.zip", bytes.NewReader(bigZip))928//		if err := os.WriteFile("/tmp/bigger.zip", biggerZip, 0666); err != nil {929//			log.Fatal(err)930//		}931//932//		biggestZip := makeZip("bigger.zip", bytes.NewReader(biggerZip))933//		if err := os.WriteFile("/tmp/biggest.zip", biggestZip, 0666); err != nil {934//			log.Fatal(err)935//		}936//	}937//938//	func makeZip(name string, r io.Reader) []byte {939//		var buf bytes.Buffer940//		w := zip.NewWriter(&buf)941//		wf, err := w.Create(name)942//		if err != nil {943//			log.Fatal(err)944//		}945//		if _, err = io.Copy(wf, r); err != nil {946//			log.Fatal(err)947//		}948//		if err := w.Close(); err != nil {949//			log.Fatal(err)950//		}951//		return buf.Bytes()952//	}953//954// The 4 GB of zeros compresses to 4 MB, which compresses to 20 kB,955// which compresses to 1252 bytes (in the hex dump below).956//957// It's here in hex for the same reason as rZipBytes above: to avoid958// problems with on-disk virus scanners or other zip processors.959func biggestZipBytes() []byte {960	s := `9610000000 50 4b 03 04 14 00 08 00 08 00 00 00 00 00 00 009620000010 00 00 00 00 00 00 00 00 00 00 0a 00 00 00 62 699630000020 67 67 65 72 2e 7a 69 70 ec dc 6b 4c 53 67 18 079640000030 f0 16 c5 ca 65 2e cb b8 94 20 61 1f 44 33 c7 cd9650000040 c0 86 4a b5 c0 62 8a 61 05 c6 cd 91 b2 54 8c 1b9660000050 63 8b 03 9c 1b 95 52 5a e3 a0 19 6c b2 05 59 449670000060 64 9d 73 83 71 11 46 61 14 b9 1d 14 09 4a c3 609680000070 2e 4c 6e a5 60 45 02 62 81 95 b6 94 9e 9e 77 e79690000080 d0 43 b6 f8 71 df 96 3c e7 a4 69 ce bf cf e9 799700000090 ce ef 79 3f bf f1 31 db b6 bb 31 76 92 e7 f3 0797100000a0 8b fc 9c ca cc 08 cc cb cc 5e d2 1c 88 d9 7e bb97200000b0 4f bb 3a 3f 75 f1 5d 7f 8f c2 68 67 77 8f 25 ff97300000c0 84 e2 93 2d ef a4 95 3d 71 4e 2c b9 b0 87 c3 be97400000d0 3d f8 a7 60 24 61 c5 ef ae 9e c8 6c 6d 4e 69 c897500000e0 67 65 34 f8 37 76 2d 76 5c 54 f3 95 65 49 c7 0f97600000f0 18 71 4b 7e 5b 6a d1 79 47 61 41 b0 4e 2a 74 459770000100 43 58 12 b2 5a a5 c6 7d 68 55 88 d4 98 75 18 6d9780000110 08 d1 1f 8f 5a 9e 96 ee 45 cf a4 84 4e 4b e8 509790000120 a7 13 d9 06 de 52 81 97 36 b2 d7 b8 fc 2b 5f 559800000130 23 1f 32 59 cf 30 27 fb e2 8a b9 de 45 dd 63 9c9810000140 4b b5 8b 96 4c 7a 62 62 cc a1 a7 cf fa f1 fe dd9820000150 54 62 11 bf 36 78 b3 c7 b1 b5 f2 61 4d 4e dd 669830000160 32 2e e6 70 34 5f f4 c9 e6 6c 43 6f da 6b c6 c39840000170 09 2c ce 09 57 7f d2 7e b4 23 ba 7c 1b 99 bc 229850000180 3e f1 de 91 2f e3 9c 1b 82 cc c2 84 39 aa e6 de9860000190 b4 69 fc cc cb 72 a6 61 45 f0 d3 1d 26 19 7c 8d98700001a0 29 c8 66 02 be 77 6a f9 3d 34 79 17 19 c8 96 2498800001b0 a3 ac e4 dd 3b 1a 8e c6 fe 96 38 6b bf 67 5a 2398900001c0 f4 16 f4 e6 8a b4 fc c2 cd bf 95 66 1d bb 35 aa99000001d0 92 7d 66 d8 08 8d a5 1f 54 2a af 09 cf 61 ff d299100001e0 85 9d 8f b6 d7 88 07 4a 86 03 db 64 f3 d9 92 7399200001f0 df ec a7 fc 23 4c 8d 83 79 63 2a d9 fd 8d b3 c89930000200 8f 7e d4 19 85 e6 8d 1c 76 f0 8b 58 32 fd 9a d69940000210 85 e2 48 ad c3 d5 60 6f 7e 22 dd ef 09 49 7c 7f9950000220 3a 45 c3 71 b7 df f3 4c 63 fb b5 d9 31 5f 6e d69960000230 24 1d a4 4a fe 32 a7 5c 16 48 5c 3e 08 6b 8a d39970000240 25 1d a2 12 a5 59 24 ea 20 5f 52 6d ad 94 db 6b9980000250 94 b9 5d eb 4b a7 5c 44 bb 1e f2 3c 6b cf 52 c99990000260 e9 e5 ba 06 b9 c4 e5 0a d0 00 0d d0 00 0d d0 0010000000270 0d d0 00 0d d0 00 0d d0 00 0d d0 00 0d d0 00 0d10010000280 d0 00 0d d0 00 0d d0 00 0d d0 00 0d d0 00 0d d010020000290 00 0d d0 00 0d d0 00 0d d0 00 0d d0 00 0d d0 00100300002a0 0d d0 00 cd ff 9e 46 86 fa a7 7d 3a 43 d7 8e 10100400002b0 52 e9 be e6 6e cf eb 9e 85 4d 65 ce cc 30 c1 44100500002c0 c0 4e af bc 9c 6c 4b a0 d7 54 ff 1d d5 5c 89 fb100600002d0 b5 34 7e c4 c2 9e f5 a0 f6 5b 7e 6e ca 73 c7 ef100700002e0 5d be de f9 e8 81 eb a5 0a a5 63 54 2c d7 1c d1100800002f0 89 17 85 f8 16 94 f2 8a b2 a3 f5 b6 6d df 75 cd10090000300 90 dd 64 bd 5d 55 4e f2 55 19 1b b7 cc ef 1b ea10100000310 2e 05 9c f4 aa 1e a8 cd a6 82 c7 59 0f 5e 9d e010110000320 bb fc 6c d6 99 23 eb 36 ad c6 c5 e1 d8 e1 e2 3e10120000330 d9 90 5a f7 91 5d 6f bc 33 6d 98 47 d2 7c 2e 2f10130000340 99 a4 25 72 85 49 2c be 0b 5b af 8f e5 6e 81 a610140000350 a3 5a 6f 39 53 3a ab 7a 8b 1e 26 f7 46 6c 7d 2610150000360 53 b3 22 31 94 d3 83 f2 18 4d f5 92 33 27 53 9710160000370 0f d3 e6 55 9c a6 c5 31 87 6f d3 f3 ae 39 6f 5610170000380 10 7b ab 7e d0 b4 ca f2 b8 05 be 3f 0e 6e 5a 7510180000390 ab 0c f5 37 0e ba 8e 75 71 7a aa ed 7a dd 6a 63101900003a0 be 9b a0 97 27 6a 6f e7 d3 8b c4 7c ec d3 91 56102000003b0 d9 ac 5e bf 16 42 2f 00 1f 93 a2 23 87 bd e2 59102100003c0 a0 de 1a 66 c8 62 eb 55 8f 91 17 b4 61 42 7a 50102200003d0 40 03 34 40 03 34 40 03 34 40 03 34 40 03 34 40102300003e0 03 34 40 03 34 40 03 34 40 03 34 40 03 34 40 03102400003f0 34 40 03 34 40 03 34 ff 85 86 90 8b ea 67 90 0d10250000400 e1 42 1b d2 61 d6 79 ec fd 3e 44 28 a4 51 6c 5c10260000410 fc d2 72 ca ba 82 18 46 16 61 cd 93 a9 0f d1 2410270000420 17 99 e2 2c 71 16 84 0c c8 7a 13 0f 9a 5e c5 f010280000430 79 64 e2 12 4d c8 82 a1 81 19 2d aa 44 6d 87 5410290000440 84 71 c1 f6 d4 ca 25 8c 77 b9 08 c7 c8 5e 10 8a10300000450 8f 61 ed 8c ba 30 1f 79 9a c7 60 34 2b b9 8c f810310000460 18 a6 83 1b e3 9f ad 79 fe fd 1b 8b f1 fc 41 6f10320000470 d4 13 1f e3 b8 83 ba 64 92 e7 eb e4 77 05 8f ba10330000480 fa 3b 00 00 ff ff 50 4b 07 08 a6 18 b1 91 5e 0410340000490 00 00 e4 47 00 00 50 4b 01 02 14 00 14 00 08 00103500004a0 08 00 00 00 00 00 a6 18 b1 91 5e 04 00 00 e4 47103600004b0 00 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00103700004c0 00 00 00 00 62 69 67 67 65 72 2e 7a 69 70 50 4b103800004d0 05 06 00 00 00 00 01 00 01 00 38 00 00 00 96 04103900004e0 00 00 00 00`1040	s = regexp.MustCompile(`[0-9a-f]{7}`).ReplaceAllString(s, "")1041	s = regexp.MustCompile(`\s+`).ReplaceAllString(s, "")1042	b, err := hex.DecodeString(s)1043	if err != nil {1044		panic(err)1045	}1046	return b1047}10481049func returnBigZipBytes() (r io.ReaderAt, size int64) {1050	b := biggestZipBytes()1051	for i := 0; i < 2; i++ {1052		r, err := NewReader(bytes.NewReader(b), int64(len(b)))1053		if err != nil {1054			panic(err)1055		}1056		f, err := r.File[0].Open()1057		if err != nil {1058			panic(err)1059		}1060		b, err = io.ReadAll(f)1061		if err != nil {1062			panic(err)1063		}1064	}1065	return bytes.NewReader(b), int64(len(b))1066}10671068func TestIssue8186(t *testing.T) {1069	// Directory headers & data found in the TOC of a JAR file.1070	dirEnts := []string{1071		"PK\x01\x02\n\x00\n\x00\x00\b\x00\x004\x9d3?\xaa\x1b\x06\xf0\x81\x02\x00\x00\x81\x02\x00\x00-\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00res/drawable-xhdpi-v4/ic_actionbar_accept.png\xfe\xca\x00\x00\x00",1072		"PK\x01\x02\n\x00\n\x00\x00\b\x00\x004\x9d3?\x90K\x89\xc7t\n\x00\x00t\n\x00\x00\x0e\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd1\x02\x00\x00resources.arsc\x00\x00\x00",1073		"PK\x01\x02\x14\x00\x14\x00\b\b\b\x004\x9d3?\xff$\x18\xed3\x03\x00\x00\xb4\b\x00\x00\x13\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00t\r\x00\x00AndroidManifest.xml",1074		"PK\x01\x02\x14\x00\x14\x00\b\b\b\x004\x9d3?\x14\xc5K\xab\x192\x02\x00\xc8\xcd\x04\x00\v\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe8\x10\x00\x00classes.dex",1075		"PK\x01\x02\x14\x00\x14\x00\b\b\b\x004\x9d3?E\x96\nD\xac\x01\x00\x00P\x03\x00\x00&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:C\x02\x00res/layout/actionbar_set_wallpaper.xml",1076		"PK\x01\x02\x14\x00\x14\x00\b\b\b\x004\x9d3?Ļ\x14\xe3\xd8\x01\x00\x00\xd8\x03\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00:E\x02\x00res/layout/wallpaper_cropper.xml",1077		"PK\x01\x02\x14\x00\x14\x00\b\b\b\x004\x9d3?}\xc1\x15\x9eZ\x01\x00\x00!\x02\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`G\x02\x00META-INF/MANIFEST.MF",1078		"PK\x01\x02\x14\x00\x14\x00\b\b\b\x004\x9d3?\xe6\x98Ьo\x01\x00\x00\x84\x02\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfcH\x02\x00META-INF/CERT.SF",1079		"PK\x01\x02\x14\x00\x14\x00\b\b\b\x004\x9d3?\xbfP\x96b\x86\x04\x00\x00\xb2\x06\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xa9J\x02\x00META-INF/CERT.RSA",1080	}1081	for i, s := range dirEnts {1082		var f File1083		err := readDirectoryHeader(&f, strings.NewReader(s))1084		if err != nil {1085			t.Errorf("error reading #%d: %v", i, err)1086		}1087	}1088}10891090// Verify we return ErrUnexpectedEOF when length is short.1091func TestIssue10957(t *testing.T) {1092	data := []byte("PK\x03\x040000000PK\x01\x0200000" +1093		"0000000000000000000\x00" +1094		"\x00\x00\x00\x00\x00000000000000PK\x01" +1095		"\x020000000000000000000" +1096		"00000\v\x00\x00\x00\x00\x00000000000" +1097		"00000000000000PK\x01\x0200" +1098		"00000000000000000000" +1099		"00\v\x00\x00\x00\x00\x00000000000000" +1100		"00000000000PK\x01\x020000<" +1101		"0\x00\x0000000000000000\v\x00\v" +1102		"\x00\x00\x00\x00\x0000000000\x00\x00\x00\x00000" +1103		"00000000PK\x01\x0200000000" +1104		"0000000000000000\v\x00\x00\x00" +1105		"\x00\x0000PK\x05\x06000000\x05\x00\xfd\x00\x00\x00" +1106		"\v\x00\x00\x00\x00\x00")1107	z, err := NewReader(bytes.NewReader(data), int64(len(data)))1108	if err != nil {1109		t.Fatal(err)1110	}1111	for i, f := range z.File {1112		r, err := f.Open()1113		if err != nil {1114			continue1115		}1116		if f.UncompressedSize64 < 1e6 {1117			n, err := io.Copy(io.Discard, r)1118			if i == 3 && err != io.ErrUnexpectedEOF {1119				t.Errorf("File[3] error = %v; want io.ErrUnexpectedEOF", err)1120			}1121			if err == nil && uint64(n) != f.UncompressedSize64 {1122				t.Errorf("file %d: bad size: copied=%d; want=%d", i, n, f.UncompressedSize64)1123			}1124		}1125		r.Close()1126	}1127}11281129// Verify that this particular malformed zip file is rejected.1130func TestIssue10956(t *testing.T) {1131	data := []byte("PK\x06\x06PK\x06\a0000\x00\x00\x00\x00\x00\x00\x00\x00" +1132		"0000PK\x05\x06000000000000" +1133		"0000\v\x00000\x00\x00\x00\x00\x00\x00\x000")1134	r, err := NewReader(bytes.NewReader(data), int64(len(data)))1135	if err == nil {1136		t.Errorf("got nil error, want ErrFormat")1137	}1138	if r != nil {1139		t.Errorf("got non-nil Reader, want nil")1140	}1141}11421143// Verify we return ErrUnexpectedEOF when reading truncated data descriptor.1144func TestIssue11146(t *testing.T) {1145	data := []byte("PK\x03\x040000000000000000" +1146		"000000\x01\x00\x00\x000\x01\x00\x00\xff\xff0000" +1147		"0000000000000000PK\x01\x02" +1148		"0000\b0\b\x00000000000000" +1149		"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000PK\x05\x06\x00\x00" +1150		"\x00\x0000\x01\x00\x26\x00\x00\x008\x00\x00\x00\x00\x00")1151	z, err := NewReader(bytes.NewReader(data), int64(len(data)))1152	if err != nil {1153		t.Fatal(err)1154	}1155	r, err := z.File[0].Open()1156	if err != nil {1157		t.Fatal(err)1158	}1159	_, err = io.ReadAll(r)1160	if err != io.ErrUnexpectedEOF {1161		t.Errorf("File[0] error = %v; want io.ErrUnexpectedEOF", err)1162	}1163	r.Close()1164}11651166// Verify we do not treat non-zip64 archives as zip641167func TestIssue12449(t *testing.T) {1168	data := []byte{1169		0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x00,1170		0x00, 0x00, 0x6b, 0xb4, 0xba, 0x46, 0x00, 0x00,1171		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1172		0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0xca, 0x64,1173		0x55, 0x75, 0x78, 0x0b, 0x00, 0x50, 0x4b, 0x05,1174		0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,1175		0x00, 0x49, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00,1176		0x00, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x0a,1177		0x50, 0x4b, 0x07, 0x08, 0x1d, 0x88, 0x77, 0xb0,1178		0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,1179		0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, 0x00,1180		0x08, 0x00, 0x00, 0x00, 0x6b, 0xb4, 0xba, 0x46,1181		0x1d, 0x88, 0x77, 0xb0, 0x07, 0x00, 0x00, 0x00,1182		0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00,1183		0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1184		0xa0, 0x81, 0x00, 0x00, 0x00, 0x00, 0xca, 0x64,1185		0x55, 0x75, 0x78, 0x0b, 0x00, 0x50, 0x4b, 0x05,1186		0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01,1187		0x00, 0x49, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00,1188		0x00, 0x97, 0x2b, 0x49, 0x23, 0x05, 0xc5, 0x0b,1189		0xa7, 0xd1, 0x52, 0xa2, 0x9c, 0x50, 0x4b, 0x06,1190		0x07, 0xc8, 0x19, 0xc1, 0xaf, 0x94, 0x9c, 0x61,1191		0x44, 0xbe, 0x94, 0x19, 0x42, 0x58, 0x12, 0xc6,1192		0x5b, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00,1193		0x00, 0x01, 0x00, 0x01, 0x00, 0x69, 0x00, 0x00,1194		0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,1195	}1196	// Read in the archive.1197	_, err := NewReader(bytes.NewReader(data), int64(len(data)))1198	if err != nil {1199		t.Errorf("Error reading the archive: %v", err)1200	}1201}12021203func TestFS(t *testing.T) {1204	for _, test := range []struct {1205		file string1206		want []string1207	}{1208		{1209			"testdata/unix.zip",1210			[]string{"hello", "dir/bar", "readonly"},1211		},1212		{1213			"testdata/subdir.zip",1214			[]string{"a/b/c"},1215		},1216	} {1217		t.Run(test.file, func(t *testing.T) {1218			t.Parallel()1219			z, err := OpenReader(test.file)1220			if err != nil {1221				t.Fatal(err)1222			}1223			defer z.Close()1224			if err := fstest.TestFS(z, test.want...); err != nil {1225				t.Error(err)1226			}1227		})1228	}1229}12301231func TestFSWalk(t *testing.T) {1232	for _, test := range []struct {1233		file    string1234		want    []string1235		wantErr bool1236	}{1237		{1238			file: "testdata/unix.zip",1239			want: []string{".", "dir", "dir/bar", "dir/empty", "hello", "readonly"},1240		},1241		{1242			file: "testdata/subdir.zip",1243			want: []string{".", "a", "a/b", "a/b/c"},1244		},1245		{1246			file:    "testdata/dupdir.zip",1247			wantErr: true,1248		},1249	} {1250		t.Run(test.file, func(t *testing.T) {1251			t.Parallel()1252			z, err := OpenReader(test.file)1253			if err != nil {1254				t.Fatal(err)1255			}1256			var files []string1257			sawErr := false1258			err = fs.WalkDir(z, ".", func(path string, d fs.DirEntry, err error) error {1259				if err != nil {1260					if !test.wantErr {1261						t.Errorf("%s: %v", path, err)1262					}1263					sawErr = true1264					return nil1265				}1266				files = append(files, path)1267				return nil1268			})1269			if err != nil {1270				t.Errorf("fs.WalkDir error: %v", err)1271			}1272			if test.wantErr && !sawErr {1273				t.Error("succeeded but want error")1274			} else if !test.wantErr && sawErr {1275				t.Error("unexpected error")1276			}1277			if test.want != nil && !slices.Equal(files, test.want) {1278				t.Errorf("got %v want %v", files, test.want)1279			}1280		})1281	}1282}12831284func TestFSWalkBadFile(t *testing.T) {1285	t.Parallel()12861287	var buf bytes.Buffer1288	zw := NewWriter(&buf)1289	hdr := &FileHeader{Name: "."}1290	hdr.SetMode(fs.ModeDir | 0o755)1291	w, err := zw.CreateHeader(hdr)1292	if err != nil {1293		t.Fatalf("create zip header: %v", err)1294	}1295	_, err = w.Write([]byte("some data"))1296	if err != nil {1297		t.Fatalf("write zip contents: %v", err)12981299	}1300	err = zw.Close()1301	if err != nil {1302		t.Fatalf("close zip writer: %v", err)13031304	}13051306	zr, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))1307	if err != nil {1308		t.Fatalf("create zip reader: %v", err)13091310	}1311	var count int1312	var errRepeat = errors.New("repeated call to path")1313	err = fs.WalkDir(zr, ".", func(p string, d fs.DirEntry, err error) error {1314		count++1315		if count > 2 { // once for directory read, once for the error1316			return errRepeat1317		}1318		return err1319	})1320	if err == nil {1321		t.Fatalf("expected error from invalid file name")1322	} else if errors.Is(err, errRepeat) {1323		t.Fatal(err)1324	}1325}13261327func TestFSModTime(t *testing.T) {1328	t.Parallel()1329	z, err := OpenReader("testdata/subdir.zip")1330	if err != nil {1331		t.Fatal(err)1332	}1333	defer z.Close()13341335	for _, test := range []struct {1336		name string1337		want time.Time1338	}{1339		{1340			"a",1341			time.Date(2021, 4, 19, 12, 29, 56, 0, timeZone(-7*time.Hour)).UTC(),1342		},1343		{1344			"a/b/c",1345			time.Date(2021, 4, 19, 12, 29, 59, 0, timeZone(-7*time.Hour)).UTC(),1346		},1347	} {1348		fi, err := fs.Stat(z, test.name)1349		if err != nil {1350			t.Errorf("%s: %v", test.name, err)1351			continue1352		}1353		if got := fi.ModTime(); !got.Equal(test.want) {1354			t.Errorf("%s: got modtime %v, want %v", test.name, got, test.want)1355		}1356	}1357}13581359func TestCVE202127919(t *testing.T) {1360	t.Setenv("GODEBUG", "zipinsecurepath=0")1361	// Archive containing only the file "../test.txt"1362	data := []byte{1363		0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x00,1364		0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1365		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1366		0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x2e, 0x2e,1367		0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78,1368		0x74, 0x0a, 0xc9, 0xc8, 0x2c, 0x56, 0xc8, 0x2c,1369		0x56, 0x48, 0x54, 0x28, 0x49, 0x2d, 0x2e, 0x51,1370		0x28, 0x49, 0xad, 0x28, 0x51, 0x48, 0xcb, 0xcc,1371		0x49, 0xd5, 0xe3, 0x02, 0x04, 0x00, 0x00, 0xff,1372		0xff, 0x50, 0x4b, 0x07, 0x08, 0xc0, 0xd7, 0xed,1373		0xc3, 0x20, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00,1374		0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00, 0x14,1375		0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,1376		0x00, 0xc0, 0xd7, 0xed, 0xc3, 0x20, 0x00, 0x00,1377		0x00, 0x1a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00,1378		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1379		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e,1380		0x2e, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74,1381		0x78, 0x74, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00,1382		0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x39, 0x00,1383		0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00,1384	}1385	r, err := NewReader(bytes.NewReader(data), int64(len(data)))1386	if err != ErrInsecurePath {1387		t.Fatalf("Error reading the archive: %v", err)1388	}1389	_, err = r.Open("test.txt")1390	if err != nil {1391		t.Errorf("Error reading file: %v", err)1392	}1393	if len(r.File) != 1 {1394		t.Fatalf("No entries in the file list")1395	}1396	if r.File[0].Name != "../test.txt" {1397		t.Errorf("Unexpected entry name: %s", r.File[0].Name)1398	}1399	if _, err := r.File[0].Open(); err != nil {1400		t.Errorf("Error opening file: %v", err)1401	}1402}14031404func TestOpenReaderInsecurePath(t *testing.T) {1405	t.Setenv("GODEBUG", "zipinsecurepath=0")1406	// Archive containing only the file "../test.txt"1407	data := []byte{1408		0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x00,1409		0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1410		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1411		0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x2e, 0x2e,1412		0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78,1413		0x74, 0x0a, 0xc9, 0xc8, 0x2c, 0x56, 0xc8, 0x2c,1414		0x56, 0x48, 0x54, 0x28, 0x49, 0x2d, 0x2e, 0x51,1415		0x28, 0x49, 0xad, 0x28, 0x51, 0x48, 0xcb, 0xcc,1416		0x49, 0xd5, 0xe3, 0x02, 0x04, 0x00, 0x00, 0xff,1417		0xff, 0x50, 0x4b, 0x07, 0x08, 0xc0, 0xd7, 0xed,1418		0xc3, 0x20, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00,1419		0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00, 0x14,1420		0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00,1421		0x00, 0xc0, 0xd7, 0xed, 0xc3, 0x20, 0x00, 0x00,1422		0x00, 0x1a, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00,1423		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1424		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2e,1425		0x2e, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74,1426		0x78, 0x74, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00,1427		0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x39, 0x00,1428		0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00,1429	}14301431	// Read in the archive with the OpenReader interface1432	name := filepath.Join(t.TempDir(), "test.zip")1433	err := os.WriteFile(name, data, 0644)1434	if err != nil {1435		t.Fatalf("Unable to write out the bugos zip entry")1436	}1437	r, err := OpenReader(name)1438	if r != nil {1439		defer r.Close()1440	}14411442	if err != ErrInsecurePath {1443		t.Fatalf("Error reading the archive, we expected ErrInsecurePath but got: %v", err)1444	}1445	_, err = r.Open("test.txt")1446	if err != nil {1447		t.Errorf("Error reading file: %v", err)1448	}1449	if len(r.File) != 1 {1450		t.Fatalf("No entries in the file list")1451	}1452	if r.File[0].Name != "../test.txt" {1453		t.Errorf("Unexpected entry name: %s", r.File[0].Name)1454	}1455	if _, err := r.File[0].Open(); err != nil {1456		t.Errorf("Error opening file: %v", err)1457	}1458}14591460func TestCVE202133196(t *testing.T) {1461	// Archive that indicates it has 1 << 128 -1 files,1462	// this would previously cause a panic due to attempting1463	// to allocate a slice with 1 << 128 -1 elements.1464	data := []byte{1465		0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x08,1466		0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1467		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1468		0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x02,1469		0x03, 0x62, 0x61, 0x65, 0x03, 0x04, 0x00, 0x00,1470		0xff, 0xff, 0x50, 0x4b, 0x07, 0x08, 0xbe, 0x20,1471		0x5c, 0x6c, 0x09, 0x00, 0x00, 0x00, 0x03, 0x00,1472		0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00,1473		0x14, 0x00, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00,1474		0x00, 0x00, 0xbe, 0x20, 0x5c, 0x6c, 0x09, 0x00,1475		0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00,1476		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1477		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1478		0x01, 0x02, 0x03, 0x50, 0x4b, 0x06, 0x06, 0x2c,1479		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d,1480		0x00, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1481		0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,1482		0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,1483		0xff, 0xff, 0xff, 0x31, 0x00, 0x00, 0x00, 0x00,1484		0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00,1485		0x00, 0x00, 0x00, 0x50, 0x4b, 0x06, 0x07, 0x00,1486		0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00,1487		0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50,1488		0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0xff,1489		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,1490		0xff, 0xff, 0xff, 0x00, 0x00,1491	}1492	_, err := NewReader(bytes.NewReader(data), int64(len(data)))1493	if err != ErrFormat {1494		t.Fatalf("unexpected error, got: %v, want: %v", err, ErrFormat)1495	}14961497	// Also check that an archive containing a handful of empty1498	// files doesn't cause an issue1499	b := bytes.NewBuffer(nil)1500	w := NewWriter(b)1501	for i := 0; i < 5; i++ {1502		_, err := w.Create("")1503		if err != nil {1504			t.Fatalf("Writer.Create failed: %s", err)1505		}1506	}1507	if err := w.Close(); err != nil {1508		t.Fatalf("Writer.Close failed: %s", err)1509	}1510	r, err := NewReader(bytes.NewReader(b.Bytes()), int64(b.Len()))1511	if err != nil {1512		t.Fatalf("NewReader failed: %s", err)1513	}1514	if len(r.File) != 5 {1515		t.Errorf("Archive has unexpected number of files, got %d, want 5", len(r.File))1516	}1517}15181519func TestCVE202139293(t *testing.T) {1520	// directory size is so large, that the check in Reader.init1521	// overflows when subtracting from the archive size, causing1522	// the pre-allocation check to be bypassed.1523	data := []byte{1524		0x50, 0x4b, 0x06, 0x06, 0x05, 0x06, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b,1525		0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,1526		0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b,1527		0x06, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,1528		0x00, 0x00, 0x00, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,1529		0xff, 0x50, 0xfe, 0x00, 0xff, 0x00, 0x3a, 0x00, 0x00, 0x00, 0xff,1530	}1531	_, err := NewReader(bytes.NewReader(data), int64(len(data)))1532	if err != ErrFormat {1533		t.Fatalf("unexpected error, got: %v, want: %v", err, ErrFormat)1534	}1535}15361537func TestCVE202141772(t *testing.T) {1538	t.Setenv("GODEBUG", "zipinsecurepath=0")1539	// Archive contains a file whose name is exclusively made up of '/', '\'1540	// characters, or "../", "..\" paths, which would previously cause a panic.1541	//1542	//  Length   Method    Size  Cmpr    Date    Time   CRC-32   Name1543	// --------  ------  ------- ---- ---------- ----- --------  ----1544	//        0  Stored        0   0% 08-05-2021 18:32 00000000  /1545	//        0  Stored        0   0% 09-14-2021 12:59 00000000  //1546	//        0  Stored        0   0% 09-14-2021 12:59 00000000  \1547	//       11  Stored       11   0% 09-14-2021 13:04 0d4a1185  /test.txt1548	// --------          -------  ---                            -------1549	//       11               11   0%                            4 files1550	data := []byte{1551		0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x08,1552		0x00, 0x00, 0x06, 0x94, 0x05, 0x53, 0x00, 0x00,1553		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1554		0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2f, 0x50,1555		0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00,1556		0x00, 0x78, 0x67, 0x2e, 0x53, 0x00, 0x00, 0x00,1557		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1558		0x00, 0x02, 0x00, 0x00, 0x00, 0x2f, 0x2f, 0x50,1559		0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00,1560		0x00, 0x78, 0x67, 0x2e, 0x53, 0x00, 0x00, 0x00,1561		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1562		0x00, 0x01, 0x00, 0x00, 0x00, 0x5c, 0x50, 0x4b,1563		0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,1564		0x91, 0x68, 0x2e, 0x53, 0x85, 0x11, 0x4a, 0x0d,1565		0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,1566		0x09, 0x00, 0x00, 0x00, 0x2f, 0x74, 0x65, 0x73,1567		0x74, 0x2e, 0x74, 0x78, 0x74, 0x68, 0x65, 0x6c,1568		0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,1569		0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x0a, 0x00,1570		0x00, 0x08, 0x00, 0x00, 0x06, 0x94, 0x05, 0x53,1571		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1572		0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,1573		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,1574		0xed, 0x41, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x50,1575		0x4b, 0x01, 0x02, 0x3f, 0x00, 0x0a, 0x00, 0x00,1576		0x00, 0x00, 0x00, 0x78, 0x67, 0x2e, 0x53, 0x00,1577		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1578		0x00, 0x00, 0x00, 0x02, 0x00, 0x24, 0x00, 0x00,1579		0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,1580		0x00, 0x1f, 0x00, 0x00, 0x00, 0x2f, 0x2f, 0x0a,1581		0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,1582		0x00, 0x18, 0x00, 0x93, 0x98, 0x25, 0x57, 0x25,1583		0xa9, 0xd7, 0x01, 0x93, 0x98, 0x25, 0x57, 0x25,1584		0xa9, 0xd7, 0x01, 0x93, 0x98, 0x25, 0x57, 0x25,1585		0xa9, 0xd7, 0x01, 0x50, 0x4b, 0x01, 0x02, 0x3f,1586		0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78,1587		0x67, 0x2e, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00,1588		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,1589		0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1590		0x00, 0x20, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00,1591		0x00, 0x5c, 0x0a, 0x00, 0x20, 0x00, 0x00, 0x00,1592		0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x93, 0x98,1593		0x25, 0x57, 0x25, 0xa9, 0xd7, 0x01, 0x93, 0x98,1594		0x25, 0x57, 0x25, 0xa9, 0xd7, 0x01, 0x93, 0x98,1595		0x25, 0x57, 0x25, 0xa9, 0xd7, 0x01, 0x50, 0x4b,1596		0x01, 0x02, 0x3f, 0x00, 0x0a, 0x00, 0x00, 0x00,1597		0x00, 0x00, 0x91, 0x68, 0x2e, 0x53, 0x85, 0x11,1598		0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00,1599		0x00, 0x00, 0x09, 0x00, 0x24, 0x00, 0x00, 0x00,1600		0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,1601		0x5e, 0x00, 0x00, 0x00, 0x2f, 0x74, 0x65, 0x73,1602		0x74, 0x2e, 0x74, 0x78, 0x74, 0x0a, 0x00, 0x20,1603		0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18,1604		0x00, 0xa9, 0x80, 0x51, 0x01, 0x26, 0xa9, 0xd7,1605		0x01, 0x31, 0xd1, 0x57, 0x01, 0x26, 0xa9, 0xd7,1606		0x01, 0xdf, 0x48, 0x85, 0xf9, 0x25, 0xa9, 0xd7,1607		0x01, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00,1608		0x00, 0x04, 0x00, 0x04, 0x00, 0x31, 0x01, 0x00,1609		0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00,1610	}1611	r, err := NewReader(bytes.NewReader(data), int64(len(data)))1612	if err != ErrInsecurePath {1613		t.Fatalf("Error reading the archive: %v", err)1614	}1615	entryNames := []string{`/`, `//`, `\`, `/test.txt`}1616	var names []string1617	for _, f := range r.File {1618		names = append(names, f.Name)1619		if _, err := f.Open(); err != nil {1620			t.Errorf("Error opening %q: %v", f.Name, err)1621		}1622		if _, err := r.Open(f.Name); err == nil {1623			t.Errorf("Opening %q with fs.FS API succeeded", f.Name)1624		}1625	}1626	if !slices.Equal(names, entryNames) {1627		t.Errorf("Unexpected file entries: %q", names)1628	}1629	if _, err := r.Open(""); err == nil {1630		t.Errorf("Opening %q with fs.FS API succeeded", "")1631	}1632	if _, err := r.Open("test.txt"); err != nil {1633		t.Errorf("Error opening %q with fs.FS API: %v", "test.txt", err)1634	}1635	dirEntries, err := fs.ReadDir(r, ".")1636	if err != nil {1637		t.Fatalf("Error reading the root directory: %v", err)1638	}1639	if len(dirEntries) != 1 || dirEntries[0].Name() != "test.txt" {1640		t.Errorf("Unexpected directory entries")1641		for _, dirEntry := range dirEntries {1642			_, err := r.Open(dirEntry.Name())1643			t.Logf("%q (Open error: %v)", dirEntry.Name(), err)1644		}1645		t.FailNow()1646	}1647	info, err := dirEntries[0].Info()1648	if err != nil {1649		t.Fatalf("Error reading info entry: %v", err)1650	}1651	if name := info.Name(); name != "test.txt" {1652		t.Errorf("Inconsistent name in info entry: %v", name)1653	}1654}16551656func TestUnderSize(t *testing.T) {1657	z, err := OpenReader("testdata/readme.zip")1658	if err != nil {1659		t.Fatal(err)1660	}1661	defer z.Close()16621663	for _, f := range z.File {1664		f.UncompressedSize64 = 11665	}16661667	for _, f := range z.File {1668		t.Run(f.Name, func(t *testing.T) {1669			rd, err := f.Open()1670			if err != nil {1671				t.Fatal(err)1672			}1673			defer rd.Close()16741675			_, err = io.Copy(io.Discard, rd)1676			if err != ErrFormat {1677				t.Fatalf("Error mismatch\n\tGot:  %v\n\tWant: %v", err, ErrFormat)1678			}1679		})1680	}1681}16821683func TestIssue54801(t *testing.T) {1684	for _, input := range []string{"testdata/readme.zip", "testdata/dd.zip"} {1685		z, err := OpenReader(input)1686		if err != nil {1687			t.Fatal(err)1688		}1689		defer z.Close()16901691		for _, f := range z.File {1692			// Make file a directory1693			f.Name += "/"16941695			t.Run(f.Name, func(t *testing.T) {1696				t.Logf("CompressedSize64: %d, Flags: %#x", f.CompressedSize64, f.Flags)16971698				rd, err := f.Open()1699				if err != nil {1700					t.Fatal(err)1701				}1702				defer rd.Close()17031704				n, got := io.Copy(io.Discard, rd)1705				if n != 0 || got != ErrFormat {1706					t.Fatalf("Error mismatch, got: %d, %v, want: %v", n, got, ErrFormat)1707				}1708			})1709		}1710	}1711}17121713func TestInsecurePaths(t *testing.T) {1714	t.Setenv("GODEBUG", "zipinsecurepath=0")1715	for _, path := range []string{1716		"../foo",1717		"/foo",1718		"a/b/../../../c",1719		`a\b`,1720	} {1721		var buf bytes.Buffer1722		zw := NewWriter(&buf)1723		_, err := zw.Create(path)1724		if err != nil {1725			t.Errorf("zw.Create(%q) = %v", path, err)1726			continue1727		}1728		zw.Close()17291730		zr, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))1731		if err != ErrInsecurePath {1732			t.Errorf("NewReader for archive with file %q: got err %v, want ErrInsecurePath", path, err)1733			continue1734		}1735		var gotPaths []string1736		for _, f := range zr.File {1737			gotPaths = append(gotPaths, f.Name)1738		}1739		if !slices.Equal(gotPaths, []string{path}) {1740			t.Errorf("NewReader for archive with file %q: got files %q", path, gotPaths)1741			continue1742		}1743	}1744}17451746func TestDisableInsecurePathCheck(t *testing.T) {1747	t.Setenv("GODEBUG", "zipinsecurepath=1")1748	var buf bytes.Buffer1749	zw := NewWriter(&buf)1750	const name = "/foo"1751	_, err := zw.Create(name)1752	if err != nil {1753		t.Fatalf("zw.Create(%q) = %v", name, err)1754	}1755	zw.Close()1756	zr, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))1757	if err != nil {1758		t.Fatalf("NewReader with zipinsecurepath=1: got err %v, want nil", err)1759	}1760	var gotPaths []string1761	for _, f := range zr.File {1762		gotPaths = append(gotPaths, f.Name)1763	}1764	if want := []string{name}; !slices.Equal(gotPaths, want) {1765		t.Errorf("NewReader with zipinsecurepath=1: got files %q, want %q", gotPaths, want)1766	}1767}17681769func TestCompressedDirectory(t *testing.T) {1770	// Empty Java JAR, with a compressed directory with uncompressed size 01771	// which should not fail.1772	//1773	// Length   Method    Size  Cmpr    Date    Time   CRC-32   Name1774	// --------  ------  ------- ---- ---------- ----- --------  ----1775	//        0  Defl:N        2   0% 12-01-2022 16:50 00000000  META-INF/1776	//       60  Defl:N       59   2% 12-01-2022 16:50 af937e93  META-INF/MANIFEST.MF1777	// --------          -------  ---                            -------1778	//       60               61  -2%                            2 files1779	data := []byte{1780		0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x08,1781		0x08, 0x00, 0x49, 0x86, 0x81, 0x55, 0x00, 0x00,1782		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1783		0x00, 0x00, 0x09, 0x00, 0x04, 0x00, 0x4d, 0x45,1784		0x54, 0x41, 0x2d, 0x49, 0x4e, 0x46, 0x2f, 0xfe,1785		0xca, 0x00, 0x00, 0x03, 0x00, 0x50, 0x4b, 0x07,1786		0x08, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00,1787		0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x4b, 0x03,1788		0x04, 0x14, 0x00, 0x08, 0x08, 0x08, 0x00, 0x49,1789		0x86, 0x81, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00,1790		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,1791		0x00, 0x00, 0x00, 0x4d, 0x45, 0x54, 0x41, 0x2d,1792		0x49, 0x4e, 0x46, 0x2f, 0x4d, 0x41, 0x4e, 0x49,1793		0x46, 0x45, 0x53, 0x54, 0x2e, 0x4d, 0x46, 0xf3,1794		0x4d, 0xcc, 0xcb, 0x4c, 0x4b, 0x2d, 0x2e, 0xd1,1795		0x0d, 0x4b, 0x2d, 0x2a, 0xce, 0xcc, 0xcf, 0xb3,1796		0x52, 0x30, 0xd4, 0x33, 0xe0, 0xe5, 0x72, 0x2e,1797		0x4a, 0x4d, 0x2c, 0x49, 0x4d, 0xd1, 0x75, 0xaa,1798		0x04, 0x0a, 0x00, 0x45, 0xf4, 0x0c, 0x8d, 0x15,1799		0x34, 0xdc, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0x15,1800		0x3c, 0xf3, 0x92, 0xf5, 0x34, 0x79, 0xb9, 0x78,1801		0xb9, 0x00, 0x50, 0x4b, 0x07, 0x08, 0x93, 0x7e,1802		0x93, 0xaf, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00,1803		0x00, 0x00, 0x50, 0x4b, 0x01, 0x02, 0x14, 0x00,1804		0x14, 0x00, 0x08, 0x08, 0x08, 0x00, 0x49, 0x86,1805		0x81, 0x55, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,1806		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00,1807		0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1808		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1809		0x4d, 0x45, 0x54, 0x41, 0x2d, 0x49, 0x4e, 0x46,1810		0x2f, 0xfe, 0xca, 0x00, 0x00, 0x50, 0x4b, 0x01,1811		0x02, 0x14, 0x00, 0x14, 0x00, 0x08, 0x08, 0x08,1812		0x00, 0x49, 0x86, 0x81, 0x55, 0x93, 0x7e, 0x93,1813		0xaf, 0x3b, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00,1814		0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,1815		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d,1816		0x00, 0x00, 0x00, 0x4d, 0x45, 0x54, 0x41, 0x2d,1817		0x49, 0x4e, 0x46, 0x2f, 0x4d, 0x41, 0x4e, 0x49,1818		0x46, 0x45, 0x53, 0x54, 0x2e, 0x4d, 0x46, 0x50,1819		0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02,1820		0x00, 0x02, 0x00, 0x7d, 0x00, 0x00, 0x00, 0xba,1821		0x00, 0x00, 0x00, 0x00, 0x00,1822	}1823	r, err := NewReader(bytes.NewReader(data), int64(len(data)))1824	if err != nil {1825		t.Fatalf("unexpected error: %v", err)1826	}1827	for _, f := range r.File {1828		r, err := f.Open()1829		if err != nil {1830			t.Fatalf("unexpected error: %v", err)1831		}1832		if _, err := io.Copy(io.Discard, r); err != nil {1833			t.Fatalf("unexpected error: %v", err)1834		}1835	}1836}18371838func TestBaseOffsetPlusOverflow(t *testing.T) {1839	// directoryOffset > maxInt64 && size-directoryOffset < 01840	data := []byte{1841		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1842		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1843		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1844		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1845		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1846		0xff, 0xff, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1847		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1848		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1849		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1850		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1851		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1852		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1853		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1854		0x20, 0x20, 0x20, 0x50, 0x4b, 0x06, 0x06, 0x20,1855		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1856		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1857		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1858		0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,1859		0x20, 0xff, 0xff, 0x20, 0x00, 0x00, 0x00, 0x00,1860		0x00, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x00,1861		0x00, 0x00, 0x80, 0x50, 0x4b, 0x06, 0x07, 0x00,1862		0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x00,1863		0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50,1864		0x4b, 0x05, 0x06, 0x20, 0x20, 0x20, 0x20, 0xff,1865		0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,1866		0xff, 0xff, 0xff, 0x20, 0x00,1867	}1868	defer func() {1869		if r := recover(); r != nil {1870			t.Fatalf("NewReader panicked: %s", r)1871		}1872	}()1873	// Previously, this would trigger a panic as we attempt to read from1874	// an io.SectionReader which would access a slice at a negative offset1875	// as the section reader offset & size were < 0.1876	NewReader(bytes.NewReader(data), int64(len(data))+1875)1877}18781879func BenchmarkReaderOneDeepDir(b *testing.B) {1880	var buf bytes.Buffer1881	zw := NewWriter(&buf)18821883	for i := range 4000 {1884		name := strings.Repeat("a/", i) + "data"1885		zw.CreateHeader(&FileHeader{1886			Name:   name,1887			Method: Store,1888		})1889	}18901891	if err := zw.Close(); err != nil {1892		b.Fatal(err)1893	}1894	data := buf.Bytes()18951896	for b.Loop() {1897		zr, err := NewReader(bytes.NewReader(data), int64(len(data)))1898		if err != nil {1899			b.Fatal(err)1900		}1901		zr.Open("does-not-exist")1902	}1903}19041905func BenchmarkReaderManyDeepDirs(b *testing.B) {1906	var buf bytes.Buffer1907	zw := NewWriter(&buf)19081909	for i := range 2850 {1910		name := fmt.Sprintf("%x", i)1911		name = strings.Repeat("/"+name, i+1)[1:]19121913		zw.CreateHeader(&FileHeader{1914			Name:   name,1915			Method: Store,1916		})1917	}19181919	if err := zw.Close(); err != nil {1920		b.Fatal(err)1921	}1922	data := buf.Bytes()19231924	for b.Loop() {1925		zr, err := NewReader(bytes.NewReader(data), int64(len(data)))1926		if err != nil {1927			b.Fatal(err)1928		}1929		zr.Open("does-not-exist")1930	}1931}19321933func BenchmarkReaderManyShallowFiles(b *testing.B) {1934	var buf bytes.Buffer1935	zw := NewWriter(&buf)19361937	for i := range 310000 {1938		name := fmt.Sprintf("%v", i)1939		zw.CreateHeader(&FileHeader{1940			Name:   name,1941			Method: Store,1942		})1943	}19441945	if err := zw.Close(); err != nil {1946		b.Fatal(err)1947	}1948	data := buf.Bytes()19491950	for b.Loop() {1951		zr, err := NewReader(bytes.NewReader(data), int64(len(data)))1952		if err != nil {1953			b.Fatal(err)1954		}1955		zr.Open("does-not-exist")1956	}1957}

Code quality findings 21

Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer z.Close()
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer z.Close()
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer z.Close()
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer rd.Close()
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer z.Close()
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
path := filepath.Join("testdata", zt.Name)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, ft := range zt.File {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for j, ft := range zt.File {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, b := range b.Bytes() {
String to byte slice conversion inside loop allocates a new slice each iteration; convert once before the loop
info correctness string-to-byte-in-loop
_, err = NewReader(bytes.NewReader([]byte("foobar")), -1)
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
data, err := os.ReadFile(filepath.Join("testdata", fileName))
Regexp compiled inside function; compile once at package level to avoid recompilation on each call
info performance regexp-compile-in-func
s = regexp.MustCompile(`[0-9a-f]{7}`).ReplaceAllString(s, "")
Regexp compiled inside function; compile once at package level to avoid recompilation on each call
info performance regexp-compile-in-func
s = regexp.MustCompile(`\s+`).ReplaceAllString(s, "")
Regexp compiled inside function; compile once at package level to avoid recompilation on each call
info performance regexp-compile-in-func
s = regexp.MustCompile(`[0-9a-f]{7}`).ReplaceAllString(s, "")
Regexp compiled inside function; compile once at package level to avoid recompilation on each call
info performance regexp-compile-in-func
s = regexp.MustCompile(`\s+`).ReplaceAllString(s, "")
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, s := range dirEnts {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, f := range z.File {
Can cause issues on Windows consider filepath.Join instead
info correctness path-join-windows
name := filepath.Join(t.TempDir(), "test.zip")
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
names = append(names, f.Name)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
gotPaths = append(gotPaths, f.Name)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
gotPaths = append(gotPaths, f.Name)

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.