1// Copyright 2010 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45/*6Package zip provides support for reading and writing ZIP archives.78See the [ZIP specification] for details.910This package does not support disk spanning.1112A note about ZIP64:1314To be backwards compatible the FileHeader has both 32 and 64 bit Size15fields. The 64 bit fields will always contain the correct value and16for normal archives both fields will be the same. For files requiring17the ZIP64 format the 32 bit fields will be 0xffffffff and the 64 bit18fields must be used instead.1920[ZIP specification]: https://support.pkware.com/pkzip/appnote21*/22package zip2324import (25 "io/fs"26 "path"27 "time"28)2930// Compression methods.31const (32 Store uint16 = 0 // no compression33 Deflate uint16 = 8 // DEFLATE compressed34)3536const (37 fileHeaderSignature = 0x04034b5038 directoryHeaderSignature = 0x02014b5039 directoryEndSignature = 0x06054b5040 directory64LocSignature = 0x07064b5041 directory64EndSignature = 0x06064b5042 dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder43 fileHeaderLen = 30 // + filename + extra44 directoryHeaderLen = 46 // + filename + extra + comment45 directoryEndLen = 22 // + comment46 dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size47 dataDescriptor64Len = 24 // two uint32: signature, crc32 | two uint64: compressed size, size48 directory64LocLen = 20 //49 directory64EndLen = 56 // + extra5051 // Constants for the first byte in CreatorVersion.52 creatorFAT = 053 creatorUnix = 354 creatorNTFS = 1155 creatorVFAT = 1456 creatorMacOSX = 195758 // Version numbers.59 zipVersion20 = 20 // 2.060 zipVersion45 = 45 // 4.5 (reads and writes zip64 archives)6162 // Limits for non zip64 files.63 uint16max = (1 << 16) - 164 uint32max = (1 << 32) - 16566 // 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 specification71 // of the timezone used for the date fields), many competing extra fields72 // have been invented. Pervasive use effectively makes them "official".73 //74 // See http://mdfs.net/Docs/Comp/Archiving/Zip/ExtraField75 zip64ExtraID = 0x0001 // Zip64 extended information76 ntfsExtraID = 0x000a // NTFS77 unixExtraID = 0x000d // UNIX78 extTimeExtraID = 0x5455 // Extended timestamp79 infoZipUnixExtraID = 0x5855 // Info-ZIP Unix extension80)8182// FileHeader describes a file within a ZIP file.83// See the [ZIP specification] for details.84//85// [ZIP specification]: https://support.pkware.com/pkzip/appnote86type 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 slash91 // indicates that this file is a directory and should have no data.92 Name string9394 // Comment is any arbitrary user-defined string shorter than 64KiB.95 Comment string9697 // 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 whatever101 // 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-portable104 // ZIP file for a specific localized region. Otherwise, the Writer105 // automatically sets the ZIP format's UTF-8 flag for valid UTF-8 strings.106 NonUTF8 bool107108 CreatorVersion uint16109 ReaderVersion uint16110 Flags uint16111112 // Method is the compression method. If zero, Store is used.113 Method uint16114115 // Modified is the modified time of the file.116 //117 // When reading, an extended timestamp is preferred over the legacy MS-DOS118 // 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) is122 // always emitted. The legacy MS-DOS date field is encoded according to the123 // location of the Modified time.124 Modified time.Time125126 // ModifiedTime is an MS-DOS-encoded time.127 //128 // Deprecated: Use Modified instead.129 ModifiedTime uint16130131 // ModifiedDate is an MS-DOS-encoded date.132 //133 // Deprecated: Use Modified instead.134 ModifiedDate uint16135136 // CRC32 is the CRC32 checksum of the file content.137 CRC32 uint32138139 // CompressedSize is the compressed size of the file in bytes.140 // If either the uncompressed or compressed size of the file141 // does not fit in 32 bits, CompressedSize is set to ^uint32(0).142 //143 // Deprecated: Use CompressedSize64 instead.144 CompressedSize uint32145146 // UncompressedSize is the uncompressed size of the file in bytes.147 // If either the uncompressed or compressed size of the file148 // does not fit in 32 bits, UncompressedSize is set to ^uint32(0).149 //150 // Deprecated: Use UncompressedSize64 instead.151 UncompressedSize uint32152153 // CompressedSize64 is the compressed size of the file in bytes.154 CompressedSize64 uint64155156 // UncompressedSize64 is the uncompressed size of the file in bytes.157 UncompressedSize64 uint64158159 // Extra are the extensible data fields. The writer automatically includes160 // the appropriate Zip64 field if necessary, and [Writer.Close] appends the161 // Central Directory version of the Zip64 field to Extra.162 Extra []byte163164 ExternalAttrs uint32 // Meaning depends on CreatorVersion165}166167// FileInfo returns an fs.FileInfo for the [FileHeader].168func (h *FileHeader) FileInfo() fs.FileInfo {169 return headerFileInfo{h}170}171172// headerFileInfo implements [fs.FileInfo].173type headerFileInfo struct {174 fh *FileHeader175}176177func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) }178func (fi headerFileInfo) Size() int64 {179 if fi.fh.UncompressedSize64 > 0 {180 return int64(fi.fh.UncompressedSize64)181 }182 return int64(fi.fh.UncompressedSize)183}184func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }185func (fi headerFileInfo) ModTime() time.Time {186 if fi.fh.Modified.IsZero() {187 return fi.fh.ModTime()188 }189 return fi.fh.Modified.UTC()190}191func (fi headerFileInfo) Mode() fs.FileMode { return fi.fh.Mode() }192func (fi headerFileInfo) Type() fs.FileMode { return fi.fh.Mode().Type() }193func (fi headerFileInfo) Sys() any { return fi.fh }194195func (fi headerFileInfo) Info() (fs.FileInfo, error) { return fi, nil }196197func (fi headerFileInfo) String() string {198 return fs.FormatFileInfo(fi)199}200201// FileInfoHeader creates a partially-populated [FileHeader] from an202// fs.FileInfo.203// Because fs.FileInfo's Name method returns only the base name of204// the file it describes, it may be necessary to modify the Name field205// of the returned header to provide the full path name of the file.206// If compression is desired, callers should set the FileHeader.Method207// field; it is unset by default.208func FileInfoHeader(fi fs.FileInfo) (*FileHeader, error) {209 size := fi.Size()210 fh := &FileHeader{211 Name: fi.Name(),212 UncompressedSize64: uint64(size),213 }214 fh.SetModTime(fi.ModTime())215 fh.SetMode(fi.Mode())216 if fh.UncompressedSize64 > uint32max {217 fh.UncompressedSize = uint32max218 } else {219 fh.UncompressedSize = uint32(fh.UncompressedSize64)220 }221 return fh, nil222}223224type directoryEnd struct {225 diskNbr uint32 // unused226 dirDiskNbr uint32 // unused227 dirRecordsThisDisk uint64 // unused228 directoryRecords uint64229 directorySize uint64230 directoryOffset uint64 // relative to file231 commentLen uint16232 comment string233}234235// timeZone returns a *time.Location based on the provided offset.236// If the offset is non-sensible, then this uses an offset of zero.237func timeZone(offset time.Duration) *time.Location {238 const (239 minOffset = -12 * time.Hour // E.g., Baker island at -12:00240 maxOffset = +14 * time.Hour // E.g., Line island at +14:00241 offsetAlias = 15 * time.Minute // E.g., Nepal at +5:45242 )243 offset = offset.Round(offsetAlias)244 if offset < minOffset || maxOffset < offset {245 offset = 0246 }247 return time.FixedZone("", int(offset/time.Second))248}249250// msDosTimeToTime converts an MS-DOS date and time into a time.Time.251// The resolution is 2s.252// See: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-dosdatetimetofiletime253func msDosTimeToTime(dosDate, dosTime uint16) time.Time {254 return time.Date(255 // date bits 0-4: day of month; 5-8: month; 9-15: years since 1980256 int(dosDate>>9+1980),257 time.Month(dosDate>>5&0xf),258 int(dosDate&0x1f),259260 // time bits 0-4: second/2; 5-10: minute; 11-15: hour261 int(dosTime>>11),262 int(dosTime>>5&0x3f),263 int(dosTime&0x1f*2),264 0, // nanoseconds265266 time.UTC,267 )268}269270// timeToMsDosTime converts a time.Time to an MS-DOS date and time.271// The resolution is 2s.272// See: https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-filetimetodosdatetime273func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) {274 fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9)275 fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11)276 return277}278279// ModTime returns the modification time in UTC using the legacy280// [ModifiedDate] and [ModifiedTime] fields.281//282// Deprecated: Use [Modified] instead.283func (h *FileHeader) ModTime() time.Time {284 return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)285}286287// SetModTime sets the [Modified], [ModifiedTime], and [ModifiedDate] fields288// to the given time in UTC.289//290// Deprecated: Use [Modified] instead.291func (h *FileHeader) SetModTime(t time.Time) {292 t = t.UTC() // Convert to UTC for compatibility293 h.Modified = t294 h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t)295}296297const (298 // Unix constants. The specification doesn't mention them,299 // but these seem to be the values agreed on by tools.300 s_IFMT = 0xf000301 s_IFSOCK = 0xc000302 s_IFLNK = 0xa000303 s_IFREG = 0x8000304 s_IFBLK = 0x6000305 s_IFDIR = 0x4000306 s_IFCHR = 0x2000307 s_IFIFO = 0x1000308 s_ISUID = 0x800309 s_ISGID = 0x400310 s_ISVTX = 0x200311312 msdosDir = 0x10313 msdosReadOnly = 0x01314)315316// Mode returns the permission and mode bits for the [FileHeader].317func (h *FileHeader) Mode() (mode fs.FileMode) {318 switch h.CreatorVersion >> 8 {319 case creatorUnix, creatorMacOSX:320 mode = unixModeToFileMode(h.ExternalAttrs >> 16)321 case creatorNTFS, creatorVFAT, creatorFAT:322 mode = msdosModeToFileMode(h.ExternalAttrs)323 }324 if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' {325 mode |= fs.ModeDir326 }327 return mode328}329330// SetMode changes the permission and mode bits for the [FileHeader].331func (h *FileHeader) SetMode(mode fs.FileMode) {332 h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8333 h.ExternalAttrs = fileModeToUnixMode(mode) << 16334335 // set MSDOS attributes too, as the original zip does.336 if mode&fs.ModeDir != 0 {337 h.ExternalAttrs |= msdosDir338 }339 if mode&0200 == 0 {340 h.ExternalAttrs |= msdosReadOnly341 }342}343344func (h *FileHeader) hasDataDescriptor() bool {345 return h.Flags&0x8 != 0346}347348func msdosModeToFileMode(m uint32) (mode fs.FileMode) {349 if m&msdosDir != 0 {350 mode = fs.ModeDir | 0777351 } else {352 mode = 0666353 }354 if m&msdosReadOnly != 0 {355 mode &^= 0222356 }357 return mode358}359360func fileModeToUnixMode(mode fs.FileMode) uint32 {361 var m uint32362 switch mode & fs.ModeType {363 default:364 m = s_IFREG365 case fs.ModeDir:366 m = s_IFDIR367 case fs.ModeSymlink:368 m = s_IFLNK369 case fs.ModeNamedPipe:370 m = s_IFIFO371 case fs.ModeSocket:372 m = s_IFSOCK373 case fs.ModeDevice:374 m = s_IFBLK375 case fs.ModeDevice | fs.ModeCharDevice:376 m = s_IFCHR377 }378 if mode&fs.ModeSetuid != 0 {379 m |= s_ISUID380 }381 if mode&fs.ModeSetgid != 0 {382 m |= s_ISGID383 }384 if mode&fs.ModeSticky != 0 {385 m |= s_ISVTX386 }387 return m | uint32(mode&0777)388}389390func unixModeToFileMode(m uint32) fs.FileMode {391 mode := fs.FileMode(m & 0777)392 switch m & s_IFMT {393 case s_IFBLK:394 mode |= fs.ModeDevice395 case s_IFCHR:396 mode |= fs.ModeDevice | fs.ModeCharDevice397 case s_IFDIR:398 mode |= fs.ModeDir399 case s_IFIFO:400 mode |= fs.ModeNamedPipe401 case s_IFLNK:402 mode |= fs.ModeSymlink403 case s_IFREG:404 // nothing to do405 case s_IFSOCK:406 mode |= fs.ModeSocket407 }408 if m&s_ISGID != 0 {409 mode |= fs.ModeSetgid410 }411 if m&s_ISUID != 0 {412 mode |= fs.ModeSetuid413 }414 if m&s_ISVTX != 0 {415 mode |= fs.ModeSticky416 }417 return mode418}
Findings
✓ No findings reported for this file.