diff options
Diffstat (limited to 'libgo/go/archive/zip')
-rw-r--r-- | libgo/go/archive/zip/reader.go | 103 | ||||
-rw-r--r-- | libgo/go/archive/zip/reader_test.go | 37 | ||||
-rw-r--r-- | libgo/go/archive/zip/register.go | 27 | ||||
-rw-r--r-- | libgo/go/archive/zip/struct.go | 2 | ||||
-rw-r--r-- | libgo/go/archive/zip/writer.go | 32 | ||||
-rw-r--r-- | libgo/go/archive/zip/zip_test.go | 62 |
6 files changed, 207 insertions, 56 deletions
diff --git a/libgo/go/archive/zip/reader.go b/libgo/go/archive/zip/reader.go index 519748b..84a9d41 100644 --- a/libgo/go/archive/zip/reader.go +++ b/libgo/go/archive/zip/reader.go @@ -22,9 +22,10 @@ var ( ) type Reader struct { - r io.ReaderAt - File []*File - Comment string + r io.ReaderAt + File []*File + Comment string + decompressors map[uint16]Decompressor } type ReadCloser struct { @@ -34,6 +35,7 @@ type ReadCloser struct { type File struct { FileHeader + zip *Reader zipr io.ReaderAt zipsize int64 headerOffset int64 @@ -95,7 +97,7 @@ func (z *Reader) init(r io.ReaderAt, size int64) error { // a bad one, and then only report a ErrFormat or UnexpectedEOF if // the file count modulo 65536 is incorrect. for { - f := &File{zipr: r, zipsize: size} + f := &File{zip: z, zipr: r, zipsize: size} err = readDirectoryHeader(f, buf) if err == ErrFormat || err == io.ErrUnexpectedEOF { break @@ -113,6 +115,24 @@ func (z *Reader) init(r io.ReaderAt, size int64) error { 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() @@ -140,7 +160,7 @@ func (f *File) Open() (rc io.ReadCloser, err error) { } size := int64(f.CompressedSize64) r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size) - dcomp := decompressor(f.Method) + dcomp := f.zip.decompressor(f.Method) if dcomp == nil { err = ErrAlgorithm return @@ -261,39 +281,59 @@ func readDirectoryHeader(f *File, r io.Reader) error { f.Extra = d[filenameLen : filenameLen+extraLen] f.Comment = string(d[filenameLen+extraLen:]) + needUSize := f.UncompressedSize == ^uint32(0) + needCSize := f.CompressedSize == ^uint32(0) + needHeaderOffset := f.headerOffset == int64(^uint32(0)) + if len(f.Extra) > 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. b := readBuf(f.Extra) for len(b) >= 4 { // need at least tag and size tag := b.uint16() size := b.uint16() if int(size) > len(b) { - return ErrFormat + break } if tag == zip64ExtraId { - // update directory values from the zip64 extra block + // 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. eb := readBuf(b[:size]) - if len(eb) >= 8 { + + if needUSize { + needUSize = false + if len(eb) < 8 { + return ErrFormat + } f.UncompressedSize64 = eb.uint64() } - if len(eb) >= 8 { + if needCSize { + needCSize = false + if len(eb) < 8 { + return ErrFormat + } f.CompressedSize64 = eb.uint64() } - if len(eb) >= 8 { + if needHeaderOffset { + needHeaderOffset = false + if len(eb) < 8 { + return ErrFormat + } f.headerOffset = int64(eb.uint64()) } + break } b = b[size:] } - // Should have consumed the whole header. - // But popular zip & JAR creation tools are broken and - // may pad extra zeros at the end, so accept those - // too. See golang.org/issue/8186. - for _, v := range b { - if v != 0 { - return ErrFormat - } - } } + + if needUSize || needCSize || needHeaderOffset { + return ErrFormat + } + return nil } @@ -376,14 +416,16 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) } d.comment = string(b[:l]) - p, err := findDirectory64End(r, directoryEndOffset) - if err == nil && p >= 0 { - err = readDirectory64End(r, p, d) - } - if err != nil { - return nil, err + // 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 @@ -407,8 +449,13 @@ func findDirectory64End(r io.ReaderAt, directoryEndOffset int64) (int64, error) if sig := b.uint32(); sig != directory64LocSignature { return -1, nil } - b = b[4:] // skip number of the disk with the start of the zip64 end of central directory - p := b.uint64() // relative offset of the zip64 end of central directory record + 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 } diff --git a/libgo/go/archive/zip/reader_test.go b/libgo/go/archive/zip/reader_test.go index 547dd39..8f7e8bf 100644 --- a/libgo/go/archive/zip/reader_test.go +++ b/libgo/go/archive/zip/reader_test.go @@ -605,3 +605,40 @@ func TestIssue11146(t *testing.T) { } 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/libgo/go/archive/zip/register.go b/libgo/go/archive/zip/register.go index 4211ec7..8fccbf7 100644 --- a/libgo/go/archive/zip/register.go +++ b/libgo/go/archive/zip/register.go @@ -12,15 +12,19 @@ import ( "sync" ) -// A Compressor returns a compressing writer, writing to the -// provided writer. On Close, any pending data should be flushed. -type Compressor func(io.Writer) (io.WriteCloser, error) - -// Decompressor is a function that wraps a Reader with a decompressing Reader. -// The decompressed ReadCloser is returned to callers who open files from -// within the archive. These callers are responsible for closing this reader -// when they're finished reading. -type Decompressor func(io.Reader) io.ReadCloser +// 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 @@ -75,14 +79,15 @@ var ( ) // RegisterDecompressor allows custom decompressors for a specified method ID. -func RegisterDecompressor(method uint16, d Decompressor) { +// The common methods Store and Deflate are built in. +func RegisterDecompressor(method uint16, dcomp Decompressor) { mu.Lock() defer mu.Unlock() if _, ok := decompressors[method]; ok { panic("decompressor already registered") } - decompressors[method] = d + decompressors[method] = dcomp } // RegisterCompressor registers custom compressors for a specified method ID. diff --git a/libgo/go/archive/zip/struct.go b/libgo/go/archive/zip/struct.go index 137d049..5ee4f88 100644 --- a/libgo/go/archive/zip/struct.go +++ b/libgo/go/archive/zip/struct.go @@ -235,7 +235,7 @@ func (h *FileHeader) SetMode(mode os.FileMode) { // isZip64 reports whether the file size exceeds the 32 bit limit func (fh *FileHeader) isZip64() bool { - return fh.CompressedSize64 > uint32max || fh.UncompressedSize64 > uint32max + return fh.CompressedSize64 >= uint32max || fh.UncompressedSize64 >= uint32max } func msdosModeToFileMode(m uint32) (mode os.FileMode) { diff --git a/libgo/go/archive/zip/writer.go b/libgo/go/archive/zip/writer.go index 3be2b5f..5ce66e6 100644 --- a/libgo/go/archive/zip/writer.go +++ b/libgo/go/archive/zip/writer.go @@ -14,14 +14,14 @@ import ( ) // TODO(adg): support zip file comments -// TODO(adg): support specifying deflate level // Writer implements a zip file writer. type Writer struct { - cw *countWriter - dir []*header - last *fileWriter - closed bool + cw *countWriter + dir []*header + last *fileWriter + closed bool + compressors map[uint16]Compressor } type header struct { @@ -78,7 +78,7 @@ func (w *Writer) Close() error { b.uint16(h.ModifiedTime) b.uint16(h.ModifiedDate) b.uint32(h.CRC32) - if h.isZip64() || h.offset > uint32max { + 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. @@ -220,7 +220,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) { compCount: &countWriter{w: w.cw}, crc32: crc32.NewIEEE(), } - comp := compressor(fh.Method) + comp := w.compressor(fh.Method) if comp == nil { return nil, ErrAlgorithm } @@ -270,6 +270,24 @@ func writeHeader(w io.Writer, h *FileHeader) error { 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 fileWriter struct { *header zipw io.Writer diff --git a/libgo/go/archive/zip/zip_test.go b/libgo/go/archive/zip/zip_test.go index f00ff47..f785abf 100644 --- a/libgo/go/archive/zip/zip_test.go +++ b/libgo/go/archive/zip/zip_test.go @@ -10,6 +10,7 @@ import ( "bytes" "fmt" "hash" + "internal/testenv" "io" "io/ioutil" "sort" @@ -19,6 +20,9 @@ import ( ) func TestOver65kFiles(t *testing.T) { + if testing.Short() && testenv.Builder() == "" { + t.Skip("skipping in short mode") + } buf := new(bytes.Buffer) w := NewWriter(buf) const nFiles = (1 << 16) + 42 @@ -233,10 +237,24 @@ func TestZip64(t *testing.T) { testZip64DirectoryRecordLength(buf, t) } +func TestZip64EdgeCase(t *testing.T) { + if testing.Short() { + t.Skip("slow test; skipping") + } + // 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) +} + func testZip64(t testing.TB, size int64) *rleBuffer { const chunkSize = 1024 chunks := int(size / chunkSize) - // write 2^32 bytes plus "END\n" to a zip file + // write size bytes plus "END\n" to a zip file buf := new(rleBuffer) w := NewWriter(buf) f, err := w.CreateHeader(&FileHeader{ @@ -257,6 +275,12 @@ func testZip64(t testing.TB, size int64) *rleBuffer { 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 { @@ -283,6 +307,12 @@ func testZip64(t testing.TB, size int64) *rleBuffer { 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) @@ -294,14 +324,14 @@ func testZip64(t testing.TB, size int64) *rleBuffer { if err != nil { t.Fatal("closing:", err) } - if size == 1<<32 { + if size+int64(len("END\n")) >= 1<<32-1 { if got, want := f0.UncompressedSize, uint32(uint32max); got != want { - t.Errorf("UncompressedSize %d, want %d", 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 %d, want %d", got, want) + t.Errorf("UncompressedSize64 %#x, want %#x", got, want) } return buf @@ -373,9 +403,14 @@ func testValidHeader(h *FileHeader, t *testing.T) { } b := buf.Bytes() - if _, err = NewReader(bytes.NewReader(b), int64(len(b))); err != nil { + 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. @@ -388,20 +423,29 @@ func TestHeaderInvalidTagAndSize(t *testing.T) { h := FileHeader{ Name: filename, Method: Deflate, - Extra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len + Extra: []byte(ts.Format(time.RFC3339Nano)), // missing tag and len, but Extra is best-effort parsing } h.SetModTime(ts) - testInvalidHeader(&h, t) + testValidHeader(&h, t) } func TestHeaderTooShort(t *testing.T) { h := FileHeader{ Name: "foo.txt", Method: Deflate, - Extra: []byte{zip64ExtraId}, // missing size + Extra: []byte{zip64ExtraId}, // missing size and second half of tag, but Extra is best-effort parsing } - testInvalidHeader(&h, t) + testValidHeader(&h, t) +} + +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 |