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