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