github.com/karrick/go@v0.0.0-20170817181416-d5b0ec858b37/src/archive/tar/writer_test.go (about)

     1  // Copyright 2009 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 tar
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/hex"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"path"
    14  	"reflect"
    15  	"sort"
    16  	"strings"
    17  	"testing"
    18  	"testing/iotest"
    19  	"time"
    20  )
    21  
    22  func bytediff(a, b []byte) string {
    23  	const (
    24  		uniqueA  = "-  "
    25  		uniqueB  = "+  "
    26  		identity = "   "
    27  	)
    28  	var ss []string
    29  	sa := strings.Split(strings.TrimSpace(hex.Dump(a)), "\n")
    30  	sb := strings.Split(strings.TrimSpace(hex.Dump(b)), "\n")
    31  	for len(sa) > 0 && len(sb) > 0 {
    32  		if sa[0] == sb[0] {
    33  			ss = append(ss, identity+sa[0])
    34  		} else {
    35  			ss = append(ss, uniqueA+sa[0])
    36  			ss = append(ss, uniqueB+sb[0])
    37  		}
    38  		sa, sb = sa[1:], sb[1:]
    39  	}
    40  	for len(sa) > 0 {
    41  		ss = append(ss, uniqueA+sa[0])
    42  		sa = sa[1:]
    43  	}
    44  	for len(sb) > 0 {
    45  		ss = append(ss, uniqueB+sb[0])
    46  		sb = sb[1:]
    47  	}
    48  	return strings.Join(ss, "\n")
    49  }
    50  
    51  func TestWriter(t *testing.T) {
    52  	type entry struct {
    53  		header   *Header
    54  		contents string
    55  	}
    56  
    57  	vectors := []struct {
    58  		file    string // filename of expected output
    59  		entries []*entry
    60  		err     error // expected error on WriteHeader
    61  	}{{
    62  		// The writer test file was produced with this command:
    63  		// tar (GNU tar) 1.26
    64  		//   ln -s small.txt link.txt
    65  		//   tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
    66  		file: "testdata/writer.tar",
    67  		entries: []*entry{{
    68  			header: &Header{
    69  				Name:     "small.txt",
    70  				Mode:     0640,
    71  				Uid:      73025,
    72  				Gid:      5000,
    73  				Size:     5,
    74  				ModTime:  time.Unix(1246508266, 0),
    75  				Typeflag: '0',
    76  				Uname:    "dsymonds",
    77  				Gname:    "eng",
    78  			},
    79  			contents: "Kilts",
    80  		}, {
    81  			header: &Header{
    82  				Name:     "small2.txt",
    83  				Mode:     0640,
    84  				Uid:      73025,
    85  				Gid:      5000,
    86  				Size:     11,
    87  				ModTime:  time.Unix(1245217492, 0),
    88  				Typeflag: '0',
    89  				Uname:    "dsymonds",
    90  				Gname:    "eng",
    91  			},
    92  			contents: "Google.com\n",
    93  		}, {
    94  			header: &Header{
    95  				Name:     "link.txt",
    96  				Mode:     0777,
    97  				Uid:      1000,
    98  				Gid:      1000,
    99  				Size:     0,
   100  				ModTime:  time.Unix(1314603082, 0),
   101  				Typeflag: '2',
   102  				Linkname: "small.txt",
   103  				Uname:    "strings",
   104  				Gname:    "strings",
   105  			},
   106  			// no contents
   107  		}},
   108  	}, {
   109  		// The truncated test file was produced using these commands:
   110  		//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
   111  		//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
   112  		file: "testdata/writer-big.tar",
   113  		entries: []*entry{{
   114  			header: &Header{
   115  				Name:     "tmp/16gig.txt",
   116  				Mode:     0640,
   117  				Uid:      73025,
   118  				Gid:      5000,
   119  				Size:     16 << 30,
   120  				ModTime:  time.Unix(1254699560, 0),
   121  				Typeflag: '0',
   122  				Uname:    "dsymonds",
   123  				Gname:    "eng",
   124  				Devminor: -1, // Force use of GNU format
   125  			},
   126  			// fake contents
   127  			contents: strings.Repeat("\x00", 4<<10),
   128  		}},
   129  	}, {
   130  		// This truncated file was produced using this library.
   131  		// It was verified to work with GNU tar 1.27.1 and BSD tar 3.1.2.
   132  		//  dd if=/dev/zero bs=1G count=16 >> writer-big-long.tar
   133  		//  gnutar -xvf writer-big-long.tar
   134  		//  bsdtar -xvf writer-big-long.tar
   135  		//
   136  		// This file is in PAX format.
   137  		file: "testdata/writer-big-long.tar",
   138  		entries: []*entry{{
   139  			header: &Header{
   140  				Name:     strings.Repeat("longname/", 15) + "16gig.txt",
   141  				Mode:     0644,
   142  				Uid:      1000,
   143  				Gid:      1000,
   144  				Size:     16 << 30,
   145  				ModTime:  time.Unix(1399583047, 0),
   146  				Typeflag: '0',
   147  				Uname:    "guillaume",
   148  				Gname:    "guillaume",
   149  			},
   150  			// fake contents
   151  			contents: strings.Repeat("\x00", 4<<10),
   152  		}},
   153  	}, {
   154  		// This file was produced using GNU tar v1.17.
   155  		//	gnutar -b 4 --format=ustar (longname/)*15 + file.txt
   156  		file: "testdata/ustar.tar",
   157  		entries: []*entry{{
   158  			header: &Header{
   159  				Name:     strings.Repeat("longname/", 15) + "file.txt",
   160  				Mode:     0644,
   161  				Uid:      0765,
   162  				Gid:      024,
   163  				Size:     06,
   164  				ModTime:  time.Unix(1360135598, 0),
   165  				Typeflag: '0',
   166  				Uname:    "shane",
   167  				Gname:    "staff",
   168  			},
   169  			contents: "hello\n",
   170  		}},
   171  	}, {
   172  		// This file was produced using gnu tar 1.26
   173  		// echo "Slartibartfast" > file.txt
   174  		// ln file.txt hard.txt
   175  		// tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt
   176  		file: "testdata/hardlink.tar",
   177  		entries: []*entry{{
   178  			header: &Header{
   179  				Name:     "file.txt",
   180  				Mode:     0644,
   181  				Uid:      1000,
   182  				Gid:      100,
   183  				Size:     15,
   184  				ModTime:  time.Unix(1425484303, 0),
   185  				Typeflag: '0',
   186  				Uname:    "vbatts",
   187  				Gname:    "users",
   188  			},
   189  			contents: "Slartibartfast\n",
   190  		}, {
   191  			header: &Header{
   192  				Name:     "hard.txt",
   193  				Mode:     0644,
   194  				Uid:      1000,
   195  				Gid:      100,
   196  				Size:     0,
   197  				ModTime:  time.Unix(1425484303, 0),
   198  				Typeflag: '1',
   199  				Linkname: "file.txt",
   200  				Uname:    "vbatts",
   201  				Gname:    "users",
   202  			},
   203  			// no contents
   204  		}},
   205  	}, {
   206  		entries: []*entry{{
   207  			header: &Header{
   208  				Name:     "bad-null.txt",
   209  				Typeflag: '0',
   210  				Xattrs:   map[string]string{"null\x00null\x00": "fizzbuzz"},
   211  			},
   212  		}},
   213  		err: ErrHeader,
   214  	}, {
   215  		entries: []*entry{{
   216  			header: &Header{
   217  				Name:     "null\x00.txt",
   218  				Typeflag: '0',
   219  			},
   220  		}},
   221  		err: ErrHeader,
   222  	}, {
   223  		file: "testdata/gnu-utf8.tar",
   224  		entries: []*entry{{
   225  			header: &Header{
   226  				Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",
   227  				Mode: 0644,
   228  				Uid:  1000, Gid: 1000,
   229  				ModTime:  time.Unix(0, 0),
   230  				Typeflag: '0',
   231  				Uname:    "☺",
   232  				Gname:    "⚹",
   233  				Devminor: -1, // Force use of GNU format
   234  			},
   235  		}},
   236  	}, {
   237  		file: "testdata/gnu-not-utf8.tar",
   238  		entries: []*entry{{
   239  			header: &Header{
   240  				Name:     "hi\x80\x81\x82\x83bye",
   241  				Mode:     0644,
   242  				Uid:      1000,
   243  				Gid:      1000,
   244  				ModTime:  time.Unix(0, 0),
   245  				Typeflag: '0',
   246  				Uname:    "rawr",
   247  				Gname:    "dsnet",
   248  				Devminor: -1, // Force use of GNU format
   249  			},
   250  		}},
   251  	}}
   252  
   253  	for _, v := range vectors {
   254  		t.Run(path.Base(v.file), func(t *testing.T) {
   255  			buf := new(bytes.Buffer)
   256  			tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
   257  			canFail := false
   258  			for i, entry := range v.entries {
   259  				canFail = canFail || entry.header.Size > 1<<10 || v.err != nil
   260  
   261  				err := tw.WriteHeader(entry.header)
   262  				if err != v.err {
   263  					t.Fatalf("entry %d: WriteHeader() = %v, want %v", i, err, v.err)
   264  				}
   265  				if _, err := io.WriteString(tw, entry.contents); err != nil {
   266  					t.Fatalf("entry %d: WriteString() = %v, want nil", i, err)
   267  				}
   268  			}
   269  			// Only interested in Close failures for the small tests.
   270  			if err := tw.Close(); err != nil && !canFail {
   271  				t.Fatalf("Close() = %v, want nil", err)
   272  			}
   273  
   274  			if v.file != "" {
   275  				want, err := ioutil.ReadFile(v.file)
   276  				if err != nil {
   277  					t.Fatalf("ReadFile() = %v, want nil", err)
   278  				}
   279  				got := buf.Bytes()
   280  				if !bytes.Equal(want, got) {
   281  					t.Fatalf("incorrect result: (-got +want)\n%v", bytediff(got, want))
   282  				}
   283  			}
   284  		})
   285  	}
   286  }
   287  
   288  func TestPax(t *testing.T) {
   289  	// Create an archive with a large name
   290  	fileinfo, err := os.Stat("testdata/small.txt")
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  	hdr, err := FileInfoHeader(fileinfo, "")
   295  	if err != nil {
   296  		t.Fatalf("os.Stat: %v", err)
   297  	}
   298  	// Force a PAX long name to be written
   299  	longName := strings.Repeat("ab", 100)
   300  	contents := strings.Repeat(" ", int(hdr.Size))
   301  	hdr.Name = longName
   302  	var buf bytes.Buffer
   303  	writer := NewWriter(&buf)
   304  	if err := writer.WriteHeader(hdr); err != nil {
   305  		t.Fatal(err)
   306  	}
   307  	if _, err = writer.Write([]byte(contents)); err != nil {
   308  		t.Fatal(err)
   309  	}
   310  	if err := writer.Close(); err != nil {
   311  		t.Fatal(err)
   312  	}
   313  	// Simple test to make sure PAX extensions are in effect
   314  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   315  		t.Fatal("Expected at least one PAX header to be written.")
   316  	}
   317  	// Test that we can get a long name back out of the archive.
   318  	reader := NewReader(&buf)
   319  	hdr, err = reader.Next()
   320  	if err != nil {
   321  		t.Fatal(err)
   322  	}
   323  	if hdr.Name != longName {
   324  		t.Fatal("Couldn't recover long file name")
   325  	}
   326  }
   327  
   328  func TestPaxSymlink(t *testing.T) {
   329  	// Create an archive with a large linkname
   330  	fileinfo, err := os.Stat("testdata/small.txt")
   331  	if err != nil {
   332  		t.Fatal(err)
   333  	}
   334  	hdr, err := FileInfoHeader(fileinfo, "")
   335  	hdr.Typeflag = TypeSymlink
   336  	if err != nil {
   337  		t.Fatalf("os.Stat:1 %v", err)
   338  	}
   339  	// Force a PAX long linkname to be written
   340  	longLinkname := strings.Repeat("1234567890/1234567890", 10)
   341  	hdr.Linkname = longLinkname
   342  
   343  	hdr.Size = 0
   344  	var buf bytes.Buffer
   345  	writer := NewWriter(&buf)
   346  	if err := writer.WriteHeader(hdr); err != nil {
   347  		t.Fatal(err)
   348  	}
   349  	if err := writer.Close(); err != nil {
   350  		t.Fatal(err)
   351  	}
   352  	// Simple test to make sure PAX extensions are in effect
   353  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   354  		t.Fatal("Expected at least one PAX header to be written.")
   355  	}
   356  	// Test that we can get a long name back out of the archive.
   357  	reader := NewReader(&buf)
   358  	hdr, err = reader.Next()
   359  	if err != nil {
   360  		t.Fatal(err)
   361  	}
   362  	if hdr.Linkname != longLinkname {
   363  		t.Fatal("Couldn't recover long link name")
   364  	}
   365  }
   366  
   367  func TestPaxNonAscii(t *testing.T) {
   368  	// Create an archive with non ascii. These should trigger a pax header
   369  	// because pax headers have a defined utf-8 encoding.
   370  	fileinfo, err := os.Stat("testdata/small.txt")
   371  	if err != nil {
   372  		t.Fatal(err)
   373  	}
   374  
   375  	hdr, err := FileInfoHeader(fileinfo, "")
   376  	if err != nil {
   377  		t.Fatalf("os.Stat:1 %v", err)
   378  	}
   379  
   380  	// some sample data
   381  	chineseFilename := "文件名"
   382  	chineseGroupname := "組"
   383  	chineseUsername := "用戶名"
   384  
   385  	hdr.Name = chineseFilename
   386  	hdr.Gname = chineseGroupname
   387  	hdr.Uname = chineseUsername
   388  
   389  	contents := strings.Repeat(" ", int(hdr.Size))
   390  
   391  	var buf bytes.Buffer
   392  	writer := NewWriter(&buf)
   393  	if err := writer.WriteHeader(hdr); err != nil {
   394  		t.Fatal(err)
   395  	}
   396  	if _, err = writer.Write([]byte(contents)); err != nil {
   397  		t.Fatal(err)
   398  	}
   399  	if err := writer.Close(); err != nil {
   400  		t.Fatal(err)
   401  	}
   402  	// Simple test to make sure PAX extensions are in effect
   403  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   404  		t.Fatal("Expected at least one PAX header to be written.")
   405  	}
   406  	// Test that we can get a long name back out of the archive.
   407  	reader := NewReader(&buf)
   408  	hdr, err = reader.Next()
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  	if hdr.Name != chineseFilename {
   413  		t.Fatal("Couldn't recover unicode name")
   414  	}
   415  	if hdr.Gname != chineseGroupname {
   416  		t.Fatal("Couldn't recover unicode group")
   417  	}
   418  	if hdr.Uname != chineseUsername {
   419  		t.Fatal("Couldn't recover unicode user")
   420  	}
   421  }
   422  
   423  func TestPaxXattrs(t *testing.T) {
   424  	xattrs := map[string]string{
   425  		"user.key": "value",
   426  	}
   427  
   428  	// Create an archive with an xattr
   429  	fileinfo, err := os.Stat("testdata/small.txt")
   430  	if err != nil {
   431  		t.Fatal(err)
   432  	}
   433  	hdr, err := FileInfoHeader(fileinfo, "")
   434  	if err != nil {
   435  		t.Fatalf("os.Stat: %v", err)
   436  	}
   437  	contents := "Kilts"
   438  	hdr.Xattrs = xattrs
   439  	var buf bytes.Buffer
   440  	writer := NewWriter(&buf)
   441  	if err := writer.WriteHeader(hdr); err != nil {
   442  		t.Fatal(err)
   443  	}
   444  	if _, err = writer.Write([]byte(contents)); err != nil {
   445  		t.Fatal(err)
   446  	}
   447  	if err := writer.Close(); err != nil {
   448  		t.Fatal(err)
   449  	}
   450  	// Test that we can get the xattrs back out of the archive.
   451  	reader := NewReader(&buf)
   452  	hdr, err = reader.Next()
   453  	if err != nil {
   454  		t.Fatal(err)
   455  	}
   456  	if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
   457  		t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
   458  			hdr.Xattrs, xattrs)
   459  	}
   460  }
   461  
   462  func TestPaxHeadersSorted(t *testing.T) {
   463  	fileinfo, err := os.Stat("testdata/small.txt")
   464  	if err != nil {
   465  		t.Fatal(err)
   466  	}
   467  	hdr, err := FileInfoHeader(fileinfo, "")
   468  	if err != nil {
   469  		t.Fatalf("os.Stat: %v", err)
   470  	}
   471  	contents := strings.Repeat(" ", int(hdr.Size))
   472  
   473  	hdr.Xattrs = map[string]string{
   474  		"foo": "foo",
   475  		"bar": "bar",
   476  		"baz": "baz",
   477  		"qux": "qux",
   478  	}
   479  
   480  	var buf bytes.Buffer
   481  	writer := NewWriter(&buf)
   482  	if err := writer.WriteHeader(hdr); err != nil {
   483  		t.Fatal(err)
   484  	}
   485  	if _, err = writer.Write([]byte(contents)); err != nil {
   486  		t.Fatal(err)
   487  	}
   488  	if err := writer.Close(); err != nil {
   489  		t.Fatal(err)
   490  	}
   491  	// Simple test to make sure PAX extensions are in effect
   492  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   493  		t.Fatal("Expected at least one PAX header to be written.")
   494  	}
   495  
   496  	// xattr bar should always appear before others
   497  	indices := []int{
   498  		bytes.Index(buf.Bytes(), []byte("bar=bar")),
   499  		bytes.Index(buf.Bytes(), []byte("baz=baz")),
   500  		bytes.Index(buf.Bytes(), []byte("foo=foo")),
   501  		bytes.Index(buf.Bytes(), []byte("qux=qux")),
   502  	}
   503  	if !sort.IntsAreSorted(indices) {
   504  		t.Fatal("PAX headers are not sorted")
   505  	}
   506  }
   507  
   508  func TestUSTARLongName(t *testing.T) {
   509  	// Create an archive with a path that failed to split with USTAR extension in previous versions.
   510  	fileinfo, err := os.Stat("testdata/small.txt")
   511  	if err != nil {
   512  		t.Fatal(err)
   513  	}
   514  	hdr, err := FileInfoHeader(fileinfo, "")
   515  	hdr.Typeflag = TypeDir
   516  	if err != nil {
   517  		t.Fatalf("os.Stat:1 %v", err)
   518  	}
   519  	// Force a PAX long name to be written. The name was taken from a practical example
   520  	// that fails and replaced ever char through numbers to anonymize the sample.
   521  	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/"
   522  	hdr.Name = longName
   523  
   524  	hdr.Size = 0
   525  	var buf bytes.Buffer
   526  	writer := NewWriter(&buf)
   527  	if err := writer.WriteHeader(hdr); err != nil {
   528  		t.Fatal(err)
   529  	}
   530  	if err := writer.Close(); err != nil {
   531  		t.Fatal(err)
   532  	}
   533  	// Test that we can get a long name back out of the archive.
   534  	reader := NewReader(&buf)
   535  	hdr, err = reader.Next()
   536  	if err != nil {
   537  		t.Fatal(err)
   538  	}
   539  	if hdr.Name != longName {
   540  		t.Fatal("Couldn't recover long name")
   541  	}
   542  }
   543  
   544  func TestValidTypeflagWithPAXHeader(t *testing.T) {
   545  	var buffer bytes.Buffer
   546  	tw := NewWriter(&buffer)
   547  
   548  	fileName := strings.Repeat("ab", 100)
   549  
   550  	hdr := &Header{
   551  		Name:     fileName,
   552  		Size:     4,
   553  		Typeflag: 0,
   554  	}
   555  	if err := tw.WriteHeader(hdr); err != nil {
   556  		t.Fatalf("Failed to write header: %s", err)
   557  	}
   558  	if _, err := tw.Write([]byte("fooo")); err != nil {
   559  		t.Fatalf("Failed to write the file's data: %s", err)
   560  	}
   561  	tw.Close()
   562  
   563  	tr := NewReader(&buffer)
   564  
   565  	for {
   566  		header, err := tr.Next()
   567  		if err == io.EOF {
   568  			break
   569  		}
   570  		if err != nil {
   571  			t.Fatalf("Failed to read header: %s", err)
   572  		}
   573  		if header.Typeflag != 0 {
   574  			t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
   575  		}
   576  	}
   577  }
   578  
   579  // failOnceWriter fails exactly once and then always reports success.
   580  type failOnceWriter bool
   581  
   582  func (w *failOnceWriter) Write(b []byte) (int, error) {
   583  	if !*w {
   584  		return 0, io.ErrShortWrite
   585  	}
   586  	*w = true
   587  	return len(b), nil
   588  }
   589  
   590  func TestWriterErrors(t *testing.T) {
   591  	t.Run("HeaderOnly", func(t *testing.T) {
   592  		tw := NewWriter(new(bytes.Buffer))
   593  		hdr := &Header{Name: "dir/", Typeflag: TypeDir}
   594  		if err := tw.WriteHeader(hdr); err != nil {
   595  			t.Fatalf("WriteHeader() = %v, want nil", err)
   596  		}
   597  		if _, err := tw.Write([]byte{0x00}); err != ErrWriteTooLong {
   598  			t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
   599  		}
   600  	})
   601  
   602  	t.Run("NegativeSize", func(t *testing.T) {
   603  		tw := NewWriter(new(bytes.Buffer))
   604  		hdr := &Header{Name: "small.txt", Size: -1}
   605  		if err := tw.WriteHeader(hdr); err != ErrHeader {
   606  			t.Fatalf("WriteHeader() = nil, want %v", ErrHeader)
   607  		}
   608  	})
   609  
   610  	t.Run("BeforeHeader", func(t *testing.T) {
   611  		tw := NewWriter(new(bytes.Buffer))
   612  		if _, err := tw.Write([]byte("Kilts")); err != ErrWriteTooLong {
   613  			t.Fatalf("Write() = %v, want %v", err, ErrWriteTooLong)
   614  		}
   615  	})
   616  
   617  	t.Run("AfterClose", func(t *testing.T) {
   618  		tw := NewWriter(new(bytes.Buffer))
   619  		hdr := &Header{Name: "small.txt"}
   620  		if err := tw.WriteHeader(hdr); err != nil {
   621  			t.Fatalf("WriteHeader() = %v, want nil", err)
   622  		}
   623  		if err := tw.Close(); err != nil {
   624  			t.Fatalf("Close() = %v, want nil", err)
   625  		}
   626  		if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
   627  			t.Fatalf("Write() = %v, want %v", err, ErrWriteAfterClose)
   628  		}
   629  		if err := tw.Flush(); err != ErrWriteAfterClose {
   630  			t.Fatalf("Flush() = %v, want %v", err, ErrWriteAfterClose)
   631  		}
   632  		if err := tw.Close(); err != nil {
   633  			t.Fatalf("Close() = %v, want nil", err)
   634  		}
   635  	})
   636  
   637  	t.Run("PrematureFlush", func(t *testing.T) {
   638  		tw := NewWriter(new(bytes.Buffer))
   639  		hdr := &Header{Name: "small.txt", Size: 5}
   640  		if err := tw.WriteHeader(hdr); err != nil {
   641  			t.Fatalf("WriteHeader() = %v, want nil", err)
   642  		}
   643  		if err := tw.Flush(); err == nil {
   644  			t.Fatalf("Flush() = %v, want non-nil error", err)
   645  		}
   646  	})
   647  
   648  	t.Run("PrematureClose", func(t *testing.T) {
   649  		tw := NewWriter(new(bytes.Buffer))
   650  		hdr := &Header{Name: "small.txt", Size: 5}
   651  		if err := tw.WriteHeader(hdr); err != nil {
   652  			t.Fatalf("WriteHeader() = %v, want nil", err)
   653  		}
   654  		if err := tw.Close(); err == nil {
   655  			t.Fatalf("Close() = %v, want non-nil error", err)
   656  		}
   657  	})
   658  
   659  	t.Run("Persistence", func(t *testing.T) {
   660  		tw := NewWriter(new(failOnceWriter))
   661  		if err := tw.WriteHeader(&Header{}); err != io.ErrShortWrite {
   662  			t.Fatalf("WriteHeader() = %v, want %v", err, io.ErrShortWrite)
   663  		}
   664  		if err := tw.WriteHeader(&Header{Name: "small.txt"}); err == nil {
   665  			t.Errorf("WriteHeader() = got %v, want non-nil error", err)
   666  		}
   667  		if _, err := tw.Write(nil); err == nil {
   668  			t.Errorf("Write() = %v, want non-nil error", err)
   669  		}
   670  		if err := tw.Flush(); err == nil {
   671  			t.Errorf("Flush() = %v, want non-nil error", err)
   672  		}
   673  		if err := tw.Close(); err == nil {
   674  			t.Errorf("Close() = %v, want non-nil error", err)
   675  		}
   676  	})
   677  }
   678  
   679  func TestSplitUSTARPath(t *testing.T) {
   680  	sr := strings.Repeat
   681  
   682  	vectors := []struct {
   683  		input  string // Input path
   684  		prefix string // Expected output prefix
   685  		suffix string // Expected output suffix
   686  		ok     bool   // Split success?
   687  	}{
   688  		{"", "", "", false},
   689  		{"abc", "", "", false},
   690  		{"用戶名", "", "", false},
   691  		{sr("a", nameSize), "", "", false},
   692  		{sr("a", nameSize) + "/", "", "", false},
   693  		{sr("a", nameSize) + "/a", sr("a", nameSize), "a", true},
   694  		{sr("a", prefixSize) + "/", "", "", false},
   695  		{sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true},
   696  		{sr("a", nameSize+1), "", "", false},
   697  		{sr("/", nameSize+1), sr("/", nameSize-1), "/", true},
   698  		{sr("a", prefixSize) + "/" + sr("b", nameSize),
   699  			sr("a", prefixSize), sr("b", nameSize), true},
   700  		{sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false},
   701  		{sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true},
   702  	}
   703  
   704  	for _, v := range vectors {
   705  		prefix, suffix, ok := splitUSTARPath(v.input)
   706  		if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
   707  			t.Errorf("splitUSTARPath(%q):\ngot  (%q, %q, %v)\nwant (%q, %q, %v)",
   708  				v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
   709  		}
   710  	}
   711  }
   712  
   713  // TestIssue12594 tests that the Writer does not attempt to populate the prefix
   714  // field when encoding a header in the GNU format. The prefix field is valid
   715  // in USTAR and PAX, but not GNU.
   716  func TestIssue12594(t *testing.T) {
   717  	names := []string{
   718  		"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",
   719  		"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",
   720  		"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",
   721  		"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",
   722  		"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt",
   723  		"/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend",
   724  	}
   725  
   726  	for i, name := range names {
   727  		var b bytes.Buffer
   728  
   729  		tw := NewWriter(&b)
   730  		if err := tw.WriteHeader(&Header{
   731  			Name: name,
   732  			Uid:  1 << 25, // Prevent USTAR format
   733  		}); err != nil {
   734  			t.Errorf("test %d, unexpected WriteHeader error: %v", i, err)
   735  		}
   736  		if err := tw.Close(); err != nil {
   737  			t.Errorf("test %d, unexpected Close error: %v", i, err)
   738  		}
   739  
   740  		// The prefix field should never appear in the GNU format.
   741  		var blk block
   742  		copy(blk[:], b.Bytes())
   743  		prefix := string(blk.USTAR().Prefix())
   744  		if i := strings.IndexByte(prefix, 0); i >= 0 {
   745  			prefix = prefix[:i] // Truncate at the NUL terminator
   746  		}
   747  		if blk.GetFormat() == formatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) {
   748  			t.Errorf("test %d, found prefix in GNU format: %s", i, prefix)
   749  		}
   750  
   751  		tr := NewReader(&b)
   752  		hdr, err := tr.Next()
   753  		if err != nil {
   754  			t.Errorf("test %d, unexpected Next error: %v", i, err)
   755  		}
   756  		if hdr.Name != name {
   757  			t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name)
   758  		}
   759  	}
   760  }