mirror of
https://github.com/golang/go
synced 2024-11-26 02:57:57 -07:00
archive/zip: zip64 support
R=golang-dev, r, adg CC=golang-dev https://golang.org/cl/6463050
This commit is contained in:
parent
7e3ebaacd8
commit
2e6d0968e3
@ -103,7 +103,7 @@ func (z *Reader) init(r io.ReaderAt, size int64) error {
|
|||||||
}
|
}
|
||||||
z.File = append(z.File, f)
|
z.File = append(z.File, f)
|
||||||
}
|
}
|
||||||
if uint16(len(z.File)) != end.directoryRecords {
|
if uint16(len(z.File)) != uint16(end.directoryRecords) { // only compare 16 bits here
|
||||||
// Return the readDirectoryHeader error if we read
|
// Return the readDirectoryHeader error if we read
|
||||||
// the wrong number of directory entries.
|
// the wrong number of directory entries.
|
||||||
return err
|
return err
|
||||||
@ -123,7 +123,7 @@ func (f *File) Open() (rc io.ReadCloser, err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
size := int64(f.CompressedSize)
|
size := int64(f.CompressedSize64)
|
||||||
r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size)
|
r := io.NewSectionReader(f.zipr, f.headerOffset+bodyOffset, size)
|
||||||
switch f.Method {
|
switch f.Method {
|
||||||
case Store: // (no compression)
|
case Store: // (no compression)
|
||||||
@ -220,6 +220,8 @@ func readDirectoryHeader(f *File, r io.Reader) error {
|
|||||||
f.CRC32 = b.uint32()
|
f.CRC32 = b.uint32()
|
||||||
f.CompressedSize = b.uint32()
|
f.CompressedSize = b.uint32()
|
||||||
f.UncompressedSize = b.uint32()
|
f.UncompressedSize = b.uint32()
|
||||||
|
f.CompressedSize64 = uint64(f.CompressedSize)
|
||||||
|
f.UncompressedSize64 = uint64(f.UncompressedSize)
|
||||||
filenameLen := int(b.uint16())
|
filenameLen := int(b.uint16())
|
||||||
extraLen := int(b.uint16())
|
extraLen := int(b.uint16())
|
||||||
commentLen := int(b.uint16())
|
commentLen := int(b.uint16())
|
||||||
@ -233,6 +235,28 @@ func readDirectoryHeader(f *File, r io.Reader) error {
|
|||||||
f.Name = string(d[:filenameLen])
|
f.Name = string(d[:filenameLen])
|
||||||
f.Extra = d[filenameLen : filenameLen+extraLen]
|
f.Extra = d[filenameLen : filenameLen+extraLen]
|
||||||
f.Comment = string(d[filenameLen+extraLen:])
|
f.Comment = string(d[filenameLen+extraLen:])
|
||||||
|
|
||||||
|
if len(f.Extra) > 0 {
|
||||||
|
b := readBuf(f.Extra)
|
||||||
|
for len(b) > 0 {
|
||||||
|
tag := b.uint16()
|
||||||
|
size := b.uint16()
|
||||||
|
if tag == zip64ExtraId {
|
||||||
|
// update directory values from the zip64 extra block
|
||||||
|
eb := readBuf(b)
|
||||||
|
if len(eb) >= 8 {
|
||||||
|
f.UncompressedSize64 = eb.uint64()
|
||||||
|
}
|
||||||
|
if len(eb) >= 8 {
|
||||||
|
f.CompressedSize64 = eb.uint64()
|
||||||
|
}
|
||||||
|
if len(eb) >= 8 {
|
||||||
|
f.headerOffset = int64(eb.uint64())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b = b[size:]
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,15 +287,23 @@ func readDataDescriptor(r io.Reader, f *File) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
b := readBuf(buf[:12])
|
b := readBuf(buf[:12])
|
||||||
f.CRC32 = b.uint32()
|
if b.uint32() != f.CRC32 {
|
||||||
f.CompressedSize = b.uint32()
|
return ErrChecksum
|
||||||
f.UncompressedSize = b.uint32()
|
}
|
||||||
|
|
||||||
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) {
|
func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error) {
|
||||||
// look for directoryEndSignature in the last 1k, then in the last 65k
|
// look for directoryEndSignature in the last 1k, then in the last 65k
|
||||||
var buf []byte
|
var buf []byte
|
||||||
|
var directoryEndOffset int64
|
||||||
for i, bLen := range []int64{1024, 65 * 1024} {
|
for i, bLen := range []int64{1024, 65 * 1024} {
|
||||||
if bLen > size {
|
if bLen > size {
|
||||||
bLen = size
|
bLen = size
|
||||||
@ -282,6 +314,7 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error)
|
|||||||
}
|
}
|
||||||
if p := findSignatureInBlock(buf); p >= 0 {
|
if p := findSignatureInBlock(buf); p >= 0 {
|
||||||
buf = buf[p:]
|
buf = buf[p:]
|
||||||
|
directoryEndOffset = size - bLen + int64(p)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if i == 1 || bLen == size {
|
if i == 1 || bLen == size {
|
||||||
@ -292,12 +325,12 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error)
|
|||||||
// read header into struct
|
// read header into struct
|
||||||
b := readBuf(buf[4:]) // skip signature
|
b := readBuf(buf[4:]) // skip signature
|
||||||
d := &directoryEnd{
|
d := &directoryEnd{
|
||||||
diskNbr: b.uint16(),
|
diskNbr: uint32(b.uint16()),
|
||||||
dirDiskNbr: b.uint16(),
|
dirDiskNbr: uint32(b.uint16()),
|
||||||
dirRecordsThisDisk: b.uint16(),
|
dirRecordsThisDisk: uint64(b.uint16()),
|
||||||
directoryRecords: b.uint16(),
|
directoryRecords: uint64(b.uint16()),
|
||||||
directorySize: b.uint32(),
|
directorySize: uint64(b.uint32()),
|
||||||
directoryOffset: b.uint32(),
|
directoryOffset: uint64(b.uint32()),
|
||||||
commentLen: b.uint16(),
|
commentLen: b.uint16(),
|
||||||
}
|
}
|
||||||
l := int(d.commentLen)
|
l := int(d.commentLen)
|
||||||
@ -305,9 +338,62 @@ func readDirectoryEnd(r io.ReaderAt, size int64) (dir *directoryEnd, err error)
|
|||||||
return nil, errors.New("zip: invalid comment length")
|
return nil, errors.New("zip: invalid comment length")
|
||||||
}
|
}
|
||||||
d.comment = string(b[:l])
|
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
|
||||||
|
}
|
||||||
return d, nil
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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 {
|
func findSignatureInBlock(b []byte) int {
|
||||||
for i := len(b) - directoryEndLen; i >= 0; i-- {
|
for i := len(b) - directoryEndLen; i >= 0; i-- {
|
||||||
// defined from directoryEndSignature in struct.go
|
// defined from directoryEndSignature in struct.go
|
||||||
@ -335,3 +421,9 @@ func (b *readBuf) uint32() uint32 {
|
|||||||
*b = (*b)[4:]
|
*b = (*b)[4:]
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *readBuf) uint64() uint64 {
|
||||||
|
v := binary.LittleEndian.Uint64(*b)
|
||||||
|
*b = (*b)[8:]
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
@ -206,6 +206,17 @@ var tests = []ZipTest{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "zip64.zip",
|
||||||
|
File: []ZipTestFile{
|
||||||
|
{
|
||||||
|
Name: "README",
|
||||||
|
Content: []byte("This small file is in ZIP64 format.\n"),
|
||||||
|
Mtime: "08-10-12 14:33:32",
|
||||||
|
Mode: 0644,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var crossPlatform = []ZipTestFile{
|
var crossPlatform = []ZipTestFile{
|
||||||
|
@ -7,12 +7,19 @@ Package zip provides support for reading and writing ZIP archives.
|
|||||||
|
|
||||||
See: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
|
See: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
|
||||||
|
|
||||||
This package does not support ZIP64 or disk spanning.
|
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
|
package zip
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -27,11 +34,16 @@ const (
|
|||||||
fileHeaderSignature = 0x04034b50
|
fileHeaderSignature = 0x04034b50
|
||||||
directoryHeaderSignature = 0x02014b50
|
directoryHeaderSignature = 0x02014b50
|
||||||
directoryEndSignature = 0x06054b50
|
directoryEndSignature = 0x06054b50
|
||||||
|
directory64LocSignature = 0x07064b50
|
||||||
|
directory64EndSignature = 0x06064b50
|
||||||
dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder
|
dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder
|
||||||
fileHeaderLen = 30 // + filename + extra
|
fileHeaderLen = 30 // + filename + extra
|
||||||
directoryHeaderLen = 46 // + filename + extra + comment
|
directoryHeaderLen = 46 // + filename + extra + comment
|
||||||
directoryEndLen = 22 // + comment
|
directoryEndLen = 22 // + comment
|
||||||
dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size
|
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
|
// Constants for the first byte in CreatorVersion
|
||||||
creatorFAT = 0
|
creatorFAT = 0
|
||||||
@ -39,22 +51,35 @@ const (
|
|||||||
creatorNTFS = 11
|
creatorNTFS = 11
|
||||||
creatorVFAT = 14
|
creatorVFAT = 14
|
||||||
creatorMacOSX = 19
|
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 id's
|
||||||
|
zip64ExtraId = 0x0001 // zip64 Extended Information Extra Field
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileHeader struct {
|
type FileHeader struct {
|
||||||
Name string
|
Name string
|
||||||
CreatorVersion uint16
|
CreatorVersion uint16
|
||||||
ReaderVersion uint16
|
ReaderVersion uint16
|
||||||
Flags uint16
|
Flags uint16
|
||||||
Method uint16
|
Method uint16
|
||||||
ModifiedTime uint16 // MS-DOS time
|
ModifiedTime uint16 // MS-DOS time
|
||||||
ModifiedDate uint16 // MS-DOS date
|
ModifiedDate uint16 // MS-DOS date
|
||||||
CRC32 uint32
|
CRC32 uint32
|
||||||
CompressedSize uint32
|
CompressedSize uint32 // deprecated; use CompressedSize64
|
||||||
UncompressedSize uint32
|
UncompressedSize uint32 // deprecated; use UncompressedSize64
|
||||||
Extra []byte
|
CompressedSize64 uint64
|
||||||
ExternalAttrs uint32 // Meaning depends on CreatorVersion
|
UncompressedSize64 uint64
|
||||||
Comment string
|
Extra []byte
|
||||||
|
ExternalAttrs uint32 // Meaning depends on CreatorVersion
|
||||||
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileInfo returns an os.FileInfo for the FileHeader.
|
// FileInfo returns an os.FileInfo for the FileHeader.
|
||||||
@ -67,8 +92,13 @@ type headerFileInfo struct {
|
|||||||
fh *FileHeader
|
fh *FileHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fi headerFileInfo) Name() string { return fi.fh.Name }
|
func (fi headerFileInfo) Name() string { return fi.fh.Name }
|
||||||
func (fi headerFileInfo) Size() int64 { return int64(fi.fh.UncompressedSize) }
|
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) IsDir() bool { return fi.Mode().IsDir() }
|
||||||
func (fi headerFileInfo) ModTime() time.Time { return fi.fh.ModTime() }
|
func (fi headerFileInfo) ModTime() time.Time { return fi.fh.ModTime() }
|
||||||
func (fi headerFileInfo) Mode() os.FileMode { return fi.fh.Mode() }
|
func (fi headerFileInfo) Mode() os.FileMode { return fi.fh.Mode() }
|
||||||
@ -78,25 +108,27 @@ func (fi headerFileInfo) Sys() interface{} { return fi.fh }
|
|||||||
// os.FileInfo.
|
// os.FileInfo.
|
||||||
func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) {
|
func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) {
|
||||||
size := fi.Size()
|
size := fi.Size()
|
||||||
if size > (1<<32 - 1) {
|
|
||||||
return nil, errors.New("zip: file over 4GB")
|
|
||||||
}
|
|
||||||
fh := &FileHeader{
|
fh := &FileHeader{
|
||||||
Name: fi.Name(),
|
Name: fi.Name(),
|
||||||
UncompressedSize: uint32(size),
|
UncompressedSize64: uint64(size),
|
||||||
}
|
}
|
||||||
fh.SetModTime(fi.ModTime())
|
fh.SetModTime(fi.ModTime())
|
||||||
fh.SetMode(fi.Mode())
|
fh.SetMode(fi.Mode())
|
||||||
|
if fh.UncompressedSize64 > uint32max {
|
||||||
|
fh.UncompressedSize = uint32max
|
||||||
|
} else {
|
||||||
|
fh.UncompressedSize = uint32(fh.UncompressedSize64)
|
||||||
|
}
|
||||||
return fh, nil
|
return fh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type directoryEnd struct {
|
type directoryEnd struct {
|
||||||
diskNbr uint16 // unused
|
diskNbr uint32 // unused
|
||||||
dirDiskNbr uint16 // unused
|
dirDiskNbr uint32 // unused
|
||||||
dirRecordsThisDisk uint16 // unused
|
dirRecordsThisDisk uint64 // unused
|
||||||
directoryRecords uint16
|
directoryRecords uint64
|
||||||
directorySize uint32
|
directorySize uint64
|
||||||
directoryOffset uint32 // relative to file
|
directoryOffset uint64 // relative to file
|
||||||
commentLen uint16
|
commentLen uint16
|
||||||
comment string
|
comment string
|
||||||
}
|
}
|
||||||
@ -190,6 +222,11 @@ func (h *FileHeader) SetMode(mode os.FileMode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isZip64 returns true if the file size exceeds the 32 bit limit
|
||||||
|
func (fh *FileHeader) isZip64() bool {
|
||||||
|
return fh.CompressedSize64 > uint32max || fh.UncompressedSize64 > uint32max
|
||||||
|
}
|
||||||
|
|
||||||
func msdosModeToFileMode(m uint32) (mode os.FileMode) {
|
func msdosModeToFileMode(m uint32) (mode os.FileMode) {
|
||||||
if m&msdosDir != 0 {
|
if m&msdosDir != 0 {
|
||||||
mode = os.ModeDir | 0777
|
mode = os.ModeDir | 0777
|
||||||
|
BIN
src/pkg/archive/zip/testdata/zip64.zip
vendored
Normal file
BIN
src/pkg/archive/zip/testdata/zip64.zip
vendored
Normal file
Binary file not shown.
@ -27,7 +27,7 @@ type Writer struct {
|
|||||||
|
|
||||||
type header struct {
|
type header struct {
|
||||||
*FileHeader
|
*FileHeader
|
||||||
offset uint32
|
offset uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWriter returns a new Writer writing a zip file to w.
|
// NewWriter returns a new Writer writing a zip file to w.
|
||||||
@ -62,14 +62,36 @@ func (w *Writer) Close() error {
|
|||||||
b.uint16(h.ModifiedTime)
|
b.uint16(h.ModifiedTime)
|
||||||
b.uint16(h.ModifiedDate)
|
b.uint16(h.ModifiedDate)
|
||||||
b.uint32(h.CRC32)
|
b.uint32(h.CRC32)
|
||||||
b.uint32(h.CompressedSize)
|
if h.isZip64() || h.offset > uint32max {
|
||||||
b.uint32(h.UncompressedSize)
|
// 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.Name)))
|
||||||
b.uint16(uint16(len(h.Extra)))
|
b.uint16(uint16(len(h.Extra)))
|
||||||
b.uint16(uint16(len(h.Comment)))
|
b.uint16(uint16(len(h.Comment)))
|
||||||
b = b[4:] // skip disk number start and internal file attr (2x uint16)
|
b = b[4:] // skip disk number start and internal file attr (2x uint16)
|
||||||
b.uint32(h.ExternalAttrs)
|
b.uint32(h.ExternalAttrs)
|
||||||
b.uint32(h.offset)
|
if h.offset > uint32max {
|
||||||
|
b.uint32(uint32max)
|
||||||
|
} else {
|
||||||
|
b.uint32(uint32(h.offset))
|
||||||
|
}
|
||||||
if _, err := w.cw.Write(buf[:]); err != nil {
|
if _, err := w.cw.Write(buf[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -85,15 +107,52 @@ func (w *Writer) Close() error {
|
|||||||
}
|
}
|
||||||
end := w.cw.count
|
end := w.cw.count
|
||||||
|
|
||||||
|
records := uint64(len(w.dir))
|
||||||
|
size := uint64(end - start)
|
||||||
|
offset := uint64(start)
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
// that the zip64 values should be used instead
|
||||||
|
records = uint16max
|
||||||
|
size = uint32max
|
||||||
|
offset = uint32max
|
||||||
|
}
|
||||||
|
|
||||||
// write end record
|
// write end record
|
||||||
var buf [directoryEndLen]byte
|
var buf [directoryEndLen]byte
|
||||||
b := writeBuf(buf[:])
|
b := writeBuf(buf[:])
|
||||||
b.uint32(uint32(directoryEndSignature))
|
b.uint32(uint32(directoryEndSignature))
|
||||||
b = b[4:] // skip over disk number and first disk number (2x uint16)
|
b = b[4:] // skip over disk number and first disk number (2x uint16)
|
||||||
b.uint16(uint16(len(w.dir))) // number of entries this disk
|
b.uint16(uint16(records)) // number of entries this disk
|
||||||
b.uint16(uint16(len(w.dir))) // number of entries total
|
b.uint16(uint16(records)) // number of entries total
|
||||||
b.uint32(uint32(end - start)) // size of directory
|
b.uint32(uint32(size)) // size of directory
|
||||||
b.uint32(uint32(start)) // start of directory
|
b.uint32(uint32(offset)) // start of directory
|
||||||
// skipped size of comment (always zero)
|
// skipped size of comment (always zero)
|
||||||
if _, err := w.cw.Write(buf[:]); err != nil {
|
if _, err := w.cw.Write(buf[:]); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -127,8 +186,9 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fh.Flags |= 0x8 // we will write a data descriptor
|
fh.Flags |= 0x8 // we will write a data descriptor
|
||||||
fh.CreatorVersion = fh.CreatorVersion&0xff00 | 0x14
|
|
||||||
fh.ReaderVersion = 0x14
|
fh.CreatorVersion = fh.CreatorVersion&0xff00 | zipVersion20 // preserve compatibility byte
|
||||||
|
fh.ReaderVersion = zipVersion20
|
||||||
|
|
||||||
fw := &fileWriter{
|
fw := &fileWriter{
|
||||||
zipw: w.cw,
|
zipw: w.cw,
|
||||||
@ -151,7 +211,7 @@ func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) {
|
|||||||
|
|
||||||
h := &header{
|
h := &header{
|
||||||
FileHeader: fh,
|
FileHeader: fh,
|
||||||
offset: uint32(w.cw.count),
|
offset: uint64(w.cw.count),
|
||||||
}
|
}
|
||||||
w.dir = append(w.dir, h)
|
w.dir = append(w.dir, h)
|
||||||
fw.header = h
|
fw.header = h
|
||||||
@ -173,9 +233,9 @@ func writeHeader(w io.Writer, h *FileHeader) error {
|
|||||||
b.uint16(h.Method)
|
b.uint16(h.Method)
|
||||||
b.uint16(h.ModifiedTime)
|
b.uint16(h.ModifiedTime)
|
||||||
b.uint16(h.ModifiedDate)
|
b.uint16(h.ModifiedDate)
|
||||||
b.uint32(h.CRC32)
|
b.uint32(0) // since we are writing a data descriptor crc32,
|
||||||
b.uint32(h.CompressedSize)
|
b.uint32(0) // compressed size,
|
||||||
b.uint32(h.UncompressedSize)
|
b.uint32(0) // and uncompressed size should be zero
|
||||||
b.uint16(uint16(len(h.Name)))
|
b.uint16(uint16(len(h.Name)))
|
||||||
b.uint16(uint16(len(h.Extra)))
|
b.uint16(uint16(len(h.Extra)))
|
||||||
if _, err := w.Write(buf[:]); err != nil {
|
if _, err := w.Write(buf[:]); err != nil {
|
||||||
@ -218,17 +278,40 @@ func (w *fileWriter) close() error {
|
|||||||
// update FileHeader
|
// update FileHeader
|
||||||
fh := w.header.FileHeader
|
fh := w.header.FileHeader
|
||||||
fh.CRC32 = w.crc32.Sum32()
|
fh.CRC32 = w.crc32.Sum32()
|
||||||
fh.CompressedSize = uint32(w.compCount.count)
|
fh.CompressedSize64 = uint64(w.compCount.count)
|
||||||
fh.UncompressedSize = uint32(w.rawCount.count)
|
fh.UncompressedSize64 = uint64(w.rawCount.count)
|
||||||
|
|
||||||
// write data descriptor
|
if fh.isZip64() {
|
||||||
var buf [dataDescriptorLen]byte
|
fh.CompressedSize = uint32max
|
||||||
b := writeBuf(buf[:])
|
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(dataDescriptorSignature) // de-facto standard, required by OS X
|
||||||
b.uint32(fh.CRC32)
|
b.uint32(fh.CRC32)
|
||||||
b.uint32(fh.CompressedSize)
|
if fh.isZip64() {
|
||||||
b.uint32(fh.UncompressedSize)
|
b.uint64(fh.CompressedSize64)
|
||||||
_, err := w.zipw.Write(buf[:])
|
b.uint64(fh.UncompressedSize64)
|
||||||
|
} else {
|
||||||
|
b.uint32(fh.CompressedSize)
|
||||||
|
b.uint32(fh.UncompressedSize)
|
||||||
|
}
|
||||||
|
_, err := w.zipw.Write(buf)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,3 +345,8 @@ func (b *writeBuf) uint32(v uint32) {
|
|||||||
binary.LittleEndian.PutUint32(*b, v)
|
binary.LittleEndian.PutUint32(*b, v)
|
||||||
*b = (*b)[4:]
|
*b = (*b)[4:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *writeBuf) uint64(v uint64) {
|
||||||
|
binary.LittleEndian.PutUint64(*b, v)
|
||||||
|
*b = (*b)[8:]
|
||||||
|
}
|
||||||
|
@ -9,7 +9,8 @@ package zip
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -58,6 +59,33 @@ func TestModTime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
func TestFileHeaderRoundTrip(t *testing.T) {
|
||||||
fh := &FileHeader{
|
fh := &FileHeader{
|
||||||
Name: "foo.txt",
|
Name: "foo.txt",
|
||||||
@ -65,17 +93,83 @@ func TestFileHeaderRoundTrip(t *testing.T) {
|
|||||||
ModifiedTime: 1234,
|
ModifiedTime: 1234,
|
||||||
ModifiedDate: 5678,
|
ModifiedDate: 5678,
|
||||||
}
|
}
|
||||||
fi := fh.FileInfo()
|
testHeaderRoundTrip(fh, fh.UncompressedSize, uint64(fh.UncompressedSize), t)
|
||||||
fh2, err := FileInfoHeader(fi)
|
}
|
||||||
|
|
||||||
// Ignore these fields:
|
func TestFileHeaderRoundTrip64(t *testing.T) {
|
||||||
fh2.CreatorVersion = 0
|
fh := &FileHeader{
|
||||||
fh2.ExternalAttrs = 0
|
Name: "foo.txt",
|
||||||
|
UncompressedSize64: 9876543210,
|
||||||
if !reflect.DeepEqual(fh, fh2) {
|
ModifiedTime: 1234,
|
||||||
t.Errorf("mismatch\n input=%#v\noutput=%#v\nerr=%v", fh, fh2, err)
|
ModifiedDate: 5678,
|
||||||
}
|
}
|
||||||
if sysfh, ok := fi.Sys().(*FileHeader); !ok && sysfh != fh {
|
testHeaderRoundTrip(fh, uint32max, fh.UncompressedSize64, t)
|
||||||
t.Errorf("Sys didn't return original *FileHeader")
|
}
|
||||||
|
|
||||||
|
func TestZip64(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Logf("slow test; skipping")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write 2^32 bytes plus "END\n" to a zip file
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
w := NewWriter(buf)
|
||||||
|
f, err := w.Create("huge.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
chunk := make([]byte, 1024)
|
||||||
|
for i := range chunk {
|
||||||
|
chunk[i] = '.'
|
||||||
|
}
|
||||||
|
chunk[len(chunk)-1] = '\n'
|
||||||
|
end := []byte("END\n")
|
||||||
|
for i := 0; i < (1<<32)/1024; i++ {
|
||||||
|
_, err := f.Write(chunk)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("write chunk:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, 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(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("reader:", err)
|
||||||
|
}
|
||||||
|
f0 := r.File[0]
|
||||||
|
rc, err := f0.Open()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("opening:", err)
|
||||||
|
}
|
||||||
|
for i := 0; i < (1<<32)/1024; i++ {
|
||||||
|
_, err := io.ReadFull(rc, chunk)
|
||||||
|
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 got, want := f0.UncompressedSize, uint32(uint32max); got != want {
|
||||||
|
t.Errorf("UncompressedSize %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := f0.UncompressedSize64, (1<<32)+uint64(len(end)); got != want {
|
||||||
|
t.Errorf("UncompressedSize64 %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user