]> Cypherpunks.ru repositories - gostls13.git/blob - src/archive/zip/writer_test.go
archive/zip: add AddFS method to zip Writer
[gostls13.git] / src / archive / zip / writer_test.go
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package zip
6
7 import (
8         "bytes"
9         "compress/flate"
10         "encoding/binary"
11         "fmt"
12         "hash/crc32"
13         "io"
14         "io/fs"
15         "math/rand"
16         "os"
17         "strings"
18         "testing"
19         "testing/fstest"
20         "time"
21 )
22
23 // TODO(adg): a more sophisticated test suite
24
25 type WriteTest struct {
26         Name   string
27         Data   []byte
28         Method uint16
29         Mode   fs.FileMode
30 }
31
32 var writeTests = []WriteTest{
33         {
34                 Name:   "foo",
35                 Data:   []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."),
36                 Method: Store,
37                 Mode:   0666,
38         },
39         {
40                 Name:   "bar",
41                 Data:   nil, // large data set in the test
42                 Method: Deflate,
43                 Mode:   0644,
44         },
45         {
46                 Name:   "setuid",
47                 Data:   []byte("setuid file"),
48                 Method: Deflate,
49                 Mode:   0755 | fs.ModeSetuid,
50         },
51         {
52                 Name:   "setgid",
53                 Data:   []byte("setgid file"),
54                 Method: Deflate,
55                 Mode:   0755 | fs.ModeSetgid,
56         },
57         {
58                 Name:   "symlink",
59                 Data:   []byte("../link/target"),
60                 Method: Deflate,
61                 Mode:   0755 | fs.ModeSymlink,
62         },
63         {
64                 Name:   "device",
65                 Data:   []byte("device file"),
66                 Method: Deflate,
67                 Mode:   0755 | fs.ModeDevice,
68         },
69         {
70                 Name:   "chardevice",
71                 Data:   []byte("char device file"),
72                 Method: Deflate,
73                 Mode:   0755 | fs.ModeDevice | fs.ModeCharDevice,
74         },
75 }
76
77 func TestWriter(t *testing.T) {
78         largeData := make([]byte, 1<<17)
79         if _, err := rand.Read(largeData); err != nil {
80                 t.Fatal("rand.Read failed:", err)
81         }
82         writeTests[1].Data = largeData
83         defer func() {
84                 writeTests[1].Data = nil
85         }()
86
87         // write a zip file
88         buf := new(bytes.Buffer)
89         w := NewWriter(buf)
90
91         for _, wt := range writeTests {
92                 testCreate(t, w, &wt)
93         }
94
95         if err := w.Close(); err != nil {
96                 t.Fatal(err)
97         }
98
99         // read it back
100         r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
101         if err != nil {
102                 t.Fatal(err)
103         }
104         for i, wt := range writeTests {
105                 testReadFile(t, r.File[i], &wt)
106         }
107 }
108
109 // TestWriterComment is test for EOCD comment read/write.
110 func TestWriterComment(t *testing.T) {
111         var tests = []struct {
112                 comment string
113                 ok      bool
114         }{
115                 {"hi, hello", true},
116                 {"hi, こんにちわ", true},
117                 {strings.Repeat("a", uint16max), true},
118                 {strings.Repeat("a", uint16max+1), false},
119         }
120
121         for _, test := range tests {
122                 // write a zip file
123                 buf := new(bytes.Buffer)
124                 w := NewWriter(buf)
125                 if err := w.SetComment(test.comment); err != nil {
126                         if test.ok {
127                                 t.Fatalf("SetComment: unexpected error %v", err)
128                         }
129                         continue
130                 } else {
131                         if !test.ok {
132                                 t.Fatalf("SetComment: unexpected success, want error")
133                         }
134                 }
135
136                 if err := w.Close(); test.ok == (err != nil) {
137                         t.Fatal(err)
138                 }
139
140                 if w.closed != test.ok {
141                         t.Fatalf("Writer.closed: got %v, want %v", w.closed, test.ok)
142                 }
143
144                 // skip read test in failure cases
145                 if !test.ok {
146                         continue
147                 }
148
149                 // read it back
150                 r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
151                 if err != nil {
152                         t.Fatal(err)
153                 }
154                 if r.Comment != test.comment {
155                         t.Fatalf("Reader.Comment: got %v, want %v", r.Comment, test.comment)
156                 }
157         }
158 }
159
160 func TestWriterUTF8(t *testing.T) {
161         var utf8Tests = []struct {
162                 name    string
163                 comment string
164                 nonUTF8 bool
165                 flags   uint16
166         }{
167                 {
168                         name:    "hi, hello",
169                         comment: "in the world",
170                         flags:   0x8,
171                 },
172                 {
173                         name:    "hi, こんにちわ",
174                         comment: "in the world",
175                         flags:   0x808,
176                 },
177                 {
178                         name:    "hi, こんにちわ",
179                         comment: "in the world",
180                         nonUTF8: true,
181                         flags:   0x8,
182                 },
183                 {
184                         name:    "hi, hello",
185                         comment: "in the 世界",
186                         flags:   0x808,
187                 },
188                 {
189                         name:    "hi, こんにちわ",
190                         comment: "in the 世界",
191                         flags:   0x808,
192                 },
193                 {
194                         name:    "the replacement rune is �",
195                         comment: "the replacement rune is �",
196                         flags:   0x808,
197                 },
198                 {
199                         // Name is Japanese encoded in Shift JIS.
200                         name:    "\x93\xfa\x96{\x8c\xea.txt",
201                         comment: "in the 世界",
202                         flags:   0x008, // UTF-8 must not be set
203                 },
204         }
205
206         // write a zip file
207         buf := new(bytes.Buffer)
208         w := NewWriter(buf)
209
210         for _, test := range utf8Tests {
211                 h := &FileHeader{
212                         Name:    test.name,
213                         Comment: test.comment,
214                         NonUTF8: test.nonUTF8,
215                         Method:  Deflate,
216                 }
217                 w, err := w.CreateHeader(h)
218                 if err != nil {
219                         t.Fatal(err)
220                 }
221                 w.Write([]byte{})
222         }
223
224         if err := w.Close(); err != nil {
225                 t.Fatal(err)
226         }
227
228         // read it back
229         r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
230         if err != nil {
231                 t.Fatal(err)
232         }
233         for i, test := range utf8Tests {
234                 flags := r.File[i].Flags
235                 if flags != test.flags {
236                         t.Errorf("CreateHeader(name=%q comment=%q nonUTF8=%v): flags=%#x, want %#x", test.name, test.comment, test.nonUTF8, flags, test.flags)
237                 }
238         }
239 }
240
241 func TestWriterTime(t *testing.T) {
242         var buf bytes.Buffer
243         h := &FileHeader{
244                 Name:     "test.txt",
245                 Modified: time.Date(2017, 10, 31, 21, 11, 57, 0, timeZone(-7*time.Hour)),
246         }
247         w := NewWriter(&buf)
248         if _, err := w.CreateHeader(h); err != nil {
249                 t.Fatalf("unexpected CreateHeader error: %v", err)
250         }
251         if err := w.Close(); err != nil {
252                 t.Fatalf("unexpected Close error: %v", err)
253         }
254
255         want, err := os.ReadFile("testdata/time-go.zip")
256         if err != nil {
257                 t.Fatalf("unexpected ReadFile error: %v", err)
258         }
259         if got := buf.Bytes(); !bytes.Equal(got, want) {
260                 fmt.Printf("%x\n%x\n", got, want)
261                 t.Error("contents of time-go.zip differ")
262         }
263 }
264
265 func TestWriterOffset(t *testing.T) {
266         largeData := make([]byte, 1<<17)
267         if _, err := rand.Read(largeData); err != nil {
268                 t.Fatal("rand.Read failed:", err)
269         }
270         writeTests[1].Data = largeData
271         defer func() {
272                 writeTests[1].Data = nil
273         }()
274
275         // write a zip file
276         buf := new(bytes.Buffer)
277         existingData := []byte{1, 2, 3, 1, 2, 3, 1, 2, 3}
278         n, _ := buf.Write(existingData)
279         w := NewWriter(buf)
280         w.SetOffset(int64(n))
281
282         for _, wt := range writeTests {
283                 testCreate(t, w, &wt)
284         }
285
286         if err := w.Close(); err != nil {
287                 t.Fatal(err)
288         }
289
290         // read it back
291         r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
292         if err != nil {
293                 t.Fatal(err)
294         }
295         for i, wt := range writeTests {
296                 testReadFile(t, r.File[i], &wt)
297         }
298 }
299
300 func TestWriterFlush(t *testing.T) {
301         var buf bytes.Buffer
302         w := NewWriter(struct{ io.Writer }{&buf})
303         _, err := w.Create("foo")
304         if err != nil {
305                 t.Fatal(err)
306         }
307         if buf.Len() > 0 {
308                 t.Fatalf("Unexpected %d bytes already in buffer", buf.Len())
309         }
310         if err := w.Flush(); err != nil {
311                 t.Fatal(err)
312         }
313         if buf.Len() == 0 {
314                 t.Fatal("No bytes written after Flush")
315         }
316 }
317
318 func TestWriterDir(t *testing.T) {
319         w := NewWriter(io.Discard)
320         dw, err := w.Create("dir/")
321         if err != nil {
322                 t.Fatal(err)
323         }
324         if _, err := dw.Write(nil); err != nil {
325                 t.Errorf("Write(nil) to directory: got %v, want nil", err)
326         }
327         if _, err := dw.Write([]byte("hello")); err == nil {
328                 t.Error(`Write("hello") to directory: got nil error, want non-nil`)
329         }
330 }
331
332 func TestWriterDirAttributes(t *testing.T) {
333         var buf bytes.Buffer
334         w := NewWriter(&buf)
335         if _, err := w.CreateHeader(&FileHeader{
336                 Name:               "dir/",
337                 Method:             Deflate,
338                 CompressedSize64:   1234,
339                 UncompressedSize64: 5678,
340         }); err != nil {
341                 t.Fatal(err)
342         }
343         if err := w.Close(); err != nil {
344                 t.Fatal(err)
345         }
346         b := buf.Bytes()
347
348         var sig [4]byte
349         binary.LittleEndian.PutUint32(sig[:], uint32(fileHeaderSignature))
350
351         idx := bytes.Index(b, sig[:])
352         if idx == -1 {
353                 t.Fatal("file header not found")
354         }
355         b = b[idx:]
356
357         if !bytes.Equal(b[6:10], []byte{0, 0, 0, 0}) { // FileHeader.Flags: 0, FileHeader.Method: 0
358                 t.Errorf("unexpected method and flags: %v", b[6:10])
359         }
360
361         if !bytes.Equal(b[14:26], make([]byte, 12)) { // FileHeader.{CRC32,CompressSize,UncompressedSize} all zero.
362                 t.Errorf("unexpected crc, compress and uncompressed size to be 0 was: %v", b[14:26])
363         }
364
365         binary.LittleEndian.PutUint32(sig[:], uint32(dataDescriptorSignature))
366         if bytes.Contains(b, sig[:]) {
367                 t.Error("there should be no data descriptor")
368         }
369 }
370
371 func TestWriterCopy(t *testing.T) {
372         // make a zip file
373         buf := new(bytes.Buffer)
374         w := NewWriter(buf)
375         for _, wt := range writeTests {
376                 testCreate(t, w, &wt)
377         }
378         if err := w.Close(); err != nil {
379                 t.Fatal(err)
380         }
381
382         // read it back
383         src, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
384         if err != nil {
385                 t.Fatal(err)
386         }
387         for i, wt := range writeTests {
388                 testReadFile(t, src.File[i], &wt)
389         }
390
391         // make a new zip file copying the old compressed data.
392         buf2 := new(bytes.Buffer)
393         dst := NewWriter(buf2)
394         for _, f := range src.File {
395                 if err := dst.Copy(f); err != nil {
396                         t.Fatal(err)
397                 }
398         }
399         if err := dst.Close(); err != nil {
400                 t.Fatal(err)
401         }
402
403         // read the new one back
404         r, err := NewReader(bytes.NewReader(buf2.Bytes()), int64(buf2.Len()))
405         if err != nil {
406                 t.Fatal(err)
407         }
408         for i, wt := range writeTests {
409                 testReadFile(t, r.File[i], &wt)
410         }
411 }
412
413 func TestWriterCreateRaw(t *testing.T) {
414         files := []struct {
415                 name             string
416                 content          []byte
417                 method           uint16
418                 flags            uint16
419                 crc32            uint32
420                 uncompressedSize uint64
421                 compressedSize   uint64
422         }{
423                 {
424                         name:    "small store w desc",
425                         content: []byte("gophers"),
426                         method:  Store,
427                         flags:   0x8,
428                 },
429                 {
430                         name:    "small deflate wo desc",
431                         content: bytes.Repeat([]byte("abcdefg"), 2048),
432                         method:  Deflate,
433                 },
434         }
435
436         // write a zip file
437         archive := new(bytes.Buffer)
438         w := NewWriter(archive)
439
440         for i := range files {
441                 f := &files[i]
442                 f.crc32 = crc32.ChecksumIEEE(f.content)
443                 size := uint64(len(f.content))
444                 f.uncompressedSize = size
445                 f.compressedSize = size
446
447                 var compressedContent []byte
448                 if f.method == Deflate {
449                         var buf bytes.Buffer
450                         w, err := flate.NewWriter(&buf, flate.BestSpeed)
451                         if err != nil {
452                                 t.Fatalf("flate.NewWriter err = %v", err)
453                         }
454                         _, err = w.Write(f.content)
455                         if err != nil {
456                                 t.Fatalf("flate Write err = %v", err)
457                         }
458                         err = w.Close()
459                         if err != nil {
460                                 t.Fatalf("flate Writer.Close err = %v", err)
461                         }
462                         compressedContent = buf.Bytes()
463                         f.compressedSize = uint64(len(compressedContent))
464                 }
465
466                 h := &FileHeader{
467                         Name:               f.name,
468                         Method:             f.method,
469                         Flags:              f.flags,
470                         CRC32:              f.crc32,
471                         CompressedSize64:   f.compressedSize,
472                         UncompressedSize64: f.uncompressedSize,
473                 }
474                 w, err := w.CreateRaw(h)
475                 if err != nil {
476                         t.Fatal(err)
477                 }
478                 if compressedContent != nil {
479                         _, err = w.Write(compressedContent)
480                 } else {
481                         _, err = w.Write(f.content)
482                 }
483                 if err != nil {
484                         t.Fatalf("%s Write got %v; want nil", f.name, err)
485                 }
486         }
487
488         if err := w.Close(); err != nil {
489                 t.Fatal(err)
490         }
491
492         // read it back
493         r, err := NewReader(bytes.NewReader(archive.Bytes()), int64(archive.Len()))
494         if err != nil {
495                 t.Fatal(err)
496         }
497         for i, want := range files {
498                 got := r.File[i]
499                 if got.Name != want.name {
500                         t.Errorf("got Name %s; want %s", got.Name, want.name)
501                 }
502                 if got.Method != want.method {
503                         t.Errorf("%s: got Method %#x; want %#x", want.name, got.Method, want.method)
504                 }
505                 if got.Flags != want.flags {
506                         t.Errorf("%s: got Flags %#x; want %#x", want.name, got.Flags, want.flags)
507                 }
508                 if got.CRC32 != want.crc32 {
509                         t.Errorf("%s: got CRC32 %#x; want %#x", want.name, got.CRC32, want.crc32)
510                 }
511                 if got.CompressedSize64 != want.compressedSize {
512                         t.Errorf("%s: got CompressedSize64 %d; want %d", want.name, got.CompressedSize64, want.compressedSize)
513                 }
514                 if got.UncompressedSize64 != want.uncompressedSize {
515                         t.Errorf("%s: got UncompressedSize64 %d; want %d", want.name, got.UncompressedSize64, want.uncompressedSize)
516                 }
517
518                 r, err := got.Open()
519                 if err != nil {
520                         t.Errorf("%s: Open err = %v", got.Name, err)
521                         continue
522                 }
523
524                 buf, err := io.ReadAll(r)
525                 if err != nil {
526                         t.Errorf("%s: ReadAll err = %v", got.Name, err)
527                         continue
528                 }
529
530                 if !bytes.Equal(buf, want.content) {
531                         t.Errorf("%v: ReadAll returned unexpected bytes", got.Name)
532                 }
533         }
534 }
535
536 func testCreate(t *testing.T, w *Writer, wt *WriteTest) {
537         header := &FileHeader{
538                 Name:   wt.Name,
539                 Method: wt.Method,
540         }
541         if wt.Mode != 0 {
542                 header.SetMode(wt.Mode)
543         }
544         f, err := w.CreateHeader(header)
545         if err != nil {
546                 t.Fatal(err)
547         }
548         _, err = f.Write(wt.Data)
549         if err != nil {
550                 t.Fatal(err)
551         }
552 }
553
554 func testReadFile(t *testing.T, f *File, wt *WriteTest) {
555         if f.Name != wt.Name {
556                 t.Fatalf("File name: got %q, want %q", f.Name, wt.Name)
557         }
558         testFileMode(t, f, wt.Mode)
559         rc, err := f.Open()
560         if err != nil {
561                 t.Fatalf("opening %s: %v", f.Name, err)
562         }
563         b, err := io.ReadAll(rc)
564         if err != nil {
565                 t.Fatalf("reading %s: %v", f.Name, err)
566         }
567         err = rc.Close()
568         if err != nil {
569                 t.Fatalf("closing %s: %v", f.Name, err)
570         }
571         if !bytes.Equal(b, wt.Data) {
572                 t.Errorf("File contents %q, want %q", b, wt.Data)
573         }
574 }
575
576 func BenchmarkCompressedZipGarbage(b *testing.B) {
577         bigBuf := bytes.Repeat([]byte("a"), 1<<20)
578
579         runOnce := func(buf *bytes.Buffer) {
580                 buf.Reset()
581                 zw := NewWriter(buf)
582                 for j := 0; j < 3; j++ {
583                         w, _ := zw.CreateHeader(&FileHeader{
584                                 Name:   "foo",
585                                 Method: Deflate,
586                         })
587                         w.Write(bigBuf)
588                 }
589                 zw.Close()
590         }
591
592         b.ReportAllocs()
593         // Run once and then reset the timer.
594         // This effectively discards the very large initial flate setup cost,
595         // as well as the initialization of bigBuf.
596         runOnce(&bytes.Buffer{})
597         b.ResetTimer()
598
599         b.RunParallel(func(pb *testing.PB) {
600                 var buf bytes.Buffer
601                 for pb.Next() {
602                         runOnce(&buf)
603                 }
604         })
605 }
606
607 func writeTestsToFS(tests []WriteTest) fs.FS {
608         fsys := fstest.MapFS{}
609         for _, wt := range tests {
610                 fsys[wt.Name] = &fstest.MapFile{
611                         Data: wt.Data,
612                         Mode: wt.Mode,
613                 }
614         }
615         return fsys
616 }
617
618 func TestWriterAddFS(t *testing.T) {
619         buf := new(bytes.Buffer)
620         w := NewWriter(buf)
621         tests := []WriteTest{
622                 {
623                         Name: "file.go",
624                         Data: []byte("hello"),
625                         Mode: 0644,
626                 },
627                 {
628                         Name: "subfolder/another.go",
629                         Data: []byte("world"),
630                         Mode: 0644,
631                 },
632         }
633         err := w.AddFS(writeTestsToFS(tests))
634         if err != nil {
635                 t.Fatal(err)
636         }
637
638         if err := w.Close(); err != nil {
639                 t.Fatal(err)
640         }
641
642         // read it back
643         r, err := NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
644         if err != nil {
645                 t.Fatal(err)
646         }
647         for i, wt := range tests {
648                 testReadFile(t, r.File[i], &wt)
649         }
650 }