From 957ab96be4da9a3c5344c8dfd5748aaa549a6666 Mon Sep 17 00:00:00 2001 From: Arran Walker Date: Sun, 7 Jun 2020 09:45:26 +0000 Subject: [PATCH] Remove patched archive/zip, replace with klauspost's klauspost kindly implemented the CreateHeaderRaw proposal (https://github.com/golang/go/issues/34974) in https://github.com/klauspost/compress/pull/214. This repository was vendoring the patched version (https://go-review.googlesource.com/c/go/+/202217/) but having it supported in a well maintained library already used by fastzip is a plus. --- archiver.go | 2 +- archiver_test.go | 2 +- archiver_unix.go | 2 +- archiver_windows.go | 2 +- extractor.go | 2 +- extractor_test.go | 2 +- go.mod | 2 +- go.sum | 4 +- internal/zip/README.md | 6 - internal/zip/example_test.go | 93 -- internal/zip/reader.go | 609 ---------- internal/zip/reader_test.go | 1056 ----------------- internal/zip/register.go | 148 --- internal/zip/struct.go | 387 ------ internal/zip/testdata/crc32-not-streamed.zip | Bin 314 -> 0 bytes internal/zip/testdata/dd.zip | Bin 154 -> 0 bytes internal/zip/testdata/go-no-datadesc-sig.zip | Bin 330 -> 0 bytes .../zip/testdata/go-with-datadesc-sig.zip | Bin 242 -> 0 bytes internal/zip/testdata/gophercolor16x16.png | Bin 785 -> 0 bytes internal/zip/testdata/readme.notzip | Bin 1906 -> 0 bytes internal/zip/testdata/readme.zip | Bin 1886 -> 0 bytes internal/zip/testdata/symlink.zip | Bin 173 -> 0 bytes internal/zip/testdata/test-trailing-junk.zip | Bin 1184 -> 0 bytes internal/zip/testdata/test.zip | Bin 1170 -> 0 bytes internal/zip/testdata/time-22738.zip | Bin 140 -> 0 bytes internal/zip/testdata/time-7zip.zip | Bin 150 -> 0 bytes internal/zip/testdata/time-go.zip | Bin 148 -> 0 bytes internal/zip/testdata/time-infozip.zip | Bin 166 -> 0 bytes internal/zip/testdata/time-osx.zip | Bin 142 -> 0 bytes internal/zip/testdata/time-win7.zip | Bin 114 -> 0 bytes internal/zip/testdata/time-winrar.zip | Bin 150 -> 0 bytes internal/zip/testdata/time-winzip.zip | Bin 150 -> 0 bytes internal/zip/testdata/unix.zip | Bin 620 -> 0 bytes internal/zip/testdata/utf8-7zip.zip | Bin 146 -> 0 bytes internal/zip/testdata/utf8-infozip.zip | Bin 162 -> 0 bytes internal/zip/testdata/utf8-osx.zip | Bin 138 -> 0 bytes internal/zip/testdata/utf8-winrar.zip | Bin 146 -> 0 bytes internal/zip/testdata/utf8-winzip.zip | Bin 146 -> 0 bytes internal/zip/testdata/winxp.zip | Bin 412 -> 0 bytes internal/zip/testdata/zip64-2.zip | Bin 266 -> 0 bytes internal/zip/testdata/zip64.zip | Bin 242 -> 0 bytes internal/zip/writer.go | 575 --------- internal/zip/writer_test.go | 467 -------- internal/zip/zip_test.go | 828 ------------- 44 files changed, 9 insertions(+), 4178 deletions(-) delete mode 100644 internal/zip/README.md delete mode 100644 internal/zip/example_test.go delete mode 100644 internal/zip/reader.go delete mode 100644 internal/zip/reader_test.go delete mode 100644 internal/zip/register.go delete mode 100644 internal/zip/struct.go delete mode 100644 internal/zip/testdata/crc32-not-streamed.zip delete mode 100644 internal/zip/testdata/dd.zip delete mode 100644 internal/zip/testdata/go-no-datadesc-sig.zip delete mode 100644 internal/zip/testdata/go-with-datadesc-sig.zip delete mode 100644 internal/zip/testdata/gophercolor16x16.png delete mode 100644 internal/zip/testdata/readme.notzip delete mode 100644 internal/zip/testdata/readme.zip delete mode 100644 internal/zip/testdata/symlink.zip delete mode 100644 internal/zip/testdata/test-trailing-junk.zip delete mode 100644 internal/zip/testdata/test.zip delete mode 100644 internal/zip/testdata/time-22738.zip delete mode 100644 internal/zip/testdata/time-7zip.zip delete mode 100644 internal/zip/testdata/time-go.zip delete mode 100644 internal/zip/testdata/time-infozip.zip delete mode 100644 internal/zip/testdata/time-osx.zip delete mode 100644 internal/zip/testdata/time-win7.zip delete mode 100644 internal/zip/testdata/time-winrar.zip delete mode 100644 internal/zip/testdata/time-winzip.zip delete mode 100644 internal/zip/testdata/unix.zip delete mode 100644 internal/zip/testdata/utf8-7zip.zip delete mode 100644 internal/zip/testdata/utf8-infozip.zip delete mode 100644 internal/zip/testdata/utf8-osx.zip delete mode 100644 internal/zip/testdata/utf8-winrar.zip delete mode 100644 internal/zip/testdata/utf8-winzip.zip delete mode 100644 internal/zip/testdata/winxp.zip delete mode 100644 internal/zip/testdata/zip64-2.zip delete mode 100644 internal/zip/testdata/zip64.zip delete mode 100644 internal/zip/writer.go delete mode 100644 internal/zip/writer_test.go delete mode 100644 internal/zip/zip_test.go diff --git a/archiver.go b/archiver.go index b9ec202..4cb64c7 100644 --- a/archiver.go +++ b/archiver.go @@ -13,8 +13,8 @@ import ( "sync" "sync/atomic" + "github.com/klauspost/compress/zip" "github.com/saracen/fastzip/internal/filepool" - "github.com/saracen/fastzip/internal/zip" "golang.org/x/sync/errgroup" ) diff --git a/archiver_test.go b/archiver_test.go index 495d473..ac76653 100644 --- a/archiver_test.go +++ b/archiver_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/saracen/fastzip/internal/zip" + "github.com/klauspost/compress/zip" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/archiver_unix.go b/archiver_unix.go index 17053dc..11d754a 100644 --- a/archiver_unix.go +++ b/archiver_unix.go @@ -8,7 +8,7 @@ import ( "os" "syscall" - "github.com/saracen/fastzip/internal/zip" + "github.com/klauspost/compress/zip" "github.com/saracen/zipextra" ) diff --git a/archiver_windows.go b/archiver_windows.go index 96491af..f83a6f6 100644 --- a/archiver_windows.go +++ b/archiver_windows.go @@ -6,7 +6,7 @@ import ( "io" "os" - "github.com/saracen/fastzip/internal/zip" + "github.com/klauspost/compress/zip" ) func (a *Archiver) createHeader(fi os.FileInfo, hdr *zip.FileHeader) (io.Writer, error) { diff --git a/extractor.go b/extractor.go index eb469a1..b8e86c3 100644 --- a/extractor.go +++ b/extractor.go @@ -13,7 +13,7 @@ import ( "sync/atomic" "time" - "github.com/saracen/fastzip/internal/zip" + "github.com/klauspost/compress/zip" "github.com/saracen/zipextra" "golang.org/x/sync/errgroup" ) diff --git a/extractor_test.go b/extractor_test.go index 2b6b802..080f26c 100644 --- a/extractor_test.go +++ b/extractor_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/saracen/fastzip/internal/zip" + "github.com/klauspost/compress/zip" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/go.mod b/go.mod index ac85ff3..86ea7e7 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/saracen/fastzip go 1.13 require ( - github.com/klauspost/compress v1.10.2 + github.com/klauspost/compress v1.10.9-0.20200607082256-01fb7573b37c github.com/saracen/zipextra v0.0.0-20191018034721-f8fcc2e1b58e github.com/stretchr/testify v1.4.0 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e diff --git a/go.sum b/go.sum index 3f80074..285a28f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/klauspost/compress v1.10.2 h1:Znfn6hXZAHaLPNnlqUYRrBSReFHYybslgv4PTiyz6P0= -github.com/klauspost/compress v1.10.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.9-0.20200607082256-01fb7573b37c h1:4LIA4AoZrqzzOiXb+nJQP26uQrIW/fXqYhQR4Foonks= +github.com/klauspost/compress v1.10.9-0.20200607082256-01fb7573b37c/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/saracen/zipextra v0.0.0-20191018034721-f8fcc2e1b58e h1:psyRxwoYbvGxzM8WucxDD5TJ994ePVteYpeb4BIZVic= diff --git a/internal/zip/README.md b/internal/zip/README.md deleted file mode 100644 index ee6d8f7..0000000 --- a/internal/zip/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# ZIP Package - -This is the standard library's `archive/zip` code, but with [this patch](https://go-review.googlesource.com/c/go/+/202217) applied to allow for adding raw data to a zip archive. Hopefully this will be merged upstream and this internal package removed. - - - diff --git a/internal/zip/example_test.go b/internal/zip/example_test.go deleted file mode 100644 index 1eed304..0000000 --- a/internal/zip/example_test.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package zip_test - -import ( - "archive/zip" - "bytes" - "compress/flate" - "fmt" - "io" - "log" - "os" -) - -func ExampleWriter() { - // Create a buffer to write our archive to. - buf := new(bytes.Buffer) - - // Create a new zip archive. - w := zip.NewWriter(buf) - - // Add some files to the archive. - var files = []struct { - Name, Body string - }{ - {"readme.txt", "This archive contains some text files."}, - {"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"}, - {"todo.txt", "Get animal handling licence.\nWrite more examples."}, - } - for _, file := range files { - f, err := w.Create(file.Name) - if err != nil { - log.Fatal(err) - } - _, err = f.Write([]byte(file.Body)) - if err != nil { - log.Fatal(err) - } - } - - // Make sure to check the error on Close. - err := w.Close() - if err != nil { - log.Fatal(err) - } -} - -func ExampleReader() { - // Open a zip archive for reading. - r, err := zip.OpenReader("testdata/readme.zip") - if err != nil { - log.Fatal(err) - } - defer r.Close() - - // Iterate through the files in the archive, - // printing some of their contents. - for _, f := range r.File { - fmt.Printf("Contents of %s:\n", f.Name) - rc, err := f.Open() - if err != nil { - log.Fatal(err) - } - _, err = io.CopyN(os.Stdout, rc, 68) - if err != nil { - log.Fatal(err) - } - rc.Close() - fmt.Println() - } - // Output: - // Contents of README: - // This is the source code repository for the Go programming language. -} - -func ExampleWriter_RegisterCompressor() { - // Override the default Deflate compressor with a higher compression level. - - // Create a buffer to write our archive to. - buf := new(bytes.Buffer) - - // Create a new zip archive. - w := zip.NewWriter(buf) - - // Register a custom Deflate compressor. - w.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) { - return flate.NewWriter(out, flate.BestCompression) - }) - - // Proceed to add files to w. -} diff --git a/internal/zip/reader.go b/internal/zip/reader.go deleted file mode 100644 index 13ff9dd..0000000 --- a/internal/zip/reader.go +++ /dev/null @@ -1,609 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package zip - -import ( - "bufio" - "encoding/binary" - "errors" - "hash" - "hash/crc32" - "io" - "os" - "time" -) - -var ( - ErrFormat = errors.New("zip: not a valid zip file") - ErrAlgorithm = errors.New("zip: unsupported compression algorithm") - ErrChecksum = errors.New("zip: checksum error") -) - -type Reader struct { - r io.ReaderAt - File []*File - Comment string - decompressors map[uint16]Decompressor -} - -type ReadCloser struct { - f *os.File - Reader -} - -type File struct { - FileHeader - zip *Reader - zipr io.ReaderAt - zipsize int64 - headerOffset int64 -} - -func (f *File) hasDataDescriptor() bool { - return f.Flags&0x8 != 0 -} - -// OpenReader will open the Zip file specified by name and return a ReadCloser. -func OpenReader(name string) (*ReadCloser, error) { - f, err := os.Open(name) - if err != nil { - return nil, err - } - fi, err := f.Stat() - if err != nil { - f.Close() - return nil, err - } - r := new(ReadCloser) - if err := r.init(f, fi.Size()); err != nil { - f.Close() - return nil, err - } - r.f = f - return r, nil -} - -// NewReader returns a new Reader reading from r, which is assumed to -// have the given size in bytes. -func NewReader(r io.ReaderAt, size int64) (*Reader, error) { - if size < 0 { - return nil, errors.New("zip: size cannot be negative") - } - zr := new(Reader) - if err := zr.init(r, size); err != nil { - return nil, err - } - return zr, nil -} - -func (z *Reader) init(r io.ReaderAt, size int64) error { - end, err := readDirectoryEnd(r, size) - if err != nil { - return err - } - z.r = r - z.File = make([]*File, 0, end.directoryRecords) - z.Comment = end.comment - rs := io.NewSectionReader(r, 0, size) - if _, err = rs.Seek(int64(end.directoryOffset), io.SeekStart); err != nil { - return err - } - buf := bufio.NewReader(rs) - - // The count of files inside a zip is truncated to fit in a uint16. - // Gloss over this by reading headers until we encounter - // a bad one, and then only report an ErrFormat or UnexpectedEOF if - // the file count modulo 65536 is incorrect. - for { - f := &File{zip: z, zipr: r, zipsize: size} - err = readDirectoryHeader(f, buf) - if err == ErrFormat || err == io.ErrUnexpectedEOF { - break - } - if err != nil { - return err - } - z.File = append(z.File, f) - } - if uint16(len(z.File)) != uint16(end.directoryRecords) { // only compare 16 bits here - // Return the readDirectoryHeader error if we read - // the wrong number of directory entries. - return err - } - return nil -} - -// RegisterDecompressor registers or overrides a custom decompressor for a -// specific method ID. If a decompressor for a given method is not found, -// Reader will default to looking up the decompressor at the package level. -func (z *Reader) RegisterDecompressor(method uint16, dcomp Decompressor) { - if z.decompressors == nil { - z.decompressors = make(map[uint16]Decompressor) - } - z.decompressors[method] = dcomp -} - -func (z *Reader) decompressor(method uint16) Decompressor { - dcomp := z.decompressors[method] - if dcomp == nil { - dcomp = decompressor(method) - } - return dcomp -} - -// Close closes the Zip file, rendering it unusable for I/O. -func (rc *ReadCloser) Close() error { - return rc.f.Close() -} - -// DataOffset returns the offset of the file's possibly-compressed -// data, relative to the beginning of the zip file. -// -// Most callers should instead use Open, which transparently -// decompresses data and verifies checksums. -func (f *File) DataOffset() (offset int64, err error) { - bodyOffset, err := f.findBodyOffset() - if err != nil { - return - } - return f.headerOffset + bodyOffset, nil -} - -// Open returns a ReadCloser that provides access to the File's contents. -// Multiple files may be read concurrently. -func (f *File) Open() (io.ReadCloser, error) { - bodyOffset, err := f.findBodyOffset() - if err != nil { - return nil, err - } - size := int64(f.CompressedSize64) - r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size) - dcomp := f.zip.decompressor(f.Method) - if dcomp == nil { - return nil, ErrAlgorithm - } - var rc io.ReadCloser = dcomp(r) - var desr io.Reader - if f.hasDataDescriptor() { - desr = io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset+size, dataDescriptorLen) - } - rc = &checksumReader{ - rc: rc, - hash: crc32.NewIEEE(), - f: f, - desr: desr, - } - return rc, nil -} - -type checksumReader struct { - rc io.ReadCloser - hash hash.Hash32 - nread uint64 // number of bytes read so far - f *File - desr io.Reader // if non-nil, where to read the data descriptor - err error // sticky error -} - -func (r *checksumReader) Read(b []byte) (n int, err error) { - if r.err != nil { - return 0, r.err - } - n, err = r.rc.Read(b) - r.hash.Write(b[:n]) - r.nread += uint64(n) - if err == nil { - return - } - if err == io.EOF { - if r.nread != r.f.UncompressedSize64 { - return 0, io.ErrUnexpectedEOF - } - if r.desr != nil { - if err1 := readDataDescriptor(r.desr, r.f); err1 != nil { - if err1 == io.EOF { - err = io.ErrUnexpectedEOF - } else { - err = err1 - } - } else if r.hash.Sum32() != r.f.CRC32 { - err = ErrChecksum - } - } else { - // If there's not a data descriptor, we still compare - // the CRC32 of what we've read against the file header - // or TOC's CRC32, if it seems like it was set. - if r.f.CRC32 != 0 && r.hash.Sum32() != r.f.CRC32 { - err = ErrChecksum - } - } - } - r.err = err - return -} - -func (r *checksumReader) Close() error { return r.rc.Close() } - -// findBodyOffset does the minimum work to verify the file has a header -// and returns the file body offset. -func (f *File) findBodyOffset() (int64, error) { - var buf [fileHeaderLen]byte - if _, err := f.zipr.ReadAt(buf[:], f.headerOffset); err != nil { - return 0, err - } - b := readBuf(buf[:]) - if sig := b.uint32(); sig != fileHeaderSignature { - return 0, ErrFormat - } - b = b[22:] // skip over most of the header - filenameLen := int(b.uint16()) - extraLen := int(b.uint16()) - return int64(fileHeaderLen + filenameLen + extraLen), nil -} - -// readDirectoryHeader attempts to read a directory header from r. -// It returns io.ErrUnexpectedEOF if it cannot read a complete header, -// and ErrFormat if it doesn't find a valid header signature. -func readDirectoryHeader(f *File, r io.Reader) error { - var buf [directoryHeaderLen]byte - if _, err := io.ReadFull(r, buf[:]); err != nil { - return err - } - b := readBuf(buf[:]) - if sig := b.uint32(); sig != directoryHeaderSignature { - return ErrFormat - } - f.CreatorVersion = b.uint16() - f.ReaderVersion = b.uint16() - f.Flags = b.uint16() - f.Method = b.uint16() - f.ModifiedTime = b.uint16() - f.ModifiedDate = b.uint16() - f.CRC32 = b.uint32() - f.CompressedSize = b.uint32() - f.UncompressedSize = b.uint32() - f.CompressedSize64 = uint64(f.CompressedSize) - f.UncompressedSize64 = uint64(f.UncompressedSize) - filenameLen := int(b.uint16()) - extraLen := int(b.uint16()) - commentLen := int(b.uint16()) - b = b[4:] // skipped start disk number and internal attributes (2x uint16) - f.ExternalAttrs = b.uint32() - f.headerOffset = int64(b.uint32()) - d := make([]byte, filenameLen+extraLen+commentLen) - if _, err := io.ReadFull(r, d); err != nil { - return err - } - f.Name = string(d[:filenameLen]) - f.Extra = d[filenameLen : filenameLen+extraLen] - f.Comment = string(d[filenameLen+extraLen:]) - - // Determine the character encoding. - utf8Valid1, utf8Require1 := detectUTF8(f.Name) - utf8Valid2, utf8Require2 := detectUTF8(f.Comment) - switch { - case !utf8Valid1 || !utf8Valid2: - // Name and Comment definitely not UTF-8. - f.NonUTF8 = true - case !utf8Require1 && !utf8Require2: - // Name and Comment use only single-byte runes that overlap with UTF-8. - f.NonUTF8 = false - default: - // Might be UTF-8, might be some other encoding; preserve existing flag. - // Some ZIP writers use UTF-8 encoding without setting the UTF-8 flag. - // Since it is impossible to always distinguish valid UTF-8 from some - // other encoding (e.g., GBK or Shift-JIS), we trust the flag. - f.NonUTF8 = f.Flags&0x800 == 0 - } - - needUSize := f.UncompressedSize == ^uint32(0) - needCSize := f.CompressedSize == ^uint32(0) - needHeaderOffset := f.headerOffset == int64(^uint32(0)) - - // Best effort to find what we need. - // Other zip authors might not even follow the basic format, - // and we'll just ignore the Extra content in that case. - var modified time.Time -parseExtras: - for extra := readBuf(f.Extra); len(extra) >= 4; { // need at least tag and size - fieldTag := extra.uint16() - fieldSize := int(extra.uint16()) - if len(extra) < fieldSize { - break - } - fieldBuf := extra.sub(fieldSize) - - switch fieldTag { - case zip64ExtraID: - // update directory values from the zip64 extra block. - // They should only be consulted if the sizes read earlier - // are maxed out. - // See golang.org/issue/13367. - if needUSize { - needUSize = false - if len(fieldBuf) < 8 { - return ErrFormat - } - f.UncompressedSize64 = fieldBuf.uint64() - } - if needCSize { - needCSize = false - if len(fieldBuf) < 8 { - return ErrFormat - } - f.CompressedSize64 = fieldBuf.uint64() - } - if needHeaderOffset { - needHeaderOffset = false - if len(fieldBuf) < 8 { - return ErrFormat - } - f.headerOffset = int64(fieldBuf.uint64()) - } - case ntfsExtraID: - if len(fieldBuf) < 4 { - continue parseExtras - } - fieldBuf.uint32() // reserved (ignored) - for len(fieldBuf) >= 4 { // need at least tag and size - attrTag := fieldBuf.uint16() - attrSize := int(fieldBuf.uint16()) - if len(fieldBuf) < attrSize { - continue parseExtras - } - attrBuf := fieldBuf.sub(attrSize) - if attrTag != 1 || attrSize != 24 { - continue // Ignore irrelevant attributes - } - - const ticksPerSecond = 1e7 // Windows timestamp resolution - ts := int64(attrBuf.uint64()) // ModTime since Windows epoch - secs := int64(ts / ticksPerSecond) - nsecs := (1e9 / ticksPerSecond) * int64(ts%ticksPerSecond) - epoch := time.Date(1601, time.January, 1, 0, 0, 0, 0, time.UTC) - modified = time.Unix(epoch.Unix()+secs, nsecs) - } - case unixExtraID, infoZipUnixExtraID: - if len(fieldBuf) < 8 { - continue parseExtras - } - fieldBuf.uint32() // AcTime (ignored) - ts := int64(fieldBuf.uint32()) // ModTime since Unix epoch - modified = time.Unix(ts, 0) - case extTimeExtraID: - if len(fieldBuf) < 5 || fieldBuf.uint8()&1 == 0 { - continue parseExtras - } - ts := int64(fieldBuf.uint32()) // ModTime since Unix epoch - modified = time.Unix(ts, 0) - } - } - - msdosModified := msDosTimeToTime(f.ModifiedDate, f.ModifiedTime) - f.Modified = msdosModified - if !modified.IsZero() { - f.Modified = modified.UTC() - - // If legacy MS-DOS timestamps are set, we can use the delta between - // the legacy and extended versions to estimate timezone offset. - // - // A non-UTC timezone is always used (even if offset is zero). - // Thus, FileHeader.Modified.Location() == time.UTC is useful for - // determining whether extended timestamps are present. - // This is necessary for users that need to do additional time - // calculations when dealing with legacy ZIP formats. - if f.ModifiedTime != 0 || f.ModifiedDate != 0 { - f.Modified = modified.In(timeZone(msdosModified.Sub(modified))) - } - } - - // Assume that uncompressed size 2³²-1 could plausibly happen in - // an old zip32 file that was sharding inputs into the largest chunks - // possible (or is just malicious; search the web for 42.zip). - // If needUSize is true still, it means we didn't see a zip64 extension. - // As long as the compressed size is not also 2³²-1 (implausible) - // and the header is not also 2³²-1 (equally implausible), - // accept the uncompressed size 2³²-1 as valid. - // If nothing else, this keeps archive/zip working with 42.zip. - _ = needUSize - - if needCSize || needHeaderOffset { - return ErrFormat - } - - return nil -} - -func readDataDescriptor(r io.Reader, f *File) error { - var buf [dataDescriptorLen]byte - - // The spec says: "Although not originally assigned a - // signature, the value 0x08074b50 has commonly been adopted - // as a signature value for the data descriptor record. - // Implementers should be aware that ZIP files may be - // encountered with or without this signature marking data - // descriptors and should account for either case when reading - // ZIP files to ensure compatibility." - // - // dataDescriptorLen includes the size of the signature but - // first read just those 4 bytes to see if it exists. - if _, err := io.ReadFull(r, buf[:4]); err != nil { - return err - } - off := 0 - maybeSig := readBuf(buf[:4]) - if maybeSig.uint32() != dataDescriptorSignature { - // No data descriptor signature. Keep these four - // bytes. - off += 4 - } - if _, err := io.ReadFull(r, buf[off:12]); err != nil { - return err - } - b := readBuf(buf[:12]) - if b.uint32() != f.CRC32 { - return ErrChecksum - } - - // The two sizes that follow here can be either 32 bits or 64 bits - // but the spec is not very clear on this and different - // interpretations has been made causing incompatibilities. We - // already have the sizes from the central directory so we can - // just ignore these. - - return nil -} - -func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) { - // look for directoryEndSignature in the last 1k, then in the last 65k - var buf []byte - var directoryEndOffset int64 - for i, bLen := range []int64{1024, 65 * 1024} { - if bLen > size { - bLen = size - } - buf = make([]byte, int(bLen)) - if _, err := r.ReadAt(buf, size-bLen); err != nil && err != io.EOF { - return nil, err - } - if p := findSignatureInBlock(buf); p >= 0 { - buf = buf[p:] - directoryEndOffset = size - bLen + int64(p) - break - } - if i == 1 || bLen == size { - return nil, ErrFormat - } - } - - // read header into struct - b := readBuf(buf[4:]) // skip signature - d := &directoryEnd{ - diskNbr: uint32(b.uint16()), - dirDiskNbr: uint32(b.uint16()), - dirRecordsThisDisk: uint64(b.uint16()), - directoryRecords: uint64(b.uint16()), - directorySize: uint64(b.uint32()), - directoryOffset: uint64(b.uint32()), - commentLen: b.uint16(), - } - l := int(d.commentLen) - if l > len(b) { - return nil, errors.New("zip: invalid comment length") - } - d.comment = string(b[:l]) - - // These values mean that the file can be a zip64 file - if d.directoryRecords == 0xffff || d.directorySize == 0xffff || d.directoryOffset == 0xffffffff { - p, err := findDirectory64End(r, directoryEndOffset) - if err == nil && p >= 0 { - err = readDirectory64End(r, p, d) - } - if err != nil { - return nil, err - } - } - // Make sure directoryOffset points to somewhere in our file. - if o := int64(d.directoryOffset); o < 0 || o >= size { - return nil, ErrFormat - } - return d, nil -} - -// findDirectory64End tries to read the zip64 locator just before the -// directory end and returns the offset of the zip64 directory end if -// found. -func findDirectory64End(r io.ReaderAt, directoryEndOffset int64) (int64, error) { - locOffset := directoryEndOffset - directory64LocLen - if locOffset < 0 { - return -1, nil // no need to look for a header outside the file - } - buf := make([]byte, directory64LocLen) - if _, err := r.ReadAt(buf, locOffset); err != nil { - return -1, err - } - b := readBuf(buf) - if sig := b.uint32(); sig != directory64LocSignature { - return -1, nil - } - if b.uint32() != 0 { // number of the disk with the start of the zip64 end of central directory - return -1, nil // the file is not a valid zip64-file - } - p := b.uint64() // relative offset of the zip64 end of central directory record - if b.uint32() != 1 { // total number of disks - return -1, nil // the file is not a valid zip64-file - } - return int64(p), nil -} - -// readDirectory64End reads the zip64 directory end and updates the -// directory end with the zip64 directory end values. -func readDirectory64End(r io.ReaderAt, offset int64, d *directoryEnd) (err error) { - buf := make([]byte, directory64EndLen) - if _, err := r.ReadAt(buf, offset); err != nil { - return err - } - - b := readBuf(buf) - if sig := b.uint32(); sig != directory64EndSignature { - return ErrFormat - } - - b = b[12:] // skip dir size, version and version needed (uint64 + 2x uint16) - d.diskNbr = b.uint32() // number of this disk - d.dirDiskNbr = b.uint32() // number of the disk with the start of the central directory - d.dirRecordsThisDisk = b.uint64() // total number of entries in the central directory on this disk - d.directoryRecords = b.uint64() // total number of entries in the central directory - d.directorySize = b.uint64() // size of the central directory - d.directoryOffset = b.uint64() // offset of start of central directory with respect to the starting disk number - - return nil -} - -func findSignatureInBlock(b []byte) int { - for i := len(b) - directoryEndLen; i >= 0; i-- { - // defined from directoryEndSignature in struct.go - if b[i] == 'P' && b[i+1] == 'K' && b[i+2] == 0x05 && b[i+3] == 0x06 { - // n is length of comment - n := int(b[i+directoryEndLen-2]) | int(b[i+directoryEndLen-1])<<8 - if n+directoryEndLen+i <= len(b) { - return i - } - } - } - return -1 -} - -type readBuf []byte - -func (b *readBuf) uint8() uint8 { - v := (*b)[0] - *b = (*b)[1:] - return v -} - -func (b *readBuf) uint16() uint16 { - v := binary.LittleEndian.Uint16(*b) - *b = (*b)[2:] - return v -} - -func (b *readBuf) uint32() uint32 { - v := binary.LittleEndian.Uint32(*b) - *b = (*b)[4:] - return v -} - -func (b *readBuf) uint64() uint64 { - v := binary.LittleEndian.Uint64(*b) - *b = (*b)[8:] - return v -} - -func (b *readBuf) sub(n int) readBuf { - b2 := (*b)[:n] - *b = (*b)[n:] - return b2 -} diff --git a/internal/zip/reader_test.go b/internal/zip/reader_test.go deleted file mode 100644 index 328559c..0000000 --- a/internal/zip/reader_test.go +++ /dev/null @@ -1,1056 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package zip - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "io" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - "testing" - "time" -) - -type ZipTest struct { - Name string - Source func() (r io.ReaderAt, size int64) // if non-nil, used instead of testdata/ file - Comment string - File []ZipTestFile - Error error // the error that Opening this file should return -} - -type ZipTestFile struct { - Name string - Mode os.FileMode - NonUTF8 bool - ModTime time.Time - Modified time.Time - - // Information describing expected zip file content. - // First, reading the entire content should produce the error ContentErr. - // Second, if ContentErr==nil, the content should match Content. - // If content is large, an alternative to setting Content is to set File, - // which names a file in the testdata/ directory containing the - // uncompressed expected content. - // If content is very large, an alternative to setting Content or File - // is to set Size, which will then be checked against the header-reported size - // but will bypass the decompressing of the actual data. - // This last option is used for testing very large (multi-GB) compressed files. - ContentErr error - Content []byte - File string - Size uint64 -} - -var tests = []ZipTest{ - { - Name: "test.zip", - Comment: "This is a zipfile comment.", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte("This is a test text file.\n"), - Modified: time.Date(2010, 9, 5, 12, 12, 1, 0, timeZone(+10*time.Hour)), - Mode: 0644, - }, - { - Name: "gophercolor16x16.png", - File: "gophercolor16x16.png", - Modified: time.Date(2010, 9, 5, 15, 52, 58, 0, timeZone(+10*time.Hour)), - Mode: 0644, - }, - }, - }, - { - Name: "test-trailing-junk.zip", - Comment: "This is a zipfile comment.", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte("This is a test text file.\n"), - Modified: time.Date(2010, 9, 5, 12, 12, 1, 0, timeZone(+10*time.Hour)), - Mode: 0644, - }, - { - Name: "gophercolor16x16.png", - File: "gophercolor16x16.png", - Modified: time.Date(2010, 9, 5, 15, 52, 58, 0, timeZone(+10*time.Hour)), - Mode: 0644, - }, - }, - }, - { - Name: "r.zip", - Source: returnRecursiveZip, - File: []ZipTestFile{ - { - Name: "r/r.zip", - Content: rZipBytes(), - Modified: time.Date(2010, 3, 4, 0, 24, 16, 0, time.UTC), - Mode: 0666, - }, - }, - }, - { - Name: "symlink.zip", - File: []ZipTestFile{ - { - Name: "symlink", - Content: []byte("../target"), - Modified: time.Date(2012, 2, 3, 19, 56, 48, 0, timeZone(-2*time.Hour)), - Mode: 0777 | os.ModeSymlink, - }, - }, - }, - { - Name: "readme.zip", - }, - { - Name: "readme.notzip", - Error: ErrFormat, - }, - { - Name: "dd.zip", - File: []ZipTestFile{ - { - Name: "filename", - Content: []byte("This is a test textfile.\n"), - Modified: time.Date(2011, 2, 2, 13, 6, 20, 0, time.UTC), - Mode: 0666, - }, - }, - }, - { - // created in windows XP file manager. - Name: "winxp.zip", - File: []ZipTestFile{ - { - Name: "hello", - Content: []byte("world \r\n"), - Modified: time.Date(2011, 12, 8, 10, 4, 24, 0, time.UTC), - Mode: 0666, - }, - { - Name: "dir/bar", - Content: []byte("foo \r\n"), - Modified: time.Date(2011, 12, 8, 10, 4, 50, 0, time.UTC), - Mode: 0666, - }, - { - Name: "dir/empty/", - Content: []byte{}, - Modified: time.Date(2011, 12, 8, 10, 8, 6, 0, time.UTC), - Mode: os.ModeDir | 0777, - }, - { - Name: "readonly", - Content: []byte("important \r\n"), - Modified: time.Date(2011, 12, 8, 10, 6, 8, 0, time.UTC), - Mode: 0444, - }, - }, - }, - { - // created by Zip 3.0 under Linux - Name: "unix.zip", - File: []ZipTestFile{ - { - Name: "hello", - Content: []byte("world \r\n"), - Modified: time.Date(2011, 12, 8, 10, 4, 24, 0, timeZone(0)), - Mode: 0666, - }, - { - Name: "dir/bar", - Content: []byte("foo \r\n"), - Modified: time.Date(2011, 12, 8, 10, 4, 50, 0, timeZone(0)), - Mode: 0666, - }, - { - Name: "dir/empty/", - Content: []byte{}, - Modified: time.Date(2011, 12, 8, 10, 8, 6, 0, timeZone(0)), - Mode: os.ModeDir | 0777, - }, - { - Name: "readonly", - Content: []byte("important \r\n"), - Modified: time.Date(2011, 12, 8, 10, 6, 8, 0, timeZone(0)), - Mode: 0444, - }, - }, - }, - { - // created by Go, before we wrote the "optional" data - // descriptor signatures (which are required by OS X) - Name: "go-no-datadesc-sig.zip", - File: []ZipTestFile{ - { - Name: "foo.txt", - Content: []byte("foo\n"), - Modified: time.Date(2012, 3, 8, 16, 59, 10, 0, timeZone(-8*time.Hour)), - Mode: 0644, - }, - { - Name: "bar.txt", - Content: []byte("bar\n"), - Modified: time.Date(2012, 3, 8, 16, 59, 12, 0, timeZone(-8*time.Hour)), - Mode: 0644, - }, - }, - }, - { - // created by Go, after we wrote the "optional" data - // descriptor signatures (which are required by OS X) - Name: "go-with-datadesc-sig.zip", - File: []ZipTestFile{ - { - Name: "foo.txt", - Content: []byte("foo\n"), - Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC), - Mode: 0666, - }, - { - Name: "bar.txt", - Content: []byte("bar\n"), - Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC), - Mode: 0666, - }, - }, - }, - { - Name: "Bad-CRC32-in-data-descriptor", - Source: returnCorruptCRC32Zip, - File: []ZipTestFile{ - { - Name: "foo.txt", - Content: []byte("foo\n"), - Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC), - Mode: 0666, - ContentErr: ErrChecksum, - }, - { - Name: "bar.txt", - Content: []byte("bar\n"), - Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC), - Mode: 0666, - }, - }, - }, - // Tests that we verify (and accept valid) crc32s on files - // with crc32s in their file header (not in data descriptors) - { - Name: "crc32-not-streamed.zip", - File: []ZipTestFile{ - { - Name: "foo.txt", - Content: []byte("foo\n"), - Modified: time.Date(2012, 3, 8, 16, 59, 10, 0, timeZone(-8*time.Hour)), - Mode: 0644, - }, - { - Name: "bar.txt", - Content: []byte("bar\n"), - Modified: time.Date(2012, 3, 8, 16, 59, 12, 0, timeZone(-8*time.Hour)), - Mode: 0644, - }, - }, - }, - // Tests that we verify (and reject invalid) crc32s on files - // with crc32s in their file header (not in data descriptors) - { - Name: "crc32-not-streamed.zip", - Source: returnCorruptNotStreamedZip, - File: []ZipTestFile{ - { - Name: "foo.txt", - Content: []byte("foo\n"), - Modified: time.Date(2012, 3, 8, 16, 59, 10, 0, timeZone(-8*time.Hour)), - Mode: 0644, - ContentErr: ErrChecksum, - }, - { - Name: "bar.txt", - Content: []byte("bar\n"), - Modified: time.Date(2012, 3, 8, 16, 59, 12, 0, timeZone(-8*time.Hour)), - Mode: 0644, - }, - }, - }, - { - Name: "zip64.zip", - File: []ZipTestFile{ - { - Name: "README", - Content: []byte("This small file is in ZIP64 format.\n"), - Modified: time.Date(2012, 8, 10, 14, 33, 32, 0, time.UTC), - Mode: 0644, - }, - }, - }, - // Another zip64 file with different Extras fields. (golang.org/issue/7069) - { - Name: "zip64-2.zip", - File: []ZipTestFile{ - { - Name: "README", - Content: []byte("This small file is in ZIP64 format.\n"), - Modified: time.Date(2012, 8, 10, 14, 33, 32, 0, timeZone(-4*time.Hour)), - Mode: 0644, - }, - }, - }, - // Largest possible non-zip64 file, with no zip64 header. - { - Name: "big.zip", - Source: returnBigZipBytes, - File: []ZipTestFile{ - { - Name: "big.file", - Content: nil, - Size: 1<<32 - 1, - Modified: time.Date(1979, 11, 30, 0, 0, 0, 0, time.UTC), - Mode: 0666, - }, - }, - }, - { - Name: "utf8-7zip.zip", - File: []ZipTestFile{ - { - Name: "世界", - Content: []byte{}, - Mode: 0666, - Modified: time.Date(2017, 11, 6, 13, 9, 27, 867862500, timeZone(-8*time.Hour)), - }, - }, - }, - { - Name: "utf8-infozip.zip", - File: []ZipTestFile{ - { - Name: "世界", - Content: []byte{}, - Mode: 0644, - // Name is valid UTF-8, but format does not have UTF-8 flag set. - // We don't do UTF-8 detection for multi-byte runes due to - // false-positives with other encodings (e.g., Shift-JIS). - // Format says encoding is not UTF-8, so we trust it. - NonUTF8: true, - Modified: time.Date(2017, 11, 6, 13, 9, 27, 0, timeZone(-8*time.Hour)), - }, - }, - }, - { - Name: "utf8-osx.zip", - File: []ZipTestFile{ - { - Name: "世界", - Content: []byte{}, - Mode: 0644, - // Name is valid UTF-8, but format does not have UTF-8 set. - NonUTF8: true, - Modified: time.Date(2017, 11, 6, 13, 9, 27, 0, timeZone(-8*time.Hour)), - }, - }, - }, - { - Name: "utf8-winrar.zip", - File: []ZipTestFile{ - { - Name: "世界", - Content: []byte{}, - Mode: 0666, - Modified: time.Date(2017, 11, 6, 13, 9, 27, 867862500, timeZone(-8*time.Hour)), - }, - }, - }, - { - Name: "utf8-winzip.zip", - File: []ZipTestFile{ - { - Name: "世界", - Content: []byte{}, - Mode: 0666, - Modified: time.Date(2017, 11, 6, 13, 9, 27, 867000000, timeZone(-8*time.Hour)), - }, - }, - }, - { - Name: "time-7zip.zip", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte{}, - Size: 1<<32 - 1, - Modified: time.Date(2017, 10, 31, 21, 11, 57, 244817900, timeZone(-7*time.Hour)), - Mode: 0666, - }, - }, - }, - { - Name: "time-infozip.zip", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte{}, - Size: 1<<32 - 1, - Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)), - Mode: 0644, - }, - }, - }, - { - Name: "time-osx.zip", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte{}, - Size: 1<<32 - 1, - Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)), - Mode: 0644, - }, - }, - }, - { - Name: "time-win7.zip", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte{}, - Size: 1<<32 - 1, - Modified: time.Date(2017, 10, 31, 21, 11, 58, 0, time.UTC), - Mode: 0666, - }, - }, - }, - { - Name: "time-winrar.zip", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte{}, - Size: 1<<32 - 1, - Modified: time.Date(2017, 10, 31, 21, 11, 57, 244817900, timeZone(-7*time.Hour)), - Mode: 0666, - }, - }, - }, - { - Name: "time-winzip.zip", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte{}, - Size: 1<<32 - 1, - Modified: time.Date(2017, 10, 31, 21, 11, 57, 244000000, timeZone(-7*time.Hour)), - Mode: 0666, - }, - }, - }, - { - Name: "time-go.zip", - File: []ZipTestFile{ - { - Name: "test.txt", - Content: []byte{}, - Size: 1<<32 - 1, - Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)), - Mode: 0666, - }, - }, - }, - { - Name: "time-22738.zip", - File: []ZipTestFile{ - { - Name: "file", - Content: []byte{}, - Mode: 0666, - Modified: time.Date(1999, 12, 31, 19, 0, 0, 0, timeZone(-5*time.Hour)), - ModTime: time.Date(1999, 12, 31, 19, 0, 0, 0, time.UTC), - }, - }, - }, -} - -func TestReader(t *testing.T) { - for _, zt := range tests { - t.Run(zt.Name, func(t *testing.T) { - readTestZip(t, zt) - }) - } -} - -func readTestZip(t *testing.T, zt ZipTest) { - var z *Reader - var err error - if zt.Source != nil { - rat, size := zt.Source() - z, err = NewReader(rat, size) - } else { - var rc *ReadCloser - rc, err = OpenReader(filepath.Join("testdata", zt.Name)) - if err == nil { - defer rc.Close() - z = &rc.Reader - } - } - if err != zt.Error { - t.Errorf("error=%v, want %v", err, zt.Error) - return - } - - // bail if file is not zip - if err == ErrFormat { - return - } - - // bail here if no Files expected to be tested - // (there may actually be files in the zip, but we don't care) - if zt.File == nil { - return - } - - if z.Comment != zt.Comment { - t.Errorf("comment=%q, want %q", z.Comment, zt.Comment) - } - if len(z.File) != len(zt.File) { - t.Fatalf("file count=%d, want %d", len(z.File), len(zt.File)) - } - - // test read of each file - for i, ft := range zt.File { - readTestFile(t, zt, ft, z.File[i]) - } - if t.Failed() { - return - } - - // test simultaneous reads - n := 0 - done := make(chan bool) - for i := 0; i < 5; i++ { - for j, ft := range zt.File { - go func(j int, ft ZipTestFile) { - readTestFile(t, zt, ft, z.File[j]) - done <- true - }(j, ft) - n++ - } - } - for ; n > 0; n-- { - <-done - } -} - -func equalTimeAndZone(t1, t2 time.Time) bool { - name1, offset1 := t1.Zone() - name2, offset2 := t2.Zone() - return t1.Equal(t2) && name1 == name2 && offset1 == offset2 -} - -func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File) { - if f.Name != ft.Name { - t.Errorf("name=%q, want %q", f.Name, ft.Name) - } - if !ft.Modified.IsZero() && !equalTimeAndZone(f.Modified, ft.Modified) { - t.Errorf("%s: Modified=%s, want %s", f.Name, f.Modified, ft.Modified) - } - if !ft.ModTime.IsZero() && !equalTimeAndZone(f.ModTime(), ft.ModTime) { - t.Errorf("%s: ModTime=%s, want %s", f.Name, f.ModTime(), ft.ModTime) - } - - testFileMode(t, f, ft.Mode) - - size := uint64(f.UncompressedSize) - if size == uint32max { - size = f.UncompressedSize64 - } else if size != f.UncompressedSize64 { - t.Errorf("%v: UncompressedSize=%#x does not match UncompressedSize64=%#x", f.Name, size, f.UncompressedSize64) - } - - r, err := f.Open() - if err != nil { - t.Errorf("%v", err) - return - } - - // For very large files, just check that the size is correct. - // The content is expected to be all zeros. - // Don't bother uncompressing: too big. - if ft.Content == nil && ft.File == "" && ft.Size > 0 { - if size != ft.Size { - t.Errorf("%v: uncompressed size %#x, want %#x", ft.Name, size, ft.Size) - } - r.Close() - return - } - - var b bytes.Buffer - _, err = io.Copy(&b, r) - if err != ft.ContentErr { - t.Errorf("copying contents: %v (want %v)", err, ft.ContentErr) - } - if err != nil { - return - } - r.Close() - - if g := uint64(b.Len()); g != size { - t.Errorf("%v: read %v bytes but f.UncompressedSize == %v", f.Name, g, size) - } - - var c []byte - if ft.Content != nil { - c = ft.Content - } else if c, err = ioutil.ReadFile("testdata/" + ft.File); err != nil { - t.Error(err) - return - } - - if b.Len() != len(c) { - t.Errorf("%s: len=%d, want %d", f.Name, b.Len(), len(c)) - return - } - - for i, b := range b.Bytes() { - if b != c[i] { - t.Errorf("%s: content[%d]=%q want %q", f.Name, i, b, c[i]) - return - } - } -} - -func testFileMode(t *testing.T, f *File, want os.FileMode) { - mode := f.Mode() - if want == 0 { - t.Errorf("%s mode: got %v, want none", f.Name, mode) - } else if mode != want { - t.Errorf("%s mode: want %v, got %v", f.Name, want, mode) - } -} - -func TestInvalidFiles(t *testing.T) { - const size = 1024 * 70 // 70kb - b := make([]byte, size) - - // zeroes - _, err := NewReader(bytes.NewReader(b), size) - if err != ErrFormat { - t.Errorf("zeroes: error=%v, want %v", err, ErrFormat) - } - - // repeated directoryEndSignatures - sig := make([]byte, 4) - binary.LittleEndian.PutUint32(sig, directoryEndSignature) - for i := 0; i < size-4; i += 4 { - copy(b[i:i+4], sig) - } - _, err = NewReader(bytes.NewReader(b), size) - if err != ErrFormat { - t.Errorf("sigs: error=%v, want %v", err, ErrFormat) - } - - // negative size - _, err = NewReader(bytes.NewReader([]byte("foobar")), -1) - if err == nil { - t.Errorf("archive/zip.NewReader: expected error when negative size is passed") - } -} - -func messWith(fileName string, corrupter func(b []byte)) (r io.ReaderAt, size int64) { - data, err := ioutil.ReadFile(filepath.Join("testdata", fileName)) - if err != nil { - panic("Error reading " + fileName + ": " + err.Error()) - } - corrupter(data) - return bytes.NewReader(data), int64(len(data)) -} - -func returnCorruptCRC32Zip() (r io.ReaderAt, size int64) { - return messWith("go-with-datadesc-sig.zip", func(b []byte) { - // Corrupt one of the CRC32s in the data descriptor: - b[0x2d]++ - }) -} - -func returnCorruptNotStreamedZip() (r io.ReaderAt, size int64) { - return messWith("crc32-not-streamed.zip", func(b []byte) { - // Corrupt foo.txt's final crc32 byte, in both - // the file header and TOC. (0x7e -> 0x7f) - b[0x11]++ - b[0x9d]++ - - // TODO(bradfitz): add a new test that only corrupts - // one of these values, and verify that that's also an - // error. Currently, the reader code doesn't verify the - // fileheader and TOC's crc32 match if they're both - // non-zero and only the second line above, the TOC, - // is what matters. - }) -} - -// rZipBytes returns the bytes of a recursive zip file, without -// putting it on disk and triggering certain virus scanners. -func rZipBytes() []byte { - s := ` -0000000 50 4b 03 04 14 00 00 00 08 00 08 03 64 3c f9 f4 -0000010 89 64 48 01 00 00 b8 01 00 00 07 00 00 00 72 2f -0000020 72 2e 7a 69 70 00 25 00 da ff 50 4b 03 04 14 00 -0000030 00 00 08 00 08 03 64 3c f9 f4 89 64 48 01 00 00 -0000040 b8 01 00 00 07 00 00 00 72 2f 72 2e 7a 69 70 00 -0000050 2f 00 d0 ff 00 25 00 da ff 50 4b 03 04 14 00 00 -0000060 00 08 00 08 03 64 3c f9 f4 89 64 48 01 00 00 b8 -0000070 01 00 00 07 00 00 00 72 2f 72 2e 7a 69 70 00 2f -0000080 00 d0 ff c2 54 8e 57 39 00 05 00 fa ff c2 54 8e -0000090 57 39 00 05 00 fa ff 00 05 00 fa ff 00 14 00 eb -00000a0 ff c2 54 8e 57 39 00 05 00 fa ff 00 05 00 fa ff -00000b0 00 14 00 eb ff 42 88 21 c4 00 00 14 00 eb ff 42 -00000c0 88 21 c4 00 00 14 00 eb ff 42 88 21 c4 00 00 14 -00000d0 00 eb ff 42 88 21 c4 00 00 14 00 eb ff 42 88 21 -00000e0 c4 00 00 00 00 ff ff 00 00 00 ff ff 00 34 00 cb -00000f0 ff 42 88 21 c4 00 00 00 00 ff ff 00 00 00 ff ff -0000100 00 34 00 cb ff 42 e8 21 5e 0f 00 00 00 ff ff 0a -0000110 f0 66 64 12 61 c0 15 dc e8 a0 48 bf 48 af 2a b3 -0000120 20 c0 9b 95 0d c4 67 04 42 53 06 06 06 40 00 06 -0000130 00 f9 ff 6d 01 00 00 00 00 42 e8 21 5e 0f 00 00 -0000140 00 ff ff 0a f0 66 64 12 61 c0 15 dc e8 a0 48 bf -0000150 48 af 2a b3 20 c0 9b 95 0d c4 67 04 42 53 06 06 -0000160 06 40 00 06 00 f9 ff 6d 01 00 00 00 00 50 4b 01 -0000170 02 14 00 14 00 00 00 08 00 08 03 64 3c f9 f4 89 -0000180 64 48 01 00 00 b8 01 00 00 07 00 00 00 00 00 00 -0000190 00 00 00 00 00 00 00 00 00 00 00 72 2f 72 2e 7a -00001a0 69 70 50 4b 05 06 00 00 00 00 01 00 01 00 35 00 -00001b0 00 00 6d 01 00 00 00 00` - s = regexp.MustCompile(`[0-9a-f]{7}`).ReplaceAllString(s, "") - s = regexp.MustCompile(`\s+`).ReplaceAllString(s, "") - b, err := hex.DecodeString(s) - if err != nil { - panic(err) - } - return b -} - -func returnRecursiveZip() (r io.ReaderAt, size int64) { - b := rZipBytes() - return bytes.NewReader(b), int64(len(b)) -} - -// biggestZipBytes returns the bytes of a zip file biggest.zip -// that contains a zip file bigger.zip that contains a zip file -// big.zip that contains big.file, which contains 2³²-1 zeros. -// The big.zip file is interesting because it has no zip64 header, -// much like the innermost zip files in the well-known 42.zip. -// -// biggest.zip was generated by changing isZip64 to use > uint32max -// instead of >= uint32max and then running this program: -// -// package main -// -// import ( -// "archive/zip" -// "bytes" -// "io" -// "io/ioutil" -// "log" -// ) -// -// type zeros struct{} -// -// func (zeros) Read(b []byte) (int, error) { -// for i := range b { -// b[i] = 0 -// } -// return len(b), nil -// } -// -// func main() { -// bigZip := makeZip("big.file", io.LimitReader(zeros{}, 1<<32-1)) -// if err := ioutil.WriteFile("/tmp/big.zip", bigZip, 0666); err != nil { -// log.Fatal(err) -// } -// -// biggerZip := makeZip("big.zip", bytes.NewReader(bigZip)) -// if err := ioutil.WriteFile("/tmp/bigger.zip", biggerZip, 0666); err != nil { -// log.Fatal(err) -// } -// -// biggestZip := makeZip("bigger.zip", bytes.NewReader(biggerZip)) -// if err := ioutil.WriteFile("/tmp/biggest.zip", biggestZip, 0666); err != nil { -// log.Fatal(err) -// } -// } -// -// func makeZip(name string, r io.Reader) []byte { -// var buf bytes.Buffer -// w := zip.NewWriter(&buf) -// wf, err := w.Create(name) -// if err != nil { -// log.Fatal(err) -// } -// if _, err = io.Copy(wf, r); err != nil { -// log.Fatal(err) -// } -// if err := w.Close(); err != nil { -// log.Fatal(err) -// } -// return buf.Bytes() -// } -// -// The 4 GB of zeros compresses to 4 MB, which compresses to 20 kB, -// which compresses to 1252 bytes (in the hex dump below). -// -// It's here in hex for the same reason as rZipBytes above: to avoid -// problems with on-disk virus scanners or other zip processors. -// -func biggestZipBytes() []byte { - s := ` -0000000 50 4b 03 04 14 00 08 00 08 00 00 00 00 00 00 00 -0000010 00 00 00 00 00 00 00 00 00 00 0a 00 00 00 62 69 -0000020 67 67 65 72 2e 7a 69 70 ec dc 6b 4c 53 67 18 07 -0000030 f0 16 c5 ca 65 2e cb b8 94 20 61 1f 44 33 c7 cd -0000040 c0 86 4a b5 c0 62 8a 61 05 c6 cd 91 b2 54 8c 1b -0000050 63 8b 03 9c 1b 95 52 5a e3 a0 19 6c b2 05 59 44 -0000060 64 9d 73 83 71 11 46 61 14 b9 1d 14 09 4a c3 60 -0000070 2e 4c 6e a5 60 45 02 62 81 95 b6 94 9e 9e 77 e7 -0000080 d0 43 b6 f8 71 df 96 3c e7 a4 69 ce bf cf e9 79 -0000090 ce ef 79 3f bf f1 31 db b6 bb 31 76 92 e7 f3 07 -00000a0 8b fc 9c ca cc 08 cc cb cc 5e d2 1c 88 d9 7e bb -00000b0 4f bb 3a 3f 75 f1 5d 7f 8f c2 68 67 77 8f 25 ff -00000c0 84 e2 93 2d ef a4 95 3d 71 4e 2c b9 b0 87 c3 be -00000d0 3d f8 a7 60 24 61 c5 ef ae 9e c8 6c 6d 4e 69 c8 -00000e0 67 65 34 f8 37 76 2d 76 5c 54 f3 95 65 49 c7 0f -00000f0 18 71 4b 7e 5b 6a d1 79 47 61 41 b0 4e 2a 74 45 -0000100 43 58 12 b2 5a a5 c6 7d 68 55 88 d4 98 75 18 6d -0000110 08 d1 1f 8f 5a 9e 96 ee 45 cf a4 84 4e 4b e8 50 -0000120 a7 13 d9 06 de 52 81 97 36 b2 d7 b8 fc 2b 5f 55 -0000130 23 1f 32 59 cf 30 27 fb e2 8a b9 de 45 dd 63 9c -0000140 4b b5 8b 96 4c 7a 62 62 cc a1 a7 cf fa f1 fe dd -0000150 54 62 11 bf 36 78 b3 c7 b1 b5 f2 61 4d 4e dd 66 -0000160 32 2e e6 70 34 5f f4 c9 e6 6c 43 6f da 6b c6 c3 -0000170 09 2c ce 09 57 7f d2 7e b4 23 ba 7c 1b 99 bc 22 -0000180 3e f1 de 91 2f e3 9c 1b 82 cc c2 84 39 aa e6 de -0000190 b4 69 fc cc cb 72 a6 61 45 f0 d3 1d 26 19 7c 8d -00001a0 29 c8 66 02 be 77 6a f9 3d 34 79 17 19 c8 96 24 -00001b0 a3 ac e4 dd 3b 1a 8e c6 fe 96 38 6b bf 67 5a 23 -00001c0 f4 16 f4 e6 8a b4 fc c2 cd bf 95 66 1d bb 35 aa -00001d0 92 7d 66 d8 08 8d a5 1f 54 2a af 09 cf 61 ff d2 -00001e0 85 9d 8f b6 d7 88 07 4a 86 03 db 64 f3 d9 92 73 -00001f0 df ec a7 fc 23 4c 8d 83 79 63 2a d9 fd 8d b3 c8 -0000200 8f 7e d4 19 85 e6 8d 1c 76 f0 8b 58 32 fd 9a d6 -0000210 85 e2 48 ad c3 d5 60 6f 7e 22 dd ef 09 49 7c 7f -0000220 3a 45 c3 71 b7 df f3 4c 63 fb b5 d9 31 5f 6e d6 -0000230 24 1d a4 4a fe 32 a7 5c 16 48 5c 3e 08 6b 8a d3 -0000240 25 1d a2 12 a5 59 24 ea 20 5f 52 6d ad 94 db 6b -0000250 94 b9 5d eb 4b a7 5c 44 bb 1e f2 3c 6b cf 52 c9 -0000260 e9 e5 ba 06 b9 c4 e5 0a d0 00 0d d0 00 0d d0 00 -0000270 0d d0 00 0d d0 00 0d d0 00 0d d0 00 0d d0 00 0d -0000280 d0 00 0d d0 00 0d d0 00 0d d0 00 0d d0 00 0d d0 -0000290 00 0d d0 00 0d d0 00 0d d0 00 0d d0 00 0d d0 00 -00002a0 0d d0 00 cd ff 9e 46 86 fa a7 7d 3a 43 d7 8e 10 -00002b0 52 e9 be e6 6e cf eb 9e 85 4d 65 ce cc 30 c1 44 -00002c0 c0 4e af bc 9c 6c 4b a0 d7 54 ff 1d d5 5c 89 fb -00002d0 b5 34 7e c4 c2 9e f5 a0 f6 5b 7e 6e ca 73 c7 ef -00002e0 5d be de f9 e8 81 eb a5 0a a5 63 54 2c d7 1c d1 -00002f0 89 17 85 f8 16 94 f2 8a b2 a3 f5 b6 6d df 75 cd -0000300 90 dd 64 bd 5d 55 4e f2 55 19 1b b7 cc ef 1b ea -0000310 2e 05 9c f4 aa 1e a8 cd a6 82 c7 59 0f 5e 9d e0 -0000320 bb fc 6c d6 99 23 eb 36 ad c6 c5 e1 d8 e1 e2 3e -0000330 d9 90 5a f7 91 5d 6f bc 33 6d 98 47 d2 7c 2e 2f -0000340 99 a4 25 72 85 49 2c be 0b 5b af 8f e5 6e 81 a6 -0000350 a3 5a 6f 39 53 3a ab 7a 8b 1e 26 f7 46 6c 7d 26 -0000360 53 b3 22 31 94 d3 83 f2 18 4d f5 92 33 27 53 97 -0000370 0f d3 e6 55 9c a6 c5 31 87 6f d3 f3 ae 39 6f 56 -0000380 10 7b ab 7e d0 b4 ca f2 b8 05 be 3f 0e 6e 5a 75 -0000390 ab 0c f5 37 0e ba 8e 75 71 7a aa ed 7a dd 6a 63 -00003a0 be 9b a0 97 27 6a 6f e7 d3 8b c4 7c ec d3 91 56 -00003b0 d9 ac 5e bf 16 42 2f 00 1f 93 a2 23 87 bd e2 59 -00003c0 a0 de 1a 66 c8 62 eb 55 8f 91 17 b4 61 42 7a 50 -00003d0 40 03 34 40 03 34 40 03 34 40 03 34 40 03 34 40 -00003e0 03 34 40 03 34 40 03 34 40 03 34 40 03 34 40 03 -00003f0 34 40 03 34 40 03 34 ff 85 86 90 8b ea 67 90 0d -0000400 e1 42 1b d2 61 d6 79 ec fd 3e 44 28 a4 51 6c 5c -0000410 fc d2 72 ca ba 82 18 46 16 61 cd 93 a9 0f d1 24 -0000420 17 99 e2 2c 71 16 84 0c c8 7a 13 0f 9a 5e c5 f0 -0000430 79 64 e2 12 4d c8 82 a1 81 19 2d aa 44 6d 87 54 -0000440 84 71 c1 f6 d4 ca 25 8c 77 b9 08 c7 c8 5e 10 8a -0000450 8f 61 ed 8c ba 30 1f 79 9a c7 60 34 2b b9 8c f8 -0000460 18 a6 83 1b e3 9f ad 79 fe fd 1b 8b f1 fc 41 6f -0000470 d4 13 1f e3 b8 83 ba 64 92 e7 eb e4 77 05 8f ba -0000480 fa 3b 00 00 ff ff 50 4b 07 08 a6 18 b1 91 5e 04 -0000490 00 00 e4 47 00 00 50 4b 01 02 14 00 14 00 08 00 -00004a0 08 00 00 00 00 00 a6 18 b1 91 5e 04 00 00 e4 47 -00004b0 00 00 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 -00004c0 00 00 00 00 62 69 67 67 65 72 2e 7a 69 70 50 4b -00004d0 05 06 00 00 00 00 01 00 01 00 38 00 00 00 96 04 -00004e0 00 00 00 00` - s = regexp.MustCompile(`[0-9a-f]{7}`).ReplaceAllString(s, "") - s = regexp.MustCompile(`\s+`).ReplaceAllString(s, "") - b, err := hex.DecodeString(s) - if err != nil { - panic(err) - } - return b -} - -func returnBigZipBytes() (r io.ReaderAt, size int64) { - b := biggestZipBytes() - for i := 0; i < 2; i++ { - r, err := NewReader(bytes.NewReader(b), int64(len(b))) - if err != nil { - panic(err) - } - f, err := r.File[0].Open() - if err != nil { - panic(err) - } - b, err = ioutil.ReadAll(f) - if err != nil { - panic(err) - } - } - return bytes.NewReader(b), int64(len(b)) -} - -func TestIssue8186(t *testing.T) { - // Directory headers & data found in the TOC of a JAR file. - dirEnts := []string{ - "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", - "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", - "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", - "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", - "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", - "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", - "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", - "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", - "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", - } - for i, s := range dirEnts { - var f File - err := readDirectoryHeader(&f, strings.NewReader(s)) - if err != nil { - t.Errorf("error reading #%d: %v", i, err) - } - } -} - -// Verify we return ErrUnexpectedEOF when length is short. -func TestIssue10957(t *testing.T) { - data := []byte("PK\x03\x040000000PK\x01\x0200000" + - "0000000000000000000\x00" + - "\x00\x00\x00\x00\x00000000000000PK\x01" + - "\x020000000000000000000" + - "00000\v\x00\x00\x00\x00\x00000000000" + - "00000000000000PK\x01\x0200" + - "00000000000000000000" + - "00\v\x00\x00\x00\x00\x00000000000000" + - "00000000000PK\x01\x020000<" + - "0\x00\x0000000000000000\v\x00\v" + - "\x00\x00\x00\x00\x0000000000\x00\x00\x00\x00000" + - "00000000PK\x01\x0200000000" + - "0000000000000000\v\x00\x00\x00" + - "\x00\x0000PK\x05\x06000000\x05\x000000" + - "\v\x00\x00\x00\x00\x00") - z, err := NewReader(bytes.NewReader(data), int64(len(data))) - if err != nil { - t.Fatal(err) - } - for i, f := range z.File { - r, err := f.Open() - if err != nil { - continue - } - if f.UncompressedSize64 < 1e6 { - n, err := io.Copy(ioutil.Discard, r) - if i == 3 && err != io.ErrUnexpectedEOF { - t.Errorf("File[3] error = %v; want io.ErrUnexpectedEOF", err) - } - if err == nil && uint64(n) != f.UncompressedSize64 { - t.Errorf("file %d: bad size: copied=%d; want=%d", i, n, f.UncompressedSize64) - } - } - r.Close() - } -} - -// Verify that this particular malformed zip file is rejected. -func TestIssue10956(t *testing.T) { - data := []byte("PK\x06\x06PK\x06\a0000\x00\x00\x00\x00\x00\x00\x00\x00" + - "0000PK\x05\x06000000000000" + - "0000\v\x00000\x00\x00\x00\x00\x00\x00\x000") - r, err := NewReader(bytes.NewReader(data), int64(len(data))) - if err == nil { - t.Errorf("got nil error, want ErrFormat") - } - if r != nil { - t.Errorf("got non-nil Reader, want nil") - } -} - -// Verify we return ErrUnexpectedEOF when reading truncated data descriptor. -func TestIssue11146(t *testing.T) { - data := []byte("PK\x03\x040000000000000000" + - "000000\x01\x00\x00\x000\x01\x00\x00\xff\xff0000" + - "0000000000000000PK\x01\x02" + - "0000\b0\b\x00000000000000" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000PK\x05\x06\x00\x00" + - "\x00\x0000\x01\x0000008\x00\x00\x00\x00\x00") - z, err := NewReader(bytes.NewReader(data), int64(len(data))) - if err != nil { - t.Fatal(err) - } - r, err := z.File[0].Open() - if err != nil { - t.Fatal(err) - } - _, err = ioutil.ReadAll(r) - if err != io.ErrUnexpectedEOF { - t.Errorf("File[0] error = %v; want io.ErrUnexpectedEOF", err) - } - r.Close() -} - -// Verify we do not treat non-zip64 archives as zip64 -func TestIssue12449(t *testing.T) { - data := []byte{ - 0x50, 0x4b, 0x03, 0x04, 0x14, 0x00, 0x08, 0x00, - 0x00, 0x00, 0x6b, 0xb4, 0xba, 0x46, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0x00, 0x18, 0x00, 0xca, 0x64, - 0x55, 0x75, 0x78, 0x0b, 0x00, 0x50, 0x4b, 0x05, - 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x49, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, - 0x00, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x0a, - 0x50, 0x4b, 0x07, 0x08, 0x1d, 0x88, 0x77, 0xb0, - 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x14, 0x00, - 0x08, 0x00, 0x00, 0x00, 0x6b, 0xb4, 0xba, 0x46, - 0x1d, 0x88, 0x77, 0xb0, 0x07, 0x00, 0x00, 0x00, - 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x18, 0x00, - 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xa0, 0x81, 0x00, 0x00, 0x00, 0x00, 0xca, 0x64, - 0x55, 0x75, 0x78, 0x0b, 0x00, 0x50, 0x4b, 0x05, - 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, - 0x00, 0x49, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, - 0x00, 0x97, 0x2b, 0x49, 0x23, 0x05, 0xc5, 0x0b, - 0xa7, 0xd1, 0x52, 0xa2, 0x9c, 0x50, 0x4b, 0x06, - 0x07, 0xc8, 0x19, 0xc1, 0xaf, 0x94, 0x9c, 0x61, - 0x44, 0xbe, 0x94, 0x19, 0x42, 0x58, 0x12, 0xc6, - 0x5b, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x00, 0x01, 0x00, 0x69, 0x00, 0x00, - 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, - } - // Read in the archive. - _, err := NewReader(bytes.NewReader([]byte(data)), int64(len(data))) - if err != nil { - t.Errorf("Error reading the archive: %v", err) - } -} diff --git a/internal/zip/register.go b/internal/zip/register.go deleted file mode 100644 index 51e9c3e..0000000 --- a/internal/zip/register.go +++ /dev/null @@ -1,148 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package zip - -import ( - "compress/flate" - "errors" - "io" - "io/ioutil" - "sync" -) - -// A Compressor returns a new compressing writer, writing to w. -// The WriteCloser's Close method must be used to flush pending data to w. -// The Compressor itself must be safe to invoke from multiple goroutines -// simultaneously, but each returned writer will be used only by -// one goroutine at a time. -type Compressor func(w io.Writer) (io.WriteCloser, error) - -// A Decompressor returns a new decompressing reader, reading from r. -// The ReadCloser's Close method must be used to release associated resources. -// The Decompressor itself must be safe to invoke from multiple goroutines -// simultaneously, but each returned reader will be used only by -// one goroutine at a time. -type Decompressor func(r io.Reader) io.ReadCloser - -var flateWriterPool sync.Pool - -func newFlateWriter(w io.Writer) io.WriteCloser { - fw, ok := flateWriterPool.Get().(*flate.Writer) - if ok { - fw.Reset(w) - } else { - fw, _ = flate.NewWriter(w, 5) - } - return &pooledFlateWriter{fw: fw} -} - -type pooledFlateWriter struct { - mu sync.Mutex // guards Close and Write - fw *flate.Writer -} - -func (w *pooledFlateWriter) Write(p []byte) (n int, err error) { - w.mu.Lock() - defer w.mu.Unlock() - if w.fw == nil { - return 0, errors.New("Write after Close") - } - return w.fw.Write(p) -} - -func (w *pooledFlateWriter) Close() error { - w.mu.Lock() - defer w.mu.Unlock() - var err error - if w.fw != nil { - err = w.fw.Close() - flateWriterPool.Put(w.fw) - w.fw = nil - } - return err -} - -var flateReaderPool sync.Pool - -func newFlateReader(r io.Reader) io.ReadCloser { - fr, ok := flateReaderPool.Get().(io.ReadCloser) - if ok { - fr.(flate.Resetter).Reset(r, nil) - } else { - fr = flate.NewReader(r) - } - return &pooledFlateReader{fr: fr} -} - -type pooledFlateReader struct { - mu sync.Mutex // guards Close and Read - fr io.ReadCloser -} - -func (r *pooledFlateReader) Read(p []byte) (n int, err error) { - r.mu.Lock() - defer r.mu.Unlock() - if r.fr == nil { - return 0, errors.New("Read after Close") - } - return r.fr.Read(p) -} - -func (r *pooledFlateReader) Close() error { - r.mu.Lock() - defer r.mu.Unlock() - var err error - if r.fr != nil { - err = r.fr.Close() - flateReaderPool.Put(r.fr) - r.fr = nil - } - return err -} - -var ( - compressors sync.Map // map[uint16]Compressor - decompressors sync.Map // map[uint16]Decompressor -) - -func init() { - compressors.Store(Store, Compressor(func(w io.Writer) (io.WriteCloser, error) { return &nopCloser{w}, nil })) - compressors.Store(Deflate, Compressor(func(w io.Writer) (io.WriteCloser, error) { return newFlateWriter(w), nil })) - - decompressors.Store(Store, Decompressor(ioutil.NopCloser)) - decompressors.Store(Deflate, Decompressor(newFlateReader)) -} - -// RegisterDecompressor allows custom decompressors for a specified method ID. -// The common methods Store and Deflate are built in. -func RegisterDecompressor(method uint16, dcomp Decompressor) { - if _, dup := decompressors.LoadOrStore(method, dcomp); dup { - panic("decompressor already registered") - } -} - -// RegisterCompressor registers custom compressors for a specified method ID. -// The common methods Store and Deflate are built in. -func RegisterCompressor(method uint16, comp Compressor) { - if _, dup := compressors.LoadOrStore(method, comp); dup { - panic("compressor already registered") - } -} - -func compressor(method uint16) Compressor { - ci, ok := compressors.Load(method) - if !ok { - return nil - } - return ci.(Compressor) -} - -func decompressor(method uint16) Decompressor { - di, ok := decompressors.Load(method) - if !ok { - return nil - } - return di.(Decompressor) -} diff --git a/internal/zip/struct.go b/internal/zip/struct.go deleted file mode 100644 index 686e797..0000000 --- a/internal/zip/struct.go +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -Package zip provides support for reading and writing ZIP archives. - -See: https://www.pkware.com/appnote - -This package does not support disk spanning. - -A note about ZIP64: - -To be backwards compatible the FileHeader has both 32 and 64 bit Size -fields. The 64 bit fields will always contain the correct value and -for normal archives both fields will be the same. For files requiring -the ZIP64 format the 32 bit fields will be 0xffffffff and the 64 bit -fields must be used instead. -*/ -package zip - -import ( - "os" - "path" - "time" -) - -// Compression methods. -const ( - Store uint16 = 0 // no compression - Deflate uint16 = 8 // DEFLATE compressed -) - -const ( - fileHeaderSignature = 0x04034b50 - directoryHeaderSignature = 0x02014b50 - directoryEndSignature = 0x06054b50 - directory64LocSignature = 0x07064b50 - directory64EndSignature = 0x06064b50 - dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder - fileHeaderLen = 30 // + filename + extra - directoryHeaderLen = 46 // + filename + extra + comment - directoryEndLen = 22 // + comment - dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size - dataDescriptor64Len = 24 // descriptor with 8 byte sizes - directory64LocLen = 20 // - directory64EndLen = 56 // + extra - - // Constants for the first byte in CreatorVersion. - creatorFAT = 0 - creatorUnix = 3 - creatorNTFS = 11 - creatorVFAT = 14 - creatorMacOSX = 19 - - // Version numbers. - zipVersion20 = 20 // 2.0 - zipVersion45 = 45 // 4.5 (reads and writes zip64 archives) - - // Limits for non zip64 files. - uint16max = (1 << 16) - 1 - uint32max = (1 << 32) - 1 - - // Extra header IDs. - // - // IDs 0..31 are reserved for official use by PKWARE. - // IDs above that range are defined by third-party vendors. - // Since ZIP lacked high precision timestamps (nor a official specification - // of the timezone used for the date fields), many competing extra fields - // have been invented. Pervasive use effectively makes them "official". - // - // See http://mdfs.net/Docs/Comp/Archiving/Zip/ExtraField - zip64ExtraID = 0x0001 // Zip64 extended information - ntfsExtraID = 0x000a // NTFS - unixExtraID = 0x000d // UNIX - extTimeExtraID = 0x5455 // Extended timestamp - infoZipUnixExtraID = 0x5855 // Info-ZIP Unix extension -) - -// FileHeader describes a file within a zip file. -// See the zip spec for details. -type FileHeader struct { - // Name is the name of the file. - // - // It must be a relative path, not start with a drive letter (such as "C:"), - // and must use forward slashes instead of back slashes. A trailing slash - // indicates that this file is a directory and should have no data. - // - // When reading zip files, the Name field is populated from - // the zip file directly and is not validated for correctness. - // It is the caller's responsibility to sanitize it as - // appropriate, including canonicalizing slash directions, - // validating that paths are relative, and preventing path - // traversal through filenames ("../../../"). - Name string - - // Comment is any arbitrary user-defined string shorter than 64KiB. - Comment string - - // NonUTF8 indicates that Name and Comment are not encoded in UTF-8. - // - // By specification, the only other encoding permitted should be CP-437, - // but historically many ZIP readers interpret Name and Comment as whatever - // the system's local character encoding happens to be. - // - // This flag should only be set if the user intends to encode a non-portable - // ZIP file for a specific localized region. Otherwise, the Writer - // automatically sets the ZIP format's UTF-8 flag for valid UTF-8 strings. - NonUTF8 bool - - CreatorVersion uint16 - ReaderVersion uint16 - Flags uint16 - - // Method is the compression method. If zero, Store is used. - Method uint16 - - // Modified is the modified time of the file. - // - // When reading, an extended timestamp is preferred over the legacy MS-DOS - // date field, and the offset between the times is used as the timezone. - // If only the MS-DOS date is present, the timezone is assumed to be UTC. - // - // When writing, an extended timestamp (which is timezone-agnostic) is - // always emitted. The legacy MS-DOS date field is encoded according to the - // location of the Modified time. - Modified time.Time - ModifiedTime uint16 // Deprecated: Legacy MS-DOS date; use Modified instead. - ModifiedDate uint16 // Deprecated: Legacy MS-DOS time; use Modified instead. - - CRC32 uint32 - CompressedSize uint32 // Deprecated: Use CompressedSize64 instead. - UncompressedSize uint32 // Deprecated: Use UncompressedSize64 instead. - CompressedSize64 uint64 - UncompressedSize64 uint64 - Extra []byte - ExternalAttrs uint32 // Meaning depends on CreatorVersion -} - -// FileInfo returns an os.FileInfo for the FileHeader. -func (h *FileHeader) FileInfo() os.FileInfo { - return headerFileInfo{h} -} - -// headerFileInfo implements os.FileInfo. -type headerFileInfo struct { - fh *FileHeader -} - -func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) } -func (fi headerFileInfo) Size() int64 { - if fi.fh.UncompressedSize64 > 0 { - return int64(fi.fh.UncompressedSize64) - } - return int64(fi.fh.UncompressedSize) -} -func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() } -func (fi headerFileInfo) ModTime() time.Time { - if fi.fh.Modified.IsZero() { - return fi.fh.ModTime() - } - return fi.fh.Modified.UTC() -} -func (fi headerFileInfo) Mode() os.FileMode { return fi.fh.Mode() } -func (fi headerFileInfo) Sys() interface{} { return fi.fh } - -// FileInfoHeader creates a partially-populated FileHeader from an -// os.FileInfo. -// Because os.FileInfo's Name method returns only the base name of -// the file it describes, it may be necessary to modify the Name field -// of the returned header to provide the full path name of the file. -// If compression is desired, callers should set the FileHeader.Method -// field; it is unset by default. -func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) { - size := fi.Size() - fh := &FileHeader{ - Name: fi.Name(), - UncompressedSize64: uint64(size), - } - fh.SetModTime(fi.ModTime()) - fh.SetMode(fi.Mode()) - if fh.UncompressedSize64 > uint32max { - fh.UncompressedSize = uint32max - } else { - fh.UncompressedSize = uint32(fh.UncompressedSize64) - } - return fh, nil -} - -type directoryEnd struct { - diskNbr uint32 // unused - dirDiskNbr uint32 // unused - dirRecordsThisDisk uint64 // unused - directoryRecords uint64 - directorySize uint64 - directoryOffset uint64 // relative to file - commentLen uint16 - comment string -} - -// timeZone returns a *time.Location based on the provided offset. -// If the offset is non-sensible, then this uses an offset of zero. -func timeZone(offset time.Duration) *time.Location { - const ( - minOffset = -12 * time.Hour // E.g., Baker island at -12:00 - maxOffset = +14 * time.Hour // E.g., Line island at +14:00 - offsetAlias = 15 * time.Minute // E.g., Nepal at +5:45 - ) - offset = offset.Round(offsetAlias) - if offset < minOffset || maxOffset < offset { - offset = 0 - } - return time.FixedZone("", int(offset/time.Second)) -} - -// msDosTimeToTime converts an MS-DOS date and time into a time.Time. -// The resolution is 2s. -// See: https://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx -func msDosTimeToTime(dosDate, dosTime uint16) time.Time { - return time.Date( - // date bits 0-4: day of month; 5-8: month; 9-15: years since 1980 - int(dosDate>>9+1980), - time.Month(dosDate>>5&0xf), - int(dosDate&0x1f), - - // time bits 0-4: second/2; 5-10: minute; 11-15: hour - int(dosTime>>11), - int(dosTime>>5&0x3f), - int(dosTime&0x1f*2), - 0, // nanoseconds - - time.UTC, - ) -} - -// timeToMsDosTime converts a time.Time to an MS-DOS date and time. -// The resolution is 2s. -// See: https://msdn.microsoft.com/en-us/library/ms724274(v=VS.85).aspx -func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) { - fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9) - fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11) - return -} - -// ModTime returns the modification time in UTC using the legacy -// ModifiedDate and ModifiedTime fields. -// -// Deprecated: Use Modified instead. -func (h *FileHeader) ModTime() time.Time { - return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime) -} - -// SetModTime sets the Modified, ModifiedTime, and ModifiedDate fields -// to the given time in UTC. -// -// Deprecated: Use Modified instead. -func (h *FileHeader) SetModTime(t time.Time) { - t = t.UTC() // Convert to UTC for compatibility - h.Modified = t - h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t) -} - -const ( - // Unix constants. The specification doesn't mention them, - // but these seem to be the values agreed on by tools. - s_IFMT = 0xf000 - s_IFSOCK = 0xc000 - s_IFLNK = 0xa000 - s_IFREG = 0x8000 - s_IFBLK = 0x6000 - s_IFDIR = 0x4000 - s_IFCHR = 0x2000 - s_IFIFO = 0x1000 - s_ISUID = 0x800 - s_ISGID = 0x400 - s_ISVTX = 0x200 - - msdosDir = 0x10 - msdosReadOnly = 0x01 -) - -// Mode returns the permission and mode bits for the FileHeader. -func (h *FileHeader) Mode() (mode os.FileMode) { - switch h.CreatorVersion >> 8 { - case creatorUnix, creatorMacOSX: - mode = unixModeToFileMode(h.ExternalAttrs >> 16) - case creatorNTFS, creatorVFAT, creatorFAT: - mode = msdosModeToFileMode(h.ExternalAttrs) - } - if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' { - mode |= os.ModeDir - } - return mode -} - -// SetMode changes the permission and mode bits for the FileHeader. -func (h *FileHeader) SetMode(mode os.FileMode) { - h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8 - h.ExternalAttrs = fileModeToUnixMode(mode) << 16 - - // set MSDOS attributes too, as the original zip does. - if mode&os.ModeDir != 0 { - h.ExternalAttrs |= msdosDir - } - if mode&0200 == 0 { - h.ExternalAttrs |= msdosReadOnly - } -} - -// isZip64 reports whether the file size exceeds the 32 bit limit -func (h *FileHeader) isZip64() bool { - return h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max -} - -func msdosModeToFileMode(m uint32) (mode os.FileMode) { - if m&msdosDir != 0 { - mode = os.ModeDir | 0777 - } else { - mode = 0666 - } - if m&msdosReadOnly != 0 { - mode &^= 0222 - } - return mode -} - -func fileModeToUnixMode(mode os.FileMode) uint32 { - var m uint32 - switch mode & os.ModeType { - default: - m = s_IFREG - case os.ModeDir: - m = s_IFDIR - case os.ModeSymlink: - m = s_IFLNK - case os.ModeNamedPipe: - m = s_IFIFO - case os.ModeSocket: - m = s_IFSOCK - case os.ModeDevice: - if mode&os.ModeCharDevice != 0 { - m = s_IFCHR - } else { - m = s_IFBLK - } - } - if mode&os.ModeSetuid != 0 { - m |= s_ISUID - } - if mode&os.ModeSetgid != 0 { - m |= s_ISGID - } - if mode&os.ModeSticky != 0 { - m |= s_ISVTX - } - return m | uint32(mode&0777) -} - -func unixModeToFileMode(m uint32) os.FileMode { - mode := os.FileMode(m & 0777) - switch m & s_IFMT { - case s_IFBLK: - mode |= os.ModeDevice - case s_IFCHR: - mode |= os.ModeDevice | os.ModeCharDevice - case s_IFDIR: - mode |= os.ModeDir - case s_IFIFO: - mode |= os.ModeNamedPipe - case s_IFLNK: - mode |= os.ModeSymlink - case s_IFREG: - // nothing to do - case s_IFSOCK: - mode |= os.ModeSocket - } - if m&s_ISGID != 0 { - mode |= os.ModeSetgid - } - if m&s_ISUID != 0 { - mode |= os.ModeSetuid - } - if m&s_ISVTX != 0 { - mode |= os.ModeSticky - } - return mode -} diff --git a/internal/zip/testdata/crc32-not-streamed.zip b/internal/zip/testdata/crc32-not-streamed.zip deleted file mode 100644 index f268d88732f837723525285c0922231d9c3fcb46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314 zcmWIWW@h1H0D;u@42Kn|Ms+MeHVCsb$S|bk=j)YJl!S(GGBDo@jr0fM(h6<{MwYLP zKvg0@Wk4ld0dPaofQG!>yod$akfg*SxFHXK27oY{AwVTSLl~Llm~pv90%#Qj1JF{2 wC5<2!+-0l~m!TPmY#64SkPUMM8U}YE&@e2n3-D%T1KG(0gtLHj7l^|E0B`I%6#xJL diff --git a/internal/zip/testdata/dd.zip b/internal/zip/testdata/dd.zip deleted file mode 100644 index e53378b0b0e56203a08c139b83c67a9b918c0b81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154 zcmWIWW@Zs#-~hr?8BTT#NPq*#PRqSdnT}#{y)7Fgt?;Lt1{mUP(nsXb39<^S#hW|I!L>21b^z zj6fA4KqWwFE@UkMaKqAohP~Xphy~fOq{Je)VGl4314?rtYYFgXWD;S_DNk{CvHViV`5j72wUzu_D!|js++T!U1?SCM6buGy*BG#+REH zu^==uGKnxC>_j#cWG55r00004XF*Lt007q5 z)K6G40000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!qe(a@st@4BKo_oJPzw>?HIhVm0L#Ej6 zcCguOm(QI#dLysUSQM1xW#7b?WhI9coK{!icDucq@rulkmX=oZy1mWeXsEqpG#cQz z^?utRB)7Nn2_^KAYm5|#yEXazMjH4t8k!0_NeXY&iE#p@M7{5k^WvsIwt5X66u zgMz_u`%zcjR>QW&SS(65Ye^9(fMuCu1-f+|cW+i$cI({q=d?f-4KkbL8H||JduEK zWEsv^R+I=c^Vc^Q6f|YEz%UF)-b=4uIMaXYiY0$xaS0d}!L(ln-P#;bsW@OrfJ(`M zqjL}(Z9R?iQmBps*cDKe=-Au$LmfdmOt7Tycmj$y<|dN@QaA~tlBZF^f-Lp@sesL# z=v+{~^TgRR$*YtpDuIysfOkp+VMc;zEC#y#TrZp+NA%AD{$()=e}R z3@I8(`tF+qL{{dnWNKppLokAoTC?UfF*Tt`)9K%C@(KhS0IXFLXxD~r|ta@O^5g7&dyB3xG)uXH!$3b5r40` zx_Z)TwPycJf*@p8yGZo8%QIme`{s}EJpZ7(yZbY-h4gwoy>wZv{T5&Vs0a;<1LtDu P00000NkvXXu0mjff_q_+ diff --git a/internal/zip/testdata/readme.notzip b/internal/zip/testdata/readme.notzip deleted file mode 100644 index 81737275c6ebf5ea69b992753ab4050f031f31b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1906 zcmZ{kdr(wW9LF#2BCy+<&KTnWqeoDYf?diGWm*(rdC0=9fSQEX-QBahSMT1l-p4LT zi4V-tcod2+1|0)s(nQGY57(e##v~0b6;fv~ZB%?PjxVCeSm)ejE3NiD`}^JBw$b3;E)Jtu!?a5JHtYiIai?^Pf=7T1>TI-|ELOGw`deaWh*IaG!;b^7}7HG3=50G zC`E2}g|Y7OUZYg1rh=3M3W}0HTt#`N%X7zme(&%OMM2tpHa{wQ<(j%t1(RPmn_j!3 zoJGF=_Mw=`e{{@P;j5nCG(F@@Lo(`*ym=@&XgIRGa&C8DXi`$>#4Odcx;gW5Q@7s> z%QQ9ib-h&*vdE{sHAYSC>qy+cH$^k>Va>Zy1DgvfcaIqxR;6C>hWb*{`|Hey&i5zI zdGvBk!zXvM2UHK1h8bZ_@>h#{stuDuPQ|-zo7`WhUNmi7(_y~7zj|PMSael)aL7?l zTXHF-={fIwIBk8+xX8zAeJSfqJvkLwB^w?WH!nr4+QZuV0lT`g)}VIwx<2attYz$s z*59{pyZ(+hJe|7csYpETiwSQlj%w5+P1D@+$`h3XdG#5P*W~wK>F7U{{#vo})WKQ% znN0^Ro3?hQuI^9m_rzsnNA3Fghql5C z=TCgIYfDeJdE;|eUsYsIE&Oh`*22luexY~GRPS-v4} z`^61)>dl7hM>P84-{Ne^n{U;B>D5<-FMcoc?b>C1<6-}@G*hS%SV`6YDAfjb*+fBItw|TJkGEX|SKtX9zXt29G9LLHKFJ-`gT z8dWCFvLUPFf}BW;4B`N7L6QiZ$O}XS7)}srmIX9t1Cjti7K+!XJPd9DoTBI!Qgk{8 zA|W!_F~Q$jbY;XiARGk=31mEd%FM)>i5eA|9xFvE;>k%C ziQ?H=WTjar5=1#s_iSBx#DyFS1py#jfcG+31AGgDw)lXyBkGg8kpO5O0vjWUJY$jM zNhQvP_aiRNE6hj`#4;8F7VpF1I(CGkL>Uq`=A;|)3k~vJO>UczK=hf%a5VNZBT$nV zl#lxhB$#8DrE=092$oBN2qf$w&qEs)&EmfhoB!85_*%cV9K&(Ib;=9DZ`fyq=v5=o9-!Wg(|(Fu@U{=5jv=Eg7*DxiCFA9fK6;#rQXoI5%xw0dSb%a9f=u@P3OhWbp*9kvtw+B!j{ z;a>p%fB++_wa|hSOcx+_{u)t&(!HFami^U2E&^8$n%wIsdzjqr^LWecQdLci_ zf)lcAtRPDw8%9nNETvs;7RCn#g{2}-TQLc;V%QwnU;lPfvQ2pgc`?Gfi*Y!y8*Y5` zWVI2?A}=8|aB{Tx5EIB!i1PqHV5uANBJjJU4^(?9W=<>0)=wdyDNpU7Q5F7&Eb<-0 Ng$TkBTgVF9KLIxg!L9%R diff --git a/internal/zip/testdata/readme.zip b/internal/zip/testdata/readme.zip deleted file mode 100644 index 5642a67e77d5f5a45c92ea701801ae0b993f4724..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1886 zcmZ{kdrVVT9LF!V3}{!Ui6aJx9zjJmXxSX1WJM9mLkg{cIvKj%wx{jY+k5JLv@kYN z!5JNo4aFBh<3P!DBFwoztb=VNPNqXQ6{aRQT~vG!dePQq(h46jfkI%g8e zQVuF8O1`*?@=TZJjr;uWq3w!-v;}N_RP?Gf^!Rp8n9Jq|ixOs_FG}=jWzwyBn5iYU=NPvm|7(PkVE$ zn%di$xNlF2X7Iz>ccTV36;$mSJ1(qRz3_GQ#iaMwnGc>DNSyodrP{_%?qmn>Ym_`BcAr;QcBZ%&iP<^W$pOL zM{9j4>rA~l6iev9+|0J$w{5-ljyF7=y6UM& zJm!lDZ!eB&(j!gtyz;8!RfBmA8IafH4_xjXIGFxwvGU}B+54D{2P_-6bfvByNbTZg zYEG9NYB-vdFO+)KO}6`|nkx(HT*IyXr^ zb^HgqOO3wPq2SAXb#rneMv5o4JvP34HlnBI=;Pp$q}mNz<3{4bQ};JSovnWG+B9qA z`L`nMm%5od^-K4rKX)VOUiZCa6BcZ#+EovCv*1kL;g;FHVQ zY{{E$Hhk&TSBEcoFZ1oX<^AJhEn}y}rX+ZV+siLrZ&J?L-d4B5xQgHL@!m?d<5)~^ z*Z%K)3sobPcVm7D-Ms(y`dgnq9J)SqM@h-LjT^3s4&Dr}Qa8&AGwPkgOp5G~&;#m~W^oaxwzIFVP8sfF#}u zffd;x;Gr7{jEML$U`M5NQ(^O z0Bu2%2%N|ZLGzUBk^a2`ij1{u-J}z1m zp-rRuS9wbr;3bYD1sn)RK|%r2p8bJ4Ay`{c!>CbwxjCX8Jq=Z9s(O9h&*GFzGk2y8VRA4d{ zkwlVXmoNryT66+rmw#{|Yy`tYt4!Mrl7l#cAs&K}tq=*Me!Al@Zc9gPMJ`MaPRAex zdNKY@B+gA+R{|VnINVkz3B2DT3|TyZYc!9C7Kvdo2zDO1M)?=OKOn%!YAv+j1TzGP z9eIkzI4m$3MsOmD#bU7n0<=v=1oIb=?(f}3mC#{Lv0lhevfzX)8!O0?$cB-V1WRd` zn}zX#L1C%L(^gD^tQa;&_Se7N)NE6pL0*jT?qVEH?1md3Jy~tUvdBwF4V)Y;KEwo$ x6yiL<4_N9(ya@a*=>yfCh?&!hvh`ERHRXvNG^)b?kVXE-a3O*)#1^uG_D`k1y14)V diff --git a/internal/zip/testdata/symlink.zip b/internal/zip/testdata/symlink.zip deleted file mode 100644 index af846938cde293ccc3dfb310fdfbda641382dd3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 173 zcmWIWW@h1H00D{l&JGuU&FkX?vO$=gL588YGB+nPFFQ1ZlYv@nk^pZ;COKwYW=Vjo0E7PvK@{9%R*1=HrUrPkvVoK_0--OE_5yJj E01XfzDgXcg diff --git a/internal/zip/testdata/test-trailing-junk.zip b/internal/zip/testdata/test-trailing-junk.zip deleted file mode 100644 index 42281b4e3053eec7924b58d234d04e492c3baa38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1184 zcmWIWW@Zs#U|`^2XiQYKJ#hW)VM!oQ3M?YSP?B0)qE}K;5*otEz+CvJ$)^m6ODnh; z7+JnDGBAL3a-Te*6UMN}rFGJkM?wmLfr}l&aaaM)^pwV1FgBTd*)~VY5GrSri z$jrb1!XgYZ4C(m=8L36d`8oMThGrFpW_ksA>0oQD44Qqcff&u2&Hz7mUM?w+fxMm` zE&U=x?Zy@V2qPe0vcxr_Bsf2F!C6nVlf&(QE?5{rm?pSGb*exy9+#I&RurKk- z+m)0yRCPG=d-HHDZeo&8l5*8w*j(kQ)h25CKV`;*MHBS|=I94cI>i2RTbstM*llm) zO8IWizic_XckQR<_w%av+wcEed*5>UK?&z&H{O(96e0boH{+WCE(bu95f2^3EZ4O=VNZ?3)UvtyJg5QDRcf$^EI~Jki zWtQzJsL_x*GnsW)scd27+nNv&O9`(nP~TLZC!C)&dpmb9Gex+~DL%5m{kC!2{41u2r}!o#L;7=ONQ zE-qq{y7hnokNZ9E^>G%L>tA=N)+ZKnN{AiY63=$`mQ27(iv*7buak|7iv?tIZF@fl zt>|OAAsR5{#f{tFon;*@eKst4U%}L6{!DE$Q;-Gc;z`Q`4>jq0{kyW^?3@=d0sB55 zxoLcI^%PC5#tAeEd>m$^DR^;2N$vDP24)a*LyE?_;6T$0ZaWgUlR!) zO)1Y`?`E^OggslIl9kNpY|u0@YV$J_L(}Dqa{cO(OH|YIR<91PW~;8;CG9SvxT%P1 zf&1?rTi;(l9{BL-LFV7-o3%9b_5H&Yx{{nGExEP)?O%T1_U}*5o%1=o!$7pc+~7{P zVEy`CyQ(f_ZB6^{)1;6Rw%g;&&eO(;vcI48f3W$#VE69d#v5C|RN3w=iCp_vEP-JK z2X8m?^Q2e6G|k}Y>gTe~DWNIAn~_P58CTww04Zev=23TwQdKa(&kYWhQ$ShU>qC|zN%!0JcoK%J6 Y{M_8syb`_Q{M=N9y!^c4R3PF40F)}f^8f$< diff --git a/internal/zip/testdata/test.zip b/internal/zip/testdata/test.zip deleted file mode 100644 index 03890c05d4c12196086a99b7f6f51276156b4394..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1170 zcmWIWW@Zs#U|`^2XiQYKJ#hW)VM!oQ3M?YSP?B0)qE}K;5*otEz+CvJ$)^m6ODnh; z7+JnDGBAL3a-Te*6UMN}rFGJkM?wmLfr}l&aaaM)^pwV1FgBTd*)~VY5GrSri z$jrb1!XgYZ4C(m=8L36d`8oMThGrFpW_ksA>0oQD44Qqcff&u2&Hz7mUM?w+fxMm` zE&U=x?Zy@V2qPe0vcxr_Bsf2F!C6nVlf&(QE?5{rm?pSGb*exy9+#I&RurKk- z+m)0yRCPG=d-HHDZeo&8l5*8w*j(kQ)h25CKV`;*MHBS|=I94cI>i2RTbstM*llm) zO8IWizic_XckQR<_w%av+wcEed*5>UK?&z&H{O(96e0boH{+WCE(bu95f2^3EZ4O=VNZ?3)UvtyJg5QDRcf$^EI~Jki zWtQzJsL_x*GnsW)scd27+nNv&O9`(nP~TLZC!C)&dpmb9Gex+~DL%5m{kC!2{41u2r}!o#L;7=ONQ zE-qq{y7hnokNZ9E^>G%L>tA=N)+ZKnN{AiY63=$`mQ27(iv*7buak|7iv?tIZF@fl zt>|OAAsR5{#f{tFon;*@eKst4U%}L6{!DE$Q;-Gc;z`Q`4>jq0{kyW^?3@=d0sB55 zxoLcI^%PC5#tAeEd>m$^DR^;2N$vDP24)a*LyE?_;6T$0ZaWgUlR!) zO)1Y`?`E^OggslIl9kNpY|u0@YV$J_L(}Dqa{cO(OH|YIR<91PW~;8;CG9SvxT%P1 zf&1?rTi;(l9{BL-LFV7-o3%9b_5H&Yx{{nGExEP)?O%T1_U}*5o%1=o!$7pc+~7{P zVEy`CyQ(f_ZB6^{)1;6Rw%g;&&eO(;vcI48f3W$#VE69d#v5C|RN3w=iCp_vEP-JK z2X8m?^Q2e6G|k}Y>gTe~DWNIAn~_P58CTww04Zev=23TwQdKa(&kYWhQ$ShU>qC|zN%!0JcoK%J6 L{M_8syb?VCG^Dz$ diff --git a/internal/zip/testdata/time-22738.zip b/internal/zip/testdata/time-22738.zip deleted file mode 100644 index eb85b57103e11a48eb210439f5d81f691c0cedc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140 zcmWIWW@Zs#;9vlP8S~Xa6b!I1a5AK2=A?#(ure?F9Ko|w2)xbIc^X400 diff --git a/internal/zip/testdata/time-go.zip b/internal/zip/testdata/time-go.zip deleted file mode 100644 index f008805fa42c982a0e28e5abe025425dbc1a9ad9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148 zcmWIWW@Zs#;9y{2s972B4W!_JgMpKwB(=CiucV?RG=!CbvDfowWPmq2NG%)$cr!AI bFyJ--t`N+?VjL?QNQMyz?SV81GcW)E?OYRQ diff --git a/internal/zip/testdata/time-infozip.zip b/internal/zip/testdata/time-infozip.zip deleted file mode 100644 index 8e6394891f0f1000d5aff4c14fff25659a00ac7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmWIWW@h1H0D;<-@!nttl;B{HVJJy0F3~HgCpPx6NtkA0Hc^2-v9sr diff --git a/internal/zip/testdata/time-osx.zip b/internal/zip/testdata/time-osx.zip deleted file mode 100644 index e82c5c229e0917b8e33029e7666e755961ab9e48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142 zcmWIWW@h1H0D;<-@!nttl;B_xU?@o~F3~HgC<%?=VYu$~GqTt7XXIB#5rzP7MkY~a iT>5xm#yBi#1Thh&aKKFo@MdKL$uR<<6Oc9oaTowu0U8JZ diff --git a/internal/zip/testdata/time-win7.zip b/internal/zip/testdata/time-win7.zip deleted file mode 100644 index 8ba222b224674153fb65b5be87ca89f434fcb110..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114 zcmWIWW@Zs#0D;<-@!nttl;8l;C8@F9Ko|w2)xbIc<#roA diff --git a/internal/zip/testdata/time-winzip.zip b/internal/zip/testdata/time-winzip.zip deleted file mode 100644 index f6e8f8ba067e462fe7a9727159390919571b8270..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 150 zcmWIWW@h1H0D;<-@!nttl;8l;C8@jkM&B_K+#0Z2@Kw1r~0|1@$8R7r{ diff --git a/internal/zip/testdata/unix.zip b/internal/zip/testdata/unix.zip deleted file mode 100644 index ce1a981b2806d7e7a4026383622bf033aac426a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 620 zcmWIWW@h1H0D+!>4*T9e!nGVgHVCsa$S`E2=H%puhHx@4ujqc@7dHEWUugw510%}| zW(Ec@QJ!CvlcK=O6#zG8CeWC9v+JtZfJT5YJJ6Vv%p(1y#3Hakhkynd%)&4zEk7T{ z80NqZd!TMO;DQ>Hnp;p(sSh@(t>=Ls2%|X(;glmlr$hT_XGm26ZQ}Xk2 zD#0cQ0Ck_l^i*bUL4Hw5VqOW@MT|^x%(y~G0_;9UAi1p(#DsO=)pfJN@7-m>O3}avrVEFIY2Q>^9azOL2h8n_gnBfL<93z8D<0YVZh)@KY Y1`0(C*Rg`)o`D4j&49t9016@o0NYf3UjP6A diff --git a/internal/zip/testdata/utf8-7zip.zip b/internal/zip/testdata/utf8-7zip.zip deleted file mode 100644 index 0e97884559fa599f7376daf2478cf6cf516dc817..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146 zcmWIWW@h1HVBlb2(92BoW+w>yVlX3XFH;U0*F@dLbBL6$Hu0$~J@hL{8Z;-VWn diff --git a/internal/zip/testdata/utf8-infozip.zip b/internal/zip/testdata/utf8-infozip.zip deleted file mode 100644 index 25a892646cec2a10d19add86db7acb516556bea6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmWIWW@h1HVBlb2(92BoWK#03@RuM*si- diff --git a/internal/zip/testdata/utf8-osx.zip b/internal/zip/testdata/utf8-osx.zip deleted file mode 100644 index 9b0c058b5b5744d389e73e8afd9f6963426aaebf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmWIWW@h1H00F(sG;c5iO0Y2qFg)2Y?fKN6&+w>yVlX3XFH;U0*F@dLbBL6$Hu0$~J@hL{8Z)OZ^i diff --git a/internal/zip/testdata/utf8-winzip.zip b/internal/zip/testdata/utf8-winzip.zip deleted file mode 100644 index 909d52ed2d9a6db86fdc7669bb355e7da2338a6e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146 zcmWIWW@h1HVBlb2(92BoW4*T9e!nGVgHVCrq~yGK=(+5{uIE^HG#C2X@#4W#Is17f5MpZb3<EG$%FNt?{GyV?yb`dg^b>%*JLMKe)obQ>FfgY#Nc!n~3 zGWsmC$cFihkI1D@-9^IQUv@AAcr!BTGV7w4^dAb?7<7Q*5U`{XL_^GFWDo$`1QG$+ z2m+xYtPIS5f>i@bE4UdLS#~KhF|c$9GXTwJV}qHZ%K)+m0vOTg1SsDFN(1$=gP1Fz Te31G8Z&r}!7+~%L(F_a#G1EK) diff --git a/internal/zip/testdata/zip64.zip b/internal/zip/testdata/zip64.zip deleted file mode 100644 index a2ee1fa33dca48e1ec8dfc7507640bfa09bddeb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 242 zcmWIWW@Zs#U|`^2Feu@2tb6`HQw7KaVKyKRa&>g^b>%*JLMKe)obQ>FfgY#Nc!n~3 zGWsmC$cFihkI1D@-9^IQUv@AAcr!BTGV7w4^dAb?7(g~az>-D~4KbIIK>%zMNCadf u2n2YuvFSjV47xxF1B_4xjP`)?VKh)5J4k2(lDYtIR*)wcVD13X3=9B3ZZ^#T diff --git a/internal/zip/writer.go b/internal/zip/writer.go deleted file mode 100644 index cf8b77d..0000000 --- a/internal/zip/writer.go +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package zip - -import ( - "bufio" - "encoding/binary" - "errors" - "hash" - "hash/crc32" - "io" - "strings" - "unicode/utf8" -) - -var ( - errLongName = errors.New("zip: FileHeader.Name too long") - errLongExtra = errors.New("zip: FileHeader.Extra too long") -) - -// Writer implements a zip file writer. -type Writer struct { - cw *countWriter - dir []*header - last *fileWriter - closed bool - compressors map[uint16]Compressor - comment string - - // testHookCloseSizeOffset if non-nil is called with the size - // of offset of the central directory at Close. - testHookCloseSizeOffset func(size, offset uint64) -} - -type header struct { - *FileHeader - offset uint64 -} - -// NewWriter returns a new Writer writing a zip file to w. -func NewWriter(w io.Writer) *Writer { - return &Writer{cw: &countWriter{w: bufio.NewWriter(w)}} -} - -// SetOffset sets the offset of the beginning of the zip data within the -// underlying writer. It should be used when the zip data is appended to an -// existing file, such as a binary executable. -// It must be called before any data is written. -func (w *Writer) SetOffset(n int64) { - if w.cw.count != 0 { - panic("zip: SetOffset called after data was written") - } - w.cw.count = n -} - -// Flush flushes any buffered data to the underlying writer. -// Calling Flush is not normally necessary; calling Close is sufficient. -func (w *Writer) Flush() error { - return w.cw.w.(*bufio.Writer).Flush() -} - -// SetComment sets the end-of-central-directory comment field. -// It can only be called before Close. -func (w *Writer) SetComment(comment string) error { - if len(comment) > uint16max { - return errors.New("zip: Writer.Comment too long") - } - w.comment = comment - return nil -} - -// Close finishes writing the zip file by writing the central directory. -// It does not close the underlying writer. -func (w *Writer) Close() error { - if w.last != nil && !w.last.closed { - if err := w.last.close(); err != nil { - return err - } - w.last = nil - } - if w.closed { - return errors.New("zip: writer closed twice") - } - w.closed = true - - // write central directory - start := w.cw.count - for _, h := range w.dir { - var buf [directoryHeaderLen]byte - b := writeBuf(buf[:]) - b.uint32(uint32(directoryHeaderSignature)) - b.uint16(h.CreatorVersion) - b.uint16(h.ReaderVersion) - b.uint16(h.Flags) - b.uint16(h.Method) - b.uint16(h.ModifiedTime) - b.uint16(h.ModifiedDate) - b.uint32(h.CRC32) - if h.isZip64() || h.offset >= uint32max { - // the file needs a zip64 header. store maxint in both - // 32 bit size fields (and offset later) to signal that the - // zip64 extra header should be used. - b.uint32(uint32max) // compressed size - b.uint32(uint32max) // uncompressed size - - // append a zip64 extra block to Extra - var buf [28]byte // 2x uint16 + 3x uint64 - eb := writeBuf(buf[:]) - eb.uint16(zip64ExtraID) - eb.uint16(24) // size = 3x uint64 - eb.uint64(h.UncompressedSize64) - eb.uint64(h.CompressedSize64) - eb.uint64(h.offset) - h.Extra = append(h.Extra, buf[:]...) - } else { - b.uint32(h.CompressedSize) - b.uint32(h.UncompressedSize) - } - - b.uint16(uint16(len(h.Name))) - b.uint16(uint16(len(h.Extra))) - b.uint16(uint16(len(h.Comment))) - b = b[4:] // skip disk number start and internal file attr (2x uint16) - b.uint32(h.ExternalAttrs) - if h.offset > uint32max { - b.uint32(uint32max) - } else { - b.uint32(uint32(h.offset)) - } - if _, err := w.cw.Write(buf[:]); err != nil { - return err - } - if _, err := io.WriteString(w.cw, h.Name); err != nil { - return err - } - if _, err := w.cw.Write(h.Extra); err != nil { - return err - } - if _, err := io.WriteString(w.cw, h.Comment); err != nil { - return err - } - } - end := w.cw.count - - records := uint64(len(w.dir)) - size := uint64(end - start) - offset := uint64(start) - - if f := w.testHookCloseSizeOffset; f != nil { - f(size, offset) - } - - if records >= uint16max || size >= uint32max || offset >= uint32max { - var buf [directory64EndLen + directory64LocLen]byte - b := writeBuf(buf[:]) - - // zip64 end of central directory record - b.uint32(directory64EndSignature) - b.uint64(directory64EndLen - 12) // length minus signature (uint32) and length fields (uint64) - b.uint16(zipVersion45) // version made by - b.uint16(zipVersion45) // version needed to extract - b.uint32(0) // number of this disk - b.uint32(0) // number of the disk with the start of the central directory - b.uint64(records) // total number of entries in the central directory on this disk - b.uint64(records) // total number of entries in the central directory - b.uint64(size) // size of the central directory - b.uint64(offset) // offset of start of central directory with respect to the starting disk number - - // zip64 end of central directory locator - b.uint32(directory64LocSignature) - b.uint32(0) // number of the disk with the start of the zip64 end of central directory - b.uint64(uint64(end)) // relative offset of the zip64 end of central directory record - b.uint32(1) // total number of disks - - if _, err := w.cw.Write(buf[:]); err != nil { - return err - } - - // store max values in the regular end record to signal - // that the zip64 values should be used instead - records = uint16max - size = uint32max - offset = uint32max - } - - // write end record - var buf [directoryEndLen]byte - b := writeBuf(buf[:]) - b.uint32(uint32(directoryEndSignature)) - b = b[4:] // skip over disk number and first disk number (2x uint16) - b.uint16(uint16(records)) // number of entries this disk - b.uint16(uint16(records)) // number of entries total - b.uint32(uint32(size)) // size of directory - b.uint32(uint32(offset)) // start of directory - b.uint16(uint16(len(w.comment))) // byte size of EOCD comment - if _, err := w.cw.Write(buf[:]); err != nil { - return err - } - if _, err := io.WriteString(w.cw, w.comment); err != nil { - return err - } - - return w.cw.w.(*bufio.Writer).Flush() -} - -// Create adds a file to the zip file using the provided name. -// It returns a Writer to which the file contents should be written. -// The file contents will be compressed using the Deflate method. -// The name must be a relative path: it must not start with a drive -// letter (e.g. C:) or leading slash, and only forward slashes are -// allowed. To create a directory instead of a file, add a trailing -// slash to the name. -// The file's contents must be written to the io.Writer before the next -// call to Create, CreateHeader, or Close. -func (w *Writer) Create(name string) (io.Writer, error) { - header := &FileHeader{ - Name: name, - Method: Deflate, - } - return w.CreateHeader(header) -} - -// detectUTF8 reports whether s is a valid UTF-8 string, and whether the string -// must be considered UTF-8 encoding (i.e., not compatible with CP-437, ASCII, -// or any other common encoding). -func detectUTF8(s string) (valid, require bool) { - for i := 0; i < len(s); { - r, size := utf8.DecodeRuneInString(s[i:]) - i += size - // Officially, ZIP uses CP-437, but many readers use the system's - // local character encoding. Most encoding are compatible with a large - // subset of CP-437, which itself is ASCII-like. - // - // Forbid 0x7e and 0x5c since EUC-KR and Shift-JIS replace those - // characters with localized currency and overline characters. - if r < 0x20 || r > 0x7d || r == 0x5c { - if !utf8.ValidRune(r) || (r == utf8.RuneError && size == 1) { - return false, false - } - require = true - } - } - return true, require -} - -// CreateHeader adds a file to the zip archive using the provided FileHeader -// for the file metadata. Writer takes ownership of fh and may mutate -// its fields. The caller must not modify fh after calling CreateHeader. -// -// This returns a Writer to which the file contents should be written. -// The file's contents must be written to the io.Writer before the next -// call to Create, CreateHeader, CreateHeaderRaw, or Close. -func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { - ow, err := w.CreateHeaderRaw(fh) - if err != nil { - return nil, err - } - - if _, ok := ow.(dirWriter); ok { - return ow, nil - } - - fw := ow.(*fileWriter) - fw.raw = false - fw.compCount = &countWriter{w: w.cw} - fw.crc32 = crc32.NewIEEE() - comp := w.compressor(fh.Method) - if comp == nil { - return nil, ErrAlgorithm - } - fw.comp, err = comp(fw.compCount) - if err != nil { - return nil, err - } - fw.rawCount = &countWriter{w: fw.comp} - - return fw, nil -} - -// CreateHeaderRaw adds a file to the zip archive using the provided FileHeader -// for the file metadata. Writer takes ownership of fh and may mutate -// its fields. The caller must not modify fh after calling CreateHeader. -// -// This returns a Writer to which the file contents should be written. -// The file's contents must be written to the io.Writer before the next -// call to Create, CreateHeader, CreateHeaderRaw, or Close. -// -// CreateHeaderRaw doesn't compress any data written to the returned writer. It -// is the callers responsibility to set the correct Method, CompressedSize64, -// UncompressedSize64 and CRC32 FileHeader fields for the data written. -func (w *Writer) CreateHeaderRaw(fh *FileHeader) (io.Writer, error) { - if w.last != nil && !w.last.closed { - if err := w.last.close(); err != nil { - return nil, err - } - } - if len(w.dir) > 0 && w.dir[len(w.dir)-1].FileHeader == fh { - // See https://golang.org/issue/11144 confusion. - return nil, errors.New("archive/zip: invalid duplicate FileHeader") - } - - // The ZIP format has a sad state of affairs regarding character encoding. - // Officially, the name and comment fields are supposed to be encoded - // in CP-437 (which is mostly compatible with ASCII), unless the UTF-8 - // flag bit is set. However, there are several problems: - // - // * Many ZIP readers still do not support UTF-8. - // * If the UTF-8 flag is cleared, several readers simply interpret the - // name and comment fields as whatever the local system encoding is. - // - // In order to avoid breaking readers without UTF-8 support, - // we avoid setting the UTF-8 flag if the strings are CP-437 compatible. - // However, if the strings require multibyte UTF-8 encoding and is a - // valid UTF-8 string, then we set the UTF-8 bit. - // - // For the case, where the user explicitly wants to specify the encoding - // as UTF-8, they will need to set the flag bit themselves. - utf8Valid1, utf8Require1 := detectUTF8(fh.Name) - utf8Valid2, utf8Require2 := detectUTF8(fh.Comment) - switch { - case fh.NonUTF8: - fh.Flags &^= 0x800 - case (utf8Require1 || utf8Require2) && (utf8Valid1 && utf8Valid2): - fh.Flags |= 0x800 - } - - fh.CreatorVersion = fh.CreatorVersion&0xff00 | zipVersion20 // preserve compatibility byte - fh.ReaderVersion = zipVersion20 - - // If Modified is set, this takes precedence over MS-DOS timestamp fields. - if !fh.Modified.IsZero() { - // Contrary to the FileHeader.SetModTime method, we intentionally - // do not convert to UTC, because we assume the user intends to encode - // the date using the specified timezone. A user may want this control - // because many legacy ZIP readers interpret the timestamp according - // to the local timezone. - // - // The timezone is only non-UTC if a user directly sets the Modified - // field directly themselves. All other approaches sets UTC. - fh.ModifiedDate, fh.ModifiedTime = timeToMsDosTime(fh.Modified) - - // Use "extended timestamp" format since this is what Info-ZIP uses. - // Nearly every major ZIP implementation uses a different format, - // but at least most seem to be able to understand the other formats. - // - // This format happens to be identical for both local and central header - // if modification time is the only timestamp being encoded. - var mbuf [9]byte // 2*SizeOf(uint16) + SizeOf(uint8) + SizeOf(uint32) - mt := uint32(fh.Modified.Unix()) - eb := writeBuf(mbuf[:]) - eb.uint16(extTimeExtraID) - eb.uint16(5) // Size: SizeOf(uint8) + SizeOf(uint32) - eb.uint8(1) // Flags: ModTime - eb.uint32(mt) // ModTime - fh.Extra = append(fh.Extra, mbuf[:]...) - } - - var ( - ow io.Writer - fw *fileWriter - ) - h := &header{ - FileHeader: fh, - offset: uint64(w.cw.count), - } - - if strings.HasSuffix(fh.Name, "/") { - // Set the compression method to Store to ensure data length is truly zero, - // which the writeHeader method always encodes for the size fields. - // This is necessary as most compression formats have non-zero lengths - // even when compressing an empty string. - fh.Method = Store - fh.Flags &^= 0x8 // we will not write a data descriptor - - // Explicitly clear sizes as they have no meaning for directories. - fh.CompressedSize = 0 - fh.CompressedSize64 = 0 - fh.UncompressedSize = 0 - fh.UncompressedSize64 = 0 - - ow = dirWriter{} - } else { - fh.Flags |= 0x8 // we will write a data descriptor - - fw = &fileWriter{ - zipw: w.cw, - raw: true, - } - fw.header = h - ow = fw - } - w.dir = append(w.dir, h) - if err := writeHeader(w.cw, fh); err != nil { - return nil, err - } - // If we're creating a directory, fw is nil. - w.last = fw - return ow, nil -} - -func writeHeader(w io.Writer, h *FileHeader) error { - const maxUint16 = 1<<16 - 1 - if len(h.Name) > maxUint16 { - return errLongName - } - if len(h.Extra) > maxUint16 { - return errLongExtra - } - - var buf [fileHeaderLen]byte - b := writeBuf(buf[:]) - b.uint32(uint32(fileHeaderSignature)) - b.uint16(h.ReaderVersion) - b.uint16(h.Flags) - b.uint16(h.Method) - b.uint16(h.ModifiedTime) - b.uint16(h.ModifiedDate) - b.uint32(0) // since we are writing a data descriptor crc32, - b.uint32(0) // compressed size, - b.uint32(0) // and uncompressed size should be zero - b.uint16(uint16(len(h.Name))) - b.uint16(uint16(len(h.Extra))) - if _, err := w.Write(buf[:]); err != nil { - return err - } - if _, err := io.WriteString(w, h.Name); err != nil { - return err - } - _, err := w.Write(h.Extra) - return err -} - -// RegisterCompressor registers or overrides a custom compressor for a specific -// method ID. If a compressor for a given method is not found, Writer will -// default to looking up the compressor at the package level. -func (w *Writer) RegisterCompressor(method uint16, comp Compressor) { - if w.compressors == nil { - w.compressors = make(map[uint16]Compressor) - } - w.compressors[method] = comp -} - -func (w *Writer) compressor(method uint16) Compressor { - comp := w.compressors[method] - if comp == nil { - comp = compressor(method) - } - return comp -} - -type dirWriter struct{} - -func (dirWriter) Write(b []byte) (int, error) { - if len(b) == 0 { - return 0, nil - } - return 0, errors.New("zip: write to directory") -} - -type fileWriter struct { - *header - zipw io.Writer - rawCount *countWriter - comp io.WriteCloser - compCount *countWriter - crc32 hash.Hash32 - closed bool - raw bool -} - -func (w *fileWriter) Write(p []byte) (int, error) { - if w.closed { - return 0, errors.New("zip: write to closed file") - } - if w.raw { - return w.zipw.Write(p) - } - w.crc32.Write(p) - return w.rawCount.Write(p) -} - -func (w *fileWriter) close() error { - if w.closed { - return errors.New("zip: file closed twice") - } - w.closed = true - - // update FileHeader - fh := w.header.FileHeader - if !w.raw { - if err := w.comp.Close(); err != nil { - return err - } - - fh.CRC32 = w.crc32.Sum32() - fh.CompressedSize64 = uint64(w.compCount.count) - fh.UncompressedSize64 = uint64(w.rawCount.count) - } - - if fh.isZip64() { - fh.CompressedSize = uint32max - fh.UncompressedSize = uint32max - fh.ReaderVersion = zipVersion45 // requires 4.5 - File uses ZIP64 format extensions - } else { - fh.CompressedSize = uint32(fh.CompressedSize64) - fh.UncompressedSize = uint32(fh.UncompressedSize64) - } - - // Write data descriptor. This is more complicated than one would - // think, see e.g. comments in zipfile.c:putextended() and - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7073588. - // The approach here is to write 8 byte sizes if needed without - // adding a zip64 extra in the local header (too late anyway). - var buf []byte - if fh.isZip64() { - buf = make([]byte, dataDescriptor64Len) - } else { - buf = make([]byte, dataDescriptorLen) - } - b := writeBuf(buf) - b.uint32(dataDescriptorSignature) // de-facto standard, required by OS X - b.uint32(fh.CRC32) - if fh.isZip64() { - b.uint64(fh.CompressedSize64) - b.uint64(fh.UncompressedSize64) - } else { - b.uint32(fh.CompressedSize) - b.uint32(fh.UncompressedSize) - } - _, err := w.zipw.Write(buf) - return err -} - -type countWriter struct { - w io.Writer - count int64 -} - -func (w *countWriter) Write(p []byte) (int, error) { - n, err := w.w.Write(p) - w.count += int64(n) - return n, err -} - -type nopCloser struct { - io.Writer -} - -func (w nopCloser) Close() error { - return nil -} - -type writeBuf []byte - -func (b *writeBuf) uint8(v uint8) { - (*b)[0] = v - *b = (*b)[1:] -} - -func (b *writeBuf) uint16(v uint16) { - binary.LittleEndian.PutUint16(*b, v) - *b = (*b)[2:] -} - -func (b *writeBuf) uint32(v uint32) { - binary.LittleEndian.PutUint32(*b, v) - *b = (*b)[4:] -} - -func (b *writeBuf) uint64(v uint64) { - binary.LittleEndian.PutUint64(*b, v) - *b = (*b)[8:] -} diff --git a/internal/zip/writer_test.go b/internal/zip/writer_test.go deleted file mode 100644 index 2ac86de..0000000 --- a/internal/zip/writer_test.go +++ /dev/null @@ -1,467 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package zip - -import ( - "bytes" - "compress/flate" - "encoding/binary" - "fmt" - "hash/crc32" - "io" - "io/ioutil" - "math/rand" - "os" - "strings" - "testing" - "time" -) - -// TODO(adg): a more sophisticated test suite - -type WriteTest struct { - Name string - Data []byte - Method uint16 - Mode os.FileMode - Raw bool -} - -var writeTests = []WriteTest{ - { - Name: "foo", - Data: []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."), - Method: Store, - Mode: 0666, - }, - { - Name: "bar", - Data: nil, // large data set in the test - Method: Deflate, - Mode: 0644, - }, - { - Name: "foo-raw", - Data: []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."), - Method: Store, - Mode: 0666, - Raw: true, - }, - { - Name: "bar-raw", - Data: nil, // large data set in the test - Method: Deflate, - Mode: 0666, - Raw: true, - }, - { - Name: "setuid", - Data: []byte("setuid file"), - Method: Deflate, - Mode: 0755 | os.ModeSetuid, - }, - { - Name: "setgid", - Data: []byte("setgid file"), - Method: Deflate, - Mode: 0755 | os.ModeSetgid, - }, - { - Name: "symlink", - Data: []byte("../link/target"), - Method: Deflate, - Mode: 0755 | os.ModeSymlink, - }, -} - -func TestWriter(t *testing.T) { - largeData := make([]byte, 1<<17) - if _, err := rand.Read(largeData); err != nil { - t.Fatal("rand.Read failed:", err) - } - writeTests[1].Data = largeData - defer func() { - writeTests[1].Data = nil - }() - - // write a zip file - buf := new(bytes.Buffer) - w := NewWriter(buf) - - for _, wt := range writeTests { - testCreate(t, w, &wt) - } - - if err := w.Close(); err != nil { - t.Fatal(err) - } - - // read it back - r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) - if err != nil { - t.Fatal(err) - } - for i, wt := range writeTests { - testReadFile(t, r.File[i], &wt) - } -} - -// TestWriterComment is test for EOCD comment read/write. -func TestWriterComment(t *testing.T) { - var tests = []struct { - comment string - ok bool - }{ - {"hi, hello", true}, - {"hi, こんにちわ", true}, - {strings.Repeat("a", uint16max), true}, - {strings.Repeat("a", uint16max+1), false}, - } - - for _, test := range tests { - // write a zip file - buf := new(bytes.Buffer) - w := NewWriter(buf) - if err := w.SetComment(test.comment); err != nil { - if test.ok { - t.Fatalf("SetComment: unexpected error %v", err) - } - continue - } else { - if !test.ok { - t.Fatalf("SetComment: unexpected success, want error") - } - } - - if err := w.Close(); test.ok == (err != nil) { - t.Fatal(err) - } - - if w.closed != test.ok { - t.Fatalf("Writer.closed: got %v, want %v", w.closed, test.ok) - } - - // skip read test in failure cases - if !test.ok { - continue - } - - // read it back - r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) - if err != nil { - t.Fatal(err) - } - if r.Comment != test.comment { - t.Fatalf("Reader.Comment: got %v, want %v", r.Comment, test.comment) - } - } -} - -func TestWriterUTF8(t *testing.T) { - var utf8Tests = []struct { - name string - comment string - nonUTF8 bool - flags uint16 - }{ - { - name: "hi, hello", - comment: "in the world", - flags: 0x8, - }, - { - name: "hi, こんにちわ", - comment: "in the world", - flags: 0x808, - }, - { - name: "hi, こんにちわ", - comment: "in the world", - nonUTF8: true, - flags: 0x8, - }, - { - name: "hi, hello", - comment: "in the 世界", - flags: 0x808, - }, - { - name: "hi, こんにちわ", - comment: "in the 世界", - flags: 0x808, - }, - { - name: "the replacement rune is �", - comment: "the replacement rune is �", - flags: 0x808, - }, - { - // Name is Japanese encoded in Shift JIS. - name: "\x93\xfa\x96{\x8c\xea.txt", - comment: "in the 世界", - flags: 0x008, // UTF-8 must not be set - }, - } - - // write a zip file - buf := new(bytes.Buffer) - w := NewWriter(buf) - - for _, test := range utf8Tests { - h := &FileHeader{ - Name: test.name, - Comment: test.comment, - NonUTF8: test.nonUTF8, - Method: Deflate, - } - w, err := w.CreateHeader(h) - if err != nil { - t.Fatal(err) - } - w.Write([]byte{}) - } - - if err := w.Close(); err != nil { - t.Fatal(err) - } - - // read it back - r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) - if err != nil { - t.Fatal(err) - } - for i, test := range utf8Tests { - flags := r.File[i].Flags - if flags != test.flags { - t.Errorf("CreateHeader(name=%q comment=%q nonUTF8=%v): flags=%#x, want %#x", test.name, test.comment, test.nonUTF8, flags, test.flags) - } - } -} - -func TestWriterTime(t *testing.T) { - var buf bytes.Buffer - h := &FileHeader{ - Name: "test.txt", - Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)), - } - w := NewWriter(&buf) - if _, err := w.CreateHeader(h); err != nil { - t.Fatalf("unexpected CreateHeader error: %v", err) - } - if err := w.Close(); err != nil { - t.Fatalf("unexpected Close error: %v", err) - } - - want, err := ioutil.ReadFile("testdata/time-go.zip") - if err != nil { - t.Fatalf("unexpected ReadFile error: %v", err) - } - if got := buf.Bytes(); !bytes.Equal(got, want) { - fmt.Printf("%x\n%x\n", got, want) - t.Error("contents of time-go.zip differ") - } -} - -func TestWriterOffset(t *testing.T) { - largeData := make([]byte, 1<<17) - if _, err := rand.Read(largeData); err != nil { - t.Fatal("rand.Read failed:", err) - } - writeTests[1].Data = largeData - defer func() { - writeTests[1].Data = nil - }() - - // write a zip file - buf := new(bytes.Buffer) - existingData := []byte{1, 2, 3, 1, 2, 3, 1, 2, 3} - n, _ := buf.Write(existingData) - w := NewWriter(buf) - w.SetOffset(int64(n)) - - for _, wt := range writeTests { - testCreate(t, w, &wt) - } - - if err := w.Close(); err != nil { - t.Fatal(err) - } - - // read it back - r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len())) - if err != nil { - t.Fatal(err) - } - for i, wt := range writeTests { - testReadFile(t, r.File[i], &wt) - } -} - -func TestWriterFlush(t *testing.T) { - var buf bytes.Buffer - w := NewWriter(struct{ io.Writer }{&buf}) - _, err := w.Create("foo") - if err != nil { - t.Fatal(err) - } - if buf.Len() > 0 { - t.Fatalf("Unexpected %d bytes already in buffer", buf.Len()) - } - if err := w.Flush(); err != nil { - t.Fatal(err) - } - if buf.Len() == 0 { - t.Fatal("No bytes written after Flush") - } -} - -func TestWriterDir(t *testing.T) { - w := NewWriter(ioutil.Discard) - dw, err := w.Create("dir/") - if err != nil { - t.Fatal(err) - } - if _, err := dw.Write(nil); err != nil { - t.Errorf("Write(nil) to directory: got %v, want nil", err) - } - if _, err := dw.Write([]byte("hello")); err == nil { - t.Error(`Write("hello") to directory: got nil error, want non-nil`) - } -} - -func TestWriterDirAttributes(t *testing.T) { - var buf bytes.Buffer - w := NewWriter(&buf) - if _, err := w.CreateHeader(&FileHeader{ - Name: "dir/", - Method: Deflate, - CompressedSize64: 1234, - UncompressedSize64: 5678, - }); err != nil { - t.Fatal(err) - } - if err := w.Close(); err != nil { - t.Fatal(err) - } - b := buf.Bytes() - - var sig [4]byte - binary.LittleEndian.PutUint32(sig[:], uint32(fileHeaderSignature)) - - idx := bytes.Index(b, sig[:]) - if idx == -1 { - t.Fatal("file header not found") - } - b = b[idx:] - - if !bytes.Equal(b[6:10], []byte{0, 0, 0, 0}) { // FileHeader.Flags: 0, FileHeader.Method: 0 - t.Errorf("unexpected method and flags: %v", b[6:10]) - } - - if !bytes.Equal(b[14:26], make([]byte, 12)) { // FileHeader.{CRC32,CompressSize,UncompressedSize} all zero. - t.Errorf("unexpected crc, compress and uncompressed size to be 0 was: %v", b[14:26]) - } - - binary.LittleEndian.PutUint32(sig[:], uint32(dataDescriptorSignature)) - if bytes.Index(b, sig[:]) != -1 { - t.Error("there should be no data descriptor") - } -} - -func testCreate(t *testing.T, w *Writer, wt *WriteTest) { - header := &FileHeader{ - Name: wt.Name, - Method: wt.Method, - } - if wt.Mode != 0 { - header.SetMode(wt.Mode) - } - data := wt.Data - - var f io.Writer - var err error - if wt.Raw { - if header.Method == Deflate { - var buf bytes.Buffer - fw, err := flate.NewWriter(&buf, flate.DefaultCompression) - if err != nil { - t.Fatal(err) - } - if _, err = fw.Write(wt.Data); err != nil { - t.Fatal(err) - } - if err = fw.Close(); err != nil { - t.Fatal(err) - } - data = buf.Bytes() - } - header.CompressedSize64 = uint64(len(data)) - header.UncompressedSize64 = uint64(len(wt.Data)) - header.CRC32 = crc32.ChecksumIEEE(wt.Data) - f, err = w.CreateHeaderRaw(header) - } else { - f, err = w.CreateHeader(header) - } - if err != nil { - t.Fatal(err) - } - _, err = f.Write(data) - if err != nil { - t.Fatal(err) - } -} - -func testReadFile(t *testing.T, f *File, wt *WriteTest) { - if f.Name != wt.Name { - t.Fatalf("File name: got %q, want %q", f.Name, wt.Name) - } - testFileMode(t, f, wt.Mode) - rc, err := f.Open() - if err != nil { - t.Fatal("opening:", err) - } - b, err := ioutil.ReadAll(rc) - if err != nil { - t.Fatal("reading:", err) - } - err = rc.Close() - if err != nil { - t.Fatal("closing:", err) - } - if !bytes.Equal(b, wt.Data) { - t.Errorf("File contents %q, want %q", b, wt.Data) - } -} - -func BenchmarkCompressedZipGarbage(b *testing.B) { - bigBuf := bytes.Repeat([]byte("a"), 1<<20) - - runOnce := func(buf *bytes.Buffer) { - buf.Reset() - zw := NewWriter(buf) - for j := 0; j < 3; j++ { - w, _ := zw.CreateHeader(&FileHeader{ - Name: "foo", - Method: Deflate, - }) - w.Write(bigBuf) - } - zw.Close() - } - - b.ReportAllocs() - // Run once and then reset the timer. - // This effectively discards the very large initial flate setup cost, - // as well as the initialization of bigBuf. - runOnce(&bytes.Buffer{}) - b.ResetTimer() - - b.RunParallel(func(pb *testing.PB) { - var buf bytes.Buffer - for pb.Next() { - runOnce(&buf) - } - }) -} diff --git a/internal/zip/zip_test.go b/internal/zip/zip_test.go deleted file mode 100644 index 3a3a4e0..0000000 --- a/internal/zip/zip_test.go +++ /dev/null @@ -1,828 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Tests that involve both reading and writing. - -package zip - -import ( - "bytes" - "errors" - "fmt" - "hash" - "io" - "io/ioutil" - "runtime" - "sort" - "strings" - "testing" - "time" -) - -func TestOver65kFiles(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - buf := new(bytes.Buffer) - w := NewWriter(buf) - const nFiles = (1 << 16) + 42 - for i := 0; i < nFiles; i++ { - _, err := w.CreateHeader(&FileHeader{ - Name: fmt.Sprintf("%d.dat", i), - Method: Store, // avoid Issue 6136 and Issue 6138 - }) - if err != nil { - t.Fatalf("creating file %d: %v", i, err) - } - } - if err := w.Close(); err != nil { - t.Fatalf("Writer.Close: %v", err) - } - s := buf.String() - zr, err := NewReader(strings.NewReader(s), int64(len(s))) - if err != nil { - t.Fatalf("NewReader: %v", err) - } - if got := len(zr.File); got != nFiles { - t.Fatalf("File contains %d files, want %d", got, nFiles) - } - for i := 0; i < nFiles; i++ { - want := fmt.Sprintf("%d.dat", i) - if zr.File[i].Name != want { - t.Fatalf("File(%d) = %q, want %q", i, zr.File[i].Name, want) - } - } -} - -func TestModTime(t *testing.T) { - var testTime = time.Date(2009, time.November, 10, 23, 45, 58, 0, time.UTC) - fh := new(FileHeader) - fh.SetModTime(testTime) - outTime := fh.ModTime() - if !outTime.Equal(testTime) { - t.Errorf("times don't match: got %s, want %s", outTime, testTime) - } -} - -func testHeaderRoundTrip(fh *FileHeader, wantUncompressedSize uint32, wantUncompressedSize64 uint64, t *testing.T) { - fi := fh.FileInfo() - fh2, err := FileInfoHeader(fi) - if err != nil { - t.Fatal(err) - } - if got, want := fh2.Name, fh.Name; got != want { - t.Errorf("Name: got %s, want %s\n", got, want) - } - if got, want := fh2.UncompressedSize, wantUncompressedSize; got != want { - t.Errorf("UncompressedSize: got %d, want %d\n", got, want) - } - if got, want := fh2.UncompressedSize64, wantUncompressedSize64; got != want { - t.Errorf("UncompressedSize64: got %d, want %d\n", got, want) - } - if got, want := fh2.ModifiedTime, fh.ModifiedTime; got != want { - t.Errorf("ModifiedTime: got %d, want %d\n", got, want) - } - if got, want := fh2.ModifiedDate, fh.ModifiedDate; got != want { - t.Errorf("ModifiedDate: got %d, want %d\n", got, want) - } - - if sysfh, ok := fi.Sys().(*FileHeader); !ok && sysfh != fh { - t.Errorf("Sys didn't return original *FileHeader") - } -} - -func TestFileHeaderRoundTrip(t *testing.T) { - fh := &FileHeader{ - Name: "foo.txt", - UncompressedSize: 987654321, - ModifiedTime: 1234, - ModifiedDate: 5678, - } - testHeaderRoundTrip(fh, fh.UncompressedSize, uint64(fh.UncompressedSize), t) -} - -func TestFileHeaderRoundTrip64(t *testing.T) { - fh := &FileHeader{ - Name: "foo.txt", - UncompressedSize64: 9876543210, - ModifiedTime: 1234, - ModifiedDate: 5678, - } - testHeaderRoundTrip(fh, uint32max, fh.UncompressedSize64, t) -} - -func TestFileHeaderRoundTripModified(t *testing.T) { - fh := &FileHeader{ - Name: "foo.txt", - UncompressedSize: 987654321, - Modified: time.Now().Local(), - ModifiedTime: 1234, - ModifiedDate: 5678, - } - fi := fh.FileInfo() - fh2, err := FileInfoHeader(fi) - if err != nil { - t.Fatal(err) - } - if got, want := fh2.Modified, fh.Modified.UTC(); got != want { - t.Errorf("Modified: got %s, want %s\n", got, want) - } - if got, want := fi.ModTime(), fh.Modified.UTC(); got != want { - t.Errorf("Modified: got %s, want %s\n", got, want) - } -} - -func TestFileHeaderRoundTripWithoutModified(t *testing.T) { - fh := &FileHeader{ - Name: "foo.txt", - UncompressedSize: 987654321, - ModifiedTime: 1234, - ModifiedDate: 5678, - } - fi := fh.FileInfo() - fh2, err := FileInfoHeader(fi) - if err != nil { - t.Fatal(err) - } - if got, want := fh2.ModTime(), fh.ModTime(); got != want { - t.Errorf("Modified: got %s, want %s\n", got, want) - } - if got, want := fi.ModTime(), fh.ModTime(); got != want { - t.Errorf("Modified: got %s, want %s\n", got, want) - } -} - -type repeatedByte struct { - off int64 - b byte - n int64 -} - -// rleBuffer is a run-length-encoded byte buffer. -// It's an io.Writer (like a bytes.Buffer) and also an io.ReaderAt, -// allowing random-access reads. -type rleBuffer struct { - buf []repeatedByte -} - -func (r *rleBuffer) Size() int64 { - if len(r.buf) == 0 { - return 0 - } - last := &r.buf[len(r.buf)-1] - return last.off + last.n -} - -func (r *rleBuffer) Write(p []byte) (n int, err error) { - var rp *repeatedByte - if len(r.buf) > 0 { - rp = &r.buf[len(r.buf)-1] - // Fast path, if p is entirely the same byte repeated. - if lastByte := rp.b; len(p) > 0 && p[0] == lastByte { - if bytes.Count(p, []byte{lastByte}) == len(p) { - rp.n += int64(len(p)) - return len(p), nil - } - } - } - - for _, b := range p { - if rp == nil || rp.b != b { - r.buf = append(r.buf, repeatedByte{r.Size(), b, 1}) - rp = &r.buf[len(r.buf)-1] - } else { - rp.n++ - } - } - return len(p), nil -} - -func min(x, y int64) int64 { - if x < y { - return x - } - return y -} - -func memset(a []byte, b byte) { - if len(a) == 0 { - return - } - // Double, until we reach power of 2 >= len(a), same as bytes.Repeat, - // but without allocation. - a[0] = b - for i, l := 1, len(a); i < l; i *= 2 { - copy(a[i:], a[:i]) - } -} - -func (r *rleBuffer) ReadAt(p []byte, off int64) (n int, err error) { - if len(p) == 0 { - return - } - skipParts := sort.Search(len(r.buf), func(i int) bool { - part := &r.buf[i] - return part.off+part.n > off - }) - parts := r.buf[skipParts:] - if len(parts) > 0 { - skipBytes := off - parts[0].off - for _, part := range parts { - repeat := int(min(part.n-skipBytes, int64(len(p)-n))) - memset(p[n:n+repeat], part.b) - n += repeat - if n == len(p) { - return - } - skipBytes = 0 - } - } - if n != len(p) { - err = io.ErrUnexpectedEOF - } - return -} - -// Just testing the rleBuffer used in the Zip64 test above. Not used by the zip code. -func TestRLEBuffer(t *testing.T) { - b := new(rleBuffer) - var all []byte - writes := []string{"abcdeee", "eeeeeee", "eeeefghaaiii"} - for _, w := range writes { - b.Write([]byte(w)) - all = append(all, w...) - } - if len(b.buf) != 10 { - t.Fatalf("len(b.buf) = %d; want 10", len(b.buf)) - } - - for i := 0; i < len(all); i++ { - for j := 0; j < len(all)-i; j++ { - buf := make([]byte, j) - n, err := b.ReadAt(buf, int64(i)) - if err != nil || n != len(buf) { - t.Errorf("ReadAt(%d, %d) = %d, %v; want %d, nil", i, j, n, err, len(buf)) - } - if !bytes.Equal(buf, all[i:i+j]) { - t.Errorf("ReadAt(%d, %d) = %q; want %q", i, j, buf, all[i:i+j]) - } - } - } -} - -// fakeHash32 is a dummy Hash32 that always returns 0. -type fakeHash32 struct { - hash.Hash32 -} - -func (fakeHash32) Write(p []byte) (int, error) { return len(p), nil } -func (fakeHash32) Sum32() uint32 { return 0 } - -func TestZip64(t *testing.T) { - if testing.Short() { - t.Skip("slow test; skipping") - } - t.Parallel() - const size = 1 << 32 // before the "END\n" part - buf := testZip64(t, size) - testZip64DirectoryRecordLength(buf, t) -} - -func TestZip64EdgeCase(t *testing.T) { - if testing.Short() { - t.Skip("slow test; skipping") - } - t.Parallel() - // Test a zip file with uncompressed size 0xFFFFFFFF. - // That's the magic marker for a 64-bit file, so even though - // it fits in a 32-bit field we must use the 64-bit field. - // Go 1.5 and earlier got this wrong, - // writing an invalid zip file. - const size = 1<<32 - 1 - int64(len("END\n")) // before the "END\n" part - buf := testZip64(t, size) - testZip64DirectoryRecordLength(buf, t) -} - -// Tests that we generate a zip64 file if the directory at offset -// 0xFFFFFFFF, but not before. -func TestZip64DirectoryOffset(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - t.Parallel() - const filename = "huge.txt" - gen := func(wantOff uint64) func(*Writer) { - return func(w *Writer) { - w.testHookCloseSizeOffset = func(size, off uint64) { - if off != wantOff { - t.Errorf("central directory offset = %d (%x); want %d", off, off, wantOff) - } - } - f, err := w.CreateHeader(&FileHeader{ - Name: filename, - Method: Store, - }) - if err != nil { - t.Fatal(err) - } - f.(*fileWriter).crc32 = fakeHash32{} - size := wantOff - fileHeaderLen - uint64(len(filename)) - dataDescriptorLen - if _, err := io.CopyN(f, zeros{}, int64(size)); err != nil { - t.Fatal(err) - } - if err := w.Close(); err != nil { - t.Fatal(err) - } - } - } - t.Run("uint32max-2_NoZip64", func(t *testing.T) { - t.Parallel() - if generatesZip64(t, gen(0xfffffffe)) { - t.Error("unexpected zip64") - } - }) - t.Run("uint32max-1_Zip64", func(t *testing.T) { - t.Parallel() - if !generatesZip64(t, gen(0xffffffff)) { - t.Error("expected zip64") - } - }) -} - -// At 16k records, we need to generate a zip64 file. -func TestZip64ManyRecords(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - t.Parallel() - gen := func(numRec int) func(*Writer) { - return func(w *Writer) { - for i := 0; i < numRec; i++ { - _, err := w.CreateHeader(&FileHeader{ - Name: "a.txt", - Method: Store, - }) - if err != nil { - t.Fatal(err) - } - } - if err := w.Close(); err != nil { - t.Fatal(err) - } - } - } - // 16k-1 records shouldn't make a zip64: - t.Run("uint16max-1_NoZip64", func(t *testing.T) { - t.Parallel() - if generatesZip64(t, gen(0xfffe)) { - t.Error("unexpected zip64") - } - }) - // 16k records should make a zip64: - t.Run("uint16max_Zip64", func(t *testing.T) { - t.Parallel() - if !generatesZip64(t, gen(0xffff)) { - t.Error("expected zip64") - } - }) -} - -// suffixSaver is an io.Writer & io.ReaderAt that remembers the last 0 -// to 'keep' bytes of data written to it. Call Suffix to get the -// suffix bytes. -type suffixSaver struct { - keep int - buf []byte - start int - size int64 -} - -func (ss *suffixSaver) Size() int64 { return ss.size } - -var errDiscardedBytes = errors.New("ReadAt of discarded bytes") - -func (ss *suffixSaver) ReadAt(p []byte, off int64) (n int, err error) { - back := ss.size - off - if back > int64(ss.keep) { - return 0, errDiscardedBytes - } - suf := ss.Suffix() - n = copy(p, suf[len(suf)-int(back):]) - if n != len(p) { - err = io.EOF - } - return -} - -func (ss *suffixSaver) Suffix() []byte { - if len(ss.buf) < ss.keep { - return ss.buf - } - buf := make([]byte, ss.keep) - n := copy(buf, ss.buf[ss.start:]) - copy(buf[n:], ss.buf[:]) - return buf -} - -func (ss *suffixSaver) Write(p []byte) (n int, err error) { - n = len(p) - ss.size += int64(len(p)) - if len(ss.buf) < ss.keep { - space := ss.keep - len(ss.buf) - add := len(p) - if add > space { - add = space - } - ss.buf = append(ss.buf, p[:add]...) - p = p[add:] - } - for len(p) > 0 { - n := copy(ss.buf[ss.start:], p) - p = p[n:] - ss.start += n - if ss.start == ss.keep { - ss.start = 0 - } - } - return -} - -// generatesZip64 reports whether f wrote a zip64 file. -// f is also responsible for closing w. -func generatesZip64(t *testing.T, f func(w *Writer)) bool { - ss := &suffixSaver{keep: 10 << 20} - w := NewWriter(ss) - f(w) - return suffixIsZip64(t, ss) -} - -type sizedReaderAt interface { - io.ReaderAt - Size() int64 -} - -func suffixIsZip64(t *testing.T, zip sizedReaderAt) bool { - d := make([]byte, 1024) - if _, err := zip.ReadAt(d, zip.Size()-int64(len(d))); err != nil { - t.Fatalf("ReadAt: %v", err) - } - - sigOff := findSignatureInBlock(d) - if sigOff == -1 { - t.Errorf("failed to find signature in block") - return false - } - - dirOff, err := findDirectory64End(zip, zip.Size()-int64(len(d))+int64(sigOff)) - if err != nil { - t.Fatalf("findDirectory64End: %v", err) - } - if dirOff == -1 { - return false - } - - d = make([]byte, directory64EndLen) - if _, err := zip.ReadAt(d, dirOff); err != nil { - t.Fatalf("ReadAt(off=%d): %v", dirOff, err) - } - - b := readBuf(d) - if sig := b.uint32(); sig != directory64EndSignature { - return false - } - - size := b.uint64() - if size != directory64EndLen-12 { - t.Errorf("expected length of %d, got %d", directory64EndLen-12, size) - } - return true -} - -// Zip64 is required if the total size of the records is uint32max. -func TestZip64LargeDirectory(t *testing.T) { - if runtime.GOARCH == "wasm" { - t.Skip("too slow on wasm") - } - if testing.Short() { - t.Skip("skipping in short mode") - } - t.Parallel() - // gen returns a func that writes a zip with a wantLen bytes - // of central directory. - gen := func(wantLen int64) func(*Writer) { - return func(w *Writer) { - w.testHookCloseSizeOffset = func(size, off uint64) { - if size != uint64(wantLen) { - t.Errorf("Close central directory size = %d; want %d", size, wantLen) - } - } - - uint16string := strings.Repeat(".", uint16max) - remain := wantLen - for remain > 0 { - commentLen := int(uint16max) - directoryHeaderLen - 1 - thisRecLen := directoryHeaderLen + int(uint16max) + commentLen - if int64(thisRecLen) > remain { - remove := thisRecLen - int(remain) - commentLen -= remove - thisRecLen -= remove - } - remain -= int64(thisRecLen) - f, err := w.CreateHeader(&FileHeader{ - Name: uint16string, - Comment: uint16string[:commentLen], - }) - if err != nil { - t.Fatalf("CreateHeader: %v", err) - } - f.(*fileWriter).crc32 = fakeHash32{} - } - if err := w.Close(); err != nil { - t.Fatalf("Close: %v", err) - } - } - } - t.Run("uint32max-1_NoZip64", func(t *testing.T) { - t.Parallel() - if generatesZip64(t, gen(uint32max-1)) { - t.Error("unexpected zip64") - } - }) - t.Run("uint32max_HasZip64", func(t *testing.T) { - t.Parallel() - if !generatesZip64(t, gen(uint32max)) { - t.Error("expected zip64") - } - }) -} - -func testZip64(t testing.TB, size int64) *rleBuffer { - const chunkSize = 1024 - chunks := int(size / chunkSize) - // write size bytes plus "END\n" to a zip file - buf := new(rleBuffer) - w := NewWriter(buf) - f, err := w.CreateHeader(&FileHeader{ - Name: "huge.txt", - Method: Store, - }) - if err != nil { - t.Fatal(err) - } - f.(*fileWriter).crc32 = fakeHash32{} - chunk := make([]byte, chunkSize) - for i := range chunk { - chunk[i] = '.' - } - for i := 0; i < chunks; i++ { - _, err := f.Write(chunk) - if err != nil { - t.Fatal("write chunk:", err) - } - } - if frag := int(size % chunkSize); frag > 0 { - _, err := f.Write(chunk[:frag]) - if err != nil { - t.Fatal("write chunk:", err) - } - } - end := []byte("END\n") - _, err = f.Write(end) - if err != nil { - t.Fatal("write end:", err) - } - if err := w.Close(); err != nil { - t.Fatal(err) - } - - // read back zip file and check that we get to the end of it - r, err := NewReader(buf, int64(buf.Size())) - if err != nil { - t.Fatal("reader:", err) - } - f0 := r.File[0] - rc, err := f0.Open() - if err != nil { - t.Fatal("opening:", err) - } - rc.(*checksumReader).hash = fakeHash32{} - for i := 0; i < chunks; i++ { - _, err := io.ReadFull(rc, chunk) - if err != nil { - t.Fatal("read:", err) - } - } - if frag := int(size % chunkSize); frag > 0 { - _, err := io.ReadFull(rc, chunk[:frag]) - if err != nil { - t.Fatal("read:", err) - } - } - gotEnd, err := ioutil.ReadAll(rc) - if err != nil { - t.Fatal("read end:", err) - } - if !bytes.Equal(gotEnd, end) { - t.Errorf("End of zip64 archive %q, want %q", gotEnd, end) - } - err = rc.Close() - if err != nil { - t.Fatal("closing:", err) - } - if size+int64(len("END\n")) >= 1<<32-1 { - if got, want := f0.UncompressedSize, uint32(uint32max); got != want { - t.Errorf("UncompressedSize %#x, want %#x", got, want) - } - } - - if got, want := f0.UncompressedSize64, uint64(size)+uint64(len(end)); got != want { - t.Errorf("UncompressedSize64 %#x, want %#x", got, want) - } - - return buf -} - -// Issue 9857 -func testZip64DirectoryRecordLength(buf *rleBuffer, t *testing.T) { - if !suffixIsZip64(t, buf) { - t.Fatal("not a zip64") - } -} - -func testValidHeader(h *FileHeader, t *testing.T) { - var buf bytes.Buffer - z := NewWriter(&buf) - - f, err := z.CreateHeader(h) - if err != nil { - t.Fatalf("error creating header: %v", err) - } - if _, err := f.Write([]byte("hi")); err != nil { - t.Fatalf("error writing content: %v", err) - } - if err := z.Close(); err != nil { - t.Fatalf("error closing zip writer: %v", err) - } - - b := buf.Bytes() - zf, err := NewReader(bytes.NewReader(b), int64(len(b))) - if err != nil { - t.Fatalf("got %v, expected nil", err) - } - zh := zf.File[0].FileHeader - if zh.Name != h.Name || zh.Method != h.Method || zh.UncompressedSize64 != uint64(len("hi")) { - t.Fatalf("got %q/%d/%d expected %q/%d/%d", zh.Name, zh.Method, zh.UncompressedSize64, h.Name, h.Method, len("hi")) - } -} - -// Issue 4302. -func TestHeaderInvalidTagAndSize(t *testing.T) { - const timeFormat = "20060102T150405.000.txt" - - ts := time.Now() - filename := ts.Format(timeFormat) - - h := FileHeader{ - Name: filename, - Method: Deflate, - Extra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len, but Extra is best-effort parsing - } - h.SetModTime(ts) - - testValidHeader(&h, t) -} - -func TestHeaderTooShort(t *testing.T) { - h := FileHeader{ - Name: "foo.txt", - Method: Deflate, - Extra: []byte{zip64ExtraID}, // missing size and second half of tag, but Extra is best-effort parsing - } - testValidHeader(&h, t) -} - -func TestHeaderTooLongErr(t *testing.T) { - var headerTests = []struct { - name string - extra []byte - wanterr error - }{ - { - name: strings.Repeat("x", 1<<16), - extra: []byte{}, - wanterr: errLongName, - }, - { - name: "long_extra", - extra: bytes.Repeat([]byte{0xff}, 1<<16), - wanterr: errLongExtra, - }, - } - - // write a zip file - buf := new(bytes.Buffer) - w := NewWriter(buf) - - for _, test := range headerTests { - h := &FileHeader{ - Name: test.name, - Extra: test.extra, - } - _, err := w.CreateHeader(h) - if err != test.wanterr { - t.Errorf("error=%v, want %v", err, test.wanterr) - } - } - - if err := w.Close(); err != nil { - t.Fatal(err) - } -} - -func TestHeaderIgnoredSize(t *testing.T) { - h := FileHeader{ - Name: "foo.txt", - Method: Deflate, - Extra: []byte{zip64ExtraID & 0xFF, zip64ExtraID >> 8, 24, 0, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, // bad size but shouldn't be consulted - } - testValidHeader(&h, t) -} - -// Issue 4393. It is valid to have an extra data header -// which contains no body. -func TestZeroLengthHeader(t *testing.T) { - h := FileHeader{ - Name: "extadata.txt", - Method: Deflate, - Extra: []byte{ - 85, 84, 5, 0, 3, 154, 144, 195, 77, // tag 21589 size 5 - 85, 120, 0, 0, // tag 30805 size 0 - }, - } - testValidHeader(&h, t) -} - -// Just benchmarking how fast the Zip64 test above is. Not related to -// our zip performance, since the test above disabled CRC32 and flate. -func BenchmarkZip64Test(b *testing.B) { - for i := 0; i < b.N; i++ { - testZip64(b, 1<<26) - } -} - -func BenchmarkZip64TestSizes(b *testing.B) { - for _, size := range []int64{1 << 12, 1 << 20, 1 << 26} { - b.Run(fmt.Sprint(size), func(b *testing.B) { - b.RunParallel(func(pb *testing.PB) { - for pb.Next() { - testZip64(b, size) - } - }) - }) - } -} - -func TestSuffixSaver(t *testing.T) { - const keep = 10 - ss := &suffixSaver{keep: keep} - ss.Write([]byte("abc")) - if got := string(ss.Suffix()); got != "abc" { - t.Errorf("got = %q; want abc", got) - } - ss.Write([]byte("defghijklmno")) - if got := string(ss.Suffix()); got != "fghijklmno" { - t.Errorf("got = %q; want fghijklmno", got) - } - if got, want := ss.Size(), int64(len("abc")+len("defghijklmno")); got != want { - t.Errorf("Size = %d; want %d", got, want) - } - buf := make([]byte, ss.Size()) - for off := int64(0); off < ss.Size(); off++ { - for size := 1; size <= int(ss.Size()-off); size++ { - readBuf := buf[:size] - n, err := ss.ReadAt(readBuf, off) - if off < ss.Size()-keep { - if err != errDiscardedBytes { - t.Errorf("off %d, size %d = %v, %v (%q); want errDiscardedBytes", off, size, n, err, readBuf[:n]) - } - continue - } - want := "abcdefghijklmno"[off : off+int64(size)] - got := string(readBuf[:n]) - if err != nil || got != want { - t.Errorf("off %d, size %d = %v, %v (%q); want %q", off, size, n, err, got, want) - } - } - } - -} - -type zeros struct{} - -func (zeros) Read(p []byte) (int, error) { - for i := range p { - p[i] = 0 - } - return len(p), nil -}