github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/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  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"os"
    13  	"reflect"
    14  	"sort"
    15  	"strings"
    16  	"testing"
    17  	"testing/iotest"
    18  	"time"
    19  )
    20  
    21  // Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
    22  func bytestr(offset int, b []byte) string {
    23  	const rowLen = 32
    24  	s := fmt.Sprintf("%04x ", offset)
    25  	for _, ch := range b {
    26  		switch {
    27  		case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
    28  			s += fmt.Sprintf("  %c", ch)
    29  		default:
    30  			s += fmt.Sprintf(" %02x", ch)
    31  		}
    32  	}
    33  	return s
    34  }
    35  
    36  // Render a pseudo-diff between two blocks of bytes.
    37  func bytediff(a []byte, b []byte) string {
    38  	const rowLen = 32
    39  	s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
    40  	for offset := 0; len(a)+len(b) > 0; offset += rowLen {
    41  		na, nb := rowLen, rowLen
    42  		if na > len(a) {
    43  			na = len(a)
    44  		}
    45  		if nb > len(b) {
    46  			nb = len(b)
    47  		}
    48  		sa := bytestr(offset, a[0:na])
    49  		sb := bytestr(offset, b[0:nb])
    50  		if sa != sb {
    51  			s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
    52  		}
    53  		a = a[na:]
    54  		b = b[nb:]
    55  	}
    56  	return s
    57  }
    58  
    59  func TestWriter(t *testing.T) {
    60  	type entry struct {
    61  		header   *Header
    62  		contents string
    63  	}
    64  
    65  	vectors := []struct {
    66  		file    string // filename of expected output
    67  		entries []*entry
    68  	}{{
    69  		// The writer test file was produced with this command:
    70  		// tar (GNU tar) 1.26
    71  		//   ln -s small.txt link.txt
    72  		//   tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
    73  		file: "testdata/writer.tar",
    74  		entries: []*entry{{
    75  			header: &Header{
    76  				Name:     "small.txt",
    77  				Mode:     0640,
    78  				Uid:      73025,
    79  				Gid:      5000,
    80  				Size:     5,
    81  				ModTime:  time.Unix(1246508266, 0),
    82  				Typeflag: '0',
    83  				Uname:    "dsymonds",
    84  				Gname:    "eng",
    85  			},
    86  			contents: "Kilts",
    87  		}, {
    88  			header: &Header{
    89  				Name:     "small2.txt",
    90  				Mode:     0640,
    91  				Uid:      73025,
    92  				Gid:      5000,
    93  				Size:     11,
    94  				ModTime:  time.Unix(1245217492, 0),
    95  				Typeflag: '0',
    96  				Uname:    "dsymonds",
    97  				Gname:    "eng",
    98  			},
    99  			contents: "Google.com\n",
   100  		}, {
   101  			header: &Header{
   102  				Name:     "link.txt",
   103  				Mode:     0777,
   104  				Uid:      1000,
   105  				Gid:      1000,
   106  				Size:     0,
   107  				ModTime:  time.Unix(1314603082, 0),
   108  				Typeflag: '2',
   109  				Linkname: "small.txt",
   110  				Uname:    "strings",
   111  				Gname:    "strings",
   112  			},
   113  			// no contents
   114  		}},
   115  	}, {
   116  		// The truncated test file was produced using these commands:
   117  		//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
   118  		//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
   119  		file: "testdata/writer-big.tar",
   120  		entries: []*entry{{
   121  			header: &Header{
   122  				Name:     "tmp/16gig.txt",
   123  				Mode:     0640,
   124  				Uid:      73025,
   125  				Gid:      5000,
   126  				Size:     16 << 30,
   127  				ModTime:  time.Unix(1254699560, 0),
   128  				Typeflag: '0',
   129  				Uname:    "dsymonds",
   130  				Gname:    "eng",
   131  			},
   132  			// fake contents
   133  			contents: strings.Repeat("\x00", 4<<10),
   134  		}},
   135  	}, {
   136  		// The truncated test file was produced using these commands:
   137  		//   dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt
   138  		//   tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar
   139  		file: "testdata/writer-big-long.tar",
   140  		entries: []*entry{{
   141  			header: &Header{
   142  				Name:     strings.Repeat("longname/", 15) + "16gig.txt",
   143  				Mode:     0644,
   144  				Uid:      1000,
   145  				Gid:      1000,
   146  				Size:     16 << 30,
   147  				ModTime:  time.Unix(1399583047, 0),
   148  				Typeflag: '0',
   149  				Uname:    "guillaume",
   150  				Gname:    "guillaume",
   151  			},
   152  			// fake contents
   153  			contents: strings.Repeat("\x00", 4<<10),
   154  		}},
   155  	}, {
   156  		// This file was produced using gnu tar 1.17
   157  		// gnutar  -b 4 --format=ustar (longname/)*15 + file.txt
   158  		file: "testdata/ustar.tar",
   159  		entries: []*entry{{
   160  			header: &Header{
   161  				Name:     strings.Repeat("longname/", 15) + "file.txt",
   162  				Mode:     0644,
   163  				Uid:      0765,
   164  				Gid:      024,
   165  				Size:     06,
   166  				ModTime:  time.Unix(1360135598, 0),
   167  				Typeflag: '0',
   168  				Uname:    "shane",
   169  				Gname:    "staff",
   170  			},
   171  			contents: "hello\n",
   172  		}},
   173  	}, {
   174  		// This file was produced using gnu tar 1.26
   175  		// echo "Slartibartfast" > file.txt
   176  		// ln file.txt hard.txt
   177  		// tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt
   178  		file: "testdata/hardlink.tar",
   179  		entries: []*entry{{
   180  			header: &Header{
   181  				Name:     "file.txt",
   182  				Mode:     0644,
   183  				Uid:      1000,
   184  				Gid:      100,
   185  				Size:     15,
   186  				ModTime:  time.Unix(1425484303, 0),
   187  				Typeflag: '0',
   188  				Uname:    "vbatts",
   189  				Gname:    "users",
   190  			},
   191  			contents: "Slartibartfast\n",
   192  		}, {
   193  			header: &Header{
   194  				Name:     "hard.txt",
   195  				Mode:     0644,
   196  				Uid:      1000,
   197  				Gid:      100,
   198  				Size:     0,
   199  				ModTime:  time.Unix(1425484303, 0),
   200  				Typeflag: '1',
   201  				Linkname: "file.txt",
   202  				Uname:    "vbatts",
   203  				Gname:    "users",
   204  			},
   205  			// no contents
   206  		}},
   207  	}}
   208  
   209  testLoop:
   210  	for i, v := range vectors {
   211  		expected, err := ioutil.ReadFile(v.file)
   212  		if err != nil {
   213  			t.Errorf("test %d: Unexpected error: %v", i, err)
   214  			continue
   215  		}
   216  
   217  		buf := new(bytes.Buffer)
   218  		tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
   219  		big := false
   220  		for j, entry := range v.entries {
   221  			big = big || entry.header.Size > 1<<10
   222  			if err := tw.WriteHeader(entry.header); err != nil {
   223  				t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
   224  				continue testLoop
   225  			}
   226  			if _, err := io.WriteString(tw, entry.contents); err != nil {
   227  				t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
   228  				continue testLoop
   229  			}
   230  		}
   231  		// Only interested in Close failures for the small tests.
   232  		if err := tw.Close(); err != nil && !big {
   233  			t.Errorf("test %d: Failed closing archive: %v", i, err)
   234  			continue testLoop
   235  		}
   236  
   237  		actual := buf.Bytes()
   238  		if !bytes.Equal(expected, actual) {
   239  			t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
   240  				i, bytediff(expected, actual))
   241  		}
   242  		if testing.Short() { // The second test is expensive.
   243  			break
   244  		}
   245  	}
   246  }
   247  
   248  func TestPax(t *testing.T) {
   249  	// Create an archive with a large name
   250  	fileinfo, err := os.Stat("testdata/small.txt")
   251  	if err != nil {
   252  		t.Fatal(err)
   253  	}
   254  	hdr, err := FileInfoHeader(fileinfo, "")
   255  	if err != nil {
   256  		t.Fatalf("os.Stat: %v", err)
   257  	}
   258  	// Force a PAX long name to be written
   259  	longName := strings.Repeat("ab", 100)
   260  	contents := strings.Repeat(" ", int(hdr.Size))
   261  	hdr.Name = longName
   262  	var buf bytes.Buffer
   263  	writer := NewWriter(&buf)
   264  	if err := writer.WriteHeader(hdr); err != nil {
   265  		t.Fatal(err)
   266  	}
   267  	if _, err = writer.Write([]byte(contents)); err != nil {
   268  		t.Fatal(err)
   269  	}
   270  	if err := writer.Close(); err != nil {
   271  		t.Fatal(err)
   272  	}
   273  	// Simple test to make sure PAX extensions are in effect
   274  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   275  		t.Fatal("Expected at least one PAX header to be written.")
   276  	}
   277  	// Test that we can get a long name back out of the archive.
   278  	reader := NewReader(&buf)
   279  	hdr, err = reader.Next()
   280  	if err != nil {
   281  		t.Fatal(err)
   282  	}
   283  	if hdr.Name != longName {
   284  		t.Fatal("Couldn't recover long file name")
   285  	}
   286  }
   287  
   288  func TestPaxSymlink(t *testing.T) {
   289  	// Create an archive with a large linkname
   290  	fileinfo, err := os.Stat("testdata/small.txt")
   291  	if err != nil {
   292  		t.Fatal(err)
   293  	}
   294  	hdr, err := FileInfoHeader(fileinfo, "")
   295  	hdr.Typeflag = TypeSymlink
   296  	if err != nil {
   297  		t.Fatalf("os.Stat:1 %v", err)
   298  	}
   299  	// Force a PAX long linkname to be written
   300  	longLinkname := strings.Repeat("1234567890/1234567890", 10)
   301  	hdr.Linkname = longLinkname
   302  
   303  	hdr.Size = 0
   304  	var buf bytes.Buffer
   305  	writer := NewWriter(&buf)
   306  	if err := writer.WriteHeader(hdr); err != nil {
   307  		t.Fatal(err)
   308  	}
   309  	if err := writer.Close(); err != nil {
   310  		t.Fatal(err)
   311  	}
   312  	// Simple test to make sure PAX extensions are in effect
   313  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   314  		t.Fatal("Expected at least one PAX header to be written.")
   315  	}
   316  	// Test that we can get a long name back out of the archive.
   317  	reader := NewReader(&buf)
   318  	hdr, err = reader.Next()
   319  	if err != nil {
   320  		t.Fatal(err)
   321  	}
   322  	if hdr.Linkname != longLinkname {
   323  		t.Fatal("Couldn't recover long link name")
   324  	}
   325  }
   326  
   327  func TestPaxNonAscii(t *testing.T) {
   328  	// Create an archive with non ascii. These should trigger a pax header
   329  	// because pax headers have a defined utf-8 encoding.
   330  	fileinfo, err := os.Stat("testdata/small.txt")
   331  	if err != nil {
   332  		t.Fatal(err)
   333  	}
   334  
   335  	hdr, err := FileInfoHeader(fileinfo, "")
   336  	if err != nil {
   337  		t.Fatalf("os.Stat:1 %v", err)
   338  	}
   339  
   340  	// some sample data
   341  	chineseFilename := "文件名"
   342  	chineseGroupname := "組"
   343  	chineseUsername := "用戶名"
   344  
   345  	hdr.Name = chineseFilename
   346  	hdr.Gname = chineseGroupname
   347  	hdr.Uname = chineseUsername
   348  
   349  	contents := strings.Repeat(" ", int(hdr.Size))
   350  
   351  	var buf bytes.Buffer
   352  	writer := NewWriter(&buf)
   353  	if err := writer.WriteHeader(hdr); err != nil {
   354  		t.Fatal(err)
   355  	}
   356  	if _, err = writer.Write([]byte(contents)); err != nil {
   357  		t.Fatal(err)
   358  	}
   359  	if err := writer.Close(); err != nil {
   360  		t.Fatal(err)
   361  	}
   362  	// Simple test to make sure PAX extensions are in effect
   363  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   364  		t.Fatal("Expected at least one PAX header to be written.")
   365  	}
   366  	// Test that we can get a long name back out of the archive.
   367  	reader := NewReader(&buf)
   368  	hdr, err = reader.Next()
   369  	if err != nil {
   370  		t.Fatal(err)
   371  	}
   372  	if hdr.Name != chineseFilename {
   373  		t.Fatal("Couldn't recover unicode name")
   374  	}
   375  	if hdr.Gname != chineseGroupname {
   376  		t.Fatal("Couldn't recover unicode group")
   377  	}
   378  	if hdr.Uname != chineseUsername {
   379  		t.Fatal("Couldn't recover unicode user")
   380  	}
   381  }
   382  
   383  func TestPaxXattrs(t *testing.T) {
   384  	xattrs := map[string]string{
   385  		"user.key": "value",
   386  	}
   387  
   388  	// Create an archive with an xattr
   389  	fileinfo, err := os.Stat("testdata/small.txt")
   390  	if err != nil {
   391  		t.Fatal(err)
   392  	}
   393  	hdr, err := FileInfoHeader(fileinfo, "")
   394  	if err != nil {
   395  		t.Fatalf("os.Stat: %v", err)
   396  	}
   397  	contents := "Kilts"
   398  	hdr.Xattrs = xattrs
   399  	var buf bytes.Buffer
   400  	writer := NewWriter(&buf)
   401  	if err := writer.WriteHeader(hdr); err != nil {
   402  		t.Fatal(err)
   403  	}
   404  	if _, err = writer.Write([]byte(contents)); err != nil {
   405  		t.Fatal(err)
   406  	}
   407  	if err := writer.Close(); err != nil {
   408  		t.Fatal(err)
   409  	}
   410  	// Test that we can get the xattrs back out of the archive.
   411  	reader := NewReader(&buf)
   412  	hdr, err = reader.Next()
   413  	if err != nil {
   414  		t.Fatal(err)
   415  	}
   416  	if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
   417  		t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
   418  			hdr.Xattrs, xattrs)
   419  	}
   420  }
   421  
   422  func TestPaxHeadersSorted(t *testing.T) {
   423  	fileinfo, err := os.Stat("testdata/small.txt")
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  	hdr, err := FileInfoHeader(fileinfo, "")
   428  	if err != nil {
   429  		t.Fatalf("os.Stat: %v", err)
   430  	}
   431  	contents := strings.Repeat(" ", int(hdr.Size))
   432  
   433  	hdr.Xattrs = map[string]string{
   434  		"foo": "foo",
   435  		"bar": "bar",
   436  		"baz": "baz",
   437  		"qux": "qux",
   438  	}
   439  
   440  	var buf bytes.Buffer
   441  	writer := NewWriter(&buf)
   442  	if err := writer.WriteHeader(hdr); err != nil {
   443  		t.Fatal(err)
   444  	}
   445  	if _, err = writer.Write([]byte(contents)); err != nil {
   446  		t.Fatal(err)
   447  	}
   448  	if err := writer.Close(); err != nil {
   449  		t.Fatal(err)
   450  	}
   451  	// Simple test to make sure PAX extensions are in effect
   452  	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
   453  		t.Fatal("Expected at least one PAX header to be written.")
   454  	}
   455  
   456  	// xattr bar should always appear before others
   457  	indices := []int{
   458  		bytes.Index(buf.Bytes(), []byte("bar=bar")),
   459  		bytes.Index(buf.Bytes(), []byte("baz=baz")),
   460  		bytes.Index(buf.Bytes(), []byte("foo=foo")),
   461  		bytes.Index(buf.Bytes(), []byte("qux=qux")),
   462  	}
   463  	if !sort.IntsAreSorted(indices) {
   464  		t.Fatal("PAX headers are not sorted")
   465  	}
   466  }
   467  
   468  func TestUSTARLongName(t *testing.T) {
   469  	// Create an archive with a path that failed to split with USTAR extension in previous versions.
   470  	fileinfo, err := os.Stat("testdata/small.txt")
   471  	if err != nil {
   472  		t.Fatal(err)
   473  	}
   474  	hdr, err := FileInfoHeader(fileinfo, "")
   475  	hdr.Typeflag = TypeDir
   476  	if err != nil {
   477  		t.Fatalf("os.Stat:1 %v", err)
   478  	}
   479  	// Force a PAX long name to be written. The name was taken from a practical example
   480  	// that fails and replaced ever char through numbers to anonymize the sample.
   481  	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/"
   482  	hdr.Name = longName
   483  
   484  	hdr.Size = 0
   485  	var buf bytes.Buffer
   486  	writer := NewWriter(&buf)
   487  	if err := writer.WriteHeader(hdr); err != nil {
   488  		t.Fatal(err)
   489  	}
   490  	if err := writer.Close(); err != nil {
   491  		t.Fatal(err)
   492  	}
   493  	// Test that we can get a long name back out of the archive.
   494  	reader := NewReader(&buf)
   495  	hdr, err = reader.Next()
   496  	if err != nil {
   497  		t.Fatal(err)
   498  	}
   499  	if hdr.Name != longName {
   500  		t.Fatal("Couldn't recover long name")
   501  	}
   502  }
   503  
   504  func TestValidTypeflagWithPAXHeader(t *testing.T) {
   505  	var buffer bytes.Buffer
   506  	tw := NewWriter(&buffer)
   507  
   508  	fileName := strings.Repeat("ab", 100)
   509  
   510  	hdr := &Header{
   511  		Name:     fileName,
   512  		Size:     4,
   513  		Typeflag: 0,
   514  	}
   515  	if err := tw.WriteHeader(hdr); err != nil {
   516  		t.Fatalf("Failed to write header: %s", err)
   517  	}
   518  	if _, err := tw.Write([]byte("fooo")); err != nil {
   519  		t.Fatalf("Failed to write the file's data: %s", err)
   520  	}
   521  	tw.Close()
   522  
   523  	tr := NewReader(&buffer)
   524  
   525  	for {
   526  		header, err := tr.Next()
   527  		if err == io.EOF {
   528  			break
   529  		}
   530  		if err != nil {
   531  			t.Fatalf("Failed to read header: %s", err)
   532  		}
   533  		if header.Typeflag != 0 {
   534  			t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
   535  		}
   536  	}
   537  }
   538  
   539  func TestWriteAfterClose(t *testing.T) {
   540  	var buffer bytes.Buffer
   541  	tw := NewWriter(&buffer)
   542  
   543  	hdr := &Header{
   544  		Name: "small.txt",
   545  		Size: 5,
   546  	}
   547  	if err := tw.WriteHeader(hdr); err != nil {
   548  		t.Fatalf("Failed to write header: %s", err)
   549  	}
   550  	tw.Close()
   551  	if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
   552  		t.Fatalf("Write: got %v; want ErrWriteAfterClose", err)
   553  	}
   554  }
   555  
   556  func TestSplitUSTARPath(t *testing.T) {
   557  	sr := strings.Repeat
   558  
   559  	vectors := []struct {
   560  		input  string // Input path
   561  		prefix string // Expected output prefix
   562  		suffix string // Expected output suffix
   563  		ok     bool   // Split success?
   564  	}{
   565  		{"", "", "", false},
   566  		{"abc", "", "", false},
   567  		{"用戶名", "", "", false},
   568  		{sr("a", nameSize), "", "", false},
   569  		{sr("a", nameSize) + "/", "", "", false},
   570  		{sr("a", nameSize) + "/a", sr("a", nameSize), "a", true},
   571  		{sr("a", prefixSize) + "/", "", "", false},
   572  		{sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true},
   573  		{sr("a", nameSize+1), "", "", false},
   574  		{sr("/", nameSize+1), sr("/", nameSize-1), "/", true},
   575  		{sr("a", prefixSize) + "/" + sr("b", nameSize),
   576  			sr("a", prefixSize), sr("b", nameSize), true},
   577  		{sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false},
   578  		{sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true},
   579  	}
   580  
   581  	for _, v := range vectors {
   582  		prefix, suffix, ok := splitUSTARPath(v.input)
   583  		if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
   584  			t.Errorf("splitUSTARPath(%q):\ngot  (%q, %q, %v)\nwant (%q, %q, %v)",
   585  				v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
   586  		}
   587  	}
   588  }