github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/archive/zip/struct.go (about) 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 the [ZIP specification] for details. 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 [ZIP specification]: https://support.pkware.com/pkzip/appnote 21 */ 22 package zip 23 24 import ( 25 "io/fs" 26 "path" 27 "time" 28 ) 29 30 // Compression methods. 31 const ( 32 Store uint16 = 0 // no compression 33 Deflate uint16 = 8 // DEFLATE compressed 34 ) 35 36 const ( 37 fileHeaderSignature = 0x04034b50 38 directoryHeaderSignature = 0x02014b50 39 directoryEndSignature = 0x06054b50 40 directory64LocSignature = 0x07064b50 41 directory64EndSignature = 0x06064b50 42 dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder 43 fileHeaderLen = 30 // + filename + extra 44 directoryHeaderLen = 46 // + filename + extra + comment 45 directoryEndLen = 22 // + comment 46 dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size 47 dataDescriptor64Len = 24 // two uint32: signature, crc32 | two uint64: compressed size, size 48 directory64LocLen = 20 // 49 directory64EndLen = 56 // + extra 50 51 // Constants for the first byte in CreatorVersion. 52 creatorFAT = 0 53 creatorUnix = 3 54 creatorNTFS = 11 55 creatorVFAT = 14 56 creatorMacOSX = 19 57 58 // Version numbers. 59 zipVersion20 = 20 // 2.0 60 zipVersion45 = 45 // 4.5 (reads and writes zip64 archives) 61 62 // Limits for non zip64 files. 63 uint16max = (1 << 16) - 1 64 uint32max = (1 << 32) - 1 65 66 // Extra header IDs. 67 // 68 // IDs 0..31 are reserved for official use by PKWARE. 69 // IDs above that range are defined by third-party vendors. 70 // Since ZIP lacked high precision timestamps (nor an official specification 71 // of the timezone used for the date fields), many competing extra fields 72 // have been invented. Pervasive use effectively makes them "official". 73 // 74 // See http://mdfs.net/Docs/Comp/Archiving/Zip/ExtraField 75 zip64ExtraID = 0x0001 // Zip64 extended information 76 ntfsExtraID = 0x000a // NTFS 77 unixExtraID = 0x000d // UNIX 78 extTimeExtraID = 0x5455 // Extended timestamp 79 infoZipUnixExtraID = 0x5855 // Info-ZIP Unix extension 80 ) 81 82 // FileHeader describes a file within a ZIP file. 83 // See the [ZIP specification] for details. 84 // 85 // [ZIP specification]: https://support.pkware.com/pkzip/appnote 86 type FileHeader struct { 87 // Name is the name of the file. 88 // 89 // It must be a relative path, not start with a drive letter (such as "C:"), 90 // and must use forward slashes instead of back slashes. A trailing slash 91 // indicates that this file is a directory and should have no data. 92 Name string 93 94 // Comment is any arbitrary user-defined string shorter than 64KiB. 95 Comment string 96 97 // NonUTF8 indicates that Name and Comment are not encoded in UTF-8. 98 // 99 // By specification, the only other encoding permitted should be CP-437, 100 // but historically many ZIP readers interpret Name and Comment as whatever 101 // the system's local character encoding happens to be. 102 // 103 // This flag should only be set if the user intends to encode a non-portable 104 // ZIP file for a specific localized region. Otherwise, the Writer 105 // automatically sets the ZIP format's UTF-8 flag for valid UTF-8 strings. 106 NonUTF8 bool 107 108 CreatorVersion uint16 109 ReaderVersion uint16 110 Flags uint16 111 112 // Method is the compression method. If zero, Store is used. 113 Method uint16 114 115 // Modified is the modified time of the file. 116 // 117 // When reading, an extended timestamp is preferred over the legacy MS-DOS 118 // date field, and the offset between the times is used as the timezone. 119 // If only the MS-DOS date is present, the timezone is assumed to be UTC. 120 // 121 // When writing, an extended timestamp (which is timezone-agnostic) is 122 // always emitted. The legacy MS-DOS date field is encoded according to the 123 // location of the Modified time. 124 Modified time.Time 125 126 // ModifiedTime is an MS-DOS-encoded time. 127 // 128 // Deprecated: Use Modified instead. 129 ModifiedTime uint16 130 131 // ModifiedDate is an MS-DOS-encoded date. 132 // 133 // Deprecated: Use Modified instead. 134 ModifiedDate uint16 135 136 // CRC32 is the CRC32 checksum of the file content. 137 CRC32 uint32 138 139 // CompressedSize is the compressed size of the file in bytes. 140 // If either the uncompressed or compressed size of the file 141 // does not fit in 32 bits, CompressedSize is set to ^uint32(0). 142 // 143 // Deprecated: Use CompressedSize64 instead. 144 CompressedSize uint32 145 146 // UncompressedSize is the compressed size of the file in bytes. 147 // If either the uncompressed or compressed size of the file 148 // does not fit in 32 bits, CompressedSize is set to ^uint32(0). 149 // 150 // Deprecated: Use UncompressedSize64 instead. 151 UncompressedSize uint32 152 153 // CompressedSize64 is the compressed size of the file in bytes. 154 CompressedSize64 uint64 155 156 // UncompressedSize64 is the uncompressed size of the file in bytes. 157 UncompressedSize64 uint64 158 159 Extra []byte 160 ExternalAttrs uint32 // Meaning depends on CreatorVersion 161 } 162 163 // FileInfo returns an fs.FileInfo for the [FileHeader]. 164 func (h *FileHeader) FileInfo() fs.FileInfo { 165 return headerFileInfo{h} 166 } 167 168 // headerFileInfo implements [fs.FileInfo]. 169 type headerFileInfo struct { 170 fh *FileHeader 171 } 172 173 func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) } 174 func (fi headerFileInfo) Size() int64 { 175 if fi.fh.UncompressedSize64 > 0 { 176 return int64(fi.fh.UncompressedSize64) 177 } 178 return int64(fi.fh.UncompressedSize) 179 } 180 func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() } 181 func (fi headerFileInfo) ModTime() time.Time { 182 if fi.fh.Modified.IsZero() { 183 return fi.fh.ModTime() 184 } 185 return fi.fh.Modified.UTC() 186 } 187 func (fi headerFileInfo) Mode() fs.FileMode { return fi.fh.Mode() } 188 func (fi headerFileInfo) Type() fs.FileMode { return fi.fh.Mode().Type() } 189 func (fi headerFileInfo) Sys() any { return fi.fh } 190 191 func (fi headerFileInfo) Info() (fs.FileInfo, error) { return fi, nil } 192 193 func (fi headerFileInfo) String() string { 194 return fs.FormatFileInfo(fi) 195 } 196 197 // FileInfoHeader creates a partially-populated [FileHeader] from an 198 // fs.FileInfo. 199 // Because fs.FileInfo's Name method returns only the base name of 200 // the file it describes, it may be necessary to modify the Name field 201 // of the returned header to provide the full path name of the file. 202 // If compression is desired, callers should set the FileHeader.Method 203 // field; it is unset by default. 204 func FileInfoHeader(fi fs.FileInfo) (*FileHeader, error) { 205 size := fi.Size() 206 fh := &FileHeader{ 207 Name: fi.Name(), 208 UncompressedSize64: uint64(size), 209 } 210 fh.SetModTime(fi.ModTime()) 211 fh.SetMode(fi.Mode()) 212 if fh.UncompressedSize64 > uint32max { 213 fh.UncompressedSize = uint32max 214 } else { 215 fh.UncompressedSize = uint32(fh.UncompressedSize64) 216 } 217 return fh, nil 218 } 219 220 type directoryEnd struct { 221 diskNbr uint32 // unused 222 dirDiskNbr uint32 // unused 223 dirRecordsThisDisk uint64 // unused 224 directoryRecords uint64 225 directorySize uint64 226 directoryOffset uint64 // relative to file 227 commentLen uint16 228 comment string 229 } 230 231 // timeZone returns a *time.Location based on the provided offset. 232 // If the offset is non-sensible, then this uses an offset of zero. 233 func timeZone(offset time.Duration) *time.Location { 234 const ( 235 minOffset = -12 * time.Hour // E.g., Baker island at -12:00 236 maxOffset = +14 * time.Hour // E.g., Line island at +14:00 237 offsetAlias = 15 * time.Minute // E.g., Nepal at +5:45 238 ) 239 offset = offset.Round(offsetAlias) 240 if offset < minOffset || maxOffset < offset { 241 offset = 0 242 } 243 return time.FixedZone("", int(offset/time.Second)) 244 } 245 246 // msDosTimeToTime converts an MS-DOS date and time into a time.Time. 247 // The resolution is 2s. 248 // See: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-dosdatetimetofiletime 249 func msDosTimeToTime(dosDate, dosTime uint16) time.Time { 250 return time.Date( 251 // date bits 0-4: day of month; 5-8: month; 9-15: years since 1980 252 int(dosDate>>9+1980), 253 time.Month(dosDate>>5&0xf), 254 int(dosDate&0x1f), 255 256 // time bits 0-4: second/2; 5-10: minute; 11-15: hour 257 int(dosTime>>11), 258 int(dosTime>>5&0x3f), 259 int(dosTime&0x1f*2), 260 0, // nanoseconds 261 262 time.UTC, 263 ) 264 } 265 266 // timeToMsDosTime converts a time.Time to an MS-DOS date and time. 267 // The resolution is 2s. 268 // See: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-filetimetodosdatetime 269 func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) { 270 fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9) 271 fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11) 272 return 273 } 274 275 // ModTime returns the modification time in UTC using the legacy 276 // [ModifiedDate] and [ModifiedTime] fields. 277 // 278 // Deprecated: Use [Modified] instead. 279 func (h *FileHeader) ModTime() time.Time { 280 return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime) 281 } 282 283 // SetModTime sets the [Modified], [ModifiedTime], and [ModifiedDate] fields 284 // to the given time in UTC. 285 // 286 // Deprecated: Use [Modified] instead. 287 func (h *FileHeader) SetModTime(t time.Time) { 288 t = t.UTC() // Convert to UTC for compatibility 289 h.Modified = t 290 h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t) 291 } 292 293 const ( 294 // Unix constants. The specification doesn't mention them, 295 // but these seem to be the values agreed on by tools. 296 s_IFMT = 0xf000 297 s_IFSOCK = 0xc000 298 s_IFLNK = 0xa000 299 s_IFREG = 0x8000 300 s_IFBLK = 0x6000 301 s_IFDIR = 0x4000 302 s_IFCHR = 0x2000 303 s_IFIFO = 0x1000 304 s_ISUID = 0x800 305 s_ISGID = 0x400 306 s_ISVTX = 0x200 307 308 msdosDir = 0x10 309 msdosReadOnly = 0x01 310 ) 311 312 // Mode returns the permission and mode bits for the [FileHeader]. 313 func (h *FileHeader) Mode() (mode fs.FileMode) { 314 switch h.CreatorVersion >> 8 { 315 case creatorUnix, creatorMacOSX: 316 mode = unixModeToFileMode(h.ExternalAttrs >> 16) 317 case creatorNTFS, creatorVFAT, creatorFAT: 318 mode = msdosModeToFileMode(h.ExternalAttrs) 319 } 320 if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' { 321 mode |= fs.ModeDir 322 } 323 return mode 324 } 325 326 // SetMode changes the permission and mode bits for the [FileHeader]. 327 func (h *FileHeader) SetMode(mode fs.FileMode) { 328 h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8 329 h.ExternalAttrs = fileModeToUnixMode(mode) << 16 330 331 // set MSDOS attributes too, as the original zip does. 332 if mode&fs.ModeDir != 0 { 333 h.ExternalAttrs |= msdosDir 334 } 335 if mode&0200 == 0 { 336 h.ExternalAttrs |= msdosReadOnly 337 } 338 } 339 340 // isZip64 reports whether the file size exceeds the 32 bit limit 341 func (h *FileHeader) isZip64() bool { 342 return h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max 343 } 344 345 func (h *FileHeader) hasDataDescriptor() bool { 346 return h.Flags&0x8 != 0 347 } 348 349 func msdosModeToFileMode(m uint32) (mode fs.FileMode) { 350 if m&msdosDir != 0 { 351 mode = fs.ModeDir | 0777 352 } else { 353 mode = 0666 354 } 355 if m&msdosReadOnly != 0 { 356 mode &^= 0222 357 } 358 return mode 359 } 360 361 func fileModeToUnixMode(mode fs.FileMode) uint32 { 362 var m uint32 363 switch mode & fs.ModeType { 364 default: 365 m = s_IFREG 366 case fs.ModeDir: 367 m = s_IFDIR 368 case fs.ModeSymlink: 369 m = s_IFLNK 370 case fs.ModeNamedPipe: 371 m = s_IFIFO 372 case fs.ModeSocket: 373 m = s_IFSOCK 374 case fs.ModeDevice: 375 m = s_IFBLK 376 case fs.ModeDevice | fs.ModeCharDevice: 377 m = s_IFCHR 378 } 379 if mode&fs.ModeSetuid != 0 { 380 m |= s_ISUID 381 } 382 if mode&fs.ModeSetgid != 0 { 383 m |= s_ISGID 384 } 385 if mode&fs.ModeSticky != 0 { 386 m |= s_ISVTX 387 } 388 return m | uint32(mode&0777) 389 } 390 391 func unixModeToFileMode(m uint32) fs.FileMode { 392 mode := fs.FileMode(m & 0777) 393 switch m & s_IFMT { 394 case s_IFBLK: 395 mode |= fs.ModeDevice 396 case s_IFCHR: 397 mode |= fs.ModeDevice | fs.ModeCharDevice 398 case s_IFDIR: 399 mode |= fs.ModeDir 400 case s_IFIFO: 401 mode |= fs.ModeNamedPipe 402 case s_IFLNK: 403 mode |= fs.ModeSymlink 404 case s_IFREG: 405 // nothing to do 406 case s_IFSOCK: 407 mode |= fs.ModeSocket 408 } 409 if m&s_ISGID != 0 { 410 mode |= fs.ModeSetgid 411 } 412 if m&s_ISUID != 0 { 413 mode |= fs.ModeSetuid 414 } 415 if m&s_ISVTX != 0 { 416 mode |= fs.ModeSticky 417 } 418 return mode 419 }