src/archive/zip/struct.go GO 419 lines View on github.com → Search inside
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.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.