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	"crypto/md5"
10	"fmt"
11	"io"
12	"io/ioutil"
13	"math"
14	"os"
15	"reflect"
16	"strings"
17	"testing"
18	"time"
19)
20
21type untarTest struct {
22	file    string    // Test input file
23	headers []*Header // Expected output headers
24	chksums []string  // MD5 checksum of files, leave as nil if not checked
25	err     error     // Expected error to occur
26}
27
28var gnuTarTest = &untarTest{
29	file: "testdata/gnu.tar",
30	headers: []*Header{
31		{
32			Name:     "small.txt",
33			Mode:     0640,
34			Uid:      73025,
35			Gid:      5000,
36			Size:     5,
37			ModTime:  time.Unix(1244428340, 0),
38			Typeflag: '0',
39			Uname:    "dsymonds",
40			Gname:    "eng",
41		},
42		{
43			Name:     "small2.txt",
44			Mode:     0640,
45			Uid:      73025,
46			Gid:      5000,
47			Size:     11,
48			ModTime:  time.Unix(1244436044, 0),
49			Typeflag: '0',
50			Uname:    "dsymonds",
51			Gname:    "eng",
52		},
53	},
54	chksums: []string{
55		"e38b27eaccb4391bdec553a7f3ae6b2f",
56		"c65bd2e50a56a2138bf1716f2fd56fe9",
57	},
58}
59
60var sparseTarTest = &untarTest{
61	file: "testdata/sparse-formats.tar",
62	headers: []*Header{
63		{
64			Name:     "sparse-gnu",
65			Mode:     420,
66			Uid:      1000,
67			Gid:      1000,
68			Size:     200,
69			ModTime:  time.Unix(1392395740, 0),
70			Typeflag: 0x53,
71			Linkname: "",
72			Uname:    "david",
73			Gname:    "david",
74			Devmajor: 0,
75			Devminor: 0,
76		},
77		{
78			Name:     "sparse-posix-0.0",
79			Mode:     420,
80			Uid:      1000,
81			Gid:      1000,
82			Size:     200,
83			ModTime:  time.Unix(1392342187, 0),
84			Typeflag: 0x30,
85			Linkname: "",
86			Uname:    "david",
87			Gname:    "david",
88			Devmajor: 0,
89			Devminor: 0,
90		},
91		{
92			Name:     "sparse-posix-0.1",
93			Mode:     420,
94			Uid:      1000,
95			Gid:      1000,
96			Size:     200,
97			ModTime:  time.Unix(1392340456, 0),
98			Typeflag: 0x30,
99			Linkname: "",
100			Uname:    "david",
101			Gname:    "david",
102			Devmajor: 0,
103			Devminor: 0,
104		},
105		{
106			Name:     "sparse-posix-1.0",
107			Mode:     420,
108			Uid:      1000,
109			Gid:      1000,
110			Size:     200,
111			ModTime:  time.Unix(1392337404, 0),
112			Typeflag: 0x30,
113			Linkname: "",
114			Uname:    "david",
115			Gname:    "david",
116			Devmajor: 0,
117			Devminor: 0,
118		},
119		{
120			Name:     "end",
121			Mode:     420,
122			Uid:      1000,
123			Gid:      1000,
124			Size:     4,
125			ModTime:  time.Unix(1392398319, 0),
126			Typeflag: 0x30,
127			Linkname: "",
128			Uname:    "david",
129			Gname:    "david",
130			Devmajor: 0,
131			Devminor: 0,
132		},
133	},
134	chksums: []string{
135		"6f53234398c2449fe67c1812d993012f",
136		"6f53234398c2449fe67c1812d993012f",
137		"6f53234398c2449fe67c1812d993012f",
138		"6f53234398c2449fe67c1812d993012f",
139		"b0061974914468de549a2af8ced10316",
140	},
141}
142
143var untarTests = []*untarTest{
144	gnuTarTest,
145	sparseTarTest,
146	{
147		file: "testdata/star.tar",
148		headers: []*Header{
149			{
150				Name:       "small.txt",
151				Mode:       0640,
152				Uid:        73025,
153				Gid:        5000,
154				Size:       5,
155				ModTime:    time.Unix(1244592783, 0),
156				Typeflag:   '0',
157				Uname:      "dsymonds",
158				Gname:      "eng",
159				AccessTime: time.Unix(1244592783, 0),
160				ChangeTime: time.Unix(1244592783, 0),
161			},
162			{
163				Name:       "small2.txt",
164				Mode:       0640,
165				Uid:        73025,
166				Gid:        5000,
167				Size:       11,
168				ModTime:    time.Unix(1244592783, 0),
169				Typeflag:   '0',
170				Uname:      "dsymonds",
171				Gname:      "eng",
172				AccessTime: time.Unix(1244592783, 0),
173				ChangeTime: time.Unix(1244592783, 0),
174			},
175		},
176	},
177	{
178		file: "testdata/v7.tar",
179		headers: []*Header{
180			{
181				Name:     "small.txt",
182				Mode:     0444,
183				Uid:      73025,
184				Gid:      5000,
185				Size:     5,
186				ModTime:  time.Unix(1244593104, 0),
187				Typeflag: '\x00',
188			},
189			{
190				Name:     "small2.txt",
191				Mode:     0444,
192				Uid:      73025,
193				Gid:      5000,
194				Size:     11,
195				ModTime:  time.Unix(1244593104, 0),
196				Typeflag: '\x00',
197			},
198		},
199	},
200	{
201		file: "testdata/pax.tar",
202		headers: []*Header{
203			{
204				Name:       "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
205				Mode:       0664,
206				Uid:        1000,
207				Gid:        1000,
208				Uname:      "shane",
209				Gname:      "shane",
210				Size:       7,
211				ModTime:    time.Unix(1350244992, 23960108),
212				ChangeTime: time.Unix(1350244992, 23960108),
213				AccessTime: time.Unix(1350244992, 23960108),
214				Typeflag:   TypeReg,
215			},
216			{
217				Name:       "a/b",
218				Mode:       0777,
219				Uid:        1000,
220				Gid:        1000,
221				Uname:      "shane",
222				Gname:      "shane",
223				Size:       0,
224				ModTime:    time.Unix(1350266320, 910238425),
225				ChangeTime: time.Unix(1350266320, 910238425),
226				AccessTime: time.Unix(1350266320, 910238425),
227				Typeflag:   TypeSymlink,
228				Linkname:   "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
229			},
230		},
231	},
232	{
233		file: "testdata/nil-uid.tar", // golang.org/issue/5290
234		headers: []*Header{
235			{
236				Name:     "P1050238.JPG.log",
237				Mode:     0664,
238				Uid:      0,
239				Gid:      0,
240				Size:     14,
241				ModTime:  time.Unix(1365454838, 0),
242				Typeflag: TypeReg,
243				Linkname: "",
244				Uname:    "eyefi",
245				Gname:    "eyefi",
246				Devmajor: 0,
247				Devminor: 0,
248			},
249		},
250	},
251	{
252		file: "testdata/xattrs.tar",
253		headers: []*Header{
254			{
255				Name:       "small.txt",
256				Mode:       0644,
257				Uid:        1000,
258				Gid:        10,
259				Size:       5,
260				ModTime:    time.Unix(1386065770, 448252320),
261				Typeflag:   '0',
262				Uname:      "alex",
263				Gname:      "wheel",
264				AccessTime: time.Unix(1389782991, 419875220),
265				ChangeTime: time.Unix(1389782956, 794414986),
266				Xattrs: map[string]string{
267					"user.key":  "value",
268					"user.key2": "value2",
269					// Interestingly, selinux encodes the terminating null inside the xattr
270					"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
271				},
272			},
273			{
274				Name:       "small2.txt",
275				Mode:       0644,
276				Uid:        1000,
277				Gid:        10,
278				Size:       11,
279				ModTime:    time.Unix(1386065770, 449252304),
280				Typeflag:   '0',
281				Uname:      "alex",
282				Gname:      "wheel",
283				AccessTime: time.Unix(1389782991, 419875220),
284				ChangeTime: time.Unix(1386065770, 449252304),
285				Xattrs: map[string]string{
286					"security.selinux": "unconfined_u:object_r:default_t:s0\x00",
287				},
288			},
289		},
290	},
291	{
292		// Matches the behavior of GNU, BSD, and STAR tar utilities.
293		file: "testdata/gnu-multi-hdrs.tar",
294		headers: []*Header{
295			{
296				Name:     "GNU2/GNU2/long-path-name",
297				Linkname: "GNU4/GNU4/long-linkpath-name",
298				ModTime:  time.Unix(0, 0),
299				Typeflag: '2',
300			},
301		},
302	},
303	{
304		// Matches the behavior of GNU and BSD tar utilities.
305		file: "testdata/pax-multi-hdrs.tar",
306		headers: []*Header{
307			{
308				Name:     "bar",
309				Linkname: "PAX4/PAX4/long-linkpath-name",
310				ModTime:  time.Unix(0, 0),
311				Typeflag: '2',
312			},
313		},
314	},
315	{
316		file: "testdata/neg-size.tar",
317		err:  ErrHeader,
318	},
319	{
320		file: "testdata/issue10968.tar",
321		err:  ErrHeader,
322	},
323	{
324		file: "testdata/issue11169.tar",
325		err:  ErrHeader,
326	},
327	{
328		file: "testdata/issue12435.tar",
329		err:  ErrHeader,
330	},
331}
332
333func TestReader(t *testing.T) {
334	for i, v := range untarTests {
335		f, err := os.Open(v.file)
336		if err != nil {
337			t.Errorf("file %s, test %d: unexpected error: %v", v.file, i, err)
338			continue
339		}
340		defer f.Close()
341
342		// Capture all headers and checksums.
343		var (
344			tr      = NewReader(f)
345			hdrs    []*Header
346			chksums []string
347			rdbuf   = make([]byte, 8)
348		)
349		for {
350			var hdr *Header
351			hdr, err = tr.Next()
352			if err != nil {
353				if err == io.EOF {
354					err = nil // Expected error
355				}
356				break
357			}
358			hdrs = append(hdrs, hdr)
359
360			if v.chksums == nil {
361				continue
362			}
363			h := md5.New()
364			_, err = io.CopyBuffer(h, tr, rdbuf) // Effectively an incremental read
365			if err != nil {
366				break
367			}
368			chksums = append(chksums, fmt.Sprintf("%x", h.Sum(nil)))
369		}
370
371		for j, hdr := range hdrs {
372			if j >= len(v.headers) {
373				t.Errorf("file %s, test %d, entry %d: unexpected header:\ngot %+v",
374					v.file, i, j, *hdr)
375				continue
376			}
377			if !reflect.DeepEqual(*hdr, *v.headers[j]) {
378				t.Errorf("file %s, test %d, entry %d: incorrect header:\ngot  %+v\nwant %+v",
379					v.file, i, j, *hdr, *v.headers[j])
380			}
381		}
382		if len(hdrs) != len(v.headers) {
383			t.Errorf("file %s, test %d: got %d headers, want %d headers",
384				v.file, i, len(hdrs), len(v.headers))
385		}
386
387		for j, sum := range chksums {
388			if j >= len(v.chksums) {
389				t.Errorf("file %s, test %d, entry %d: unexpected sum: got %s",
390					v.file, i, j, sum)
391				continue
392			}
393			if sum != v.chksums[j] {
394				t.Errorf("file %s, test %d, entry %d: incorrect checksum: got %s, want %s",
395					v.file, i, j, sum, v.chksums[j])
396			}
397		}
398
399		if err != v.err {
400			t.Errorf("file %s, test %d: unexpected error: got %v, want %v",
401				v.file, i, err, v.err)
402		}
403		f.Close()
404	}
405}
406
407func TestPartialRead(t *testing.T) {
408	f, err := os.Open("testdata/gnu.tar")
409	if err != nil {
410		t.Fatalf("Unexpected error: %v", err)
411	}
412	defer f.Close()
413
414	tr := NewReader(f)
415
416	// Read the first four bytes; Next() should skip the last byte.
417	hdr, err := tr.Next()
418	if err != nil || hdr == nil {
419		t.Fatalf("Didn't get first file: %v", err)
420	}
421	buf := make([]byte, 4)
422	if _, err := io.ReadFull(tr, buf); err != nil {
423		t.Fatalf("Unexpected error: %v", err)
424	}
425	if expected := []byte("Kilt"); !bytes.Equal(buf, expected) {
426		t.Errorf("Contents = %v, want %v", buf, expected)
427	}
428
429	// Second file
430	hdr, err = tr.Next()
431	if err != nil || hdr == nil {
432		t.Fatalf("Didn't get second file: %v", err)
433	}
434	buf = make([]byte, 6)
435	if _, err := io.ReadFull(tr, buf); err != nil {
436		t.Fatalf("Unexpected error: %v", err)
437	}
438	if expected := []byte("Google"); !bytes.Equal(buf, expected) {
439		t.Errorf("Contents = %v, want %v", buf, expected)
440	}
441}
442
443func TestParsePAXHeader(t *testing.T) {
444	paxTests := [][3]string{
445		{"a", "a=name", "10 a=name\n"}, // Test case involving multiple acceptable lengths
446		{"a", "a=name", "9 a=name\n"},  // Test case involving multiple acceptable length
447		{"mtime", "mtime=1350244992.023960108", "30 mtime=1350244992.023960108\n"}}
448	for _, test := range paxTests {
449		key, expected, raw := test[0], test[1], test[2]
450		reader := bytes.NewReader([]byte(raw))
451		headers, err := parsePAX(reader)
452		if err != nil {
453			t.Errorf("Couldn't parse correctly formatted headers: %v", err)
454			continue
455		}
456		if strings.EqualFold(headers[key], expected) {
457			t.Errorf("mtime header incorrectly parsed: got %s, wanted %s", headers[key], expected)
458			continue
459		}
460		trailer := make([]byte, 100)
461		n, err := reader.Read(trailer)
462		if err != io.EOF || n != 0 {
463			t.Error("Buffer wasn't consumed")
464		}
465	}
466	badHeaderTests := [][]byte{
467		[]byte("3 somelongkey=\n"),
468		[]byte("50 tooshort=\n"),
469	}
470	for _, test := range badHeaderTests {
471		if _, err := parsePAX(bytes.NewReader(test)); err != ErrHeader {
472			t.Fatal("Unexpected success when parsing bad header")
473		}
474	}
475}
476
477func TestParsePAXTime(t *testing.T) {
478	// Some valid PAX time values
479	timestamps := map[string]time.Time{
480		"1350244992.023960108":  time.Unix(1350244992, 23960108), // The common case
481		"1350244992.02396010":   time.Unix(1350244992, 23960100), // Lower precision value
482		"1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value
483		"1350244992":            time.Unix(1350244992, 0),        // Low precision value
484	}
485	for input, expected := range timestamps {
486		ts, err := parsePAXTime(input)
487		if err != nil {
488			t.Fatal(err)
489		}
490		if !ts.Equal(expected) {
491			t.Fatalf("Time parsing failure %s %s", ts, expected)
492		}
493	}
494}
495
496func TestMergePAX(t *testing.T) {
497	hdr := new(Header)
498	// Test a string, integer, and time based value.
499	headers := map[string]string{
500		"path":  "a/b/c",
501		"uid":   "1000",
502		"mtime": "1350244992.023960108",
503	}
504	err := mergePAX(hdr, headers)
505	if err != nil {
506		t.Fatal(err)
507	}
508	want := &Header{
509		Name:    "a/b/c",
510		Uid:     1000,
511		ModTime: time.Unix(1350244992, 23960108),
512	}
513	if !reflect.DeepEqual(hdr, want) {
514		t.Errorf("incorrect merge: got %+v, want %+v", hdr, want)
515	}
516}
517
518func TestSparseFileReader(t *testing.T) {
519	var vectors = []struct {
520		realSize   int64         // Real size of the output file
521		sparseMap  []sparseEntry // Input sparse map
522		sparseData string        // Input compact data
523		expected   string        // Expected output data
524		err        error         // Expected error outcome
525	}{{
526		realSize: 8,
527		sparseMap: []sparseEntry{
528			{offset: 0, numBytes: 2},
529			{offset: 5, numBytes: 3},
530		},
531		sparseData: "abcde",
532		expected:   "ab\x00\x00\x00cde",
533	}, {
534		realSize: 10,
535		sparseMap: []sparseEntry{
536			{offset: 0, numBytes: 2},
537			{offset: 5, numBytes: 3},
538		},
539		sparseData: "abcde",
540		expected:   "ab\x00\x00\x00cde\x00\x00",
541	}, {
542		realSize: 8,
543		sparseMap: []sparseEntry{
544			{offset: 1, numBytes: 3},
545			{offset: 6, numBytes: 2},
546		},
547		sparseData: "abcde",
548		expected:   "\x00abc\x00\x00de",
549	}, {
550		realSize: 8,
551		sparseMap: []sparseEntry{
552			{offset: 1, numBytes: 3},
553			{offset: 6, numBytes: 0},
554			{offset: 6, numBytes: 0},
555			{offset: 6, numBytes: 2},
556		},
557		sparseData: "abcde",
558		expected:   "\x00abc\x00\x00de",
559	}, {
560		realSize: 10,
561		sparseMap: []sparseEntry{
562			{offset: 1, numBytes: 3},
563			{offset: 6, numBytes: 2},
564		},
565		sparseData: "abcde",
566		expected:   "\x00abc\x00\x00de\x00\x00",
567	}, {
568		realSize: 10,
569		sparseMap: []sparseEntry{
570			{offset: 1, numBytes: 3},
571			{offset: 6, numBytes: 2},
572			{offset: 8, numBytes: 0},
573			{offset: 8, numBytes: 0},
574			{offset: 8, numBytes: 0},
575			{offset: 8, numBytes: 0},
576		},
577		sparseData: "abcde",
578		expected:   "\x00abc\x00\x00de\x00\x00",
579	}, {
580		realSize:   2,
581		sparseMap:  []sparseEntry{},
582		sparseData: "",
583		expected:   "\x00\x00",
584	}, {
585		realSize:  -2,
586		sparseMap: []sparseEntry{},
587		err:       ErrHeader,
588	}, {
589		realSize: -10,
590		sparseMap: []sparseEntry{
591			{offset: 1, numBytes: 3},
592			{offset: 6, numBytes: 2},
593		},
594		sparseData: "abcde",
595		err:        ErrHeader,
596	}, {
597		realSize: 10,
598		sparseMap: []sparseEntry{
599			{offset: 1, numBytes: 3},
600			{offset: 6, numBytes: 5},
601		},
602		sparseData: "abcde",
603		err:        ErrHeader,
604	}, {
605		realSize: 35,
606		sparseMap: []sparseEntry{
607			{offset: 1, numBytes: 3},
608			{offset: 6, numBytes: 5},
609		},
610		sparseData: "abcde",
611		err:        io.ErrUnexpectedEOF,
612	}, {
613		realSize: 35,
614		sparseMap: []sparseEntry{
615			{offset: 1, numBytes: 3},
616			{offset: 6, numBytes: -5},
617		},
618		sparseData: "abcde",
619		err:        ErrHeader,
620	}, {
621		realSize: 35,
622		sparseMap: []sparseEntry{
623			{offset: math.MaxInt64, numBytes: 3},
624			{offset: 6, numBytes: -5},
625		},
626		sparseData: "abcde",
627		err:        ErrHeader,
628	}, {
629		realSize: 10,
630		sparseMap: []sparseEntry{
631			{offset: 1, numBytes: 3},
632			{offset: 2, numBytes: 2},
633		},
634		sparseData: "abcde",
635		err:        ErrHeader,
636	}}
637
638	for i, v := range vectors {
639		r := bytes.NewReader([]byte(v.sparseData))
640		rfr := &regFileReader{r: r, nb: int64(len(v.sparseData))}
641
642		var sfr *sparseFileReader
643		var err error
644		var buf []byte
645
646		sfr, err = newSparseFileReader(rfr, v.sparseMap, v.realSize)
647		if err != nil {
648			goto fail
649		}
650		if sfr.numBytes() != int64(len(v.sparseData)) {
651			t.Errorf("test %d, numBytes() before reading: got %d, want %d", i, sfr.numBytes(), len(v.sparseData))
652		}
653		buf, err = ioutil.ReadAll(sfr)
654		if err != nil {
655			goto fail
656		}
657		if string(buf) != v.expected {
658			t.Errorf("test %d, ReadAll(): got %q, want %q", i, string(buf), v.expected)
659		}
660		if sfr.numBytes() != 0 {
661			t.Errorf("test %d, numBytes() after reading: got %d, want %d", i, sfr.numBytes(), 0)
662		}
663
664	fail:
665		if err != v.err {
666			t.Errorf("test %d, unexpected error: got %v, want %v", i, err, v.err)
667		}
668	}
669}
670
671func TestReadGNUSparseMap0x1(t *testing.T) {
672	const (
673		maxUint = ^uint(0)
674		maxInt  = int(maxUint >> 1)
675	)
676	var (
677		big1 = fmt.Sprintf("%d", int64(maxInt))
678		big2 = fmt.Sprintf("%d", (int64(maxInt)/2)+1)
679		big3 = fmt.Sprintf("%d", (int64(maxInt) / 3))
680	)
681
682	var vectors = []struct {
683		extHdrs   map[string]string // Input data
684		sparseMap []sparseEntry     // Expected sparse entries to be outputted
685		err       error             // Expected errors that may be raised
686	}{{
687		extHdrs: map[string]string{paxGNUSparseNumBlocks: "-4"},
688		err:     ErrHeader,
689	}, {
690		extHdrs: map[string]string{paxGNUSparseNumBlocks: "fee "},
691		err:     ErrHeader,
692	}, {
693		extHdrs: map[string]string{
694			paxGNUSparseNumBlocks: big1,
695			paxGNUSparseMap:       "0,5,10,5,20,5,30,5",
696		},
697		err: ErrHeader,
698	}, {
699		extHdrs: map[string]string{
700			paxGNUSparseNumBlocks: big2,
701			paxGNUSparseMap:       "0,5,10,5,20,5,30,5",
702		},
703		err: ErrHeader,
704	}, {
705		extHdrs: map[string]string{
706			paxGNUSparseNumBlocks: big3,
707			paxGNUSparseMap:       "0,5,10,5,20,5,30,5",
708		},
709		err: ErrHeader,
710	}, {
711		extHdrs: map[string]string{
712			paxGNUSparseNumBlocks: "4",
713			paxGNUSparseMap:       "0.5,5,10,5,20,5,30,5",
714		},
715		err: ErrHeader,
716	}, {
717		extHdrs: map[string]string{
718			paxGNUSparseNumBlocks: "4",
719			paxGNUSparseMap:       "0,5.5,10,5,20,5,30,5",
720		},
721		err: ErrHeader,
722	}, {
723		extHdrs: map[string]string{
724			paxGNUSparseNumBlocks: "4",
725			paxGNUSparseMap:       "0,fewafewa.5,fewafw,5,20,5,30,5",
726		},
727		err: ErrHeader,
728	}, {
729		extHdrs: map[string]string{
730			paxGNUSparseNumBlocks: "4",
731			paxGNUSparseMap:       "0,5,10,5,20,5,30,5",
732		},
733		sparseMap: []sparseEntry{{0, 5}, {10, 5}, {20, 5}, {30, 5}},
734	}}
735
736	for i, v := range vectors {
737		sp, err := readGNUSparseMap0x1(v.extHdrs)
738		if !reflect.DeepEqual(sp, v.sparseMap) && !(len(sp) == 0 && len(v.sparseMap) == 0) {
739			t.Errorf("test %d, readGNUSparseMap0x1(...): got %v, want %v", i, sp, v.sparseMap)
740		}
741		if err != v.err {
742			t.Errorf("test %d, unexpected error: got %v, want %v", i, err, v.err)
743		}
744	}
745}
746
747func TestReadGNUSparseMap1x0(t *testing.T) {
748	var sp = []sparseEntry{{1, 2}, {3, 4}}
749	for i := 0; i < 98; i++ {
750		sp = append(sp, sparseEntry{54321, 12345})
751	}
752
753	var vectors = []struct {
754		input     string        // Input data
755		sparseMap []sparseEntry // Expected sparse entries to be outputted
756		cnt       int           // Expected number of bytes read
757		err       error         // Expected errors that may be raised
758	}{{
759		input: "",
760		cnt:   0,
761		err:   io.ErrUnexpectedEOF,
762	}, {
763		input: "ab",
764		cnt:   2,
765		err:   io.ErrUnexpectedEOF,
766	}, {
767		input: strings.Repeat("\x00", 512),
768		cnt:   512,
769		err:   io.ErrUnexpectedEOF,
770	}, {
771		input: strings.Repeat("\x00", 511) + "\n",
772		cnt:   512,
773		err:   ErrHeader,
774	}, {
775		input: strings.Repeat("\n", 512),
776		cnt:   512,
777		err:   ErrHeader,
778	}, {
779		input:     "0\n" + strings.Repeat("\x00", 510) + strings.Repeat("a", 512),
780		sparseMap: []sparseEntry{},
781		cnt:       512,
782	}, {
783		input:     strings.Repeat("0", 512) + "0\n" + strings.Repeat("\x00", 510),
784		sparseMap: []sparseEntry{},
785		cnt:       1024,
786	}, {
787		input:     strings.Repeat("0", 1024) + "1\n2\n3\n" + strings.Repeat("\x00", 506),
788		sparseMap: []sparseEntry{{2, 3}},
789		cnt:       1536,
790	}, {
791		input: strings.Repeat("0", 1024) + "1\n2\n\n" + strings.Repeat("\x00", 509),
792		cnt:   1536,
793		err:   ErrHeader,
794	}, {
795		input: strings.Repeat("0", 1024) + "1\n2\n" + strings.Repeat("\x00", 508),
796		cnt:   1536,
797		err:   io.ErrUnexpectedEOF,
798	}, {
799		input: "-1\n2\n\n" + strings.Repeat("\x00", 506),
800		cnt:   512,
801		err:   ErrHeader,
802	}, {
803		input: "1\nk\n2\n" + strings.Repeat("\x00", 506),
804		cnt:   512,
805		err:   ErrHeader,
806	}, {
807		input:     "100\n1\n2\n3\n4\n" + strings.Repeat("54321\n0000000000000012345\n", 98) + strings.Repeat("\x00", 512),
808		cnt:       2560,
809		sparseMap: sp,
810	}}
811
812	for i, v := range vectors {
813		r := strings.NewReader(v.input)
814		sp, err := readGNUSparseMap1x0(r)
815		if !reflect.DeepEqual(sp, v.sparseMap) && !(len(sp) == 0 && len(v.sparseMap) == 0) {
816			t.Errorf("test %d, readGNUSparseMap1x0(...): got %v, want %v", i, sp, v.sparseMap)
817		}
818		if numBytes := len(v.input) - r.Len(); numBytes != v.cnt {
819			t.Errorf("test %d, bytes read: got %v, want %v", i, numBytes, v.cnt)
820		}
821		if err != v.err {
822			t.Errorf("test %d, unexpected error: got %v, want %v", i, err, v.err)
823		}
824	}
825}
826
827func TestUninitializedRead(t *testing.T) {
828	test := gnuTarTest
829	f, err := os.Open(test.file)
830	if err != nil {
831		t.Fatalf("Unexpected error: %v", err)
832	}
833	defer f.Close()
834
835	tr := NewReader(f)
836	_, err = tr.Read([]byte{})
837	if err == nil || err != io.EOF {
838		t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF)
839	}
840
841}
842
843type reader struct{ io.Reader }
844type readSeeker struct{ io.ReadSeeker }
845type readBadSeeker struct{ io.ReadSeeker }
846
847func (rbs *readBadSeeker) Seek(int64, int) (int64, error) { return 0, fmt.Errorf("illegal seek") }
848
849// TestReadTruncation test the ending condition on various truncated files and
850// that truncated files are still detected even if the underlying io.Reader
851// satisfies io.Seeker.
852func TestReadTruncation(t *testing.T) {
853	var ss []string
854	for _, p := range []string{
855		"testdata/gnu.tar",
856		"testdata/ustar-file-reg.tar",
857		"testdata/pax-path-hdr.tar",
858		"testdata/sparse-formats.tar",
859	} {
860		buf, err := ioutil.ReadFile(p)
861		if err != nil {
862			t.Fatalf("unexpected error: %v", err)
863		}
864		ss = append(ss, string(buf))
865	}
866
867	data1, data2, pax, sparse := ss[0], ss[1], ss[2], ss[3]
868	data2 += strings.Repeat("\x00", 10*512)
869	trash := strings.Repeat("garbage ", 64) // Exactly 512 bytes
870
871	var vectors = []struct {
872		input string // Input stream
873		cnt   int    // Expected number of headers read
874		err   error  // Expected error outcome
875	}{
876		{"", 0, io.EOF}, // Empty file is a "valid" tar file
877		{data1[:511], 0, io.ErrUnexpectedEOF},
878		{data1[:512], 1, io.ErrUnexpectedEOF},
879		{data1[:1024], 1, io.EOF},
880		{data1[:1536], 2, io.ErrUnexpectedEOF},
881		{data1[:2048], 2, io.EOF},
882		{data1, 2, io.EOF},
883		{data1[:2048] + data2[:1536], 3, io.EOF},
884		{data2[:511], 0, io.ErrUnexpectedEOF},
885		{data2[:512], 1, io.ErrUnexpectedEOF},
886		{data2[:1195], 1, io.ErrUnexpectedEOF},
887		{data2[:1196], 1, io.EOF}, // Exact end of data and start of padding
888		{data2[:1200], 1, io.EOF},
889		{data2[:1535], 1, io.EOF},
890		{data2[:1536], 1, io.EOF}, // Exact end of padding
891		{data2[:1536] + trash[:1], 1, io.ErrUnexpectedEOF},
892		{data2[:1536] + trash[:511], 1, io.ErrUnexpectedEOF},
893		{data2[:1536] + trash, 1, ErrHeader},
894		{data2[:2048], 1, io.EOF}, // Exactly 1 empty block
895		{data2[:2048] + trash[:1], 1, io.ErrUnexpectedEOF},
896		{data2[:2048] + trash[:511], 1, io.ErrUnexpectedEOF},
897		{data2[:2048] + trash, 1, ErrHeader},
898		{data2[:2560], 1, io.EOF}, // Exactly 2 empty blocks (normal end-of-stream)
899		{data2[:2560] + trash[:1], 1, io.EOF},
900		{data2[:2560] + trash[:511], 1, io.EOF},
901		{data2[:2560] + trash, 1, io.EOF},
902		{data2[:3072], 1, io.EOF},
903		{pax, 0, io.EOF}, // PAX header without data is a "valid" tar file
904		{pax + trash[:1], 0, io.ErrUnexpectedEOF},
905		{pax + trash[:511], 0, io.ErrUnexpectedEOF},
906		{sparse[:511], 0, io.ErrUnexpectedEOF},
907		// TODO(dsnet): This should pass, but currently fails.
908		// {sparse[:512], 0, io.ErrUnexpectedEOF},
909		{sparse[:3584], 1, io.EOF},
910		{sparse[:9200], 1, io.EOF}, // Terminate in padding of sparse header
911		{sparse[:9216], 1, io.EOF},
912		{sparse[:9728], 2, io.ErrUnexpectedEOF},
913		{sparse[:10240], 2, io.EOF},
914		{sparse[:11264], 2, io.ErrUnexpectedEOF},
915		{sparse, 5, io.EOF},
916		{sparse + trash, 5, io.EOF},
917	}
918
919	for i, v := range vectors {
920		for j := 0; j < 6; j++ {
921			var tr *Reader
922			var s1, s2 string
923
924			switch j {
925			case 0:
926				tr = NewReader(&reader{strings.NewReader(v.input)})
927				s1, s2 = "io.Reader", "auto"
928			case 1:
929				tr = NewReader(&reader{strings.NewReader(v.input)})
930				s1, s2 = "io.Reader", "manual"
931			case 2:
932				tr = NewReader(&readSeeker{strings.NewReader(v.input)})
933				s1, s2 = "io.ReadSeeker", "auto"
934			case 3:
935				tr = NewReader(&readSeeker{strings.NewReader(v.input)})
936				s1, s2 = "io.ReadSeeker", "manual"
937			case 4:
938				tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})
939				s1, s2 = "ReadBadSeeker", "auto"
940			case 5:
941				tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})
942				s1, s2 = "ReadBadSeeker", "manual"
943			}
944
945			var cnt int
946			var err error
947			for {
948				if _, err = tr.Next(); err != nil {
949					break
950				}
951				cnt++
952				if s2 == "manual" {
953					if _, err = io.Copy(ioutil.Discard, tr); err != nil {
954						break
955					}
956				}
957			}
958			if err != v.err {
959				t.Errorf("test %d, NewReader(%s(...)) with %s discard: got %v, want %v",
960					i, s1, s2, err, v.err)
961			}
962			if cnt != v.cnt {
963				t.Errorf("test %d, NewReader(%s(...)) with %s discard: got %d headers, want %d headers",
964					i, s1, s2, cnt, v.cnt)
965			}
966		}
967	}
968}
969
970// TestReadHeaderOnly tests that Reader does not attempt to read special
971// header-only files.
972func TestReadHeaderOnly(t *testing.T) {
973	f, err := os.Open("testdata/hdr-only.tar")
974	if err != nil {
975		t.Fatalf("unexpected error: %v", err)
976	}
977	defer f.Close()
978
979	var hdrs []*Header
980	tr := NewReader(f)
981	for {
982		hdr, err := tr.Next()
983		if err == io.EOF {
984			break
985		}
986		if err != nil {
987			t.Errorf("Next(): got %v, want %v", err, nil)
988			continue
989		}
990		hdrs = append(hdrs, hdr)
991
992		// If a special flag, we should read nothing.
993		cnt, _ := io.ReadFull(tr, []byte{0})
994		if cnt > 0 && hdr.Typeflag != TypeReg {
995			t.Errorf("ReadFull(...): got %d bytes, want 0 bytes", cnt)
996		}
997	}
998
999	// File is crafted with 16 entries. The later 8 are identical to the first
1000	// 8 except that the size is set.
1001	if len(hdrs) != 16 {
1002		t.Fatalf("len(hdrs): got %d, want %d", len(hdrs), 16)
1003	}
1004	for i := 0; i < 8; i++ {
1005		var hdr1, hdr2 = hdrs[i+0], hdrs[i+8]
1006		hdr1.Size, hdr2.Size = 0, 0
1007		if !reflect.DeepEqual(*hdr1, *hdr2) {
1008			t.Errorf("incorrect header:\ngot  %+v\nwant %+v", *hdr1, *hdr2)
1009		}
1010	}
1011}
1012
1013func TestParsePAXRecord(t *testing.T) {
1014	var medName = strings.Repeat("CD", 50)
1015	var longName = strings.Repeat("AB", 100)
1016
1017	var vectors = []struct {
1018		input     string
1019		residual  string
1020		outputKey string
1021		outputVal string
1022		ok        bool
1023	}{
1024		{"6 k=v\n\n", "\n", "k", "v", true},
1025		{"19 path=/etc/hosts\n", "", "path", "/etc/hosts", true},
1026		{"210 path=" + longName + "\nabc", "abc", "path", longName, true},
1027		{"110 path=" + medName + "\n", "", "path", medName, true},
1028		{"9 foo=ba\n", "", "foo", "ba", true},
1029		{"11 foo=bar\n\x00", "\x00", "foo", "bar", true},
1030		{"18 foo=b=\nar=\n==\x00\n", "", "foo", "b=\nar=\n==\x00", true},
1031		{"27 foo=hello9 foo=ba\nworld\n", "", "foo", "hello9 foo=ba\nworld", true},
1032		{"27 ☺☻☹=日a本b語ç\nmeow mix", "meow mix", "☺☻☹", "日a本b語ç", true},
1033		{"17 \x00hello=\x00world\n", "", "\x00hello", "\x00world", true},
1034		{"1 k=1\n", "1 k=1\n", "", "", false},
1035		{"6 k~1\n", "6 k~1\n", "", "", false},
1036		{"6_k=1\n", "6_k=1\n", "", "", false},
1037		{"6 k=1 ", "6 k=1 ", "", "", false},
1038		{"632 k=1\n", "632 k=1\n", "", "", false},
1039		{"16 longkeyname=hahaha\n", "16 longkeyname=hahaha\n", "", "", false},
1040		{"3 somelongkey=\n", "3 somelongkey=\n", "", "", false},
1041		{"50 tooshort=\n", "50 tooshort=\n", "", "", false},
1042	}
1043
1044	for _, v := range vectors {
1045		key, val, res, err := parsePAXRecord(v.input)
1046		ok := (err == nil)
1047		if v.ok != ok {
1048			if v.ok {
1049				t.Errorf("parsePAXRecord(%q): got parsing failure, want success", v.input)
1050			} else {
1051				t.Errorf("parsePAXRecord(%q): got parsing success, want failure", v.input)
1052			}
1053		}
1054		if ok && (key != v.outputKey || val != v.outputVal) {
1055			t.Errorf("parsePAXRecord(%q): got (%q: %q), want (%q: %q)",
1056				v.input, key, val, v.outputKey, v.outputVal)
1057		}
1058		if res != v.residual {
1059			t.Errorf("parsePAXRecord(%q): got residual %q, want residual %q",
1060				v.input, res, v.residual)
1061		}
1062	}
1063}
1064
1065func TestParseNumeric(t *testing.T) {
1066	var vectors = []struct {
1067		input  string
1068		output int64
1069		ok     bool
1070	}{
1071		// Test base-256 (binary) encoded values.
1072		{"", 0, true},
1073		{"\x80", 0, true},
1074		{"\x80\x00", 0, true},
1075		{"\x80\x00\x00", 0, true},
1076		{"\xbf", (1 << 6) - 1, true},
1077		{"\xbf\xff", (1 << 14) - 1, true},
1078		{"\xbf\xff\xff", (1 << 22) - 1, true},
1079		{"\xff", -1, true},
1080		{"\xff\xff", -1, true},
1081		{"\xff\xff\xff", -1, true},
1082		{"\xc0", -1 * (1 << 6), true},
1083		{"\xc0\x00", -1 * (1 << 14), true},
1084		{"\xc0\x00\x00", -1 * (1 << 22), true},
1085		{"\x87\x76\xa2\x22\xeb\x8a\x72\x61", 537795476381659745, true},
1086		{"\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", 537795476381659745, true},
1087		{"\xf7\x76\xa2\x22\xeb\x8a\x72\x61", -615126028225187231, true},
1088		{"\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", -615126028225187231, true},
1089		{"\x80\x7f\xff\xff\xff\xff\xff\xff\xff", math.MaxInt64, true},
1090		{"\x80\x80\x00\x00\x00\x00\x00\x00\x00", 0, false},
1091		{"\xff\x80\x00\x00\x00\x00\x00\x00\x00", math.MinInt64, true},
1092		{"\xff\x7f\xff\xff\xff\xff\xff\xff\xff", 0, false},
1093		{"\xf5\xec\xd1\xc7\x7e\x5f\x26\x48\x81\x9f\x8f\x9b", 0, false},
1094
1095		// Test base-8 (octal) encoded values.
1096		{"0000000\x00", 0, true},
1097		{" \x0000000\x00", 0, true},
1098		{" \x0000003\x00", 3, true},
1099		{"00000000227\x00", 0227, true},
1100		{"032033\x00 ", 032033, true},
1101		{"320330\x00 ", 0320330, true},
1102		{"0000660\x00 ", 0660, true},
1103		{"\x00 0000660\x00 ", 0660, true},
1104		{"0123456789abcdef", 0, false},
1105		{"0123456789\x00abcdef", 0, false},
1106		{"01234567\x0089abcdef", 342391, true},
1107		{"0123\x7e\x5f\x264123", 0, false},
1108	}
1109
1110	for _, v := range vectors {
1111		var p parser
1112		num := p.parseNumeric([]byte(v.input))
1113		ok := (p.err == nil)
1114		if v.ok != ok {
1115			if v.ok {
1116				t.Errorf("parseNumeric(%q): got parsing failure, want success", v.input)
1117			} else {
1118				t.Errorf("parseNumeric(%q): got parsing success, want failure", v.input)
1119			}
1120		}
1121		if ok && num != v.output {
1122			t.Errorf("parseNumeric(%q): got %d, want %d", v.input, num, v.output)
1123		}
1124	}
1125}
1126