Dan Willemsen | 25a4e07 | 2016-08-05 16:34:03 -0700 | [diff] [blame] | 1 | // Copyright 2010 The Go Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | /* |
| 6 | Package zip provides support for reading and writing ZIP archives. |
| 7 | |
| 8 | See: https://www.pkware.com/documents/casestudies/APPNOTE.TXT |
| 9 | |
| 10 | This package does not support disk spanning. |
| 11 | |
| 12 | A note about ZIP64: |
| 13 | |
| 14 | To be backwards compatible the FileHeader has both 32 and 64 bit Size |
| 15 | fields. The 64 bit fields will always contain the correct value and |
| 16 | for normal archives both fields will be the same. For files requiring |
| 17 | the ZIP64 format the 32 bit fields will be 0xffffffff and the 64 bit |
| 18 | fields must be used instead. |
| 19 | */ |
| 20 | package zip |
| 21 | |
| 22 | import ( |
| 23 | "os" |
| 24 | "path" |
| 25 | "time" |
| 26 | ) |
| 27 | |
| 28 | // Compression methods. |
| 29 | const ( |
| 30 | Store uint16 = 0 |
| 31 | Deflate uint16 = 8 |
| 32 | ) |
| 33 | |
| 34 | const ( |
| 35 | fileHeaderSignature = 0x04034b50 |
| 36 | directoryHeaderSignature = 0x02014b50 |
| 37 | directoryEndSignature = 0x06054b50 |
| 38 | directory64LocSignature = 0x07064b50 |
| 39 | directory64EndSignature = 0x06064b50 |
| 40 | dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder |
| 41 | fileHeaderLen = 30 // + filename + extra |
| 42 | directoryHeaderLen = 46 // + filename + extra + comment |
| 43 | directoryEndLen = 22 // + comment |
| 44 | dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size |
| 45 | dataDescriptor64Len = 24 // descriptor with 8 byte sizes |
| 46 | directory64LocLen = 20 // |
| 47 | directory64EndLen = 56 // + extra |
| 48 | |
| 49 | // Constants for the first byte in CreatorVersion |
| 50 | creatorFAT = 0 |
| 51 | creatorUnix = 3 |
| 52 | creatorNTFS = 11 |
| 53 | creatorVFAT = 14 |
| 54 | creatorMacOSX = 19 |
| 55 | |
| 56 | // version numbers |
| 57 | zipVersion20 = 20 // 2.0 |
| 58 | zipVersion45 = 45 // 4.5 (reads and writes zip64 archives) |
| 59 | |
| 60 | // limits for non zip64 files |
| 61 | uint16max = (1 << 16) - 1 |
| 62 | uint32max = (1 << 32) - 1 |
| 63 | |
| 64 | // extra header id's |
| 65 | zip64ExtraId = 0x0001 // zip64 Extended Information Extra Field |
| 66 | ) |
| 67 | |
| 68 | // FileHeader describes a file within a zip file. |
| 69 | // See the zip spec for details. |
| 70 | type FileHeader struct { |
| 71 | // Name is the name of the file. |
| 72 | // It must be a relative path: it must not start with a drive |
| 73 | // letter (e.g. C:) or leading slash, and only forward slashes |
| 74 | // are allowed. |
| 75 | Name string |
| 76 | |
| 77 | CreatorVersion uint16 |
| 78 | ReaderVersion uint16 |
| 79 | Flags uint16 |
| 80 | Method uint16 |
| 81 | ModifiedTime uint16 // MS-DOS time |
| 82 | ModifiedDate uint16 // MS-DOS date |
| 83 | CRC32 uint32 |
| 84 | CompressedSize uint32 // Deprecated: Use CompressedSize64 instead. |
| 85 | UncompressedSize uint32 // Deprecated: Use UncompressedSize64 instead. |
| 86 | CompressedSize64 uint64 |
| 87 | UncompressedSize64 uint64 |
| 88 | Extra []byte |
| 89 | ExternalAttrs uint32 // Meaning depends on CreatorVersion |
| 90 | Comment string |
| 91 | } |
| 92 | |
| 93 | // FileInfo returns an os.FileInfo for the FileHeader. |
| 94 | func (h *FileHeader) FileInfo() os.FileInfo { |
| 95 | return headerFileInfo{h} |
| 96 | } |
| 97 | |
| 98 | // headerFileInfo implements os.FileInfo. |
| 99 | type headerFileInfo struct { |
| 100 | fh *FileHeader |
| 101 | } |
| 102 | |
| 103 | func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) } |
| 104 | func (fi headerFileInfo) Size() int64 { |
| 105 | if fi.fh.UncompressedSize64 > 0 { |
| 106 | return int64(fi.fh.UncompressedSize64) |
| 107 | } |
| 108 | return int64(fi.fh.UncompressedSize) |
| 109 | } |
| 110 | func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() } |
| 111 | func (fi headerFileInfo) ModTime() time.Time { return fi.fh.ModTime() } |
| 112 | func (fi headerFileInfo) Mode() os.FileMode { return fi.fh.Mode() } |
| 113 | func (fi headerFileInfo) Sys() interface{} { return fi.fh } |
| 114 | |
| 115 | // FileInfoHeader creates a partially-populated FileHeader from an |
| 116 | // os.FileInfo. |
| 117 | // Because os.FileInfo's Name method returns only the base name of |
| 118 | // the file it describes, it may be necessary to modify the Name field |
| 119 | // of the returned header to provide the full path name of the file. |
| 120 | func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) { |
| 121 | size := fi.Size() |
| 122 | fh := &FileHeader{ |
| 123 | Name: fi.Name(), |
| 124 | UncompressedSize64: uint64(size), |
| 125 | } |
| 126 | fh.SetModTime(fi.ModTime()) |
| 127 | fh.SetMode(fi.Mode()) |
| 128 | if fh.UncompressedSize64 > uint32max { |
| 129 | fh.UncompressedSize = uint32max |
| 130 | } else { |
| 131 | fh.UncompressedSize = uint32(fh.UncompressedSize64) |
| 132 | } |
| 133 | return fh, nil |
| 134 | } |
| 135 | |
| 136 | type directoryEnd struct { |
| 137 | diskNbr uint32 // unused |
| 138 | dirDiskNbr uint32 // unused |
| 139 | dirRecordsThisDisk uint64 // unused |
| 140 | directoryRecords uint64 |
| 141 | directorySize uint64 |
| 142 | directoryOffset uint64 // relative to file |
| 143 | commentLen uint16 |
| 144 | comment string |
| 145 | } |
| 146 | |
| 147 | // msDosTimeToTime converts an MS-DOS date and time into a time.Time. |
| 148 | // The resolution is 2s. |
| 149 | // See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx |
| 150 | func msDosTimeToTime(dosDate, dosTime uint16) time.Time { |
| 151 | return time.Date( |
| 152 | // date bits 0-4: day of month; 5-8: month; 9-15: years since 1980 |
| 153 | int(dosDate>>9+1980), |
| 154 | time.Month(dosDate>>5&0xf), |
| 155 | int(dosDate&0x1f), |
| 156 | |
| 157 | // time bits 0-4: second/2; 5-10: minute; 11-15: hour |
| 158 | int(dosTime>>11), |
| 159 | int(dosTime>>5&0x3f), |
| 160 | int(dosTime&0x1f*2), |
| 161 | 0, // nanoseconds |
| 162 | |
| 163 | time.UTC, |
| 164 | ) |
| 165 | } |
| 166 | |
| 167 | // timeToMsDosTime converts a time.Time to an MS-DOS date and time. |
| 168 | // The resolution is 2s. |
| 169 | // See: http://msdn.microsoft.com/en-us/library/ms724274(v=VS.85).aspx |
| 170 | func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) { |
| 171 | t = t.In(time.UTC) |
| 172 | fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9) |
| 173 | fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11) |
| 174 | return |
| 175 | } |
| 176 | |
| 177 | // ModTime returns the modification time in UTC. |
| 178 | // The resolution is 2s. |
| 179 | func (h *FileHeader) ModTime() time.Time { |
| 180 | return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime) |
| 181 | } |
| 182 | |
| 183 | // SetModTime sets the ModifiedTime and ModifiedDate fields to the given time in UTC. |
| 184 | // The resolution is 2s. |
| 185 | func (h *FileHeader) SetModTime(t time.Time) { |
| 186 | h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t) |
| 187 | } |
| 188 | |
| 189 | const ( |
| 190 | // Unix constants. The specification doesn't mention them, |
| 191 | // but these seem to be the values agreed on by tools. |
| 192 | s_IFMT = 0xf000 |
| 193 | s_IFSOCK = 0xc000 |
| 194 | s_IFLNK = 0xa000 |
| 195 | s_IFREG = 0x8000 |
| 196 | s_IFBLK = 0x6000 |
| 197 | s_IFDIR = 0x4000 |
| 198 | s_IFCHR = 0x2000 |
| 199 | s_IFIFO = 0x1000 |
| 200 | s_ISUID = 0x800 |
| 201 | s_ISGID = 0x400 |
| 202 | s_ISVTX = 0x200 |
| 203 | |
| 204 | msdosDir = 0x10 |
| 205 | msdosReadOnly = 0x01 |
| 206 | ) |
| 207 | |
| 208 | // Mode returns the permission and mode bits for the FileHeader. |
| 209 | func (h *FileHeader) Mode() (mode os.FileMode) { |
| 210 | switch h.CreatorVersion >> 8 { |
| 211 | case creatorUnix, creatorMacOSX: |
| 212 | mode = unixModeToFileMode(h.ExternalAttrs >> 16) |
| 213 | case creatorNTFS, creatorVFAT, creatorFAT: |
| 214 | mode = msdosModeToFileMode(h.ExternalAttrs) |
| 215 | } |
| 216 | if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' { |
| 217 | mode |= os.ModeDir |
| 218 | } |
| 219 | return mode |
| 220 | } |
| 221 | |
| 222 | // SetMode changes the permission and mode bits for the FileHeader. |
| 223 | func (h *FileHeader) SetMode(mode os.FileMode) { |
| 224 | h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8 |
| 225 | h.ExternalAttrs = fileModeToUnixMode(mode) << 16 |
| 226 | |
| 227 | // set MSDOS attributes too, as the original zip does. |
| 228 | if mode&os.ModeDir != 0 { |
| 229 | h.ExternalAttrs |= msdosDir |
| 230 | } |
| 231 | if mode&0200 == 0 { |
| 232 | h.ExternalAttrs |= msdosReadOnly |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | // isZip64 reports whether the file size exceeds the 32 bit limit |
| 237 | func (fh *FileHeader) isZip64() bool { |
| 238 | return fh.CompressedSize64 >= uint32max || fh.UncompressedSize64 >= uint32max |
| 239 | } |
| 240 | |
| 241 | func msdosModeToFileMode(m uint32) (mode os.FileMode) { |
| 242 | if m&msdosDir != 0 { |
| 243 | mode = os.ModeDir | 0777 |
| 244 | } else { |
| 245 | mode = 0666 |
| 246 | } |
| 247 | if m&msdosReadOnly != 0 { |
| 248 | mode &^= 0222 |
| 249 | } |
| 250 | return mode |
| 251 | } |
| 252 | |
| 253 | func fileModeToUnixMode(mode os.FileMode) uint32 { |
| 254 | var m uint32 |
| 255 | switch mode & os.ModeType { |
| 256 | default: |
| 257 | m = s_IFREG |
| 258 | case os.ModeDir: |
| 259 | m = s_IFDIR |
| 260 | case os.ModeSymlink: |
| 261 | m = s_IFLNK |
| 262 | case os.ModeNamedPipe: |
| 263 | m = s_IFIFO |
| 264 | case os.ModeSocket: |
| 265 | m = s_IFSOCK |
| 266 | case os.ModeDevice: |
| 267 | if mode&os.ModeCharDevice != 0 { |
| 268 | m = s_IFCHR |
| 269 | } else { |
| 270 | m = s_IFBLK |
| 271 | } |
| 272 | } |
| 273 | if mode&os.ModeSetuid != 0 { |
| 274 | m |= s_ISUID |
| 275 | } |
| 276 | if mode&os.ModeSetgid != 0 { |
| 277 | m |= s_ISGID |
| 278 | } |
| 279 | if mode&os.ModeSticky != 0 { |
| 280 | m |= s_ISVTX |
| 281 | } |
| 282 | return m | uint32(mode&0777) |
| 283 | } |
| 284 | |
| 285 | func unixModeToFileMode(m uint32) os.FileMode { |
| 286 | mode := os.FileMode(m & 0777) |
| 287 | switch m & s_IFMT { |
| 288 | case s_IFBLK: |
| 289 | mode |= os.ModeDevice |
| 290 | case s_IFCHR: |
| 291 | mode |= os.ModeDevice | os.ModeCharDevice |
| 292 | case s_IFDIR: |
| 293 | mode |= os.ModeDir |
| 294 | case s_IFIFO: |
| 295 | mode |= os.ModeNamedPipe |
| 296 | case s_IFLNK: |
| 297 | mode |= os.ModeSymlink |
| 298 | case s_IFREG: |
| 299 | // nothing to do |
| 300 | case s_IFSOCK: |
| 301 | mode |= os.ModeSocket |
| 302 | } |
| 303 | if m&s_ISGID != 0 { |
| 304 | mode |= os.ModeSetgid |
| 305 | } |
| 306 | if m&s_ISUID != 0 { |
| 307 | mode |= os.ModeSetuid |
| 308 | } |
| 309 | if m&s_ISVTX != 0 { |
| 310 | mode |= os.ModeSticky |
| 311 | } |
| 312 | return mode |
| 313 | } |