Source file
src/archive/tar/writer_test.go
1
2
3
4
5 package tar
6
7 import (
8 "bytes"
9 "encoding/hex"
10 "errors"
11 "internal/obscuretestdata"
12 "io"
13 "io/fs"
14 "maps"
15 "os"
16 "path"
17 "slices"
18 "sort"
19 "strings"
20 "testing"
21 "testing/fstest"
22 "testing/iotest"
23 "time"
24 )
25
26 func bytediff(a, b []byte) string {
27 const (
28 uniqueA = "- "
29 uniqueB = "+ "
30 identity = " "
31 )
32 var ss []string
33 sa := strings.Split(strings.TrimSpace(hex.Dump(a)), "\n")
34 sb := strings.Split(strings.TrimSpace(hex.Dump(b)), "\n")
35 for len(sa) > 0 && len(sb) > 0 {
36 if sa[0] == sb[0] {
37 ss = append(ss, identity+sa[0])
38 } else {
39 ss = append(ss, uniqueA+sa[0])
40 ss = append(ss, uniqueB+sb[0])
41 }
42 sa, sb = sa[1:], sb[1:]
43 }
44 for len(sa) > 0 {
45 ss = append(ss, uniqueA+sa[0])
46 sa = sa[1:]
47 }
48 for len(sb) > 0 {
49 ss = append(ss, uniqueB+sb[0])
50 sb = sb[1:]
51 }
52 return strings.Join(ss, "\n")
53 }
54
55 func TestWriter(t *testing.T) {
56 type (
57 testHeader struct {
58 hdr Header
59 wantErr error
60 }
61 testWrite struct {
62 str string
63 wantCnt int
64 wantErr error
65 }
66 testReadFrom struct {
67 ops fileOps
68 wantCnt int64
69 wantErr error
70 }
71 testClose struct {
72 wantErr error
73 }
74 testFnc any
75 )
76
77 vectors := []struct {
78 file string
79 obscured bool
80 tests []testFnc
81 }{{
82
83
84
85
86 file: "testdata/writer.tar",
87 tests: []testFnc{
88 testHeader{Header{
89 Typeflag: TypeReg,
90 Name: "small.txt",
91 Size: 5,
92 Mode: 0640,
93 Uid: 73025,
94 Gid: 5000,
95 Uname: "dsymonds",
96 Gname: "eng",
97 ModTime: time.Unix(1246508266, 0),
98 }, nil},
99 testWrite{"Kilts", 5, nil},
100
101 testHeader{Header{
102 Typeflag: TypeReg,
103 Name: "small2.txt",
104 Size: 11,
105 Mode: 0640,
106 Uid: 73025,
107 Uname: "dsymonds",
108 Gname: "eng",
109 Gid: 5000,
110 ModTime: time.Unix(1245217492, 0),
111 }, nil},
112 testWrite{"Google.com\n", 11, nil},
113
114 testHeader{Header{
115 Typeflag: TypeSymlink,
116 Name: "link.txt",
117 Linkname: "small.txt",
118 Mode: 0777,
119 Uid: 1000,
120 Gid: 1000,
121 Uname: "strings",
122 Gname: "strings",
123 ModTime: time.Unix(1314603082, 0),
124 }, nil},
125 testWrite{"", 0, nil},
126
127 testClose{nil},
128 },
129 }, {
130
131
132
133 file: "testdata/writer-big.tar",
134 tests: []testFnc{
135 testHeader{Header{
136 Typeflag: TypeReg,
137 Name: "tmp/16gig.txt",
138 Size: 16 << 30,
139 Mode: 0640,
140 Uid: 73025,
141 Gid: 5000,
142 Uname: "dsymonds",
143 Gname: "eng",
144 ModTime: time.Unix(1254699560, 0),
145 Format: FormatGNU,
146 }, nil},
147 },
148 }, {
149
150
151
152
153
154
155
156 file: "testdata/writer-big-long.tar.base64",
157 obscured: true,
158 tests: []testFnc{
159 testHeader{Header{
160 Typeflag: TypeReg,
161 Name: strings.Repeat("longname/", 15) + "16gig.txt",
162 Size: 16 << 30,
163 Mode: 0644,
164 Uid: 1000,
165 Gid: 1000,
166 Uname: "guillaume",
167 Gname: "guillaume",
168 ModTime: time.Unix(1399583047, 0),
169 }, nil},
170 },
171 }, {
172
173
174 file: "testdata/ustar.tar",
175 tests: []testFnc{
176 testHeader{Header{
177 Typeflag: TypeReg,
178 Name: strings.Repeat("longname/", 15) + "file.txt",
179 Size: 6,
180 Mode: 0644,
181 Uid: 501,
182 Gid: 20,
183 Uname: "shane",
184 Gname: "staff",
185 ModTime: time.Unix(1360135598, 0),
186 }, nil},
187 testWrite{"hello\n", 6, nil},
188 testClose{nil},
189 },
190 }, {
191
192
193
194
195 file: "testdata/hardlink.tar",
196 tests: []testFnc{
197 testHeader{Header{
198 Typeflag: TypeReg,
199 Name: "file.txt",
200 Size: 15,
201 Mode: 0644,
202 Uid: 1000,
203 Gid: 100,
204 Uname: "vbatts",
205 Gname: "users",
206 ModTime: time.Unix(1425484303, 0),
207 }, nil},
208 testWrite{"Slartibartfast\n", 15, nil},
209
210 testHeader{Header{
211 Typeflag: TypeLink,
212 Name: "hard.txt",
213 Linkname: "file.txt",
214 Mode: 0644,
215 Uid: 1000,
216 Gid: 100,
217 Uname: "vbatts",
218 Gname: "users",
219 ModTime: time.Unix(1425484303, 0),
220 }, nil},
221 testWrite{"", 0, nil},
222
223 testClose{nil},
224 },
225 }, {
226 tests: []testFnc{
227 testHeader{Header{
228 Typeflag: TypeReg,
229 Name: "bad-null.txt",
230 Xattrs: map[string]string{"null\x00null\x00": "fizzbuzz"},
231 }, headerError{}},
232 },
233 }, {
234 tests: []testFnc{
235 testHeader{Header{
236 Typeflag: TypeReg,
237 Name: "null\x00.txt",
238 }, headerError{}},
239 },
240 }, {
241 file: "testdata/pax-records.tar",
242 tests: []testFnc{
243 testHeader{Header{
244 Typeflag: TypeReg,
245 Name: "file",
246 Uname: strings.Repeat("long", 10),
247 PAXRecords: map[string]string{
248 "path": "FILE",
249 "GNU.sparse.map": "0,0",
250 "comment": "Hello, 世界",
251 "GOLANG.pkg": "tar",
252 },
253 }, nil},
254 testClose{nil},
255 },
256 }, {
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287 file: "testdata/pax-global-records.tar",
288 tests: []testFnc{
289 testHeader{Header{
290 Typeflag: TypeXGlobalHeader,
291 PAXRecords: map[string]string{"path": "global1", "mtime": "1500000000.0"},
292 }, nil},
293 testHeader{Header{
294 Typeflag: TypeReg, Name: "file1",
295 }, nil},
296 testHeader{Header{
297 Typeflag: TypeReg,
298 Name: "file2",
299 PAXRecords: map[string]string{"path": "file2"},
300 }, nil},
301 testHeader{Header{
302 Typeflag: TypeXGlobalHeader,
303 PAXRecords: map[string]string{"path": ""},
304 }, nil},
305 testHeader{Header{
306 Typeflag: TypeReg, Name: "file3",
307 }, nil},
308 testHeader{Header{
309 Typeflag: TypeReg,
310 Name: "file4",
311 ModTime: time.Unix(1400000000, 0),
312 PAXRecords: map[string]string{"mtime": "1400000000"},
313 }, nil},
314 testClose{nil},
315 },
316 }, {
317 file: "testdata/gnu-utf8.tar",
318 tests: []testFnc{
319 testHeader{Header{
320 Typeflag: TypeReg,
321 Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",
322 Mode: 0644,
323 Uid: 1000, Gid: 1000,
324 Uname: "☺",
325 Gname: "⚹",
326 ModTime: time.Unix(0, 0),
327 Format: FormatGNU,
328 }, nil},
329 testClose{nil},
330 },
331 }, {
332 file: "testdata/gnu-not-utf8.tar",
333 tests: []testFnc{
334 testHeader{Header{
335 Typeflag: TypeReg,
336 Name: "hi\x80\x81\x82\x83bye",
337 Mode: 0644,
338 Uid: 1000,
339 Gid: 1000,
340 Uname: "rawr",
341 Gname: "dsnet",
342 ModTime: time.Unix(0, 0),
343 Format: FormatGNU,
344 }, nil},
345 testClose{nil},
346 },
347
348
349
465 }, {
466 file: "testdata/trailing-slash.tar",
467 tests: []testFnc{
468 testHeader{Header{Name: strings.Repeat("123456789/", 30)}, nil},
469 testClose{nil},
470 },
471 }, {
472
473 file: "testdata/file-and-dir.tar",
474 tests: []testFnc{
475 testHeader{Header{Name: "small.txt", Size: 5}, nil},
476 testWrite{"Kilts", 5, nil},
477 testHeader{Header{Name: "dir/"}, nil},
478 testClose{nil},
479 },
480 }}
481
482 equalError := func(x, y error) bool {
483 _, ok1 := x.(headerError)
484 _, ok2 := y.(headerError)
485 if ok1 || ok2 {
486 return ok1 && ok2
487 }
488 return x == y
489 }
490 for _, v := range vectors {
491 t.Run(strings.TrimSuffix(path.Base(v.file), ".base64"), func(t *testing.T) {
492 const maxSize = 10 << 10
493 buf := new(bytes.Buffer)
494 tw := NewWriter(iotest.TruncateWriter(buf, maxSize))
495
496 for i, tf := range v.tests {
497 switch tf := tf.(type) {
498 case testHeader:
499 err := tw.WriteHeader(&tf.hdr)
500 if !equalError(err, tf.wantErr) {
501 t.Fatalf("test %d, WriteHeader() = %v, want %v", i, err, tf.wantErr)
502 }
503 case testWrite:
504 got, err := tw.Write([]byte(tf.str))
505 if got != tf.wantCnt || !equalError(err, tf.wantErr) {
506 t.Fatalf("test %d, Write() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr)
507 }
508 case testReadFrom:
509 f := &testFile{ops: tf.ops}
510 got, err := tw.readFrom(f)
511 if _, ok := err.(testError); ok {
512 t.Errorf("test %d, ReadFrom(): %v", i, err)
513 } else if got != tf.wantCnt || !equalError(err, tf.wantErr) {
514 t.Errorf("test %d, ReadFrom() = (%d, %v), want (%d, %v)", i, got, err, tf.wantCnt, tf.wantErr)
515 }
516 if len(f.ops) > 0 {
517 t.Errorf("test %d, expected %d more operations", i, len(f.ops))
518 }
519 case testClose:
520 err := tw.Close()
521 if !equalError(err, tf.wantErr) {
522 t.Fatalf("test %d, Close() = %v, want %v", i, err, tf.wantErr)
523 }
524 default:
525 t.Fatalf("test %d, unknown test operation: %T", i, tf)
526 }
527 }
528
529 if v.file != "" {
530 path := v.file
531 if v.obscured {
532 tf, err := obscuretestdata.DecodeToTempFile(path)
533 if err != nil {
534 t.Fatalf("obscuretestdata.DecodeToTempFile(%s): %v", path, err)
535 }
536 path = tf
537 }
538
539 want, err := os.ReadFile(path)
540 if err != nil {
541 t.Fatalf("ReadFile() = %v, want nil", err)
542 }
543 got := buf.Bytes()
544 if !bytes.Equal(want, got) {
545 t.Fatalf("incorrect result: (-got +want)\n%v", bytediff(got, want))
546 }
547 }
548 })
549 }
550 }
551
552 func TestPax(t *testing.T) {
553
554 fileinfo, err := os.Stat("testdata/small.txt")
555 if err != nil {
556 t.Fatal(err)
557 }
558 hdr, err := FileInfoHeader(fileinfo, "")
559 if err != nil {
560 t.Fatalf("os.Stat: %v", err)
561 }
562
563 longName := strings.Repeat("ab", 100)
564 contents := strings.Repeat(" ", int(hdr.Size))
565 hdr.Name = longName
566 var buf bytes.Buffer
567 writer := NewWriter(&buf)
568 if err := writer.WriteHeader(hdr); err != nil {
569 t.Fatal(err)
570 }
571 if _, err = writer.Write([]byte(contents)); err != nil {
572 t.Fatal(err)
573 }
574 if err := writer.Close(); err != nil {
575 t.Fatal(err)
576 }
577
578 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
579 t.Fatal("Expected at least one PAX header to be written.")
580 }
581
582 reader := NewReader(&buf)
583 hdr, err = reader.Next()
584 if err != nil {
585 t.Fatal(err)
586 }
587 if hdr.Name != longName {
588 t.Fatal("Couldn't recover long file name")
589 }
590 }
591
592 func TestPaxSymlink(t *testing.T) {
593
594 fileinfo, err := os.Stat("testdata/small.txt")
595 if err != nil {
596 t.Fatal(err)
597 }
598 hdr, err := FileInfoHeader(fileinfo, "")
599 if err != nil {
600 t.Fatalf("os.Stat:1 %v", err)
601 }
602 hdr.Typeflag = TypeSymlink
603
604 longLinkname := strings.Repeat("1234567890/1234567890", 10)
605 hdr.Linkname = longLinkname
606
607 hdr.Size = 0
608 var buf bytes.Buffer
609 writer := NewWriter(&buf)
610 if err := writer.WriteHeader(hdr); err != nil {
611 t.Fatal(err)
612 }
613 if err := writer.Close(); err != nil {
614 t.Fatal(err)
615 }
616
617 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
618 t.Fatal("Expected at least one PAX header to be written.")
619 }
620
621 reader := NewReader(&buf)
622 hdr, err = reader.Next()
623 if err != nil {
624 t.Fatal(err)
625 }
626 if hdr.Linkname != longLinkname {
627 t.Fatal("Couldn't recover long link name")
628 }
629 }
630
631 func TestPaxNonAscii(t *testing.T) {
632
633
634 fileinfo, err := os.Stat("testdata/small.txt")
635 if err != nil {
636 t.Fatal(err)
637 }
638
639 hdr, err := FileInfoHeader(fileinfo, "")
640 if err != nil {
641 t.Fatalf("os.Stat:1 %v", err)
642 }
643
644
645 chineseFilename := "文件名"
646 chineseGroupname := "組"
647 chineseUsername := "用戶名"
648
649 hdr.Name = chineseFilename
650 hdr.Gname = chineseGroupname
651 hdr.Uname = chineseUsername
652
653 contents := strings.Repeat(" ", int(hdr.Size))
654
655 var buf bytes.Buffer
656 writer := NewWriter(&buf)
657 if err := writer.WriteHeader(hdr); err != nil {
658 t.Fatal(err)
659 }
660 if _, err = writer.Write([]byte(contents)); err != nil {
661 t.Fatal(err)
662 }
663 if err := writer.Close(); err != nil {
664 t.Fatal(err)
665 }
666
667 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
668 t.Fatal("Expected at least one PAX header to be written.")
669 }
670
671 reader := NewReader(&buf)
672 hdr, err = reader.Next()
673 if err != nil {
674 t.Fatal(err)
675 }
676 if hdr.Name != chineseFilename {
677 t.Fatal("Couldn't recover unicode name")
678 }
679 if hdr.Gname != chineseGroupname {
680 t.Fatal("Couldn't recover unicode group")
681 }
682 if hdr.Uname != chineseUsername {
683 t.Fatal("Couldn't recover unicode user")
684 }
685 }
686
687 func TestPaxXattrs(t *testing.T) {
688 xattrs := map[string]string{
689 "user.key": "value",
690 }
691
692
693 fileinfo, err := os.Stat("testdata/small.txt")
694 if err != nil {
695 t.Fatal(err)
696 }
697 hdr, err := FileInfoHeader(fileinfo, "")
698 if err != nil {
699 t.Fatalf("os.Stat: %v", err)
700 }
701 contents := "Kilts"
702 hdr.Xattrs = xattrs
703 var buf bytes.Buffer
704 writer := NewWriter(&buf)
705 if err := writer.WriteHeader(hdr); err != nil {
706 t.Fatal(err)
707 }
708 if _, err = writer.Write([]byte(contents)); err != nil {
709 t.Fatal(err)
710 }
711 if err := writer.Close(); err != nil {
712 t.Fatal(err)
713 }
714
715 reader := NewReader(&buf)
716 hdr, err = reader.Next()
717 if err != nil {
718 t.Fatal(err)
719 }
720 if !maps.Equal(hdr.Xattrs, xattrs) {
721 t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
722 hdr.Xattrs, xattrs)
723 }
724 }
725
726 func TestPaxHeadersSorted(t *testing.T) {
727 fileinfo, err := os.Stat("testdata/small.txt")
728 if err != nil {
729 t.Fatal(err)
730 }
731 hdr, err := FileInfoHeader(fileinfo, "")
732 if err != nil {
733 t.Fatalf("os.Stat: %v", err)
734 }
735 contents := strings.Repeat(" ", int(hdr.Size))
736
737 hdr.Xattrs = map[string]string{
738 "foo": "foo",
739 "bar": "bar",
740 "baz": "baz",
741 "qux": "qux",
742 }
743
744 var buf bytes.Buffer
745 writer := NewWriter(&buf)
746 if err := writer.WriteHeader(hdr); err != nil {
747 t.Fatal(err)
748 }
749 if _, err = writer.Write([]byte(contents)); err != nil {
750 t.Fatal(err)
751 }
752 if err := writer.Close(); err != nil {
753 t.Fatal(err)
754 }
755
756 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
757 t.Fatal("Expected at least one PAX header to be written.")
758 }
759
760
761 indices := []int{
762 bytes.Index(buf.Bytes(), []byte("bar=bar")),
763 bytes.Index(buf.Bytes(), []byte("baz=baz")),
764 bytes.Index(buf.Bytes(), []byte("foo=foo")),
765 bytes.Index(buf.Bytes(), []byte("qux=qux")),
766 }
767 if !slices.IsSorted(indices) {
768 t.Fatal("PAX headers are not sorted")
769 }
770 }
771
772 func TestUSTARLongName(t *testing.T) {
773
774 fileinfo, err := os.Stat("testdata/small.txt")
775 if err != nil {
776 t.Fatal(err)
777 }
778 hdr, err := FileInfoHeader(fileinfo, "")
779 if err != nil {
780 t.Fatalf("os.Stat:1 %v", err)
781 }
782 hdr.Typeflag = TypeDir
783
784
785 longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
786 hdr.Name = longName
787
788 hdr.Size = 0
789 var buf bytes.Buffer
790 writer := NewWriter(&buf)
791 if err := writer.WriteHeader(hdr); err != nil {
792 t.Fatal(err)
793 }
794 if err := writer.Close(); err != nil {
795 t.Fatal(err)
796 }
797
798 reader := NewReader(&buf)
799 hdr, err = reader.Next()
800 if err != nil && err != ErrInsecurePath {
801 t.Fatal(err)
802 }
803 if hdr.Name != longName {
804 t.Fatal("Couldn't recover long name")
805 }
806 }
807
808 func TestValidTypeflagWithPAXHeader(t *testing.T) {
809 var buffer bytes.Buffer
810 tw := NewWriter(&buffer)
811
812 fileName := strings.Repeat("ab", 100)
813
814 hdr := &Header{
815 Name: fileName,
816 Size: 4,
817 Typeflag: 0,
818 }
819 if err := tw.WriteHeader(hdr); err != nil {
820 t.Fatalf("Failed to write header: %s", err)
821 }
822 if _, err := tw.Write([]byte("fooo")); err != nil {
823 t.Fatalf("Failed to write the file's data: %s", err)
824 }
825 tw.Close()
826
827 tr := NewReader(&buffer)
828
829 for {
830 header, err := tr.Next()
831 if err == io.EOF {
832 break
833 }
834 if err != nil {
835 t.Fatalf("Failed to read header: %s", err)
836 }
837 if header.Typeflag != TypeReg {
838 t.Fatalf("Typeflag should've been %d, found %d", TypeReg, header.Typeflag)
839 }
840 }
841 }
842
843
844 type failOnceWriter bool
845
846 func (w *failOnceWriter) Write(b []byte) (int, error) {
847 if !*w {
848 return 0, io.ErrShortWrite
849 }
850 *w = true
851 return len(b), nil
852 }
853
854 func TestWriterErrors(t *testing.T) {
855 t.Run("HeaderOnly", func(t *testing.T) {
856 tw := NewWriter(new(bytes.Buffer))
857 hdr := &Header{Name: "dir/", Typeflag: TypeDir}
858 if err := tw.WriteHeader(hdr); err != nil {
859 t.Fatalf("WriteHeader() = %v, want nil", err)
860 }
861 if _, err := tw.Write([]byte{0x00}); err != ErrWriteTooLong {
862 t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
863 }
864 })
865
866 t.Run("NegativeSize", func(t *testing.T) {
867 tw := NewWriter(new(bytes.Buffer))
868 hdr := &Header{Name: "small.txt", Size: -1}
869 if err := tw.WriteHeader(hdr); err == nil {
870 t.Fatalf("WriteHeader() = nil, want non-nil error")
871 }
872 })
873
874 t.Run("BeforeHeader", func(t *testing.T) {
875 tw := NewWriter(new(bytes.Buffer))
876 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteTooLong {
877 t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
878 }
879 })
880
881 t.Run("AfterClose", func(t *testing.T) {
882 tw := NewWriter(new(bytes.Buffer))
883 hdr := &Header{Name: "small.txt"}
884 if err := tw.WriteHeader(hdr); err != nil {
885 t.Fatalf("WriteHeader() = %v, want nil", err)
886 }
887 if err := tw.Close(); err != nil {
888 t.Fatalf("Close() = %v, want nil", err)
889 }
890 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
891 t.Fatalf("Write() = %v, want %v", err, ErrWriteAfterClose)
892 }
893 if err := tw.Flush(); err != ErrWriteAfterClose {
894 t.Fatalf("Flush() = %v, want %v", err, ErrWriteAfterClose)
895 }
896 if err := tw.Close(); err != nil {
897 t.Fatalf("Close() = %v, want nil", err)
898 }
899 })
900
901 t.Run("PrematureFlush", func(t *testing.T) {
902 tw := NewWriter(new(bytes.Buffer))
903 hdr := &Header{Name: "small.txt", Size: 5}
904 if err := tw.WriteHeader(hdr); err != nil {
905 t.Fatalf("WriteHeader() = %v, want nil", err)
906 }
907 if err := tw.Flush(); err == nil {
908 t.Fatalf("Flush() = %v, want non-nil error", err)
909 }
910 })
911
912 t.Run("PrematureClose", func(t *testing.T) {
913 tw := NewWriter(new(bytes.Buffer))
914 hdr := &Header{Name: "small.txt", Size: 5}
915 if err := tw.WriteHeader(hdr); err != nil {
916 t.Fatalf("WriteHeader() = %v, want nil", err)
917 }
918 if err := tw.Close(); err == nil {
919 t.Fatalf("Close() = %v, want non-nil error", err)
920 }
921 })
922
923 t.Run("Persistence", func(t *testing.T) {
924 tw := NewWriter(new(failOnceWriter))
925 if err := tw.WriteHeader(&Header{}); err != io.ErrShortWrite {
926 t.Fatalf("WriteHeader() = %v, want %v", err, io.ErrShortWrite)
927 }
928 if err := tw.WriteHeader(&Header{Name: "small.txt"}); err == nil {
929 t.Errorf("WriteHeader() = got %v, want non-nil error", err)
930 }
931 if _, err := tw.Write(nil); err == nil {
932 t.Errorf("Write() = %v, want non-nil error", err)
933 }
934 if err := tw.Flush(); err == nil {
935 t.Errorf("Flush() = %v, want non-nil error", err)
936 }
937 if err := tw.Close(); err == nil {
938 t.Errorf("Close() = %v, want non-nil error", err)
939 }
940 })
941 }
942
943 func TestSplitUSTARPath(t *testing.T) {
944 sr := strings.Repeat
945
946 vectors := []struct {
947 input string
948 prefix string
949 suffix string
950 ok bool
951 }{
952 {"", "", "", false},
953 {"abc", "", "", false},
954 {"用戶名", "", "", false},
955 {sr("a", nameSize), "", "", false},
956 {sr("a", nameSize) + "/", "", "", false},
957 {sr("a", nameSize) + "/a", sr("a", nameSize), "a", true},
958 {sr("a", prefixSize) + "/", "", "", false},
959 {sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true},
960 {sr("a", nameSize+1), "", "", false},
961 {sr("/", nameSize+1), sr("/", nameSize-1), "/", true},
962 {sr("a", prefixSize) + "/" + sr("b", nameSize),
963 sr("a", prefixSize), sr("b", nameSize), true},
964 {sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false},
965 {sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true},
966 }
967
968 for _, v := range vectors {
969 prefix, suffix, ok := splitUSTARPath(v.input)
970 if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
971 t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)",
972 v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
973 }
974 }
975 }
976
977
978
979
980 func TestIssue12594(t *testing.T) {
981 names := []string{
982 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/file.txt",
983 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/file.txt",
984 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/333/file.txt",
985 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/file.txt",
986 "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt",
987 "/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend",
988 }
989
990 for i, name := range names {
991 var b bytes.Buffer
992
993 tw := NewWriter(&b)
994 if err := tw.WriteHeader(&Header{
995 Name: name,
996 Uid: 1 << 25,
997 }); err != nil {
998 t.Errorf("test %d, unexpected WriteHeader error: %v", i, err)
999 }
1000 if err := tw.Close(); err != nil {
1001 t.Errorf("test %d, unexpected Close error: %v", i, err)
1002 }
1003
1004
1005 var blk block
1006 copy(blk[:], b.Bytes())
1007 prefix := string(blk.toUSTAR().prefix())
1008 prefix, _, _ = strings.Cut(prefix, "\x00")
1009 if blk.getFormat() == FormatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) {
1010 t.Errorf("test %d, found prefix in GNU format: %s", i, prefix)
1011 }
1012
1013 tr := NewReader(&b)
1014 hdr, err := tr.Next()
1015 if err != nil && err != ErrInsecurePath {
1016 t.Errorf("test %d, unexpected Next error: %v", i, err)
1017 }
1018 if hdr.Name != name {
1019 t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name)
1020 }
1021 }
1022 }
1023
1024 func TestWriteLongHeader(t *testing.T) {
1025 for _, test := range []struct {
1026 name string
1027 h *Header
1028 }{{
1029 name: "name too long",
1030 h: &Header{Name: strings.Repeat("a", maxSpecialFileSize)},
1031 }, {
1032 name: "linkname too long",
1033 h: &Header{Linkname: strings.Repeat("a", maxSpecialFileSize)},
1034 }, {
1035 name: "uname too long",
1036 h: &Header{Uname: strings.Repeat("a", maxSpecialFileSize)},
1037 }, {
1038 name: "gname too long",
1039 h: &Header{Gname: strings.Repeat("a", maxSpecialFileSize)},
1040 }, {
1041 name: "PAX header too long",
1042 h: &Header{PAXRecords: map[string]string{"GOLANG.x": strings.Repeat("a", maxSpecialFileSize)}},
1043 }} {
1044 w := NewWriter(io.Discard)
1045 if err := w.WriteHeader(test.h); err != ErrFieldTooLong {
1046 t.Errorf("%v: w.WriteHeader() = %v, want ErrFieldTooLong", test.name, err)
1047 }
1048 }
1049 }
1050
1051
1052
1053 type testNonEmptyWriter struct{ io.Writer }
1054
1055 func (w testNonEmptyWriter) Write(b []byte) (int, error) {
1056 if len(b) == 0 {
1057 return 0, errors.New("unexpected empty Write call")
1058 }
1059 return w.Writer.Write(b)
1060 }
1061
1062 func TestFileWriter(t *testing.T) {
1063 type (
1064 testWrite struct {
1065 str string
1066 wantCnt int
1067 wantErr error
1068 }
1069 testReadFrom struct {
1070 ops fileOps
1071 wantCnt int64
1072 wantErr error
1073 }
1074 testRemaining struct {
1075 wantLCnt int64
1076 wantPCnt int64
1077 }
1078 testFnc any
1079 )
1080
1081 type (
1082 makeReg struct {
1083 size int64
1084 wantStr string
1085 }
1086 makeSparse struct {
1087 makeReg makeReg
1088 sph sparseHoles
1089 size int64
1090 }
1091 fileMaker any
1092 )
1093
1094 vectors := []struct {
1095 maker fileMaker
1096 tests []testFnc
1097 }{{
1098 maker: makeReg{0, ""},
1099 tests: []testFnc{
1100 testRemaining{0, 0},
1101 testWrite{"", 0, nil},
1102 testWrite{"a", 0, ErrWriteTooLong},
1103 testReadFrom{fileOps{""}, 0, nil},
1104 testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong},
1105 testRemaining{0, 0},
1106 },
1107 }, {
1108 maker: makeReg{1, "a"},
1109 tests: []testFnc{
1110 testRemaining{1, 1},
1111 testWrite{"", 0, nil},
1112 testWrite{"a", 1, nil},
1113 testWrite{"bcde", 0, ErrWriteTooLong},
1114 testWrite{"", 0, nil},
1115 testReadFrom{fileOps{""}, 0, nil},
1116 testReadFrom{fileOps{"a"}, 0, ErrWriteTooLong},
1117 testRemaining{0, 0},
1118 },
1119 }, {
1120 maker: makeReg{5, "hello"},
1121 tests: []testFnc{
1122 testRemaining{5, 5},
1123 testWrite{"hello", 5, nil},
1124 testRemaining{0, 0},
1125 },
1126 }, {
1127 maker: makeReg{5, "\x00\x00\x00\x00\x00"},
1128 tests: []testFnc{
1129 testRemaining{5, 5},
1130 testReadFrom{fileOps{"\x00\x00\x00\x00\x00"}, 5, nil},
1131 testRemaining{0, 0},
1132 },
1133 }, {
1134 maker: makeReg{5, "\x00\x00\x00\x00\x00"},
1135 tests: []testFnc{
1136 testRemaining{5, 5},
1137 testReadFrom{fileOps{"\x00\x00\x00\x00\x00extra"}, 5, ErrWriteTooLong},
1138 testRemaining{0, 0},
1139 },
1140 }, {
1141 maker: makeReg{5, "abc\x00\x00"},
1142 tests: []testFnc{
1143 testRemaining{5, 5},
1144 testWrite{"abc", 3, nil},
1145 testRemaining{2, 2},
1146 testReadFrom{fileOps{"\x00\x00"}, 2, nil},
1147 testRemaining{0, 0},
1148 },
1149 }, {
1150 maker: makeReg{5, "\x00\x00abc"},
1151 tests: []testFnc{
1152 testRemaining{5, 5},
1153 testWrite{"\x00\x00", 2, nil},
1154 testRemaining{3, 3},
1155 testWrite{"abc", 3, nil},
1156 testReadFrom{fileOps{"z"}, 0, ErrWriteTooLong},
1157 testWrite{"z", 0, ErrWriteTooLong},
1158 testRemaining{0, 0},
1159 },
1160 }, {
1161 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
1162 tests: []testFnc{
1163 testRemaining{8, 5},
1164 testWrite{"ab\x00\x00\x00cde", 8, nil},
1165 testWrite{"a", 0, ErrWriteTooLong},
1166 testRemaining{0, 0},
1167 },
1168 }, {
1169 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
1170 tests: []testFnc{
1171 testWrite{"ab\x00\x00\x00cdez", 8, ErrWriteTooLong},
1172 testRemaining{0, 0},
1173 },
1174 }, {
1175 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
1176 tests: []testFnc{
1177 testWrite{"ab\x00", 3, nil},
1178 testRemaining{5, 3},
1179 testWrite{"\x00\x00cde", 5, nil},
1180 testWrite{"a", 0, ErrWriteTooLong},
1181 testRemaining{0, 0},
1182 },
1183 }, {
1184 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
1185 tests: []testFnc{
1186 testWrite{"ab", 2, nil},
1187 testRemaining{6, 3},
1188 testReadFrom{fileOps{int64(3), "cde"}, 6, nil},
1189 testRemaining{0, 0},
1190 },
1191 }, {
1192 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
1193 tests: []testFnc{
1194 testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, nil},
1195 testRemaining{0, 0},
1196 },
1197 }, {
1198 maker: makeSparse{makeReg{5, "abcde"}, sparseHoles{{2, 3}}, 8},
1199 tests: []testFnc{
1200 testReadFrom{fileOps{"ab", int64(3), "cdeX"}, 8, ErrWriteTooLong},
1201 testRemaining{0, 0},
1202 },
1203 }, {
1204 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
1205 tests: []testFnc{
1206 testReadFrom{fileOps{"ab", int64(3), "cd"}, 7, io.ErrUnexpectedEOF},
1207 testRemaining{1, 0},
1208 },
1209 }, {
1210 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
1211 tests: []testFnc{
1212 testReadFrom{fileOps{"ab", int64(3), "cde"}, 7, errMissData},
1213 testRemaining{1, 0},
1214 },
1215 }, {
1216 maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8},
1217 tests: []testFnc{
1218 testReadFrom{fileOps{"ab", int64(3), "cde"}, 8, errUnrefData},
1219 testRemaining{0, 1},
1220 },
1221 }, {
1222 maker: makeSparse{makeReg{4, "abcd"}, sparseHoles{{2, 3}}, 8},
1223 tests: []testFnc{
1224 testWrite{"ab", 2, nil},
1225 testRemaining{6, 2},
1226 testWrite{"\x00\x00\x00", 3, nil},
1227 testRemaining{3, 2},
1228 testWrite{"cde", 2, errMissData},
1229 testRemaining{1, 0},
1230 },
1231 }, {
1232 maker: makeSparse{makeReg{6, "abcde"}, sparseHoles{{2, 3}}, 8},
1233 tests: []testFnc{
1234 testWrite{"ab", 2, nil},
1235 testRemaining{6, 4},
1236 testWrite{"\x00\x00\x00", 3, nil},
1237 testRemaining{3, 4},
1238 testWrite{"cde", 3, errUnrefData},
1239 testRemaining{0, 1},
1240 },
1241 }, {
1242 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
1243 tests: []testFnc{
1244 testRemaining{7, 3},
1245 testWrite{"\x00\x00abc\x00\x00", 7, nil},
1246 testRemaining{0, 0},
1247 },
1248 }, {
1249 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
1250 tests: []testFnc{
1251 testRemaining{7, 3},
1252 testReadFrom{fileOps{int64(2), "abc", int64(1), "\x00"}, 7, nil},
1253 testRemaining{0, 0},
1254 },
1255 }, {
1256 maker: makeSparse{makeReg{3, ""}, sparseHoles{{0, 2}, {5, 2}}, 7},
1257 tests: []testFnc{
1258 testWrite{"abcdefg", 0, errWriteHole},
1259 },
1260 }, {
1261 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
1262 tests: []testFnc{
1263 testWrite{"\x00\x00abcde", 5, errWriteHole},
1264 },
1265 }, {
1266 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
1267 tests: []testFnc{
1268 testWrite{"\x00\x00abc\x00\x00z", 7, ErrWriteTooLong},
1269 testRemaining{0, 0},
1270 },
1271 }, {
1272 maker: makeSparse{makeReg{3, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
1273 tests: []testFnc{
1274 testWrite{"\x00\x00", 2, nil},
1275 testRemaining{5, 3},
1276 testWrite{"abc", 3, nil},
1277 testRemaining{2, 0},
1278 testWrite{"\x00\x00", 2, nil},
1279 testRemaining{0, 0},
1280 },
1281 }, {
1282 maker: makeSparse{makeReg{2, "ab"}, sparseHoles{{0, 2}, {5, 2}}, 7},
1283 tests: []testFnc{
1284 testWrite{"\x00\x00", 2, nil},
1285 testWrite{"abc", 2, errMissData},
1286 testWrite{"\x00\x00", 0, errMissData},
1287 },
1288 }, {
1289 maker: makeSparse{makeReg{4, "abc"}, sparseHoles{{0, 2}, {5, 2}}, 7},
1290 tests: []testFnc{
1291 testWrite{"\x00\x00", 2, nil},
1292 testWrite{"abc", 3, nil},
1293 testWrite{"\x00\x00", 2, errUnrefData},
1294 },
1295 }}
1296
1297 for i, v := range vectors {
1298 var wantStr string
1299 bb := new(strings.Builder)
1300 w := testNonEmptyWriter{bb}
1301 var fw fileWriter
1302 switch maker := v.maker.(type) {
1303 case makeReg:
1304 fw = ®FileWriter{w, maker.size}
1305 wantStr = maker.wantStr
1306 case makeSparse:
1307 if !validateSparseEntries(maker.sph, maker.size) {
1308 t.Fatalf("invalid sparse map: %v", maker.sph)
1309 }
1310 spd := invertSparseEntries(maker.sph, maker.size)
1311 fw = ®FileWriter{w, maker.makeReg.size}
1312 fw = &sparseFileWriter{fw, spd, 0}
1313 wantStr = maker.makeReg.wantStr
1314 default:
1315 t.Fatalf("test %d, unknown make operation: %T", i, maker)
1316 }
1317
1318 for j, tf := range v.tests {
1319 switch tf := tf.(type) {
1320 case testWrite:
1321 got, err := fw.Write([]byte(tf.str))
1322 if got != tf.wantCnt || err != tf.wantErr {
1323 t.Errorf("test %d.%d, Write(%s):\ngot (%d, %v)\nwant (%d, %v)", i, j, tf.str, got, err, tf.wantCnt, tf.wantErr)
1324 }
1325 case testReadFrom:
1326 f := &testFile{ops: tf.ops}
1327 got, err := fw.ReadFrom(f)
1328 if _, ok := err.(testError); ok {
1329 t.Errorf("test %d.%d, ReadFrom(): %v", i, j, err)
1330 } else if got != tf.wantCnt || err != tf.wantErr {
1331 t.Errorf("test %d.%d, ReadFrom() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr)
1332 }
1333 if len(f.ops) > 0 {
1334 t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
1335 }
1336 case testRemaining:
1337 if got := fw.logicalRemaining(); got != tf.wantLCnt {
1338 t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
1339 }
1340 if got := fw.physicalRemaining(); got != tf.wantPCnt {
1341 t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
1342 }
1343 default:
1344 t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
1345 }
1346 }
1347
1348 if got := bb.String(); got != wantStr {
1349 t.Fatalf("test %d, String() = %q, want %q", i, got, wantStr)
1350 }
1351 }
1352 }
1353
1354 func TestWriterAddFS(t *testing.T) {
1355 fsys := fstest.MapFS{
1356 "emptyfolder": {Mode: 0o755 | os.ModeDir},
1357 "file.go": {Data: []byte("hello")},
1358 "subfolder/another.go": {Data: []byte("world")},
1359 "symlink.go": {Mode: 0o777 | os.ModeSymlink, Data: []byte("file.go")},
1360
1361
1362 }
1363 var buf bytes.Buffer
1364 tw := NewWriter(&buf)
1365 if err := tw.AddFS(fsys); err != nil {
1366 t.Fatal(err)
1367 }
1368 if err := tw.Close(); err != nil {
1369 t.Fatal(err)
1370 }
1371
1372
1373 fsys["subfolder"] = &fstest.MapFile{Mode: 0o555 | os.ModeDir}
1374
1375
1376 tr := NewReader(&buf)
1377
1378 names := make([]string, 0, len(fsys))
1379 for name := range fsys {
1380 names = append(names, name)
1381 }
1382 sort.Strings(names)
1383
1384 entriesLeft := len(fsys)
1385 for _, name := range names {
1386 entriesLeft--
1387
1388 entryInfo, err := fsys.Lstat(name)
1389 if err != nil {
1390 t.Fatalf("getting entry info error: %v", err)
1391 }
1392 hdr, err := tr.Next()
1393 if err == io.EOF {
1394 break
1395 }
1396 if err != nil {
1397 t.Fatal(err)
1398 }
1399
1400 tmpName := name
1401 if entryInfo.IsDir() {
1402 tmpName += "/"
1403 }
1404 if hdr.Name != tmpName {
1405 t.Errorf("test fs has filename %v; archive header has %v",
1406 name, hdr.Name)
1407 }
1408
1409 if entryInfo.Mode() != hdr.FileInfo().Mode() {
1410 t.Errorf("%s: test fs has mode %v; archive header has %v",
1411 name, entryInfo.Mode(), hdr.FileInfo().Mode())
1412 }
1413
1414 switch entryInfo.Mode().Type() {
1415 case fs.ModeDir:
1416
1417 case fs.ModeSymlink:
1418 origtarget := string(fsys[name].Data)
1419 if hdr.Linkname != origtarget {
1420 t.Fatalf("test fs has link content %s; archive header %v", origtarget, hdr.Linkname)
1421 }
1422 default:
1423 data, err := io.ReadAll(tr)
1424 if err != nil {
1425 t.Fatal(err)
1426 }
1427 origdata := fsys[name].Data
1428 if string(data) != string(origdata) {
1429 t.Fatalf("test fs has file content %v; archive header has %v", origdata, data)
1430 }
1431 }
1432 }
1433 if entriesLeft > 0 {
1434 t.Fatalf("not all entries are in the archive")
1435 }
1436 }
1437
1438 func TestWriterAddFSNonRegularFiles(t *testing.T) {
1439 fsys := fstest.MapFS{
1440 "device": {Data: []byte("hello"), Mode: 0755 | fs.ModeDevice},
1441 "symlink": {Data: []byte("world"), Mode: 0755 | fs.ModeSymlink},
1442 }
1443 var buf bytes.Buffer
1444 tw := NewWriter(&buf)
1445 if err := tw.AddFS(fsys); err == nil {
1446 t.Fatal("expected error, got nil")
1447 }
1448 }
1449
View as plain text