Home | History | Annotate | Download | only in 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/documents/casestudies/APPNOTE.TXT
      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 id's
     65 	zip64ExtraId = 0x0001 // zip64 Extended Information Extra Field
     66 )
     67 
     68 // FileHeader describes a file within a zip file.
     69 // See the zip spec for details.
     70 type FileHeader struct {
     71 	// Name is the name of the file.
     72 	// It must be a relative path: it must not start with a drive
     73 	// letter (e.g. C:) or leading slash, and only forward slashes
     74 	// are allowed.
     75 	Name string
     76 
     77 	CreatorVersion     uint16
     78 	ReaderVersion      uint16
     79 	Flags              uint16
     80 	Method             uint16
     81 	ModifiedTime       uint16 // MS-DOS time
     82 	ModifiedDate       uint16 // MS-DOS date
     83 	CRC32              uint32
     84 	CompressedSize     uint32 // Deprecated: Use CompressedSize64 instead.
     85 	UncompressedSize   uint32 // Deprecated: Use UncompressedSize64 instead.
     86 	CompressedSize64   uint64
     87 	UncompressedSize64 uint64
     88 	Extra              []byte
     89 	ExternalAttrs      uint32 // Meaning depends on CreatorVersion
     90 	Comment            string
     91 }
     92 
     93 // FileInfo returns an os.FileInfo for the FileHeader.
     94 func (h *FileHeader) FileInfo() os.FileInfo {
     95 	return headerFileInfo{h}
     96 }
     97 
     98 // headerFileInfo implements os.FileInfo.
     99 type headerFileInfo struct {
    100 	fh *FileHeader
    101 }
    102 
    103 func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) }
    104 func (fi headerFileInfo) Size() int64 {
    105 	if fi.fh.UncompressedSize64 > 0 {
    106 		return int64(fi.fh.UncompressedSize64)
    107 	}
    108 	return int64(fi.fh.UncompressedSize)
    109 }
    110 func (fi headerFileInfo) IsDir() bool        { return fi.Mode().IsDir() }
    111 func (fi headerFileInfo) ModTime() time.Time { return fi.fh.ModTime() }
    112 func (fi headerFileInfo) Mode() os.FileMode  { return fi.fh.Mode() }
    113 func (fi headerFileInfo) Sys() interface{}   { return fi.fh }
    114 
    115 // FileInfoHeader creates a partially-populated FileHeader from an
    116 // os.FileInfo.
    117 // Because os.FileInfo's Name method returns only the base name of
    118 // the file it describes, it may be necessary to modify the Name field
    119 // of the returned header to provide the full path name of the file.
    120 func FileInfoHeader(fi os.FileInfo) (*FileHeader, error) {
    121 	size := fi.Size()
    122 	fh := &FileHeader{
    123 		Name:               fi.Name(),
    124 		UncompressedSize64: uint64(size),
    125 	}
    126 	fh.SetModTime(fi.ModTime())
    127 	fh.SetMode(fi.Mode())
    128 	if fh.UncompressedSize64 > uint32max {
    129 		fh.UncompressedSize = uint32max
    130 	} else {
    131 		fh.UncompressedSize = uint32(fh.UncompressedSize64)
    132 	}
    133 	return fh, nil
    134 }
    135 
    136 type directoryEnd struct {
    137 	diskNbr            uint32 // unused
    138 	dirDiskNbr         uint32 // unused
    139 	dirRecordsThisDisk uint64 // unused
    140 	directoryRecords   uint64
    141 	directorySize      uint64
    142 	directoryOffset    uint64 // relative to file
    143 	commentLen         uint16
    144 	comment            string
    145 }
    146 
    147 // msDosTimeToTime converts an MS-DOS date and time into a time.Time.
    148 // The resolution is 2s.
    149 // See: http://msdn.microsoft.com/en-us/library/ms724247(v=VS.85).aspx
    150 func msDosTimeToTime(dosDate, dosTime uint16) time.Time {
    151 	return time.Date(
    152 		// date bits 0-4: day of month; 5-8: month; 9-15: years since 1980
    153 		int(dosDate>>9+1980),
    154 		time.Month(dosDate>>5&0xf),
    155 		int(dosDate&0x1f),
    156 
    157 		// time bits 0-4: second/2; 5-10: minute; 11-15: hour
    158 		int(dosTime>>11),
    159 		int(dosTime>>5&0x3f),
    160 		int(dosTime&0x1f*2),
    161 		0, // nanoseconds
    162 
    163 		time.UTC,
    164 	)
    165 }
    166 
    167 // timeToMsDosTime converts a time.Time to an MS-DOS date and time.
    168 // The resolution is 2s.
    169 // See: http://msdn.microsoft.com/en-us/library/ms724274(v=VS.85).aspx
    170 func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) {
    171 	t = t.In(time.UTC)
    172 	fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9)
    173 	fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11)
    174 	return
    175 }
    176 
    177 // ModTime returns the modification time in UTC.
    178 // The resolution is 2s.
    179 func (h *FileHeader) ModTime() time.Time {
    180 	return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
    181 }
    182 
    183 // SetModTime sets the ModifiedTime and ModifiedDate fields to the given time in UTC.
    184 // The resolution is 2s.
    185 func (h *FileHeader) SetModTime(t time.Time) {
    186 	h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t)
    187 }
    188 
    189 const (
    190 	// Unix constants. The specification doesn't mention them,
    191 	// but these seem to be the values agreed on by tools.
    192 	s_IFMT   = 0xf000
    193 	s_IFSOCK = 0xc000
    194 	s_IFLNK  = 0xa000
    195 	s_IFREG  = 0x8000
    196 	s_IFBLK  = 0x6000
    197 	s_IFDIR  = 0x4000
    198 	s_IFCHR  = 0x2000
    199 	s_IFIFO  = 0x1000
    200 	s_ISUID  = 0x800
    201 	s_ISGID  = 0x400
    202 	s_ISVTX  = 0x200
    203 
    204 	msdosDir      = 0x10
    205 	msdosReadOnly = 0x01
    206 )
    207 
    208 // Mode returns the permission and mode bits for the FileHeader.
    209 func (h *FileHeader) Mode() (mode os.FileMode) {
    210 	switch h.CreatorVersion >> 8 {
    211 	case creatorUnix, creatorMacOSX:
    212 		mode = unixModeToFileMode(h.ExternalAttrs >> 16)
    213 	case creatorNTFS, creatorVFAT, creatorFAT:
    214 		mode = msdosModeToFileMode(h.ExternalAttrs)
    215 	}
    216 	if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' {
    217 		mode |= os.ModeDir
    218 	}
    219 	return mode
    220 }
    221 
    222 // SetMode changes the permission and mode bits for the FileHeader.
    223 func (h *FileHeader) SetMode(mode os.FileMode) {
    224 	h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8
    225 	h.ExternalAttrs = fileModeToUnixMode(mode) << 16
    226 
    227 	// set MSDOS attributes too, as the original zip does.
    228 	if mode&os.ModeDir != 0 {
    229 		h.ExternalAttrs |= msdosDir
    230 	}
    231 	if mode&0200 == 0 {
    232 		h.ExternalAttrs |= msdosReadOnly
    233 	}
    234 }
    235 
    236 // isZip64 reports whether the file size exceeds the 32 bit limit
    237 func (fh *FileHeader) isZip64() bool {
    238 	return fh.CompressedSize64 >= uint32max || fh.UncompressedSize64 >= uint32max
    239 }
    240 
    241 func msdosModeToFileMode(m uint32) (mode os.FileMode) {
    242 	if m&msdosDir != 0 {
    243 		mode = os.ModeDir | 0777
    244 	} else {
    245 		mode = 0666
    246 	}
    247 	if m&msdosReadOnly != 0 {
    248 		mode &^= 0222
    249 	}
    250 	return mode
    251 }
    252 
    253 func fileModeToUnixMode(mode os.FileMode) uint32 {
    254 	var m uint32
    255 	switch mode & os.ModeType {
    256 	default:
    257 		m = s_IFREG
    258 	case os.ModeDir:
    259 		m = s_IFDIR
    260 	case os.ModeSymlink:
    261 		m = s_IFLNK
    262 	case os.ModeNamedPipe:
    263 		m = s_IFIFO
    264 	case os.ModeSocket:
    265 		m = s_IFSOCK
    266 	case os.ModeDevice:
    267 		if mode&os.ModeCharDevice != 0 {
    268 			m = s_IFCHR
    269 		} else {
    270 			m = s_IFBLK
    271 		}
    272 	}
    273 	if mode&os.ModeSetuid != 0 {
    274 		m |= s_ISUID
    275 	}
    276 	if mode&os.ModeSetgid != 0 {
    277 		m |= s_ISGID
    278 	}
    279 	if mode&os.ModeSticky != 0 {
    280 		m |= s_ISVTX
    281 	}
    282 	return m | uint32(mode&0777)
    283 }
    284 
    285 func unixModeToFileMode(m uint32) os.FileMode {
    286 	mode := os.FileMode(m & 0777)
    287 	switch m & s_IFMT {
    288 	case s_IFBLK:
    289 		mode |= os.ModeDevice
    290 	case s_IFCHR:
    291 		mode |= os.ModeDevice | os.ModeCharDevice
    292 	case s_IFDIR:
    293 		mode |= os.ModeDir
    294 	case s_IFIFO:
    295 		mode |= os.ModeNamedPipe
    296 	case s_IFLNK:
    297 		mode |= os.ModeSymlink
    298 	case s_IFREG:
    299 		// nothing to do
    300 	case s_IFSOCK:
    301 		mode |= os.ModeSocket
    302 	}
    303 	if m&s_ISGID != 0 {
    304 		mode |= os.ModeSetgid
    305 	}
    306 	if m&s_ISUID != 0 {
    307 		mode |= os.ModeSetuid
    308 	}
    309 	if m&s_ISVTX != 0 {
    310 		mode |= os.ModeSticky
    311 	}
    312 	return mode
    313 }
    314