summaryrefslogtreecommitdiffstats
path: root/src/archive/zip/struct.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/archive/zip/struct.go')
-rw-r--r--src/archive/zip/struct.go415
1 files changed, 415 insertions, 0 deletions
diff --git a/src/archive/zip/struct.go b/src/archive/zip/struct.go
new file mode 100644
index 0000000..9c37084
--- /dev/null
+++ b/src/archive/zip/struct.go
@@ -0,0 +1,415 @@
+// 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 the [ZIP specification] for details.
+
+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.
+
+[ZIP specification]: https://www.pkware.com/appnote
+*/
+package zip
+
+import (
+ "io/fs"
+ "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 // two uint32: signature, crc32 | two uint64: compressed size, size
+ 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 specification] for details.
+//
+// [ZIP specification]: https://www.pkware.com/appnote
+type FileHeader struct {
+ // Name is the name of the file.
+ //
+ // It must be a relative path, not start with a drive letter (such as "C:"),
+ // and must use forward slashes instead of back slashes. A trailing slash
+ // indicates that this file is a directory and should have no data.
+ 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 is an MS-DOS-encoded time.
+ //
+ // Deprecated: Use Modified instead.
+ ModifiedTime uint16
+
+ // ModifiedDate is an MS-DOS-encoded date.
+ //
+ // Deprecated: Use Modified instead.
+ ModifiedDate uint16
+
+ // CRC32 is the CRC32 checksum of the file content.
+ CRC32 uint32
+
+ // CompressedSize is the compressed size of the file in bytes.
+ // If either the uncompressed or compressed size of the file
+ // does not fit in 32 bits, CompressedSize is set to ^uint32(0).
+ //
+ // Deprecated: Use CompressedSize64 instead.
+ CompressedSize uint32
+
+ // UncompressedSize is the compressed size of the file in bytes.
+ // If either the uncompressed or compressed size of the file
+ // does not fit in 32 bits, CompressedSize is set to ^uint32(0).
+ //
+ // Deprecated: Use UncompressedSize64 instead.
+ UncompressedSize uint32
+
+ // CompressedSize64 is the compressed size of the file in bytes.
+ CompressedSize64 uint64
+
+ // UncompressedSize64 is the uncompressed size of the file in bytes.
+ UncompressedSize64 uint64
+
+ Extra []byte
+ ExternalAttrs uint32 // Meaning depends on CreatorVersion
+}
+
+// FileInfo returns an fs.FileInfo for the FileHeader.
+func (h *FileHeader) FileInfo() fs.FileInfo {
+ return headerFileInfo{h}
+}
+
+// headerFileInfo implements fs.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 {
+ if fi.fh.Modified.IsZero() {
+ return fi.fh.ModTime()
+ }
+ return fi.fh.Modified.UTC()
+}
+func (fi headerFileInfo) Mode() fs.FileMode { return fi.fh.Mode() }
+func (fi headerFileInfo) Type() fs.FileMode { return fi.fh.Mode().Type() }
+func (fi headerFileInfo) Sys() any { return fi.fh }
+
+func (fi headerFileInfo) Info() (fs.FileInfo, error) { return fi, nil }
+
+// FileInfoHeader creates a partially-populated FileHeader from an
+// fs.FileInfo.
+// Because fs.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 fs.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: https://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: https://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 fs.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 |= fs.ModeDir
+ }
+ return mode
+}
+
+// SetMode changes the permission and mode bits for the FileHeader.
+func (h *FileHeader) SetMode(mode fs.FileMode) {
+ h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8
+ h.ExternalAttrs = fileModeToUnixMode(mode) << 16
+
+ // set MSDOS attributes too, as the original zip does.
+ if mode&fs.ModeDir != 0 {
+ h.ExternalAttrs |= msdosDir
+ }
+ if mode&0200 == 0 {
+ h.ExternalAttrs |= msdosReadOnly
+ }
+}
+
+// isZip64 reports whether the file size exceeds the 32 bit limit
+func (h *FileHeader) isZip64() bool {
+ return h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max
+}
+
+func (f *FileHeader) hasDataDescriptor() bool {
+ return f.Flags&0x8 != 0
+}
+
+func msdosModeToFileMode(m uint32) (mode fs.FileMode) {
+ if m&msdosDir != 0 {
+ mode = fs.ModeDir | 0777
+ } else {
+ mode = 0666
+ }
+ if m&msdosReadOnly != 0 {
+ mode &^= 0222
+ }
+ return mode
+}
+
+func fileModeToUnixMode(mode fs.FileMode) uint32 {
+ var m uint32
+ switch mode & fs.ModeType {
+ default:
+ m = s_IFREG
+ case fs.ModeDir:
+ m = s_IFDIR
+ case fs.ModeSymlink:
+ m = s_IFLNK
+ case fs.ModeNamedPipe:
+ m = s_IFIFO
+ case fs.ModeSocket:
+ m = s_IFSOCK
+ case fs.ModeDevice:
+ m = s_IFBLK
+ case fs.ModeDevice | fs.ModeCharDevice:
+ m = s_IFCHR
+ }
+ if mode&fs.ModeSetuid != 0 {
+ m |= s_ISUID
+ }
+ if mode&fs.ModeSetgid != 0 {
+ m |= s_ISGID
+ }
+ if mode&fs.ModeSticky != 0 {
+ m |= s_ISVTX
+ }
+ return m | uint32(mode&0777)
+}
+
+func unixModeToFileMode(m uint32) fs.FileMode {
+ mode := fs.FileMode(m & 0777)
+ switch m & s_IFMT {
+ case s_IFBLK:
+ mode |= fs.ModeDevice
+ case s_IFCHR:
+ mode |= fs.ModeDevice | fs.ModeCharDevice
+ case s_IFDIR:
+ mode |= fs.ModeDir
+ case s_IFIFO:
+ mode |= fs.ModeNamedPipe
+ case s_IFLNK:
+ mode |= fs.ModeSymlink
+ case s_IFREG:
+ // nothing to do
+ case s_IFSOCK:
+ mode |= fs.ModeSocket
+ }
+ if m&s_ISGID != 0 {
+ mode |= fs.ModeSetgid
+ }
+ if m&s_ISUID != 0 {
+ mode |= fs.ModeSetuid
+ }
+ if m&s_ISVTX != 0 {
+ mode |= fs.ModeSticky
+ }
+ return mode
+}