| // 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 (e.g. C:), | |
| // and must use forward slashes instead of back slashes. | |
| 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 { return fi.fh.ModTime() } | |
| 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: http://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: http://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 (fh *FileHeader) isZip64() bool { | |
| return fh.CompressedSize64 >= uint32max || fh.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 | |
| } |