Source file src/archive/zip/struct.go

Documentation: archive/zip

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

View as plain text