2011-07-10 11:30:16 +10:00
|
|
|
// 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: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
|
|
|
|
|
|
2012-08-22 11:05:24 +10:00
|
|
|
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.
|
2011-07-10 11:30:16 +10:00
|
|
|
*/
|
2010-09-30 11:59:46 +10:00
|
|
|
package zip
|
|
|
|
|
|
2011-11-30 12:01:46 -05:00
|
|
|
import (
|
2011-12-12 15:22:55 -05:00
|
|
|
"os"
|
archive/tar,zip: implement the os.FileInfo interface correctly.
This is potentially an API-breaking change, but it is an important bug fix.
The CL https://golang.org/cl/7305072/ added stuff to make
the tar file look more like a file system internally, including providing an
implementation of os.FileInfo for the file headers within the archive.
But the code is incorrect because FileInfo.Name is supposed to return
the base name only; this implementation returns the full path. A round
trip test added in the same shows this in action, as the slashes are
preserved as we create a header using the local implementation of
FileInfo.
The CL here changes the behavior of the tar (and zip) FileInfo to honor
the Go spec for that interface. It also clarifies that the FileInfoHeader
function, which takes a FileInfo as an argument, will therefore create
a header with only the base name of the file recorded, and that
subsequent adjustment may be necessary.
There may be code out there that depends on the broken behavior.
We can call out the risk in the release notes.
Fixes #6180.
R=golang-dev, dsymonds, adg, bradfitz
CC=golang-dev
https://golang.org/cl/13118043
2013-08-21 08:29:41 +10:00
|
|
|
"path"
|
2011-11-30 12:01:46 -05:00
|
|
|
"time"
|
|
|
|
|
)
|
2011-07-10 11:30:16 +10:00
|
|
|
|
|
|
|
|
// Compression methods.
|
|
|
|
|
const (
|
|
|
|
|
Store uint16 = 0
|
|
|
|
|
Deflate uint16 = 8
|
|
|
|
|
)
|
|
|
|
|
|
2010-09-30 11:59:46 +10:00
|
|
|
const (
|
|
|
|
|
fileHeaderSignature = 0x04034b50
|
|
|
|
|
directoryHeaderSignature = 0x02014b50
|
|
|
|
|
directoryEndSignature = 0x06054b50
|
2012-08-22 11:05:24 +10:00
|
|
|
directory64LocSignature = 0x07064b50
|
|
|
|
|
directory64EndSignature = 0x06064b50
|
2012-03-09 14:12:02 -08:00
|
|
|
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
|
2012-08-22 11:05:24 +10:00
|
|
|
dataDescriptor64Len = 24 // descriptor with 8 byte sizes
|
|
|
|
|
directory64LocLen = 20 //
|
|
|
|
|
directory64EndLen = 56 // + extra
|
2011-09-25 20:48:03 -03:00
|
|
|
|
|
|
|
|
// Constants for the first byte in CreatorVersion
|
2011-12-12 15:22:55 -05:00
|
|
|
creatorFAT = 0
|
|
|
|
|
creatorUnix = 3
|
|
|
|
|
creatorNTFS = 11
|
|
|
|
|
creatorVFAT = 14
|
|
|
|
|
creatorMacOSX = 19
|
2012-08-22 11:05:24 +10:00
|
|
|
|
|
|
|
|
// 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
|
2010-09-30 11:59:46 +10:00
|
|
|
)
|
|
|
|
|
|
2013-04-17 13:25:12 -07:00
|
|
|
// FileHeader describes a file within a zip file.
|
|
|
|
|
// See the zip spec for details.
|
2010-09-30 11:59:46 +10:00
|
|
|
type FileHeader struct {
|
2013-04-17 13:25:12 -07:00
|
|
|
// Name is the name of the file.
|
|
|
|
|
// It 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.
|
|
|
|
|
Name string
|
|
|
|
|
|
2012-08-22 11:05:24 +10:00
|
|
|
CreatorVersion uint16
|
|
|
|
|
ReaderVersion uint16
|
|
|
|
|
Flags uint16
|
|
|
|
|
Method uint16
|
|
|
|
|
ModifiedTime uint16 // MS-DOS time
|
|
|
|
|
ModifiedDate uint16 // MS-DOS date
|
|
|
|
|
CRC32 uint32
|
|
|
|
|
CompressedSize uint32 // deprecated; use CompressedSize64
|
|
|
|
|
UncompressedSize uint32 // deprecated; use UncompressedSize64
|
|
|
|
|
CompressedSize64 uint64
|
|
|
|
|
UncompressedSize64 uint64
|
|
|
|
|
Extra []byte
|
|
|
|
|
ExternalAttrs uint32 // Meaning depends on CreatorVersion
|
|
|
|
|
Comment string
|
2010-09-30 11:59:46 +10:00
|
|
|
}
|
|
|
|
|
|
2012-01-26 15:31:09 -08:00
|
|
|
// FileInfo returns an os.FileInfo for the FileHeader.
|
2012-01-30 11:58:49 -08:00
|
|
|
func (h *FileHeader) FileInfo() os.FileInfo {
|
|
|
|
|
return headerFileInfo{h}
|
2012-01-26 15:31:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// headerFileInfo implements os.FileInfo.
|
|
|
|
|
type headerFileInfo struct {
|
|
|
|
|
fh *FileHeader
|
|
|
|
|
}
|
|
|
|
|
|
archive/tar,zip: implement the os.FileInfo interface correctly.
This is potentially an API-breaking change, but it is an important bug fix.
The CL https://golang.org/cl/7305072/ added stuff to make
the tar file look more like a file system internally, including providing an
implementation of os.FileInfo for the file headers within the archive.
But the code is incorrect because FileInfo.Name is supposed to return
the base name only; this implementation returns the full path. A round
trip test added in the same shows this in action, as the slashes are
preserved as we create a header using the local implementation of
FileInfo.
The CL here changes the behavior of the tar (and zip) FileInfo to honor
the Go spec for that interface. It also clarifies that the FileInfoHeader
function, which takes a FileInfo as an argument, will therefore create
a header with only the base name of the file recorded, and that
subsequent adjustment may be necessary.
There may be code out there that depends on the broken behavior.
We can call out the risk in the release notes.
Fixes #6180.
R=golang-dev, dsymonds, adg, bradfitz
CC=golang-dev
https://golang.org/cl/13118043
2013-08-21 08:29:41 +10:00
|
|
|
func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) }
|
2012-08-22 11:05:24 +10:00
|
|
|
func (fi headerFileInfo) Size() int64 {
|
|
|
|
|
if fi.fh.UncompressedSize64 > 0 {
|
|
|
|
|
return int64(fi.fh.UncompressedSize64)
|
|
|
|
|
}
|
|
|
|
|
return int64(fi.fh.UncompressedSize)
|
|
|
|
|
}
|
2012-01-26 15:31:09 -08:00
|
|
|
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
|
|
|
|
func (fi headerFileInfo) ModTime() time.Time { return fi.fh.ModTime() }
|
|
|
|
|
func (fi headerFileInfo) Mode() os.FileMode { return fi.fh.Mode() }
|
2012-02-03 00:16:18 -02:00
|
|
|
func (fi headerFileInfo) Sys() interface{} { return fi.fh }
|
2012-01-26 15:31:09 -08:00
|
|
|
|
|
|
|
|
// FileInfoHeader creates a partially-populated FileHeader from an
|
|
|
|
|
// os.FileInfo.
|
archive/tar,zip: implement the os.FileInfo interface correctly.
This is potentially an API-breaking change, but it is an important bug fix.
The CL https://golang.org/cl/7305072/ added stuff to make
the tar file look more like a file system internally, including providing an
implementation of os.FileInfo for the file headers within the archive.
But the code is incorrect because FileInfo.Name is supposed to return
the base name only; this implementation returns the full path. A round
trip test added in the same shows this in action, as the slashes are
preserved as we create a header using the local implementation of
FileInfo.
The CL here changes the behavior of the tar (and zip) FileInfo to honor
the Go spec for that interface. It also clarifies that the FileInfoHeader
function, which takes a FileInfo as an argument, will therefore create
a header with only the base name of the file recorded, and that
subsequent adjustment may be necessary.
There may be code out there that depends on the broken behavior.
We can call out the risk in the release notes.
Fixes #6180.
R=golang-dev, dsymonds, adg, bradfitz
CC=golang-dev
https://golang.org/cl/13118043
2013-08-21 08:29:41 +10:00
|
|
|
// 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.
|
2012-01-26 15:31:09 -08:00
|
|
|
func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) {
|
|
|
|
|
size := fi.Size()
|
|
|
|
|
fh := &FileHeader{
|
2012-08-22 11:05:24 +10:00
|
|
|
Name: fi.Name(),
|
|
|
|
|
UncompressedSize64: uint64(size),
|
2012-01-26 15:31:09 -08:00
|
|
|
}
|
|
|
|
|
fh.SetModTime(fi.ModTime())
|
|
|
|
|
fh.SetMode(fi.Mode())
|
2012-08-22 11:05:24 +10:00
|
|
|
if fh.UncompressedSize64 > uint32max {
|
|
|
|
|
fh.UncompressedSize = uint32max
|
|
|
|
|
} else {
|
|
|
|
|
fh.UncompressedSize = uint32(fh.UncompressedSize64)
|
|
|
|
|
}
|
2012-01-26 15:31:09 -08:00
|
|
|
return fh, nil
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-30 11:59:46 +10:00
|
|
|
type directoryEnd struct {
|
2012-08-22 11:05:24 +10:00
|
|
|
diskNbr uint32 // unused
|
|
|
|
|
dirDiskNbr uint32 // unused
|
|
|
|
|
dirRecordsThisDisk uint64 // unused
|
|
|
|
|
directoryRecords uint64
|
|
|
|
|
directorySize uint64
|
|
|
|
|
directoryOffset uint64 // relative to file
|
2010-09-30 11:59:46 +10:00
|
|
|
commentLen uint16
|
|
|
|
|
comment string
|
|
|
|
|
}
|
2011-07-10 11:30:16 +10:00
|
|
|
|
2011-07-18 20:30:44 -07:00
|
|
|
// msDosTimeToTime converts an MS-DOS date and time into a time.Time.
|
|
|
|
|
// The resolution is 2s.
|
|
|
|
|
// See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx
|
|
|
|
|
func msDosTimeToTime(dosDate, dosTime uint16) time.Time {
|
2011-11-30 12:01:46 -05:00
|
|
|
return time.Date(
|
2011-07-18 20:30:44 -07:00
|
|
|
// date bits 0-4: day of month; 5-8: month; 9-15: years since 1980
|
2011-11-30 12:01:46 -05:00
|
|
|
int(dosDate>>9+1980),
|
|
|
|
|
time.Month(dosDate>>5&0xf),
|
|
|
|
|
int(dosDate&0x1f),
|
2011-07-18 20:30:44 -07:00
|
|
|
|
|
|
|
|
// time bits 0-4: second/2; 5-10: minute; 11-15: hour
|
2011-11-30 12:01:46 -05:00
|
|
|
int(dosTime>>11),
|
|
|
|
|
int(dosTime>>5&0x3f),
|
|
|
|
|
int(dosTime&0x1f*2),
|
|
|
|
|
0, // nanoseconds
|
|
|
|
|
|
|
|
|
|
time.UTC,
|
|
|
|
|
)
|
2011-07-18 20:30:44 -07:00
|
|
|
}
|
|
|
|
|
|
2011-12-19 14:59:41 +11:00
|
|
|
// timeToMsDosTime converts a time.Time to an MS-DOS date and time.
|
|
|
|
|
// The resolution is 2s.
|
|
|
|
|
// See: http://msdn.microsoft.com/en-us/library/ms724274(v=VS.85).aspx
|
|
|
|
|
func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) {
|
|
|
|
|
t = t.In(time.UTC)
|
|
|
|
|
fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9)
|
|
|
|
|
fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-30 12:01:46 -05:00
|
|
|
// ModTime returns the modification time.
|
2011-07-18 20:30:44 -07:00
|
|
|
// The resolution is 2s.
|
2011-11-30 12:01:46 -05:00
|
|
|
func (h *FileHeader) ModTime() time.Time {
|
|
|
|
|
return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
|
2011-07-18 20:30:44 -07:00
|
|
|
}
|
2011-09-25 20:48:03 -03:00
|
|
|
|
2011-12-19 14:59:41 +11:00
|
|
|
// SetModTime sets the ModifiedTime and ModifiedDate fields to the given time.
|
|
|
|
|
// The resolution is 2s.
|
|
|
|
|
func (h *FileHeader) SetModTime(t time.Time) {
|
|
|
|
|
h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t)
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-12 15:22:55 -05:00
|
|
|
const (
|
2012-02-06 11:58:32 -02:00
|
|
|
// 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
|
2011-12-12 15:22:55 -05:00
|
|
|
|
|
|
|
|
msdosDir = 0x10
|
|
|
|
|
msdosReadOnly = 0x01
|
|
|
|
|
)
|
|
|
|
|
|
2011-09-25 20:48:03 -03:00
|
|
|
// Mode returns the permission and mode bits for the FileHeader.
|
2012-01-26 15:31:09 -08:00
|
|
|
func (h *FileHeader) Mode() (mode os.FileMode) {
|
2011-12-12 15:22:55 -05:00
|
|
|
switch h.CreatorVersion >> 8 {
|
|
|
|
|
case creatorUnix, creatorMacOSX:
|
|
|
|
|
mode = unixModeToFileMode(h.ExternalAttrs >> 16)
|
|
|
|
|
case creatorNTFS, creatorVFAT, creatorFAT:
|
|
|
|
|
mode = msdosModeToFileMode(h.ExternalAttrs)
|
2011-09-25 20:48:03 -03:00
|
|
|
}
|
2011-12-12 15:22:55 -05:00
|
|
|
if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' {
|
|
|
|
|
mode |= os.ModeDir
|
|
|
|
|
}
|
2012-01-26 15:31:09 -08:00
|
|
|
return mode
|
2011-09-25 20:48:03 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetMode changes the permission and mode bits for the FileHeader.
|
2011-12-12 15:22:55 -05:00
|
|
|
func (h *FileHeader) SetMode(mode os.FileMode) {
|
2011-09-25 20:48:03 -03:00
|
|
|
h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8
|
2011-12-12 15:22:55 -05:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2012-08-22 11:05:24 +10:00
|
|
|
// isZip64 returns true if the file size exceeds the 32 bit limit
|
|
|
|
|
func (fh *FileHeader) isZip64() bool {
|
|
|
|
|
return fh.CompressedSize64 > uint32max || fh.UncompressedSize64 > uint32max
|
|
|
|
|
}
|
|
|
|
|
|
2011-12-12 15:22:55 -05:00
|
|
|
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
|
2012-02-06 11:58:32 -02:00
|
|
|
switch mode & os.ModeType {
|
|
|
|
|
default:
|
2011-12-12 15:22:55 -05:00
|
|
|
m = s_IFREG
|
2012-02-06 11:58:32 -02:00
|
|
|
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
|
|
|
|
|
}
|
2011-12-12 15:22:55 -05:00
|
|
|
}
|
|
|
|
|
if mode&os.ModeSetuid != 0 {
|
|
|
|
|
m |= s_ISUID
|
|
|
|
|
}
|
|
|
|
|
if mode&os.ModeSetgid != 0 {
|
|
|
|
|
m |= s_ISGID
|
|
|
|
|
}
|
2012-02-06 11:58:32 -02:00
|
|
|
if mode&os.ModeSticky != 0 {
|
|
|
|
|
m |= s_ISVTX
|
|
|
|
|
}
|
2011-12-12 15:22:55 -05:00
|
|
|
return m | uint32(mode&0777)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func unixModeToFileMode(m uint32) os.FileMode {
|
2012-02-06 11:58:32 -02:00
|
|
|
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:
|
2011-12-12 15:22:55 -05:00
|
|
|
mode |= os.ModeDir
|
2012-02-06 11:58:32 -02:00
|
|
|
case s_IFIFO:
|
|
|
|
|
mode |= os.ModeNamedPipe
|
|
|
|
|
case s_IFLNK:
|
|
|
|
|
mode |= os.ModeSymlink
|
|
|
|
|
case s_IFREG:
|
|
|
|
|
// nothing to do
|
|
|
|
|
case s_IFSOCK:
|
|
|
|
|
mode |= os.ModeSocket
|
2011-12-12 15:22:55 -05:00
|
|
|
}
|
|
|
|
|
if m&s_ISGID != 0 {
|
|
|
|
|
mode |= os.ModeSetgid
|
|
|
|
|
}
|
|
|
|
|
if m&s_ISUID != 0 {
|
|
|
|
|
mode |= os.ModeSetuid
|
|
|
|
|
}
|
2012-02-06 11:58:32 -02:00
|
|
|
if m&s_ISVTX != 0 {
|
|
|
|
|
mode |= os.ModeSticky
|
|
|
|
|
}
|
|
|
|
|
return mode
|
2011-09-25 20:48:03 -03:00
|
|
|
}
|