Source file
src/archive/tar/reader_test.go
1
2
3
4
5 package tar
6
7 import (
8 "bytes"
9 "compress/bzip2"
10 "crypto/md5"
11 "errors"
12 "fmt"
13 "io"
14 "math"
15 "os"
16 "path"
17 "reflect"
18 "strconv"
19 "strings"
20 "testing"
21 "time"
22 )
23
24 func TestReader(t *testing.T) {
25 vectors := []struct {
26 file string
27 headers []*Header
28 chksums []string
29 err error
30 }{{
31 file: "testdata/gnu.tar",
32 headers: []*Header{{
33 Name: "small.txt",
34 Mode: 0640,
35 Uid: 73025,
36 Gid: 5000,
37 Size: 5,
38 ModTime: time.Unix(1244428340, 0),
39 Typeflag: '0',
40 Uname: "dsymonds",
41 Gname: "eng",
42 Format: FormatGNU,
43 }, {
44 Name: "small2.txt",
45 Mode: 0640,
46 Uid: 73025,
47 Gid: 5000,
48 Size: 11,
49 ModTime: time.Unix(1244436044, 0),
50 Typeflag: '0',
51 Uname: "dsymonds",
52 Gname: "eng",
53 Format: FormatGNU,
54 }},
55 chksums: []string{
56 "e38b27eaccb4391bdec553a7f3ae6b2f",
57 "c65bd2e50a56a2138bf1716f2fd56fe9",
58 },
59 }, {
60 file: "testdata/sparse-formats.tar",
61 headers: []*Header{{
62 Name: "sparse-gnu",
63 Mode: 420,
64 Uid: 1000,
65 Gid: 1000,
66 Size: 200,
67 ModTime: time.Unix(1392395740, 0),
68 Typeflag: 0x53,
69 Linkname: "",
70 Uname: "david",
71 Gname: "david",
72 Devmajor: 0,
73 Devminor: 0,
74 Format: FormatGNU,
75 }, {
76 Name: "sparse-posix-0.0",
77 Mode: 420,
78 Uid: 1000,
79 Gid: 1000,
80 Size: 200,
81 ModTime: time.Unix(1392342187, 0),
82 Typeflag: 0x30,
83 Linkname: "",
84 Uname: "david",
85 Gname: "david",
86 Devmajor: 0,
87 Devminor: 0,
88 PAXRecords: map[string]string{
89 "GNU.sparse.size": "200",
90 "GNU.sparse.numblocks": "95",
91 "GNU.sparse.map": "1,1,3,1,5,1,7,1,9,1,11,1,13,1,15,1,17,1,19,1,21,1,23,1,25,1,27,1,29,1,31,1,33,1,35,1,37,1,39,1,41,1,43,1,45,1,47,1,49,1,51,1,53,1,55,1,57,1,59,1,61,1,63,1,65,1,67,1,69,1,71,1,73,1,75,1,77,1,79,1,81,1,83,1,85,1,87,1,89,1,91,1,93,1,95,1,97,1,99,1,101,1,103,1,105,1,107,1,109,1,111,1,113,1,115,1,117,1,119,1,121,1,123,1,125,1,127,1,129,1,131,1,133,1,135,1,137,1,139,1,141,1,143,1,145,1,147,1,149,1,151,1,153,1,155,1,157,1,159,1,161,1,163,1,165,1,167,1,169,1,171,1,173,1,175,1,177,1,179,1,181,1,183,1,185,1,187,1,189,1",
92 },
93 Format: FormatPAX,
94 }, {
95 Name: "sparse-posix-0.1",
96 Mode: 420,
97 Uid: 1000,
98 Gid: 1000,
99 Size: 200,
100 ModTime: time.Unix(1392340456, 0),
101 Typeflag: 0x30,
102 Linkname: "",
103 Uname: "david",
104 Gname: "david",
105 Devmajor: 0,
106 Devminor: 0,
107 PAXRecords: map[string]string{
108 "GNU.sparse.size": "200",
109 "GNU.sparse.numblocks": "95",
110 "GNU.sparse.map": "1,1,3,1,5,1,7,1,9,1,11,1,13,1,15,1,17,1,19,1,21,1,23,1,25,1,27,1,29,1,31,1,33,1,35,1,37,1,39,1,41,1,43,1,45,1,47,1,49,1,51,1,53,1,55,1,57,1,59,1,61,1,63,1,65,1,67,1,69,1,71,1,73,1,75,1,77,1,79,1,81,1,83,1,85,1,87,1,89,1,91,1,93,1,95,1,97,1,99,1,101,1,103,1,105,1,107,1,109,1,111,1,113,1,115,1,117,1,119,1,121,1,123,1,125,1,127,1,129,1,131,1,133,1,135,1,137,1,139,1,141,1,143,1,145,1,147,1,149,1,151,1,153,1,155,1,157,1,159,1,161,1,163,1,165,1,167,1,169,1,171,1,173,1,175,1,177,1,179,1,181,1,183,1,185,1,187,1,189,1",
111 "GNU.sparse.name": "sparse-posix-0.1",
112 },
113 Format: FormatPAX,
114 }, {
115 Name: "sparse-posix-1.0",
116 Mode: 420,
117 Uid: 1000,
118 Gid: 1000,
119 Size: 200,
120 ModTime: time.Unix(1392337404, 0),
121 Typeflag: 0x30,
122 Linkname: "",
123 Uname: "david",
124 Gname: "david",
125 Devmajor: 0,
126 Devminor: 0,
127 PAXRecords: map[string]string{
128 "GNU.sparse.major": "1",
129 "GNU.sparse.minor": "0",
130 "GNU.sparse.realsize": "200",
131 "GNU.sparse.name": "sparse-posix-1.0",
132 },
133 Format: FormatPAX,
134 }, {
135 Name: "end",
136 Mode: 420,
137 Uid: 1000,
138 Gid: 1000,
139 Size: 4,
140 ModTime: time.Unix(1392398319, 0),
141 Typeflag: 0x30,
142 Linkname: "",
143 Uname: "david",
144 Gname: "david",
145 Devmajor: 0,
146 Devminor: 0,
147 Format: FormatGNU,
148 }},
149 chksums: []string{
150 "6f53234398c2449fe67c1812d993012f",
151 "6f53234398c2449fe67c1812d993012f",
152 "6f53234398c2449fe67c1812d993012f",
153 "6f53234398c2449fe67c1812d993012f",
154 "b0061974914468de549a2af8ced10316",
155 },
156 }, {
157 file: "testdata/star.tar",
158 headers: []*Header{{
159 Name: "small.txt",
160 Mode: 0640,
161 Uid: 73025,
162 Gid: 5000,
163 Size: 5,
164 ModTime: time.Unix(1244592783, 0),
165 Typeflag: '0',
166 Uname: "dsymonds",
167 Gname: "eng",
168 AccessTime: time.Unix(1244592783, 0),
169 ChangeTime: time.Unix(1244592783, 0),
170 }, {
171 Name: "small2.txt",
172 Mode: 0640,
173 Uid: 73025,
174 Gid: 5000,
175 Size: 11,
176 ModTime: time.Unix(1244592783, 0),
177 Typeflag: '0',
178 Uname: "dsymonds",
179 Gname: "eng",
180 AccessTime: time.Unix(1244592783, 0),
181 ChangeTime: time.Unix(1244592783, 0),
182 }},
183 }, {
184 file: "testdata/v7.tar",
185 headers: []*Header{{
186 Name: "small.txt",
187 Mode: 0444,
188 Uid: 73025,
189 Gid: 5000,
190 Size: 5,
191 ModTime: time.Unix(1244593104, 0),
192 Typeflag: '0',
193 }, {
194 Name: "small2.txt",
195 Mode: 0444,
196 Uid: 73025,
197 Gid: 5000,
198 Size: 11,
199 ModTime: time.Unix(1244593104, 0),
200 Typeflag: '0',
201 }},
202 }, {
203 file: "testdata/pax.tar",
204 headers: []*Header{{
205 Name: "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
206 Mode: 0664,
207 Uid: 1000,
208 Gid: 1000,
209 Uname: "shane",
210 Gname: "shane",
211 Size: 7,
212 ModTime: time.Unix(1350244992, 23960108),
213 ChangeTime: time.Unix(1350244992, 23960108),
214 AccessTime: time.Unix(1350244992, 23960108),
215 Typeflag: TypeReg,
216 PAXRecords: map[string]string{
217 "path": "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
218 "mtime": "1350244992.023960108",
219 "atime": "1350244992.023960108",
220 "ctime": "1350244992.023960108",
221 },
222 Format: FormatPAX,
223 }, {
224 Name: "a/b",
225 Mode: 0777,
226 Uid: 1000,
227 Gid: 1000,
228 Uname: "shane",
229 Gname: "shane",
230 Size: 0,
231 ModTime: time.Unix(1350266320, 910238425),
232 ChangeTime: time.Unix(1350266320, 910238425),
233 AccessTime: time.Unix(1350266320, 910238425),
234 Typeflag: TypeSymlink,
235 Linkname: "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
236 PAXRecords: map[string]string{
237 "linkpath": "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",
238 "mtime": "1350266320.910238425",
239 "atime": "1350266320.910238425",
240 "ctime": "1350266320.910238425",
241 },
242 Format: FormatPAX,
243 }},
244 }, {
245 file: "testdata/pax-bad-hdr-file.tar",
246 err: ErrHeader,
247 }, {
248 file: "testdata/pax-bad-hdr-large.tar.bz2",
249 err: ErrFieldTooLong,
250 }, {
251 file: "testdata/pax-bad-mtime-file.tar",
252 err: ErrHeader,
253 }, {
254 file: "testdata/pax-pos-size-file.tar",
255 headers: []*Header{{
256 Name: "foo",
257 Mode: 0640,
258 Uid: 319973,
259 Gid: 5000,
260 Size: 999,
261 ModTime: time.Unix(1442282516, 0),
262 Typeflag: '0',
263 Uname: "joetsai",
264 Gname: "eng",
265 PAXRecords: map[string]string{
266 "size": "000000000000000000000999",
267 },
268 Format: FormatPAX,
269 }},
270 chksums: []string{
271 "0afb597b283fe61b5d4879669a350556",
272 },
273 }, {
274 file: "testdata/pax-records.tar",
275 headers: []*Header{{
276 Typeflag: TypeReg,
277 Name: "file",
278 Uname: strings.Repeat("long", 10),
279 ModTime: time.Unix(0, 0),
280 PAXRecords: map[string]string{
281 "GOLANG.pkg": "tar",
282 "comment": "Hello, 世界",
283 "uname": strings.Repeat("long", 10),
284 },
285 Format: FormatPAX,
286 }},
287 }, {
288 file: "testdata/pax-global-records.tar",
289 headers: []*Header{{
290 Typeflag: TypeXGlobalHeader,
291 Name: "global1",
292 PAXRecords: map[string]string{"path": "global1", "mtime": "1500000000.0"},
293 Format: FormatPAX,
294 }, {
295 Typeflag: TypeReg,
296 Name: "file1",
297 ModTime: time.Unix(0, 0),
298 Format: FormatUSTAR,
299 }, {
300 Typeflag: TypeReg,
301 Name: "file2",
302 PAXRecords: map[string]string{"path": "file2"},
303 ModTime: time.Unix(0, 0),
304 Format: FormatPAX,
305 }, {
306 Typeflag: TypeXGlobalHeader,
307 Name: "GlobalHead.0.0",
308 PAXRecords: map[string]string{"path": ""},
309 Format: FormatPAX,
310 }, {
311 Typeflag: TypeReg,
312 Name: "file3",
313 ModTime: time.Unix(0, 0),
314 Format: FormatUSTAR,
315 }, {
316 Typeflag: TypeReg,
317 Name: "file4",
318 ModTime: time.Unix(1400000000, 0),
319 PAXRecords: map[string]string{"mtime": "1400000000"},
320 Format: FormatPAX,
321 }},
322 }, {
323 file: "testdata/nil-uid.tar",
324 headers: []*Header{{
325 Name: "P1050238.JPG.log",
326 Mode: 0664,
327 Uid: 0,
328 Gid: 0,
329 Size: 14,
330 ModTime: time.Unix(1365454838, 0),
331 Typeflag: TypeReg,
332 Linkname: "",
333 Uname: "eyefi",
334 Gname: "eyefi",
335 Devmajor: 0,
336 Devminor: 0,
337 Format: FormatGNU,
338 }},
339 }, {
340 file: "testdata/xattrs.tar",
341 headers: []*Header{{
342 Name: "small.txt",
343 Mode: 0644,
344 Uid: 1000,
345 Gid: 10,
346 Size: 5,
347 ModTime: time.Unix(1386065770, 448252320),
348 Typeflag: '0',
349 Uname: "alex",
350 Gname: "wheel",
351 AccessTime: time.Unix(1389782991, 419875220),
352 ChangeTime: time.Unix(1389782956, 794414986),
353 Xattrs: map[string]string{
354 "user.key": "value",
355 "user.key2": "value2",
356
357 "security.selinux": "unconfined_u:object_r:default_t:s0\x00",
358 },
359 PAXRecords: map[string]string{
360 "mtime": "1386065770.44825232",
361 "atime": "1389782991.41987522",
362 "ctime": "1389782956.794414986",
363 "SCHILY.xattr.user.key": "value",
364 "SCHILY.xattr.user.key2": "value2",
365 "SCHILY.xattr.security.selinux": "unconfined_u:object_r:default_t:s0\x00",
366 },
367 Format: FormatPAX,
368 }, {
369 Name: "small2.txt",
370 Mode: 0644,
371 Uid: 1000,
372 Gid: 10,
373 Size: 11,
374 ModTime: time.Unix(1386065770, 449252304),
375 Typeflag: '0',
376 Uname: "alex",
377 Gname: "wheel",
378 AccessTime: time.Unix(1389782991, 419875220),
379 ChangeTime: time.Unix(1386065770, 449252304),
380 Xattrs: map[string]string{
381 "security.selinux": "unconfined_u:object_r:default_t:s0\x00",
382 },
383 PAXRecords: map[string]string{
384 "mtime": "1386065770.449252304",
385 "atime": "1389782991.41987522",
386 "ctime": "1386065770.449252304",
387 "SCHILY.xattr.security.selinux": "unconfined_u:object_r:default_t:s0\x00",
388 },
389 Format: FormatPAX,
390 }},
391 }, {
392
393 file: "testdata/gnu-multi-hdrs.tar",
394 headers: []*Header{{
395 Name: "GNU2/GNU2/long-path-name",
396 Linkname: "GNU4/GNU4/long-linkpath-name",
397 ModTime: time.Unix(0, 0),
398 Typeflag: '2',
399 Format: FormatGNU,
400 }},
401 }, {
402
403
404
405 file: "testdata/gnu-incremental.tar",
406 headers: []*Header{{
407 Name: "test2/",
408 Mode: 16877,
409 Uid: 1000,
410 Gid: 1000,
411 Size: 14,
412 ModTime: time.Unix(1441973427, 0),
413 Typeflag: 'D',
414 Uname: "rawr",
415 Gname: "dsnet",
416 AccessTime: time.Unix(1441974501, 0),
417 ChangeTime: time.Unix(1441973436, 0),
418 Format: FormatGNU,
419 }, {
420 Name: "test2/foo",
421 Mode: 33188,
422 Uid: 1000,
423 Gid: 1000,
424 Size: 64,
425 ModTime: time.Unix(1441973363, 0),
426 Typeflag: '0',
427 Uname: "rawr",
428 Gname: "dsnet",
429 AccessTime: time.Unix(1441974501, 0),
430 ChangeTime: time.Unix(1441973436, 0),
431 Format: FormatGNU,
432 }, {
433 Name: "test2/sparse",
434 Mode: 33188,
435 Uid: 1000,
436 Gid: 1000,
437 Size: 536870912,
438 ModTime: time.Unix(1441973427, 0),
439 Typeflag: 'S',
440 Uname: "rawr",
441 Gname: "dsnet",
442 AccessTime: time.Unix(1441991948, 0),
443 ChangeTime: time.Unix(1441973436, 0),
444 Format: FormatGNU,
445 }},
446 }, {
447
448 file: "testdata/pax-multi-hdrs.tar",
449 headers: []*Header{{
450 Name: "bar",
451 Linkname: "PAX4/PAX4/long-linkpath-name",
452 ModTime: time.Unix(0, 0),
453 Typeflag: '2',
454 PAXRecords: map[string]string{
455 "linkpath": "PAX4/PAX4/long-linkpath-name",
456 },
457 Format: FormatPAX,
458 }},
459 }, {
460
461
462
463 file: "testdata/gnu-long-nul.tar",
464 headers: []*Header{{
465 Name: "0123456789",
466 Mode: 0644,
467 Uid: 1000,
468 Gid: 1000,
469 ModTime: time.Unix(1486082191, 0),
470 Typeflag: '0',
471 Uname: "rawr",
472 Gname: "dsnet",
473 Format: FormatGNU,
474 }},
475 }, {
476
477
478
479
480
481 file: "testdata/gnu-utf8.tar",
482 headers: []*Header{{
483 Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",
484 Mode: 0644,
485 Uid: 1000, Gid: 1000,
486 ModTime: time.Unix(0, 0),
487 Typeflag: '0',
488 Uname: "☺",
489 Gname: "⚹",
490 Format: FormatGNU,
491 }},
492 }, {
493
494
495
496
497
498 file: "testdata/gnu-not-utf8.tar",
499 headers: []*Header{{
500 Name: "hi\x80\x81\x82\x83bye",
501 Mode: 0644,
502 Uid: 1000,
503 Gid: 1000,
504 ModTime: time.Unix(0, 0),
505 Typeflag: '0',
506 Uname: "rawr",
507 Gname: "dsnet",
508 Format: FormatGNU,
509 }},
510 }, {
511
512
513 file: "testdata/pax-nul-xattrs.tar",
514 err: ErrHeader,
515 }, {
516
517
518
519
520
521 file: "testdata/pax-nul-path.tar",
522 err: ErrHeader,
523 }, {
524 file: "testdata/neg-size.tar",
525 err: ErrHeader,
526 }, {
527 file: "testdata/issue10968.tar",
528 err: ErrHeader,
529 }, {
530 file: "testdata/issue11169.tar",
531 err: ErrHeader,
532 }, {
533 file: "testdata/issue12435.tar",
534 err: ErrHeader,
535 }, {
536
537
538 file: "testdata/invalid-go17.tar",
539 headers: []*Header{{
540 Name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/foo",
541 Uid: 010000000,
542 ModTime: time.Unix(0, 0),
543 Typeflag: '0',
544 }},
545 }, {
546
547 file: "testdata/ustar-file-devs.tar",
548 headers: []*Header{{
549 Name: "file",
550 Mode: 0644,
551 Typeflag: '0',
552 ModTime: time.Unix(0, 0),
553 Devmajor: 1,
554 Devminor: 1,
555 Format: FormatUSTAR,
556 }},
557 }, {
558
559 file: "testdata/gnu-nil-sparse-data.tar",
560 headers: []*Header{{
561 Name: "sparse.db",
562 Typeflag: TypeGNUSparse,
563 Size: 1000,
564 ModTime: time.Unix(0, 0),
565 Format: FormatGNU,
566 }},
567 }, {
568
569 file: "testdata/gnu-nil-sparse-hole.tar",
570 headers: []*Header{{
571 Name: "sparse.db",
572 Typeflag: TypeGNUSparse,
573 Size: 1000,
574 ModTime: time.Unix(0, 0),
575 Format: FormatGNU,
576 }},
577 }, {
578
579 file: "testdata/pax-nil-sparse-data.tar",
580 headers: []*Header{{
581 Name: "sparse.db",
582 Typeflag: TypeReg,
583 Size: 1000,
584 ModTime: time.Unix(0, 0),
585 PAXRecords: map[string]string{
586 "size": "1512",
587 "GNU.sparse.major": "1",
588 "GNU.sparse.minor": "0",
589 "GNU.sparse.realsize": "1000",
590 "GNU.sparse.name": "sparse.db",
591 },
592 Format: FormatPAX,
593 }},
594 }, {
595
596 file: "testdata/pax-nil-sparse-hole.tar",
597 headers: []*Header{{
598 Name: "sparse.db",
599 Typeflag: TypeReg,
600 Size: 1000,
601 ModTime: time.Unix(0, 0),
602 PAXRecords: map[string]string{
603 "size": "512",
604 "GNU.sparse.major": "1",
605 "GNU.sparse.minor": "0",
606 "GNU.sparse.realsize": "1000",
607 "GNU.sparse.name": "sparse.db",
608 },
609 Format: FormatPAX,
610 }},
611 }, {
612 file: "testdata/trailing-slash.tar",
613 headers: []*Header{{
614 Typeflag: TypeDir,
615 Name: strings.Repeat("123456789/", 30),
616 ModTime: time.Unix(0, 0),
617 PAXRecords: map[string]string{
618 "path": strings.Repeat("123456789/", 30),
619 },
620 Format: FormatPAX,
621 }},
622 }}
623
624 for _, v := range vectors {
625 t.Run(path.Base(v.file), func(t *testing.T) {
626 f, err := os.Open(v.file)
627 if err != nil {
628 t.Fatalf("unexpected error: %v", err)
629 }
630 defer f.Close()
631
632 var fr io.Reader = f
633 if strings.HasSuffix(v.file, ".bz2") {
634 fr = bzip2.NewReader(fr)
635 }
636
637
638 var (
639 tr = NewReader(fr)
640 hdrs []*Header
641 chksums []string
642 rdbuf = make([]byte, 8)
643 )
644 for {
645 var hdr *Header
646 hdr, err = tr.Next()
647 if err != nil {
648 if err == io.EOF {
649 err = nil
650 }
651 break
652 }
653 hdrs = append(hdrs, hdr)
654
655 if v.chksums == nil {
656 continue
657 }
658 h := md5.New()
659 _, err = io.CopyBuffer(h, tr, rdbuf)
660 if err != nil {
661 break
662 }
663 chksums = append(chksums, fmt.Sprintf("%x", h.Sum(nil)))
664 }
665
666 for i, hdr := range hdrs {
667 if i >= len(v.headers) {
668 t.Fatalf("entry %d: unexpected header:\ngot %+v", i, *hdr)
669 }
670 if !reflect.DeepEqual(*hdr, *v.headers[i]) {
671 t.Fatalf("entry %d: incorrect header:\ngot %+v\nwant %+v", i, *hdr, *v.headers[i])
672 }
673 }
674 if len(hdrs) != len(v.headers) {
675 t.Fatalf("got %d headers, want %d headers", len(hdrs), len(v.headers))
676 }
677
678 for i, sum := range chksums {
679 if i >= len(v.chksums) {
680 t.Fatalf("entry %d: unexpected sum: got %s", i, sum)
681 }
682 if sum != v.chksums[i] {
683 t.Fatalf("entry %d: incorrect checksum: got %s, want %s", i, sum, v.chksums[i])
684 }
685 }
686
687 if err != v.err {
688 t.Fatalf("unexpected error: got %v, want %v", err, v.err)
689 }
690 f.Close()
691 })
692 }
693 }
694
695 func TestPartialRead(t *testing.T) {
696 type testCase struct {
697 cnt int
698 output string
699 }
700 vectors := []struct {
701 file string
702 cases []testCase
703 }{{
704 file: "testdata/gnu.tar",
705 cases: []testCase{
706 {4, "Kilt"},
707 {6, "Google"},
708 },
709 }, {
710 file: "testdata/sparse-formats.tar",
711 cases: []testCase{
712 {2, "\x00G"},
713 {4, "\x00G\x00o"},
714 {6, "\x00G\x00o\x00G"},
715 {8, "\x00G\x00o\x00G\x00o"},
716 {4, "end\n"},
717 },
718 }}
719
720 for _, v := range vectors {
721 t.Run(path.Base(v.file), func(t *testing.T) {
722 f, err := os.Open(v.file)
723 if err != nil {
724 t.Fatalf("Open() error: %v", err)
725 }
726 defer f.Close()
727
728 tr := NewReader(f)
729 for i, tc := range v.cases {
730 hdr, err := tr.Next()
731 if err != nil || hdr == nil {
732 t.Fatalf("entry %d, Next(): got %v, want %v", i, err, nil)
733 }
734 buf := make([]byte, tc.cnt)
735 if _, err := io.ReadFull(tr, buf); err != nil {
736 t.Fatalf("entry %d, ReadFull(): got %v, want %v", i, err, nil)
737 }
738 if string(buf) != tc.output {
739 t.Fatalf("entry %d, ReadFull(): got %q, want %q", i, string(buf), tc.output)
740 }
741 }
742
743 if _, err := tr.Next(); err != io.EOF {
744 t.Fatalf("Next(): got %v, want EOF", err)
745 }
746 })
747 }
748 }
749
750 func TestUninitializedRead(t *testing.T) {
751 f, err := os.Open("testdata/gnu.tar")
752 if err != nil {
753 t.Fatalf("Unexpected error: %v", err)
754 }
755 defer f.Close()
756
757 tr := NewReader(f)
758 _, err = tr.Read([]byte{})
759 if err == nil || err != io.EOF {
760 t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF)
761 }
762
763 }
764
765 type reader struct{ io.Reader }
766 type readSeeker struct{ io.ReadSeeker }
767 type readBadSeeker struct{ io.ReadSeeker }
768
769 func (rbs *readBadSeeker) Seek(int64, int) (int64, error) { return 0, fmt.Errorf("illegal seek") }
770
771
772
773
774 func TestReadTruncation(t *testing.T) {
775 var ss []string
776 for _, p := range []string{
777 "testdata/gnu.tar",
778 "testdata/ustar-file-reg.tar",
779 "testdata/pax-path-hdr.tar",
780 "testdata/sparse-formats.tar",
781 } {
782 buf, err := os.ReadFile(p)
783 if err != nil {
784 t.Fatalf("unexpected error: %v", err)
785 }
786 ss = append(ss, string(buf))
787 }
788
789 data1, data2, pax, sparse := ss[0], ss[1], ss[2], ss[3]
790 data2 += strings.Repeat("\x00", 10*512)
791 trash := strings.Repeat("garbage ", 64)
792
793 vectors := []struct {
794 input string
795 cnt int
796 err error
797 }{
798 {"", 0, io.EOF},
799 {data1[:511], 0, io.ErrUnexpectedEOF},
800 {data1[:512], 1, io.ErrUnexpectedEOF},
801 {data1[:1024], 1, io.EOF},
802 {data1[:1536], 2, io.ErrUnexpectedEOF},
803 {data1[:2048], 2, io.EOF},
804 {data1, 2, io.EOF},
805 {data1[:2048] + data2[:1536], 3, io.EOF},
806 {data2[:511], 0, io.ErrUnexpectedEOF},
807 {data2[:512], 1, io.ErrUnexpectedEOF},
808 {data2[:1195], 1, io.ErrUnexpectedEOF},
809 {data2[:1196], 1, io.EOF},
810 {data2[:1200], 1, io.EOF},
811 {data2[:1535], 1, io.EOF},
812 {data2[:1536], 1, io.EOF},
813 {data2[:1536] + trash[:1], 1, io.ErrUnexpectedEOF},
814 {data2[:1536] + trash[:511], 1, io.ErrUnexpectedEOF},
815 {data2[:1536] + trash, 1, ErrHeader},
816 {data2[:2048], 1, io.EOF},
817 {data2[:2048] + trash[:1], 1, io.ErrUnexpectedEOF},
818 {data2[:2048] + trash[:511], 1, io.ErrUnexpectedEOF},
819 {data2[:2048] + trash, 1, ErrHeader},
820 {data2[:2560], 1, io.EOF},
821 {data2[:2560] + trash[:1], 1, io.EOF},
822 {data2[:2560] + trash[:511], 1, io.EOF},
823 {data2[:2560] + trash, 1, io.EOF},
824 {data2[:3072], 1, io.EOF},
825 {pax, 0, io.EOF},
826 {pax + trash[:1], 0, io.ErrUnexpectedEOF},
827 {pax + trash[:511], 0, io.ErrUnexpectedEOF},
828 {sparse[:511], 0, io.ErrUnexpectedEOF},
829 {sparse[:512], 0, io.ErrUnexpectedEOF},
830 {sparse[:3584], 1, io.EOF},
831 {sparse[:9200], 1, io.EOF},
832 {sparse[:9216], 1, io.EOF},
833 {sparse[:9728], 2, io.ErrUnexpectedEOF},
834 {sparse[:10240], 2, io.EOF},
835 {sparse[:11264], 2, io.ErrUnexpectedEOF},
836 {sparse, 5, io.EOF},
837 {sparse + trash, 5, io.EOF},
838 }
839
840 for i, v := range vectors {
841 for j := 0; j < 6; j++ {
842 var tr *Reader
843 var s1, s2 string
844
845 switch j {
846 case 0:
847 tr = NewReader(&reader{strings.NewReader(v.input)})
848 s1, s2 = "io.Reader", "auto"
849 case 1:
850 tr = NewReader(&reader{strings.NewReader(v.input)})
851 s1, s2 = "io.Reader", "manual"
852 case 2:
853 tr = NewReader(&readSeeker{strings.NewReader(v.input)})
854 s1, s2 = "io.ReadSeeker", "auto"
855 case 3:
856 tr = NewReader(&readSeeker{strings.NewReader(v.input)})
857 s1, s2 = "io.ReadSeeker", "manual"
858 case 4:
859 tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})
860 s1, s2 = "ReadBadSeeker", "auto"
861 case 5:
862 tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})
863 s1, s2 = "ReadBadSeeker", "manual"
864 }
865
866 var cnt int
867 var err error
868 for {
869 if _, err = tr.Next(); err != nil {
870 break
871 }
872 cnt++
873 if s2 == "manual" {
874 if _, err = tr.writeTo(io.Discard); err != nil {
875 break
876 }
877 }
878 }
879 if err != v.err {
880 t.Errorf("test %d, NewReader(%s) with %s discard: got %v, want %v",
881 i, s1, s2, err, v.err)
882 }
883 if cnt != v.cnt {
884 t.Errorf("test %d, NewReader(%s) with %s discard: got %d headers, want %d headers",
885 i, s1, s2, cnt, v.cnt)
886 }
887 }
888 }
889 }
890
891
892
893 func TestReadHeaderOnly(t *testing.T) {
894 f, err := os.Open("testdata/hdr-only.tar")
895 if err != nil {
896 t.Fatalf("unexpected error: %v", err)
897 }
898 defer f.Close()
899
900 var hdrs []*Header
901 tr := NewReader(f)
902 for {
903 hdr, err := tr.Next()
904 if err == io.EOF {
905 break
906 }
907 if err != nil {
908 t.Errorf("Next(): got %v, want %v", err, nil)
909 continue
910 }
911 hdrs = append(hdrs, hdr)
912
913
914 cnt, _ := io.ReadFull(tr, []byte{0})
915 if cnt > 0 && hdr.Typeflag != TypeReg {
916 t.Errorf("ReadFull(...): got %d bytes, want 0 bytes", cnt)
917 }
918 }
919
920
921
922 if len(hdrs) != 16 {
923 t.Fatalf("len(hdrs): got %d, want %d", len(hdrs), 16)
924 }
925 for i := 0; i < 8; i++ {
926 hdr1, hdr2 := hdrs[i+0], hdrs[i+8]
927 hdr1.Size, hdr2.Size = 0, 0
928 if !reflect.DeepEqual(*hdr1, *hdr2) {
929 t.Errorf("incorrect header:\ngot %+v\nwant %+v", *hdr1, *hdr2)
930 }
931 }
932 }
933
934 func TestMergePAX(t *testing.T) {
935 vectors := []struct {
936 in map[string]string
937 want *Header
938 ok bool
939 }{{
940 in: map[string]string{
941 "path": "a/b/c",
942 "uid": "1000",
943 "mtime": "1350244992.023960108",
944 },
945 want: &Header{
946 Name: "a/b/c",
947 Uid: 1000,
948 ModTime: time.Unix(1350244992, 23960108),
949 PAXRecords: map[string]string{
950 "path": "a/b/c",
951 "uid": "1000",
952 "mtime": "1350244992.023960108",
953 },
954 },
955 ok: true,
956 }, {
957 in: map[string]string{
958 "gid": "gtgergergersagersgers",
959 },
960 ok: false,
961 }, {
962 in: map[string]string{
963 "missing": "missing",
964 "SCHILY.xattr.key": "value",
965 },
966 want: &Header{
967 Xattrs: map[string]string{"key": "value"},
968 PAXRecords: map[string]string{
969 "missing": "missing",
970 "SCHILY.xattr.key": "value",
971 },
972 },
973 ok: true,
974 }}
975
976 for i, v := range vectors {
977 got := new(Header)
978 err := mergePAX(got, v.in)
979 if v.ok && !reflect.DeepEqual(*got, *v.want) {
980 t.Errorf("test %d, mergePAX(...):\ngot %+v\nwant %+v", i, *got, *v.want)
981 }
982 if ok := err == nil; ok != v.ok {
983 t.Errorf("test %d, mergePAX(...): got %v, want %v", i, ok, v.ok)
984 }
985 }
986 }
987
988 func TestParsePAX(t *testing.T) {
989 vectors := []struct {
990 in string
991 want map[string]string
992 ok bool
993 }{
994 {"", nil, true},
995 {"6 k=1\n", map[string]string{"k": "1"}, true},
996 {"10 a=name\n", map[string]string{"a": "name"}, true},
997 {"9 a=name\n", map[string]string{"a": "name"}, true},
998 {"30 mtime=1350244992.023960108\n", map[string]string{"mtime": "1350244992.023960108"}, true},
999 {"3 somelongkey=\n", nil, false},
1000 {"50 tooshort=\n", nil, false},
1001 {"13 key1=haha\n13 key2=nana\n13 key3=kaka\n",
1002 map[string]string{"key1": "haha", "key2": "nana", "key3": "kaka"}, true},
1003 {"13 key1=val1\n13 key2=val2\n8 key1=\n",
1004 map[string]string{"key1": "", "key2": "val2"}, true},
1005 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=2\n" +
1006 "23 GNU.sparse.offset=1\n25 GNU.sparse.numbytes=2\n" +
1007 "23 GNU.sparse.offset=3\n25 GNU.sparse.numbytes=4\n",
1008 map[string]string{paxGNUSparseSize: "10", paxGNUSparseNumBlocks: "2", paxGNUSparseMap: "1,2,3,4"}, true},
1009 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" +
1010 "25 GNU.sparse.numbytes=2\n23 GNU.sparse.offset=1\n",
1011 nil, false},
1012 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" +
1013 "25 GNU.sparse.offset=1,2\n25 GNU.sparse.numbytes=2\n",
1014 nil, false},
1015 }
1016
1017 for i, v := range vectors {
1018 r := strings.NewReader(v.in)
1019 got, err := parsePAX(r)
1020 if !reflect.DeepEqual(got, v.want) && !(len(got) == 0 && len(v.want) == 0) {
1021 t.Errorf("test %d, parsePAX():\ngot %v\nwant %v", i, got, v.want)
1022 }
1023 if ok := err == nil; ok != v.ok {
1024 t.Errorf("test %d, parsePAX(): got %v, want %v", i, ok, v.ok)
1025 }
1026 }
1027 }
1028
1029 func TestReadOldGNUSparseMap(t *testing.T) {
1030 populateSparseMap := func(sa sparseArray, sps []string) []string {
1031 for i := 0; len(sps) > 0 && i < sa.maxEntries(); i++ {
1032 copy(sa.entry(i), sps[0])
1033 sps = sps[1:]
1034 }
1035 if len(sps) > 0 {
1036 copy(sa.isExtended(), "\x80")
1037 }
1038 return sps
1039 }
1040
1041 makeInput := func(format Format, size string, sps ...string) (out []byte) {
1042
1043 var blk block
1044 gnu := blk.toGNU()
1045 sparse := gnu.sparse()
1046 copy(gnu.realSize(), size)
1047 sps = populateSparseMap(sparse, sps)
1048 if format != FormatUnknown {
1049 blk.setFormat(format)
1050 }
1051 out = append(out, blk[:]...)
1052
1053
1054 for len(sps) > 0 {
1055 var blk block
1056 sps = populateSparseMap(blk.toSparse(), sps)
1057 out = append(out, blk[:]...)
1058 }
1059 return out
1060 }
1061
1062 makeSparseStrings := func(sp []sparseEntry) (out []string) {
1063 var f formatter
1064 for _, s := range sp {
1065 var b [24]byte
1066 f.formatNumeric(b[:12], s.Offset)
1067 f.formatNumeric(b[12:], s.Length)
1068 out = append(out, string(b[:]))
1069 }
1070 return out
1071 }
1072
1073 vectors := []struct {
1074 input []byte
1075 wantMap sparseDatas
1076 wantSize int64
1077 wantErr error
1078 }{{
1079 input: makeInput(FormatUnknown, ""),
1080 wantErr: ErrHeader,
1081 }, {
1082 input: makeInput(FormatGNU, "1234", "fewa"),
1083 wantSize: 01234,
1084 wantErr: ErrHeader,
1085 }, {
1086 input: makeInput(FormatGNU, "0031"),
1087 wantSize: 031,
1088 }, {
1089 input: makeInput(FormatGNU, "80"),
1090 wantErr: ErrHeader,
1091 }, {
1092 input: makeInput(FormatGNU, "1234",
1093 makeSparseStrings(sparseDatas{{0, 0}, {1, 1}})...),
1094 wantMap: sparseDatas{{0, 0}, {1, 1}},
1095 wantSize: 01234,
1096 }, {
1097 input: makeInput(FormatGNU, "1234",
1098 append(makeSparseStrings(sparseDatas{{0, 0}, {1, 1}}), []string{"", "blah"}...)...),
1099 wantMap: sparseDatas{{0, 0}, {1, 1}},
1100 wantSize: 01234,
1101 }, {
1102 input: makeInput(FormatGNU, "3333",
1103 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}})...),
1104 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}},
1105 wantSize: 03333,
1106 }, {
1107 input: makeInput(FormatGNU, "",
1108 append(append(
1109 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}}),
1110 []string{"", ""}...),
1111 makeSparseStrings(sparseDatas{{4, 1}, {6, 1}})...)...),
1112 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}},
1113 }, {
1114 input: makeInput(FormatGNU, "",
1115 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:blockSize],
1116 wantErr: io.ErrUnexpectedEOF,
1117 }, {
1118 input: makeInput(FormatGNU, "",
1119 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:3*blockSize/2],
1120 wantErr: io.ErrUnexpectedEOF,
1121 }, {
1122 input: makeInput(FormatGNU, "",
1123 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...),
1124 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}},
1125 }, {
1126 input: makeInput(FormatGNU, "",
1127 makeSparseStrings(sparseDatas{{10 << 30, 512}, {20 << 30, 512}})...),
1128 wantMap: sparseDatas{{10 << 30, 512}, {20 << 30, 512}},
1129 }}
1130
1131 for i, v := range vectors {
1132 var blk block
1133 var hdr Header
1134 v.input = v.input[copy(blk[:], v.input):]
1135 tr := Reader{r: bytes.NewReader(v.input)}
1136 got, err := tr.readOldGNUSparseMap(&hdr, &blk)
1137 if !equalSparseEntries(got, v.wantMap) {
1138 t.Errorf("test %d, readOldGNUSparseMap(): got %v, want %v", i, got, v.wantMap)
1139 }
1140 if err != v.wantErr {
1141 t.Errorf("test %d, readOldGNUSparseMap() = %v, want %v", i, err, v.wantErr)
1142 }
1143 if hdr.Size != v.wantSize {
1144 t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize)
1145 }
1146 }
1147 }
1148
1149 func TestReadGNUSparsePAXHeaders(t *testing.T) {
1150 padInput := func(s string) string {
1151 return s + string(zeroBlock[:blockPadding(int64(len(s)))])
1152 }
1153
1154 vectors := []struct {
1155 inputData string
1156 inputHdrs map[string]string
1157 wantMap sparseDatas
1158 wantSize int64
1159 wantName string
1160 wantErr error
1161 }{{
1162 inputHdrs: nil,
1163 wantErr: nil,
1164 }, {
1165 inputHdrs: map[string]string{
1166 paxGNUSparseNumBlocks: strconv.FormatInt(math.MaxInt64, 10),
1167 paxGNUSparseMap: "0,1,2,3",
1168 },
1169 wantErr: ErrHeader,
1170 }, {
1171 inputHdrs: map[string]string{
1172 paxGNUSparseNumBlocks: "4\x00",
1173 paxGNUSparseMap: "0,1,2,3",
1174 },
1175 wantErr: ErrHeader,
1176 }, {
1177 inputHdrs: map[string]string{
1178 paxGNUSparseNumBlocks: "4",
1179 paxGNUSparseMap: "0,1,2,3",
1180 },
1181 wantErr: ErrHeader,
1182 }, {
1183 inputHdrs: map[string]string{
1184 paxGNUSparseNumBlocks: "2",
1185 paxGNUSparseMap: "0,1,2,3",
1186 },
1187 wantMap: sparseDatas{{0, 1}, {2, 3}},
1188 }, {
1189 inputHdrs: map[string]string{
1190 paxGNUSparseNumBlocks: "2",
1191 paxGNUSparseMap: "0, 1,2,3",
1192 },
1193 wantErr: ErrHeader,
1194 }, {
1195 inputHdrs: map[string]string{
1196 paxGNUSparseNumBlocks: "2",
1197 paxGNUSparseMap: "0,1,02,3",
1198 paxGNUSparseRealSize: "4321",
1199 },
1200 wantMap: sparseDatas{{0, 1}, {2, 3}},
1201 wantSize: 4321,
1202 }, {
1203 inputHdrs: map[string]string{
1204 paxGNUSparseNumBlocks: "2",
1205 paxGNUSparseMap: "0,one1,2,3",
1206 },
1207 wantErr: ErrHeader,
1208 }, {
1209 inputHdrs: map[string]string{
1210 paxGNUSparseMajor: "0",
1211 paxGNUSparseMinor: "0",
1212 paxGNUSparseNumBlocks: "2",
1213 paxGNUSparseMap: "0,1,2,3",
1214 paxGNUSparseSize: "1234",
1215 paxGNUSparseRealSize: "4321",
1216 paxGNUSparseName: "realname",
1217 },
1218 wantMap: sparseDatas{{0, 1}, {2, 3}},
1219 wantSize: 1234,
1220 wantName: "realname",
1221 }, {
1222 inputHdrs: map[string]string{
1223 paxGNUSparseMajor: "0",
1224 paxGNUSparseMinor: "0",
1225 paxGNUSparseNumBlocks: "1",
1226 paxGNUSparseMap: "10737418240,512",
1227 paxGNUSparseSize: "10737418240",
1228 paxGNUSparseName: "realname",
1229 },
1230 wantMap: sparseDatas{{10737418240, 512}},
1231 wantSize: 10737418240,
1232 wantName: "realname",
1233 }, {
1234 inputHdrs: map[string]string{
1235 paxGNUSparseMajor: "0",
1236 paxGNUSparseMinor: "0",
1237 paxGNUSparseNumBlocks: "0",
1238 paxGNUSparseMap: "",
1239 },
1240 wantMap: sparseDatas{},
1241 }, {
1242 inputHdrs: map[string]string{
1243 paxGNUSparseMajor: "0",
1244 paxGNUSparseMinor: "1",
1245 paxGNUSparseNumBlocks: "4",
1246 paxGNUSparseMap: "0,5,10,5,20,5,30,5",
1247 },
1248 wantMap: sparseDatas{{0, 5}, {10, 5}, {20, 5}, {30, 5}},
1249 }, {
1250 inputHdrs: map[string]string{
1251 paxGNUSparseMajor: "1",
1252 paxGNUSparseMinor: "0",
1253 paxGNUSparseNumBlocks: "4",
1254 paxGNUSparseMap: "0,5,10,5,20,5,30,5",
1255 },
1256 wantErr: io.ErrUnexpectedEOF,
1257 }, {
1258 inputData: padInput("0\n"),
1259 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1260 wantMap: sparseDatas{},
1261 }, {
1262 inputData: padInput("0\n")[:blockSize-1] + "#",
1263 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1264 wantMap: sparseDatas{},
1265 }, {
1266 inputData: padInput("0"),
1267 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1268 wantErr: io.ErrUnexpectedEOF,
1269 }, {
1270 inputData: padInput("ab\n"),
1271 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1272 wantErr: ErrHeader,
1273 }, {
1274 inputData: padInput("1\n2\n3\n"),
1275 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1276 wantMap: sparseDatas{{2, 3}},
1277 }, {
1278 inputData: padInput("1\n2\n"),
1279 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1280 wantErr: io.ErrUnexpectedEOF,
1281 }, {
1282 inputData: padInput("1\n2\n\n"),
1283 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1284 wantErr: ErrHeader,
1285 }, {
1286 inputData: string(zeroBlock[:]) + padInput("0\n"),
1287 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1288 wantErr: ErrHeader,
1289 }, {
1290 inputData: strings.Repeat("0", blockSize) + padInput("1\n5\n1\n"),
1291 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1292 wantMap: sparseDatas{{5, 1}},
1293 }, {
1294 inputData: padInput(fmt.Sprintf("%d\n", int64(math.MaxInt64))),
1295 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1296 wantErr: ErrHeader,
1297 }, {
1298 inputData: padInput(strings.Repeat("0", 300) + "1\n" + strings.Repeat("0", 1000) + "5\n" + strings.Repeat("0", 800) + "2\n"),
1299 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1300 wantMap: sparseDatas{{5, 2}},
1301 }, {
1302 inputData: padInput("2\n10737418240\n512\n21474836480\n512\n"),
1303 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1304 wantMap: sparseDatas{{10737418240, 512}, {21474836480, 512}},
1305 }, {
1306 inputData: padInput("100\n" + func() string {
1307 var ss []string
1308 for i := 0; i < 100; i++ {
1309 ss = append(ss, fmt.Sprintf("%d\n%d\n", int64(i)<<30, 512))
1310 }
1311 return strings.Join(ss, "")
1312 }()),
1313 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1314 wantMap: func() (spd sparseDatas) {
1315 for i := 0; i < 100; i++ {
1316 spd = append(spd, sparseEntry{int64(i) << 30, 512})
1317 }
1318 return spd
1319 }(),
1320 }}
1321
1322 for i, v := range vectors {
1323 var hdr Header
1324 hdr.PAXRecords = v.inputHdrs
1325 r := strings.NewReader(v.inputData + "#")
1326 tr := Reader{curr: ®FileReader{r, int64(r.Len())}}
1327 got, err := tr.readGNUSparsePAXHeaders(&hdr)
1328 if !equalSparseEntries(got, v.wantMap) {
1329 t.Errorf("test %d, readGNUSparsePAXHeaders(): got %v, want %v", i, got, v.wantMap)
1330 }
1331 if err != v.wantErr {
1332 t.Errorf("test %d, readGNUSparsePAXHeaders() = %v, want %v", i, err, v.wantErr)
1333 }
1334 if hdr.Size != v.wantSize {
1335 t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize)
1336 }
1337 if hdr.Name != v.wantName {
1338 t.Errorf("test %d, Header.Name = %s, want %s", i, hdr.Name, v.wantName)
1339 }
1340 if v.wantErr == nil && r.Len() == 0 {
1341 t.Errorf("test %d, canary byte unexpectedly consumed", i)
1342 }
1343 }
1344 }
1345
1346
1347
1348 type testNonEmptyReader struct{ io.Reader }
1349
1350 func (r testNonEmptyReader) Read(b []byte) (int, error) {
1351 if len(b) == 0 {
1352 return 0, errors.New("unexpected empty Read call")
1353 }
1354 return r.Reader.Read(b)
1355 }
1356
1357 func TestFileReader(t *testing.T) {
1358 type (
1359 testRead struct {
1360 cnt int
1361 wantStr string
1362 wantErr error
1363 }
1364 testWriteTo struct {
1365 ops fileOps
1366 wantCnt int64
1367 wantErr error
1368 }
1369 testRemaining struct {
1370 wantLCnt int64
1371 wantPCnt int64
1372 }
1373 testFnc any
1374 )
1375
1376 type (
1377 makeReg struct {
1378 str string
1379 size int64
1380 }
1381 makeSparse struct {
1382 makeReg makeReg
1383 spd sparseDatas
1384 size int64
1385 }
1386 fileMaker any
1387 )
1388
1389 vectors := []struct {
1390 maker fileMaker
1391 tests []testFnc
1392 }{{
1393 maker: makeReg{"", 0},
1394 tests: []testFnc{
1395 testRemaining{0, 0},
1396 testRead{0, "", io.EOF},
1397 testRead{1, "", io.EOF},
1398 testWriteTo{nil, 0, nil},
1399 testRemaining{0, 0},
1400 },
1401 }, {
1402 maker: makeReg{"", 1},
1403 tests: []testFnc{
1404 testRemaining{1, 1},
1405 testRead{5, "", io.ErrUnexpectedEOF},
1406 testWriteTo{nil, 0, io.ErrUnexpectedEOF},
1407 testRemaining{1, 1},
1408 },
1409 }, {
1410 maker: makeReg{"hello", 5},
1411 tests: []testFnc{
1412 testRemaining{5, 5},
1413 testRead{5, "hello", io.EOF},
1414 testRemaining{0, 0},
1415 },
1416 }, {
1417 maker: makeReg{"hello, world", 50},
1418 tests: []testFnc{
1419 testRemaining{50, 50},
1420 testRead{7, "hello, ", nil},
1421 testRemaining{43, 43},
1422 testRead{5, "world", nil},
1423 testRemaining{38, 38},
1424 testWriteTo{nil, 0, io.ErrUnexpectedEOF},
1425 testRead{1, "", io.ErrUnexpectedEOF},
1426 testRemaining{38, 38},
1427 },
1428 }, {
1429 maker: makeReg{"hello, world", 5},
1430 tests: []testFnc{
1431 testRemaining{5, 5},
1432 testRead{0, "", nil},
1433 testRead{4, "hell", nil},
1434 testRemaining{1, 1},
1435 testWriteTo{fileOps{"o"}, 1, nil},
1436 testRemaining{0, 0},
1437 testWriteTo{nil, 0, nil},
1438 testRead{0, "", io.EOF},
1439 },
1440 }, {
1441 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8},
1442 tests: []testFnc{
1443 testRemaining{8, 5},
1444 testRead{3, "ab\x00", nil},
1445 testRead{10, "\x00\x00cde", io.EOF},
1446 testRemaining{0, 0},
1447 },
1448 }, {
1449 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8},
1450 tests: []testFnc{
1451 testRemaining{8, 5},
1452 testWriteTo{fileOps{"ab", int64(3), "cde"}, 8, nil},
1453 testRemaining{0, 0},
1454 },
1455 }, {
1456 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 10},
1457 tests: []testFnc{
1458 testRemaining{10, 5},
1459 testRead{100, "ab\x00\x00\x00cde\x00\x00", io.EOF},
1460 testRemaining{0, 0},
1461 },
1462 }, {
1463 maker: makeSparse{makeReg{"abc", 5}, sparseDatas{{0, 2}, {5, 3}}, 10},
1464 tests: []testFnc{
1465 testRemaining{10, 5},
1466 testRead{100, "ab\x00\x00\x00c", io.ErrUnexpectedEOF},
1467 testRemaining{4, 2},
1468 },
1469 }, {
1470 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 8},
1471 tests: []testFnc{
1472 testRemaining{8, 5},
1473 testRead{8, "\x00abc\x00\x00de", io.EOF},
1474 testRemaining{0, 0},
1475 },
1476 }, {
1477 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8},
1478 tests: []testFnc{
1479 testRemaining{8, 5},
1480 testRead{8, "\x00abc\x00\x00de", io.EOF},
1481 testRemaining{0, 0},
1482 },
1483 }, {
1484 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8},
1485 tests: []testFnc{
1486 testRemaining{8, 5},
1487 testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, nil},
1488 testRemaining{0, 0},
1489 },
1490 }, {
1491 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10},
1492 tests: []testFnc{
1493 testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF},
1494 },
1495 }, {
1496 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10},
1497 tests: []testFnc{
1498 testWriteTo{fileOps{int64(1), "abc", int64(2), "de", int64(1), "\x00"}, 10, nil},
1499 },
1500 }, {
1501 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}, {8, 0}, {8, 0}, {8, 0}, {8, 0}}, 10},
1502 tests: []testFnc{
1503 testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF},
1504 },
1505 }, {
1506 maker: makeSparse{makeReg{"", 0}, sparseDatas{}, 2},
1507 tests: []testFnc{
1508 testRead{100, "\x00\x00", io.EOF},
1509 },
1510 }, {
1511 maker: makeSparse{makeReg{"", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
1512 tests: []testFnc{
1513 testRead{100, "\x00", io.ErrUnexpectedEOF},
1514 },
1515 }, {
1516 maker: makeSparse{makeReg{"ab", 2}, sparseDatas{{1, 3}, {6, 5}}, 15},
1517 tests: []testFnc{
1518 testRead{100, "\x00ab", errMissData},
1519 },
1520 }, {
1521 maker: makeSparse{makeReg{"ab", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
1522 tests: []testFnc{
1523 testRead{100, "\x00ab", io.ErrUnexpectedEOF},
1524 },
1525 }, {
1526 maker: makeSparse{makeReg{"abc", 3}, sparseDatas{{1, 3}, {6, 5}}, 15},
1527 tests: []testFnc{
1528 testRead{100, "\x00abc\x00\x00", errMissData},
1529 },
1530 }, {
1531 maker: makeSparse{makeReg{"abc", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
1532 tests: []testFnc{
1533 testRead{100, "\x00abc\x00\x00", io.ErrUnexpectedEOF},
1534 },
1535 }, {
1536 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15},
1537 tests: []testFnc{
1538 testRead{100, "\x00abc\x00\x00de", errMissData},
1539 },
1540 }, {
1541 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15},
1542 tests: []testFnc{
1543 testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, errMissData},
1544 },
1545 }, {
1546 maker: makeSparse{makeReg{"abcde", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
1547 tests: []testFnc{
1548 testRead{100, "\x00abc\x00\x00de", io.ErrUnexpectedEOF},
1549 },
1550 }, {
1551 maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15},
1552 tests: []testFnc{
1553 testRemaining{15, 13},
1554 testRead{100, "\x00abc\x00\x00defgh\x00\x00\x00\x00", errUnrefData},
1555 testWriteTo{nil, 0, errUnrefData},
1556 testRemaining{0, 5},
1557 },
1558 }, {
1559 maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15},
1560 tests: []testFnc{
1561 testRemaining{15, 13},
1562 testWriteTo{fileOps{int64(1), "abc", int64(2), "defgh", int64(4)}, 15, errUnrefData},
1563 testRead{100, "", errUnrefData},
1564 testRemaining{0, 5},
1565 },
1566 }}
1567
1568 for i, v := range vectors {
1569 var fr fileReader
1570 switch maker := v.maker.(type) {
1571 case makeReg:
1572 r := testNonEmptyReader{strings.NewReader(maker.str)}
1573 fr = ®FileReader{r, maker.size}
1574 case makeSparse:
1575 if !validateSparseEntries(maker.spd, maker.size) {
1576 t.Fatalf("invalid sparse map: %v", maker.spd)
1577 }
1578 sph := invertSparseEntries(maker.spd, maker.size)
1579 r := testNonEmptyReader{strings.NewReader(maker.makeReg.str)}
1580 fr = ®FileReader{r, maker.makeReg.size}
1581 fr = &sparseFileReader{fr, sph, 0}
1582 default:
1583 t.Fatalf("test %d, unknown make operation: %T", i, maker)
1584 }
1585
1586 for j, tf := range v.tests {
1587 switch tf := tf.(type) {
1588 case testRead:
1589 b := make([]byte, tf.cnt)
1590 n, err := fr.Read(b)
1591 if got := string(b[:n]); got != tf.wantStr || err != tf.wantErr {
1592 t.Errorf("test %d.%d, Read(%d):\ngot (%q, %v)\nwant (%q, %v)", i, j, tf.cnt, got, err, tf.wantStr, tf.wantErr)
1593 }
1594 case testWriteTo:
1595 f := &testFile{ops: tf.ops}
1596 got, err := fr.WriteTo(f)
1597 if _, ok := err.(testError); ok {
1598 t.Errorf("test %d.%d, WriteTo(): %v", i, j, err)
1599 } else if got != tf.wantCnt || err != tf.wantErr {
1600 t.Errorf("test %d.%d, WriteTo() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr)
1601 }
1602 if len(f.ops) > 0 {
1603 t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
1604 }
1605 case testRemaining:
1606 if got := fr.logicalRemaining(); got != tf.wantLCnt {
1607 t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
1608 }
1609 if got := fr.physicalRemaining(); got != tf.wantPCnt {
1610 t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
1611 }
1612 default:
1613 t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
1614 }
1615 }
1616 }
1617 }
1618
1619 func TestInsecurePaths(t *testing.T) {
1620 t.Setenv("GODEBUG", "tarinsecurepath=0")
1621 for _, path := range []string{
1622 "../foo",
1623 "/foo",
1624 "a/b/../../../c",
1625 } {
1626 var buf bytes.Buffer
1627 tw := NewWriter(&buf)
1628 tw.WriteHeader(&Header{
1629 Name: path,
1630 })
1631 const securePath = "secure"
1632 tw.WriteHeader(&Header{
1633 Name: securePath,
1634 })
1635 tw.Close()
1636
1637 tr := NewReader(&buf)
1638 h, err := tr.Next()
1639 if err != ErrInsecurePath {
1640 t.Errorf("tr.Next for file %q: got err %v, want ErrInsecurePath", path, err)
1641 continue
1642 }
1643 if h.Name != path {
1644 t.Errorf("tr.Next for file %q: got name %q, want %q", path, h.Name, path)
1645 }
1646
1647 h, err = tr.Next()
1648 if err != nil {
1649 t.Errorf("tr.Next for file %q: got err %v, want nil", securePath, err)
1650 }
1651 if h.Name != securePath {
1652 t.Errorf("tr.Next for file %q: got name %q, want %q", securePath, h.Name, securePath)
1653 }
1654 }
1655 }
1656
1657 func TestDisableInsecurePathCheck(t *testing.T) {
1658 t.Setenv("GODEBUG", "tarinsecurepath=1")
1659 var buf bytes.Buffer
1660 tw := NewWriter(&buf)
1661 const name = "/foo"
1662 tw.WriteHeader(&Header{
1663 Name: name,
1664 })
1665 tw.Close()
1666 tr := NewReader(&buf)
1667 h, err := tr.Next()
1668 if err != nil {
1669 t.Fatalf("tr.Next with tarinsecurepath=1: got err %v, want nil", err)
1670 }
1671 if h.Name != name {
1672 t.Fatalf("tr.Next with tarinsecurepath=1: got name %q, want %q", h.Name, name)
1673 }
1674 }
1675
View as plain text