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