aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/archive/zip
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/archive/zip')
-rw-r--r--libgo/go/archive/zip/reader.go103
-rw-r--r--libgo/go/archive/zip/reader_test.go37
-rw-r--r--libgo/go/archive/zip/register.go27
-rw-r--r--libgo/go/archive/zip/struct.go2
-rw-r--r--libgo/go/archive/zip/writer.go32
-rw-r--r--libgo/go/archive/zip/zip_test.go62
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