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
5package tar
6
7import (
8	"bytes"
9	"fmt"
10	"io"
11	"io/ioutil"
12	"os"
13	"strings"
14	"testing"
15	"testing/iotest"
16	"time"
17)
18
19type writerTestEntry struct {
20	header   *Header
21	contents string
22}
23
24type writerTest struct {
25	file    string // filename of expected output
26	entries []*writerTestEntry
27}
28
29var writerTests = []*writerTest{
30	// The writer test file was produced with this command:
31	// tar (GNU tar) 1.26
32	//   ln -s small.txt link.txt
33	//   tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
34	{
35		file: "testdata/writer.tar",
36		entries: []*writerTestEntry{
37			{
38				header: &Header{
39					Name:     "small.txt",
40					Mode:     0640,
41					Uid:      73025,
42					Gid:      5000,
43					Size:     5,
44					ModTime:  time.Unix(1246508266, 0),
45					Typeflag: '0',
46					Uname:    "dsymonds",
47					Gname:    "eng",
48				},
49				contents: "Kilts",
50			},
51			{
52				header: &Header{
53					Name:     "small2.txt",
54					Mode:     0640,
55					Uid:      73025,
56					Gid:      5000,
57					Size:     11,
58					ModTime:  time.Unix(1245217492, 0),
59					Typeflag: '0',
60					Uname:    "dsymonds",
61					Gname:    "eng",
62				},
63				contents: "Google.com\n",
64			},
65			{
66				header: &Header{
67					Name:     "link.txt",
68					Mode:     0777,
69					Uid:      1000,
70					Gid:      1000,
71					Size:     0,
72					ModTime:  time.Unix(1314603082, 0),
73					Typeflag: '2',
74					Linkname: "small.txt",
75					Uname:    "strings",
76					Gname:    "strings",
77				},
78				// no contents
79			},
80		},
81	},
82	// The truncated test file was produced using these commands:
83	//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
84	//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
85	{
86		file: "testdata/writer-big.tar",
87		entries: []*writerTestEntry{
88			{
89				header: &Header{
90					Name:     "tmp/16gig.txt",
91					Mode:     0640,
92					Uid:      73025,
93					Gid:      5000,
94					Size:     16 << 30,
95					ModTime:  time.Unix(1254699560, 0),
96					Typeflag: '0',
97					Uname:    "dsymonds",
98					Gname:    "eng",
99				},
100				// fake contents
101				contents: strings.Repeat("\x00", 4<<10),
102			},
103		},
104	},
105	// This file was produced using gnu tar 1.17
106	// gnutar  -b 4 --format=ustar (longname/)*15 + file.txt
107	{
108		file: "testdata/ustar.tar",
109		entries: []*writerTestEntry{
110			{
111				header: &Header{
112					Name:     strings.Repeat("longname/", 15) + "file.txt",
113					Mode:     0644,
114					Uid:      0765,
115					Gid:      024,
116					Size:     06,
117					ModTime:  time.Unix(1360135598, 0),
118					Typeflag: '0',
119					Uname:    "shane",
120					Gname:    "staff",
121				},
122				contents: "hello\n",
123			},
124		},
125	},
126}
127
128// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
129func bytestr(offset int, b []byte) string {
130	const rowLen = 32
131	s := fmt.Sprintf("%04x ", offset)
132	for _, ch := range b {
133		switch {
134		case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
135			s += fmt.Sprintf("  %c", ch)
136		default:
137			s += fmt.Sprintf(" %02x", ch)
138		}
139	}
140	return s
141}
142
143// Render a pseudo-diff between two blocks of bytes.
144func bytediff(a []byte, b []byte) string {
145	const rowLen = 32
146	s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
147	for offset := 0; len(a)+len(b) > 0; offset += rowLen {
148		na, nb := rowLen, rowLen
149		if na > len(a) {
150			na = len(a)
151		}
152		if nb > len(b) {
153			nb = len(b)
154		}
155		sa := bytestr(offset, a[0:na])
156		sb := bytestr(offset, b[0:nb])
157		if sa != sb {
158			s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
159		}
160		a = a[na:]
161		b = b[nb:]
162	}
163	return s
164}
165
166func TestWriter(t *testing.T) {
167testLoop:
168	for i, test := range writerTests {
169		expected, err := ioutil.ReadFile(test.file)
170		if err != nil {
171			t.Errorf("test %d: Unexpected error: %v", i, err)
172			continue
173		}
174
175		buf := new(bytes.Buffer)
176		tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
177		big := false
178		for j, entry := range test.entries {
179			big = big || entry.header.Size > 1<<10
180			if err := tw.WriteHeader(entry.header); err != nil {
181				t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
182				continue testLoop
183			}
184			if _, err := io.WriteString(tw, entry.contents); err != nil {
185				t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
186				continue testLoop
187			}
188		}
189		// Only interested in Close failures for the small tests.
190		if err := tw.Close(); err != nil && !big {
191			t.Errorf("test %d: Failed closing archive: %v", i, err)
192			continue testLoop
193		}
194
195		actual := buf.Bytes()
196		if !bytes.Equal(expected, actual) {
197			t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
198				i, bytediff(expected, actual))
199		}
200		if testing.Short() { // The second test is expensive.
201			break
202		}
203	}
204}
205
206func TestPax(t *testing.T) {
207	// Create an archive with a large name
208	fileinfo, err := os.Stat("testdata/small.txt")
209	if err != nil {
210		t.Fatal(err)
211	}
212	hdr, err := FileInfoHeader(fileinfo, "")
213	if err != nil {
214		t.Fatalf("os.Stat: %v", err)
215	}
216	// Force a PAX long name to be written
217	longName := strings.Repeat("ab", 100)
218	contents := strings.Repeat(" ", int(hdr.Size))
219	hdr.Name = longName
220	var buf bytes.Buffer
221	writer := NewWriter(&buf)
222	if err := writer.WriteHeader(hdr); err != nil {
223		t.Fatal(err)
224	}
225	if _, err = writer.Write([]byte(contents)); err != nil {
226		t.Fatal(err)
227	}
228	if err := writer.Close(); err != nil {
229		t.Fatal(err)
230	}
231	// Simple test to make sure PAX extensions are in effect
232	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
233		t.Fatal("Expected at least one PAX header to be written.")
234	}
235	// Test that we can get a long name back out of the archive.
236	reader := NewReader(&buf)
237	hdr, err = reader.Next()
238	if err != nil {
239		t.Fatal(err)
240	}
241	if hdr.Name != longName {
242		t.Fatal("Couldn't recover long file name")
243	}
244}
245
246func TestPaxSymlink(t *testing.T) {
247	// Create an archive with a large linkname
248	fileinfo, err := os.Stat("testdata/small.txt")
249	if err != nil {
250		t.Fatal(err)
251	}
252	hdr, err := FileInfoHeader(fileinfo, "")
253	hdr.Typeflag = TypeSymlink
254	if err != nil {
255		t.Fatalf("os.Stat:1 %v", err)
256	}
257	// Force a PAX long linkname to be written
258	longLinkname := strings.Repeat("1234567890/1234567890", 10)
259	hdr.Linkname = longLinkname
260
261	hdr.Size = 0
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.Close(); err != nil {
268		t.Fatal(err)
269	}
270	// Simple test to make sure PAX extensions are in effect
271	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
272		t.Fatal("Expected at least one PAX header to be written.")
273	}
274	// Test that we can get a long name back out of the archive.
275	reader := NewReader(&buf)
276	hdr, err = reader.Next()
277	if err != nil {
278		t.Fatal(err)
279	}
280	if hdr.Linkname != longLinkname {
281		t.Fatal("Couldn't recover long link name")
282	}
283}
284
285func TestPaxNonAscii(t *testing.T) {
286	// Create an archive with non ascii. These should trigger a pax header
287	// because pax headers have a defined utf-8 encoding.
288	fileinfo, err := os.Stat("testdata/small.txt")
289	if err != nil {
290		t.Fatal(err)
291	}
292
293	hdr, err := FileInfoHeader(fileinfo, "")
294	if err != nil {
295		t.Fatalf("os.Stat:1 %v", err)
296	}
297
298	// some sample data
299	chineseFilename := "文件名"
300	chineseGroupname := "組"
301	chineseUsername := "用戶名"
302
303	hdr.Name = chineseFilename
304	hdr.Gname = chineseGroupname
305	hdr.Uname = chineseUsername
306
307	contents := strings.Repeat(" ", int(hdr.Size))
308
309	var buf bytes.Buffer
310	writer := NewWriter(&buf)
311	if err := writer.WriteHeader(hdr); err != nil {
312		t.Fatal(err)
313	}
314	if _, err = writer.Write([]byte(contents)); err != nil {
315		t.Fatal(err)
316	}
317	if err := writer.Close(); err != nil {
318		t.Fatal(err)
319	}
320	// Simple test to make sure PAX extensions are in effect
321	if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) {
322		t.Fatal("Expected at least one PAX header to be written.")
323	}
324	// Test that we can get a long name back out of the archive.
325	reader := NewReader(&buf)
326	hdr, err = reader.Next()
327	if err != nil {
328		t.Fatal(err)
329	}
330	if hdr.Name != chineseFilename {
331		t.Fatal("Couldn't recover unicode name")
332	}
333	if hdr.Gname != chineseGroupname {
334		t.Fatal("Couldn't recover unicode group")
335	}
336	if hdr.Uname != chineseUsername {
337		t.Fatal("Couldn't recover unicode user")
338	}
339}
340
341func TestPAXHeader(t *testing.T) {
342	medName := strings.Repeat("CD", 50)
343	longName := strings.Repeat("AB", 100)
344	paxTests := [][2]string{
345		{paxPath + "=/etc/hosts", "19 path=/etc/hosts\n"},
346		{"a=b", "6 a=b\n"},          // Single digit length
347		{"a=names", "11 a=names\n"}, // Test case involving carries
348		{paxPath + "=" + longName, fmt.Sprintf("210 path=%s\n", longName)},
349		{paxPath + "=" + medName, fmt.Sprintf("110 path=%s\n", medName)}}
350
351	for _, test := range paxTests {
352		key, expected := test[0], test[1]
353		if result := paxHeader(key); result != expected {
354			t.Fatalf("paxHeader: got %s, expected %s", result, expected)
355		}
356	}
357}
358
359func TestUSTARLongName(t *testing.T) {
360	// Create an archive with a path that failed to split with USTAR extension in previous versions.
361	fileinfo, err := os.Stat("testdata/small.txt")
362	if err != nil {
363		t.Fatal(err)
364	}
365	hdr, err := FileInfoHeader(fileinfo, "")
366	hdr.Typeflag = TypeDir
367	if err != nil {
368		t.Fatalf("os.Stat:1 %v", err)
369	}
370	// Force a PAX long name to be written. The name was taken from a practical example
371	// that fails and replaced ever char through numbers to anonymize the sample.
372	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/"
373	hdr.Name = longName
374
375	hdr.Size = 0
376	var buf bytes.Buffer
377	writer := NewWriter(&buf)
378	if err := writer.WriteHeader(hdr); err != nil {
379		t.Fatal(err)
380	}
381	if err := writer.Close(); err != nil {
382		t.Fatal(err)
383	}
384	// Test that we can get a long name back out of the archive.
385	reader := NewReader(&buf)
386	hdr, err = reader.Next()
387	if err != nil {
388		t.Fatal(err)
389	}
390	if hdr.Name != longName {
391		t.Fatal("Couldn't recover long name")
392	}
393}
394