1
2
3
4
5 package zip
6
7 import (
8 "bufio"
9 "encoding/binary"
10 "errors"
11 "hash"
12 "hash/crc32"
13 "io"
14 "io/fs"
15 "strings"
16 "unicode/utf8"
17 )
18
19 var (
20 errLongName = errors.New("zip: FileHeader.Name too long")
21 errLongExtra = errors.New("zip: FileHeader.Extra too long")
22 )
23
24
25 type Writer struct {
26 cw *countWriter
27 dir []*header
28 last *fileWriter
29 closed bool
30 compressors map[uint16]Compressor
31 comment string
32
33
34
35 testHookCloseSizeOffset func(size, offset uint64)
36 }
37
38 type header struct {
39 *FileHeader
40 offset uint64
41 raw bool
42 }
43
44
45
46
47
48
49 func NewWriter(w io.Writer) *Writer {
50 return &Writer{cw: &countWriter{w: bufio.NewWriter(w)}}
51 }
52
53
54
55
56
57 func (w *Writer) SetOffset(n int64) {
58 if w.cw.count != 0 {
59 panic("zip: SetOffset called after data was written")
60 }
61 w.cw.count = n
62 }
63
64
65
66 func (w *Writer) Flush() error {
67 return w.cw.w.(*bufio.Writer).Flush()
68 }
69
70
71
72 func (w *Writer) SetComment(comment string) error {
73 if len(comment) > uint16max {
74 return errors.New("zip: Writer.Comment too long")
75 }
76 w.comment = comment
77 return nil
78 }
79
80
81
82 func (w *Writer) Close() error {
83 if w.last != nil && !w.last.closed {
84 if err := w.last.close(); err != nil {
85 return err
86 }
87 w.last = nil
88 }
89 if w.closed {
90 return errors.New("zip: writer closed twice")
91 }
92 w.closed = true
93
94
95 start := w.cw.count
96 usedZip64 := false
97 for _, h := range w.dir {
98
99
100
101
102
103
104
105
106
107
108
109
110
111 readerVersion := h.ReaderVersion
112 if h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max || h.offset >= uint32max {
113 usedZip64 = true
114 readerVersion = max(readerVersion, zipVersion45)
115 var size uint16
116 var buf [28]byte
117 eb := writeBuf(buf[:])
118 eb.uint16(zip64ExtraID)
119 eb.uint16(0)
120 if h.UncompressedSize64 >= uint32max {
121 eb.uint64(h.UncompressedSize64)
122 size += 8
123 }
124 if h.CompressedSize64 >= uint32max {
125 eb.uint64(h.CompressedSize64)
126 size += 8
127 }
128 if h.offset >= uint32max {
129 eb.uint64(h.offset)
130 size += 8
131 }
132 sb := writeBuf(buf[2:])
133 sb.uint16(size)
134 h.Extra = append(h.Extra, buf[:4+size]...)
135 }
136
137 var buf [directoryHeaderLen]byte
138 b := writeBuf(buf[:])
139 b.uint32(uint32(directoryHeaderSignature))
140 b.uint16(h.CreatorVersion)
141 b.uint16(readerVersion)
142 b.uint16(h.Flags)
143 b.uint16(h.Method)
144 b.uint16(h.ModifiedTime)
145 b.uint16(h.ModifiedDate)
146 b.uint32(h.CRC32)
147 b.uint32(uint32(min(h.CompressedSize64, uint32max)))
148 b.uint32(uint32(min(h.UncompressedSize64, uint32max)))
149 b.uint16(uint16(len(h.Name)))
150 b.uint16(uint16(len(h.Extra)))
151 b.uint16(uint16(len(h.Comment)))
152 b = b[4:]
153 b.uint32(h.ExternalAttrs)
154 b.uint32(uint32(min(h.offset, uint32max)))
155 if _, err := w.cw.Write(buf[:]); err != nil {
156 return err
157 }
158 if _, err := io.WriteString(w.cw, h.Name); err != nil {
159 return err
160 }
161 if _, err := w.cw.Write(h.Extra); err != nil {
162 return err
163 }
164 if _, err := io.WriteString(w.cw, h.Comment); err != nil {
165 return err
166 }
167 }
168 end := w.cw.count
169
170 records := uint64(len(w.dir))
171 size := uint64(end - start)
172 offset := uint64(start)
173
174 if f := w.testHookCloseSizeOffset; f != nil {
175 f(size, offset)
176 }
177
178
179
180
181
182 if usedZip64 || records >= uint16max || size >= uint32max || offset >= uint32max {
183 var buf [directory64EndLen + directory64LocLen]byte
184 b := writeBuf(buf[:])
185
186
187 b.uint32(directory64EndSignature)
188 b.uint64(directory64EndLen - 12)
189 b.uint16(zipVersion45)
190 b.uint16(zipVersion45)
191 b.uint32(0)
192 b.uint32(0)
193 b.uint64(records)
194 b.uint64(records)
195 b.uint64(size)
196 b.uint64(offset)
197
198
199 b.uint32(directory64LocSignature)
200 b.uint32(0)
201 b.uint64(uint64(end))
202 b.uint32(1)
203
204 if _, err := w.cw.Write(buf[:]); err != nil {
205 return err
206 }
207 }
208
209
210 var buf [directoryEndLen]byte
211 b := writeBuf(buf[:])
212 b.uint32(uint32(directoryEndSignature))
213 b = b[4:]
214 b.uint16(uint16(min(uint16max, records)))
215 b.uint16(uint16(min(uint16max, records)))
216 b.uint32(uint32(min(uint32max, size)))
217 b.uint32(uint32(min(uint32max, offset)))
218 b.uint16(uint16(len(w.comment)))
219 if _, err := w.cw.Write(buf[:]); err != nil {
220 return err
221 }
222 if _, err := io.WriteString(w.cw, w.comment); err != nil {
223 return err
224 }
225
226 return w.cw.w.(*bufio.Writer).Flush()
227 }
228
229
230
231
232
233
234
235
236
237
238
239 func (w *Writer) Create(name string) (io.Writer, error) {
240 header := &FileHeader{
241 Name: name,
242 Method: Deflate,
243 }
244 return w.CreateHeader(header)
245 }
246
247
248
249
250 func detectUTF8(s string) (valid, require bool) {
251 for i := 0; i < len(s); {
252 r, size := utf8.DecodeRuneInString(s[i:])
253 i += size
254
255
256
257
258
259
260 if r < 0x20 || r > 0x7d || r == 0x5c {
261 if !utf8.ValidRune(r) || (r == utf8.RuneError && size == 1) {
262 return false, false
263 }
264 require = true
265 }
266 }
267 return true, require
268 }
269
270
271
272 func (w *Writer) prepare(fh *FileHeader) error {
273 if w.last != nil && !w.last.closed {
274 if err := w.last.close(); err != nil {
275 return err
276 }
277 }
278 if len(w.dir) > 0 && w.dir[len(w.dir)-1].FileHeader == fh {
279
280 return errors.New("archive/zip: invalid duplicate FileHeader")
281 }
282 return nil
283 }
284
285
286
287
288
289
290
291
292 func (w *Writer) CreateHeader(fh *FileHeader) (io.Writer, error) {
293 if err := w.prepare(fh); err != nil {
294 return nil, err
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313 utf8Valid1, utf8Require1 := detectUTF8(fh.Name)
314 utf8Valid2, utf8Require2 := detectUTF8(fh.Comment)
315 switch {
316 case fh.NonUTF8:
317 fh.Flags &^= 0x800
318 case (utf8Require1 || utf8Require2) && (utf8Valid1 && utf8Valid2):
319 fh.Flags |= 0x800
320 }
321
322 fh.CreatorVersion = fh.CreatorVersion&0xff00 | zipVersion20
323 fh.ReaderVersion = zipVersion20
324
325
326 if !fh.Modified.IsZero() {
327
328
329
330
331
332
333
334
335 fh.ModifiedDate, fh.ModifiedTime = timeToMsDosTime(fh.Modified)
336
337
338
339
340
341
342
343 var mbuf [9]byte
344 mt := uint32(fh.Modified.Unix())
345 eb := writeBuf(mbuf[:])
346 eb.uint16(extTimeExtraID)
347 eb.uint16(5)
348 eb.uint8(1)
349 eb.uint32(mt)
350 fh.Extra = append(fh.Extra, mbuf[:]...)
351 }
352
353 var (
354 ow io.Writer
355 fw *fileWriter
356 )
357 h := &header{
358 FileHeader: fh,
359 offset: uint64(w.cw.count),
360 }
361
362 if strings.HasSuffix(fh.Name, "/") {
363
364
365
366
367 fh.Method = Store
368 fh.Flags &^= 0x8
369
370
371 fh.CompressedSize = 0
372 fh.CompressedSize64 = 0
373 fh.UncompressedSize = 0
374 fh.UncompressedSize64 = 0
375
376 ow = dirWriter{}
377 } else {
378 fh.Flags |= 0x8
379
380 fw = &fileWriter{
381 zipw: w.cw,
382 compCount: &countWriter{w: w.cw},
383 crc32: crc32.NewIEEE(),
384 }
385 comp := w.compressor(fh.Method)
386 if comp == nil {
387 return nil, ErrAlgorithm
388 }
389 var err error
390 fw.comp, err = comp(fw.compCount)
391 if err != nil {
392 return nil, err
393 }
394 fw.rawCount = &countWriter{w: fw.comp}
395 fw.header = h
396 ow = fw
397 }
398 w.dir = append(w.dir, h)
399 if err := writeHeader(w.cw, h); err != nil {
400 return nil, err
401 }
402
403 w.last = fw
404 return ow, nil
405 }
406
407 func writeHeader(w io.Writer, h *header) error {
408 const maxUint16 = 1<<16 - 1
409 if len(h.Name) > maxUint16 {
410 return errLongName
411 }
412 if len(h.Extra) > maxUint16 {
413 return errLongExtra
414 }
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439 var zip64ExtraInfo []byte
440 readerVersion := h.ReaderVersion
441 noDataDescriptor := h.raw && !h.hasDataDescriptor()
442 if noDataDescriptor && (h.CompressedSize64 > uint32max || h.UncompressedSize64 > uint32max) {
443 readerVersion = max(readerVersion, zipVersion45)
444 zip64ExtraInfo = make([]byte, 20)
445 b := writeBuf(zip64ExtraInfo)
446 b.uint16(zip64ExtraID)
447 b.uint16(16)
448 b.uint64(h.UncompressedSize64)
449 b.uint64(h.CompressedSize64)
450 }
451
452 var buf [fileHeaderLen]byte
453 b := writeBuf(buf[:])
454 b.uint32(uint32(fileHeaderSignature))
455 b.uint16(readerVersion)
456 b.uint16(h.Flags)
457 b.uint16(h.Method)
458 b.uint16(h.ModifiedTime)
459 b.uint16(h.ModifiedDate)
460 if noDataDescriptor {
461 b.uint32(h.CRC32)
462 if zip64ExtraInfo != nil {
463 b.uint32(uint32max)
464 b.uint32(uint32max)
465 } else {
466 b.uint32(uint32(h.CompressedSize64))
467 b.uint32(uint32(h.UncompressedSize64))
468 }
469 } else {
470 b.uint32(0)
471 b.uint32(0)
472 b.uint32(0)
473 }
474 b.uint16(uint16(len(h.Name)))
475 b.uint16(uint16(len(h.Extra) + len(zip64ExtraInfo)))
476 if _, err := w.Write(buf[:]); err != nil {
477 return err
478 }
479 if _, err := io.WriteString(w, h.Name); err != nil {
480 return err
481 }
482 if _, err := w.Write(h.Extra); err != nil {
483 return err
484 }
485 if _, err := w.Write(zip64ExtraInfo); err != nil {
486 return err
487 }
488 return nil
489 }
490
491
492
493
494
495
496
497
498
499
500
501 func (w *Writer) CreateRaw(fh *FileHeader) (io.Writer, error) {
502 if err := w.prepare(fh); err != nil {
503 return nil, err
504 }
505
506 fh.CompressedSize = uint32(min(fh.CompressedSize64, uint32max))
507 fh.UncompressedSize = uint32(min(fh.UncompressedSize64, uint32max))
508
509 h := &header{
510 FileHeader: fh,
511 offset: uint64(w.cw.count),
512 raw: true,
513 }
514 w.dir = append(w.dir, h)
515 if err := writeHeader(w.cw, h); err != nil {
516 return nil, err
517 }
518
519 if strings.HasSuffix(fh.Name, "/") {
520 w.last = nil
521 return dirWriter{}, nil
522 }
523
524 fw := &fileWriter{
525 header: h,
526 zipw: w.cw,
527 }
528 w.last = fw
529 return fw, nil
530 }
531
532
533
534 func (w *Writer) Copy(f *File) error {
535 r, err := f.OpenRaw()
536 if err != nil {
537 return err
538 }
539
540
541 fh := f.FileHeader
542 fw, err := w.CreateRaw(&fh)
543 if err != nil {
544 return err
545 }
546 _, err = io.Copy(fw, r)
547 return err
548 }
549
550
551
552
553 func (w *Writer) RegisterCompressor(method uint16, comp Compressor) {
554 if w.compressors == nil {
555 w.compressors = make(map[uint16]Compressor)
556 }
557 w.compressors[method] = comp
558 }
559
560
561
562
563 func (w *Writer) AddFS(fsys fs.FS) error {
564 return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error {
565 if err != nil {
566 return err
567 }
568 if name == "." {
569 return nil
570 }
571 info, err := d.Info()
572 if err != nil {
573 return err
574 }
575 if !d.IsDir() && !info.Mode().IsRegular() {
576 return errors.New("zip: cannot add non-regular file")
577 }
578 h, err := FileInfoHeader(info)
579 if err != nil {
580 return err
581 }
582 h.Name = name
583 if d.IsDir() {
584 h.Name += "/"
585 }
586 h.Method = Deflate
587 fw, err := w.CreateHeader(h)
588 if err != nil {
589 return err
590 }
591 if d.IsDir() {
592 return nil
593 }
594 f, err := fsys.Open(name)
595 if err != nil {
596 return err
597 }
598 defer f.Close()
599 _, err = io.Copy(fw, f)
600 return err
601 })
602 }
603
604 func (w *Writer) compressor(method uint16) Compressor {
605 comp := w.compressors[method]
606 if comp == nil {
607 comp = compressor(method)
608 }
609 return comp
610 }
611
612 type dirWriter struct{}
613
614 func (dirWriter) Write(b []byte) (int, error) {
615 if len(b) == 0 {
616 return 0, nil
617 }
618 return 0, errors.New("zip: write to directory")
619 }
620
621 type fileWriter struct {
622 *header
623 zipw io.Writer
624 rawCount *countWriter
625 comp io.WriteCloser
626 compCount *countWriter
627 crc32 hash.Hash32
628 closed bool
629 }
630
631 func (w *fileWriter) Write(p []byte) (int, error) {
632 if w.closed {
633 return 0, errors.New("zip: write to closed file")
634 }
635 if w.raw {
636 return w.zipw.Write(p)
637 }
638 w.crc32.Write(p)
639 return w.rawCount.Write(p)
640 }
641
642 func (w *fileWriter) close() error {
643 if w.closed {
644 return errors.New("zip: file closed twice")
645 }
646 w.closed = true
647 if w.raw {
648 return w.writeDataDescriptor()
649 }
650 if err := w.comp.Close(); err != nil {
651 return err
652 }
653
654
655 fh := w.header.FileHeader
656 fh.CRC32 = w.crc32.Sum32()
657 fh.CompressedSize64 = uint64(w.compCount.count)
658 fh.UncompressedSize64 = uint64(w.rawCount.count)
659
660 if w.CompressedSize64 > uint32max || w.UncompressedSize64 > uint32max {
661 fh.CompressedSize = uint32max
662 fh.UncompressedSize = uint32max
663 fh.ReaderVersion = zipVersion45
664 } else {
665 fh.CompressedSize = uint32(fh.CompressedSize64)
666 fh.UncompressedSize = uint32(fh.UncompressedSize64)
667 }
668
669 return w.writeDataDescriptor()
670 }
671
672 func (w *fileWriter) writeDataDescriptor() error {
673 if !w.hasDataDescriptor() {
674 return nil
675 }
676
677
678
679
680
681 var buf []byte
682 if w.CompressedSize64 > uint32max || w.UncompressedSize64 > uint32max {
683 buf = make([]byte, dataDescriptor64Len)
684 } else {
685 buf = make([]byte, dataDescriptorLen)
686 }
687 b := writeBuf(buf)
688 b.uint32(dataDescriptorSignature)
689 b.uint32(w.CRC32)
690 if w.CompressedSize64 > uint32max || w.UncompressedSize64 > uint32max {
691 b.uint64(w.CompressedSize64)
692 b.uint64(w.UncompressedSize64)
693 } else {
694 b.uint32(w.CompressedSize)
695 b.uint32(w.UncompressedSize)
696 }
697 _, err := w.zipw.Write(buf)
698 return err
699 }
700
701 type countWriter struct {
702 w io.Writer
703 count int64
704 }
705
706 func (w *countWriter) Write(p []byte) (int, error) {
707 n, err := w.w.Write(p)
708 w.count += int64(n)
709 return n, err
710 }
711
712 type nopCloser struct {
713 io.Writer
714 }
715
716 func (w nopCloser) Close() error {
717 return nil
718 }
719
720 type writeBuf []byte
721
722 func (b *writeBuf) uint8(v uint8) {
723 (*b)[0] = v
724 *b = (*b)[1:]
725 }
726
727 func (b *writeBuf) uint16(v uint16) {
728 binary.LittleEndian.PutUint16(*b, v)
729 *b = (*b)[2:]
730 }
731
732 func (b *writeBuf) uint32(v uint32) {
733 binary.LittleEndian.PutUint32(*b, v)
734 *b = (*b)[4:]
735 }
736
737 func (b *writeBuf) uint64(v uint64) {
738 binary.LittleEndian.PutUint64(*b, v)
739 *b = (*b)[8:]
740 }
741
View as plain text