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
194
195
196
197
198
199
200 func FileInfoHeader(fi fs.FileInfo) (*FileHeader, error) {
201 size := fi.Size()
202 fh := &FileHeader{
203 Name: fi.Name(),
204 UncompressedSize64: uint64(size),
205 }
206 fh.SetModTime(fi.ModTime())
207 fh.SetMode(fi.Mode())
208 if fh.UncompressedSize64 > uint32max {
209 fh.UncompressedSize = uint32max
210 } else {
211 fh.UncompressedSize = uint32(fh.UncompressedSize64)
212 }
213 return fh, nil
214 }
215
216 type directoryEnd struct {
217 diskNbr uint32
218 dirDiskNbr uint32
219 dirRecordsThisDisk uint64
220 directoryRecords uint64
221 directorySize uint64
222 directoryOffset uint64
223 commentLen uint16
224 comment string
225 }
226
227
228
229 func timeZone(offset time.Duration) *time.Location {
230 const (
231 minOffset = -12 * time.Hour
232 maxOffset = +14 * time.Hour
233 offsetAlias = 15 * time.Minute
234 )
235 offset = offset.Round(offsetAlias)
236 if offset < minOffset || maxOffset < offset {
237 offset = 0
238 }
239 return time.FixedZone("", int(offset/time.Second))
240 }
241
242
243
244
245 func msDosTimeToTime(dosDate, dosTime uint16) time.Time {
246 return time.Date(
247
248 int(dosDate>>9+1980),
249 time.Month(dosDate>>5&0xf),
250 int(dosDate&0x1f),
251
252
253 int(dosTime>>11),
254 int(dosTime>>5&0x3f),
255 int(dosTime&0x1f*2),
256 0,
257
258 time.UTC,
259 )
260 }
261
262
263
264
265 func timeToMsDosTime(t time.Time) (fDate uint16, fTime uint16) {
266 fDate = uint16(t.Day() + int(t.Month())<<5 + (t.Year()-1980)<<9)
267 fTime = uint16(t.Second()/2 + t.Minute()<<5 + t.Hour()<<11)
268 return
269 }
270
271
272
273
274
275 func (h *FileHeader) ModTime() time.Time {
276 return msDosTimeToTime(h.ModifiedDate, h.ModifiedTime)
277 }
278
279
280
281
282
283 func (h *FileHeader) SetModTime(t time.Time) {
284 t = t.UTC()
285 h.Modified = t
286 h.ModifiedDate, h.ModifiedTime = timeToMsDosTime(t)
287 }
288
289 const (
290
291
292 s_IFMT = 0xf000
293 s_IFSOCK = 0xc000
294 s_IFLNK = 0xa000
295 s_IFREG = 0x8000
296 s_IFBLK = 0x6000
297 s_IFDIR = 0x4000
298 s_IFCHR = 0x2000
299 s_IFIFO = 0x1000
300 s_ISUID = 0x800
301 s_ISGID = 0x400
302 s_ISVTX = 0x200
303
304 msdosDir = 0x10
305 msdosReadOnly = 0x01
306 )
307
308
309 func (h *FileHeader) Mode() (mode fs.FileMode) {
310 switch h.CreatorVersion >> 8 {
311 case creatorUnix, creatorMacOSX:
312 mode = unixModeToFileMode(h.ExternalAttrs >> 16)
313 case creatorNTFS, creatorVFAT, creatorFAT:
314 mode = msdosModeToFileMode(h.ExternalAttrs)
315 }
316 if len(h.Name) > 0 && h.Name[len(h.Name)-1] == '/' {
317 mode |= fs.ModeDir
318 }
319 return mode
320 }
321
322
323 func (h *FileHeader) SetMode(mode fs.FileMode) {
324 h.CreatorVersion = h.CreatorVersion&0xff | creatorUnix<<8
325 h.ExternalAttrs = fileModeToUnixMode(mode) << 16
326
327
328 if mode&fs.ModeDir != 0 {
329 h.ExternalAttrs |= msdosDir
330 }
331 if mode&0200 == 0 {
332 h.ExternalAttrs |= msdosReadOnly
333 }
334 }
335
336
337 func (h *FileHeader) isZip64() bool {
338 return h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max
339 }
340
341 func (f *FileHeader) hasDataDescriptor() bool {
342 return f.Flags&0x8 != 0
343 }
344
345 func msdosModeToFileMode(m uint32) (mode fs.FileMode) {
346 if m&msdosDir != 0 {
347 mode = fs.ModeDir | 0777
348 } else {
349 mode = 0666
350 }
351 if m&msdosReadOnly != 0 {
352 mode &^= 0222
353 }
354 return mode
355 }
356
357 func fileModeToUnixMode(mode fs.FileMode) uint32 {
358 var m uint32
359 switch mode & fs.ModeType {
360 default:
361 m = s_IFREG
362 case fs.ModeDir:
363 m = s_IFDIR
364 case fs.ModeSymlink:
365 m = s_IFLNK
366 case fs.ModeNamedPipe:
367 m = s_IFIFO
368 case fs.ModeSocket:
369 m = s_IFSOCK
370 case fs.ModeDevice:
371 m = s_IFBLK
372 case fs.ModeDevice | fs.ModeCharDevice:
373 m = s_IFCHR
374 }
375 if mode&fs.ModeSetuid != 0 {
376 m |= s_ISUID
377 }
378 if mode&fs.ModeSetgid != 0 {
379 m |= s_ISGID
380 }
381 if mode&fs.ModeSticky != 0 {
382 m |= s_ISVTX
383 }
384 return m | uint32(mode&0777)
385 }
386
387 func unixModeToFileMode(m uint32) fs.FileMode {
388 mode := fs.FileMode(m & 0777)
389 switch m & s_IFMT {
390 case s_IFBLK:
391 mode |= fs.ModeDevice
392 case s_IFCHR:
393 mode |= fs.ModeDevice | fs.ModeCharDevice
394 case s_IFDIR:
395 mode |= fs.ModeDir
396 case s_IFIFO:
397 mode |= fs.ModeNamedPipe
398 case s_IFLNK:
399 mode |= fs.ModeSymlink
400 case s_IFREG:
401
402 case s_IFSOCK:
403 mode |= fs.ModeSocket
404 }
405 if m&s_ISGID != 0 {
406 mode |= fs.ModeSetgid
407 }
408 if m&s_ISUID != 0 {
409 mode |= fs.ModeSetuid
410 }
411 if m&s_ISVTX != 0 {
412 mode |= fs.ModeSticky
413 }
414 return mode
415 }
416
View as plain text