1
2
3
4
5
22 package zip
23
24 import (
25 "io/fs"
26 "path"
27 "time"
28 )
29
30
31 const (
32 Store uint16 = 0
33 Deflate uint16 = 8
34 )
35
36 const (
37 fileHeaderSignature = 0x04034b50
38 directoryHeaderSignature = 0x02014b50
39 directoryEndSignature = 0x06054b50
40 directory64LocSignature = 0x07064b50
41 directory64EndSignature = 0x06064b50
42 dataDescriptorSignature = 0x08074b50
43 fileHeaderLen = 30
44 directoryHeaderLen = 46
45 directoryEndLen = 22
46 dataDescriptorLen = 16
47 dataDescriptor64Len = 24
48 directory64LocLen = 20
49 directory64EndLen = 56
50
51
52 creatorFAT = 0
53 creatorUnix = 3
54 creatorNTFS = 11
55 creatorVFAT = 14
56 creatorMacOSX = 19
57
58
59 zipVersion20 = 20
60 zipVersion45 = 45
61
62
63 uint16max = (1 << 16) - 1
64 uint32max = (1 << 32) - 1
65
66
67
68
69
70
71
72
73
74
75 zip64ExtraID = 0x0001
76 ntfsExtraID = 0x000a
77 unixExtraID = 0x000d
78 extTimeExtraID = 0x5455
79 infoZipUnixExtraID = 0x5855
80 )
81
82
83
84
85
86 type FileHeader struct {
87
88
89
90
91
92 Name string
93
94
95 Comment string
96
97
98
99
100
101
102
103
104
105
106 NonUTF8 bool
107
108 CreatorVersion uint16
109 ReaderVersion uint16
110 Flags uint16
111
112
113 Method uint16
114
115
116
117
118
119
120
121
122
123
124 Modified time.Time
125
126
127
128
129 ModifiedTime uint16
130
131
132
133
134 ModifiedDate uint16
135
136
137 CRC32 uint32
138
139
140
141
142
143
144 CompressedSize uint32
145
146
147
148
149
150
151 UncompressedSize uint32
152
153
154 CompressedSize64 uint64
155
156
157 UncompressedSize64 uint64
158
159 Extra []byte
160 ExternalAttrs uint32
161 }
162
163
164 func (h *FileHeader) FileInfo() fs.FileInfo {
165 return headerFileInfo{h}
166 }
167
168
169 type headerFileInfo struct {
170 fh *FileHeader
171 }
172
173 func (fi headerFileInfo) Name() string { return path.Base(fi.fh.Name) }
174 func (fi headerFileInfo) Size() int64 {
175 if fi.fh.UncompressedSize64 > 0 {
176 return int64(fi.fh.UncompressedSize64)
177 }
178 return int64(fi.fh.UncompressedSize)
179 }
180 func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
181 func (fi headerFileInfo) ModTime() time.Time {
182 if fi.fh.Modified.IsZero() {
183 return fi.fh.ModTime()
184 }
185 return fi.fh.Modified.UTC()
186 }
187 func (fi headerFileInfo) Mode() fs.FileMode { return fi.fh.Mode() }
188 func (fi headerFileInfo) Type() fs.FileMode { return fi.fh.Mode().Type() }
189 func (fi headerFileInfo) Sys() any { return fi.fh }
190
191 func (fi headerFileInfo) Info() (fs.FileInfo, error) { return fi, nil }
192
193 func (fi headerFileInfo) String() string {
194 return fs.FormatFileInfo(fi)
195 }
196
197
198
199
200
201
202
203
204 func FileInfoHeader(fi fs.FileInfo) (*FileHeader, error) {
205 size := fi.Size()
206 fh := &FileHeader{
207 Name: fi.Name(),
208 UncompressedSize64: uint64(size),
209 }
210 fh.SetModTime(fi.ModTime())
211 fh.SetMode(fi.Mode())
212 if fh.UncompressedSize64 > uint32max {
213 fh.UncompressedSize = uint32max
214 } else {
215 fh.UncompressedSize = uint32(fh.UncompressedSize64)
216 }
217 return fh, nil
218 }
219
220 type directoryEnd struct {
221 diskNbr uint32
222 dirDiskNbr uint32
223 dirRecordsThisDisk uint64
224 directoryRecords uint64
225 directorySize uint64
226 directoryOffset uint64
227 commentLen uint16
228 comment string
229 }
230
231
232
233 func timeZone(offset time.Duration) *time.Location {
234 const (
235 minOffset = -12 * time.Hour
236 maxOffset = +14 * time.Hour
237 offsetAlias = 15 * time.Minute
238 )
239 offset = offset.Round(offsetAlias)
240 if offset < minOffset || maxOffset < offset {
241 offset = 0
242 }
243 return time.FixedZone("", int(offset/time.Second))
244 }
245
246
247
248
249 func msDosTimeToTime(dosDate, dosTime uint16) time.Time {
250 return time.Date(
251
252 int(dosDate>>9+1980),
253 time.Month(dosDate>>5&0xf),
254 int(dosDate&0x1f),
255
256
257 int(dosTime>>11),
258 int(dosTime>>5&0x3f),
259 int(dosTime&0x1f*2),
260 0,
261
262 time.UTC,
263 )
264 }
265
266
267
268
269 func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) {
270 fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9)
271 fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11)
272 return
273 }
274
275
276
277
278
279 func (h *FileHeader) ModTime() time.Time {
280 return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
281 }
282
283
284
285
286
287 func (h *FileHeader) SetModTime(t time.Time) {
288 t = t.UTC()
289 h.Modified = t
290 h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t)
291 }
292
293 const (
294
295
296 s_IFMT = 0xf000
297 s_IFSOCK = 0xc000
298 s_IFLNK = 0xa000
299 s_IFREG = 0x8000
300 s_IFBLK = 0x6000
301 s_IFDIR = 0x4000
302 s_IFCHR = 0x2000
303 s_IFIFO = 0x1000
304 s_ISUID = 0x800
305 s_ISGID = 0x400
306 s_ISVTX = 0x200
307
308 msdosDir = 0x10
309 msdosReadOnly = 0x01
310 )
311
312
313 func (h *FileHeader) Mode() (mode fs.FileMode) {
314 switch h.CreatorVersion >> 8 {
315 case creatorUnix, creatorMacOSX:
316 mode = unixModeToFileMode(h.ExternalAttrs >> 16)
317 case creatorNTFS, creatorVFAT, creatorFAT:
318 mode = msdosModeToFileMode(h.ExternalAttrs)
319 }
320 if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' {
321 mode |= fs.ModeDir
322 }
323 return mode
324 }
325
326
327 func (h *FileHeader) SetMode(mode fs.FileMode) {
328 h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8
329 h.ExternalAttrs = fileModeToUnixMode(mode) << 16
330
331
332 if mode&fs.ModeDir != 0 {
333 h.ExternalAttrs |= msdosDir
334 }
335 if mode&0200 == 0 {
336 h.ExternalAttrs |= msdosReadOnly
337 }
338 }
339
340
341 func (h *FileHeader) isZip64() bool {
342 return h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max
343 }
344
345 func (h *FileHeader) hasDataDescriptor() bool {
346 return h.Flags&0x8 != 0
347 }
348
349 func msdosModeToFileMode(m uint32) (mode fs.FileMode) {
350 if m&msdosDir != 0 {
351 mode = fs.ModeDir | 0777
352 } else {
353 mode = 0666
354 }
355 if m&msdosReadOnly != 0 {
356 mode &^= 0222
357 }
358 return mode
359 }
360
361 func fileModeToUnixMode(mode fs.FileMode) uint32 {
362 var m uint32
363 switch mode & fs.ModeType {
364 default:
365 m = s_IFREG
366 case fs.ModeDir:
367 m = s_IFDIR
368 case fs.ModeSymlink:
369 m = s_IFLNK
370 case fs.ModeNamedPipe:
371 m = s_IFIFO
372 case fs.ModeSocket:
373 m = s_IFSOCK
374 case fs.ModeDevice:
375 m = s_IFBLK
376 case fs.ModeDevice | fs.ModeCharDevice:
377 m = s_IFCHR
378 }
379 if mode&fs.ModeSetuid != 0 {
380 m |= s_ISUID
381 }
382 if mode&fs.ModeSetgid != 0 {
383 m |= s_ISGID
384 }
385 if mode&fs.ModeSticky != 0 {
386 m |= s_ISVTX
387 }
388 return m | uint32(mode&0777)
389 }
390
391 func unixModeToFileMode(m uint32) fs.FileMode {
392 mode := fs.FileMode(m & 0777)
393 switch m & s_IFMT {
394 case s_IFBLK:
395 mode |= fs.ModeDevice
396 case s_IFCHR:
397 mode |= fs.ModeDevice | fs.ModeCharDevice
398 case s_IFDIR:
399 mode |= fs.ModeDir
400 case s_IFIFO:
401 mode |= fs.ModeNamedPipe
402 case s_IFLNK:
403 mode |= fs.ModeSymlink
404 case s_IFREG:
405
406 case s_IFSOCK:
407 mode |= fs.ModeSocket
408 }
409 if m&s_ISGID != 0 {
410 mode |= fs.ModeSetgid
411 }
412 if m&s_ISUID != 0 {
413 mode |= fs.ModeSetuid
414 }
415 if m&s_ISVTX != 0 {
416 mode |= fs.ModeSticky
417 }
418 return mode
419 }
420
View as plain text