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
627 file: "testdata/gnu-sparse-many-zeros.tar.bz2",
628 err: errSparseTooLong,
629 }}
630
631 for _, v := range vectors {
632 t.Run(path.Base(v.file), func(t *testing.T) {
633 f, err := os.Open(v.file)
634 if err != nil {
635 t.Fatalf("unexpected error: %v", err)
636 }
637 defer f.Close()
638
639 var fr io.Reader = f
640 if strings.HasSuffix(v.file, ".bz2") {
641 fr = bzip2.NewReader(fr)
642 }
643
644
645 var (
646 tr = NewReader(fr)
647 hdrs []*Header
648 chksums []string
649 rdbuf = make([]byte, 8)
650 )
651 for {
652 var hdr *Header
653 hdr, err = tr.Next()
654 if err != nil {
655 if err == io.EOF {
656 err = nil
657 }
658 break
659 }
660 hdrs = append(hdrs, hdr)
661
662 if v.chksums == nil {
663 continue
664 }
665 h := crc32.NewIEEE()
666 _, err = io.CopyBuffer(h, tr, rdbuf)
667 if err != nil {
668 break
669 }
670 chksums = append(chksums, fmt.Sprintf("%x", h.Sum(nil)))
671 }
672
673 for i, hdr := range hdrs {
674 if i >= len(v.headers) {
675 t.Fatalf("entry %d: unexpected header:\ngot %+v", i, *hdr)
676 }
677 if !reflect.DeepEqual(*hdr, *v.headers[i]) {
678 t.Fatalf("entry %d: incorrect header:\ngot %+v\nwant %+v", i, *hdr, *v.headers[i])
679 }
680 }
681 if len(hdrs) != len(v.headers) {
682 t.Fatalf("got %d headers, want %d headers", len(hdrs), len(v.headers))
683 }
684
685 for i, sum := range chksums {
686 if i >= len(v.chksums) {
687 t.Fatalf("entry %d: unexpected sum: got %s", i, sum)
688 }
689 if sum != v.chksums[i] {
690 t.Fatalf("entry %d: incorrect checksum: got %s, want %s", i, sum, v.chksums[i])
691 }
692 }
693
694 if err != v.err {
695 t.Fatalf("unexpected error: got %v, want %v", err, v.err)
696 }
697 f.Close()
698 })
699 }
700 }
701
702 func TestPartialRead(t *testing.T) {
703 type testCase struct {
704 cnt int
705 output string
706 }
707 vectors := []struct {
708 file string
709 cases []testCase
710 }{{
711 file: "testdata/gnu.tar",
712 cases: []testCase{
713 {4, "Kilt"},
714 {6, "Google"},
715 },
716 }, {
717 file: "testdata/sparse-formats.tar",
718 cases: []testCase{
719 {2, "\x00G"},
720 {4, "\x00G\x00o"},
721 {6, "\x00G\x00o\x00G"},
722 {8, "\x00G\x00o\x00G\x00o"},
723 {4, "end\n"},
724 },
725 }}
726
727 for _, v := range vectors {
728 t.Run(path.Base(v.file), func(t *testing.T) {
729 f, err := os.Open(v.file)
730 if err != nil {
731 t.Fatalf("Open() error: %v", err)
732 }
733 defer f.Close()
734
735 tr := NewReader(f)
736 for i, tc := range v.cases {
737 hdr, err := tr.Next()
738 if err != nil || hdr == nil {
739 t.Fatalf("entry %d, Next(): got %v, want %v", i, err, nil)
740 }
741 buf := make([]byte, tc.cnt)
742 if _, err := io.ReadFull(tr, buf); err != nil {
743 t.Fatalf("entry %d, ReadFull(): got %v, want %v", i, err, nil)
744 }
745 if string(buf) != tc.output {
746 t.Fatalf("entry %d, ReadFull(): got %q, want %q", i, string(buf), tc.output)
747 }
748 }
749
750 if _, err := tr.Next(); err != io.EOF {
751 t.Fatalf("Next(): got %v, want EOF", err)
752 }
753 })
754 }
755 }
756
757 func TestUninitializedRead(t *testing.T) {
758 f, err := os.Open("testdata/gnu.tar")
759 if err != nil {
760 t.Fatalf("Unexpected error: %v", err)
761 }
762 defer f.Close()
763
764 tr := NewReader(f)
765 _, err = tr.Read([]byte{})
766 if err == nil || err != io.EOF {
767 t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF)
768 }
769
770 }
771
772 type reader struct{ io.Reader }
773 type readSeeker struct{ io.ReadSeeker }
774 type readBadSeeker struct{ io.ReadSeeker }
775
776 func (rbs *readBadSeeker) Seek(int64, int) (int64, error) { return 0, fmt.Errorf("illegal seek") }
777
778
779
780
781 func TestReadTruncation(t *testing.T) {
782 var ss []string
783 for _, p := range []string{
784 "testdata/gnu.tar",
785 "testdata/ustar-file-reg.tar",
786 "testdata/pax-path-hdr.tar",
787 "testdata/sparse-formats.tar",
788 } {
789 buf, err := os.ReadFile(p)
790 if err != nil {
791 t.Fatalf("unexpected error: %v", err)
792 }
793 ss = append(ss, string(buf))
794 }
795
796 data1, data2, pax, sparse := ss[0], ss[1], ss[2], ss[3]
797 data2 += strings.Repeat("\x00", 10*512)
798 trash := strings.Repeat("garbage ", 64)
799
800 vectors := []struct {
801 input string
802 cnt int
803 err error
804 }{
805 {"", 0, io.EOF},
806 {data1[:511], 0, io.ErrUnexpectedEOF},
807 {data1[:512], 1, io.ErrUnexpectedEOF},
808 {data1[:1024], 1, io.EOF},
809 {data1[:1536], 2, io.ErrUnexpectedEOF},
810 {data1[:2048], 2, io.EOF},
811 {data1, 2, io.EOF},
812 {data1[:2048] + data2[:1536], 3, io.EOF},
813 {data2[:511], 0, io.ErrUnexpectedEOF},
814 {data2[:512], 1, io.ErrUnexpectedEOF},
815 {data2[:1195], 1, io.ErrUnexpectedEOF},
816 {data2[:1196], 1, io.EOF},
817 {data2[:1200], 1, io.EOF},
818 {data2[:1535], 1, io.EOF},
819 {data2[:1536], 1, io.EOF},
820 {data2[:1536] + trash[:1], 1, io.ErrUnexpectedEOF},
821 {data2[:1536] + trash[:511], 1, io.ErrUnexpectedEOF},
822 {data2[:1536] + trash, 1, ErrHeader},
823 {data2[:2048], 1, io.EOF},
824 {data2[:2048] + trash[:1], 1, io.ErrUnexpectedEOF},
825 {data2[:2048] + trash[:511], 1, io.ErrUnexpectedEOF},
826 {data2[:2048] + trash, 1, ErrHeader},
827 {data2[:2560], 1, io.EOF},
828 {data2[:2560] + trash[:1], 1, io.EOF},
829 {data2[:2560] + trash[:511], 1, io.EOF},
830 {data2[:2560] + trash, 1, io.EOF},
831 {data2[:3072], 1, io.EOF},
832 {pax, 0, io.EOF},
833 {pax + trash[:1], 0, io.ErrUnexpectedEOF},
834 {pax + trash[:511], 0, io.ErrUnexpectedEOF},
835 {sparse[:511], 0, io.ErrUnexpectedEOF},
836 {sparse[:512], 0, io.ErrUnexpectedEOF},
837 {sparse[:3584], 1, io.EOF},
838 {sparse[:9200], 1, io.EOF},
839 {sparse[:9216], 1, io.EOF},
840 {sparse[:9728], 2, io.ErrUnexpectedEOF},
841 {sparse[:10240], 2, io.EOF},
842 {sparse[:11264], 2, io.ErrUnexpectedEOF},
843 {sparse, 5, io.EOF},
844 {sparse + trash, 5, io.EOF},
845 }
846
847 for i, v := range vectors {
848 for j := 0; j < 6; j++ {
849 var tr *Reader
850 var s1, s2 string
851
852 switch j {
853 case 0:
854 tr = NewReader(&reader{strings.NewReader(v.input)})
855 s1, s2 = "io.Reader", "auto"
856 case 1:
857 tr = NewReader(&reader{strings.NewReader(v.input)})
858 s1, s2 = "io.Reader", "manual"
859 case 2:
860 tr = NewReader(&readSeeker{strings.NewReader(v.input)})
861 s1, s2 = "io.ReadSeeker", "auto"
862 case 3:
863 tr = NewReader(&readSeeker{strings.NewReader(v.input)})
864 s1, s2 = "io.ReadSeeker", "manual"
865 case 4:
866 tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})
867 s1, s2 = "ReadBadSeeker", "auto"
868 case 5:
869 tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})
870 s1, s2 = "ReadBadSeeker", "manual"
871 }
872
873 var cnt int
874 var err error
875 for {
876 if _, err = tr.Next(); err != nil {
877 break
878 }
879 cnt++
880 if s2 == "manual" {
881 if _, err = tr.writeTo(io.Discard); err != nil {
882 break
883 }
884 }
885 }
886 if err != v.err {
887 t.Errorf("test %d, NewReader(%s) with %s discard: got %v, want %v",
888 i, s1, s2, err, v.err)
889 }
890 if cnt != v.cnt {
891 t.Errorf("test %d, NewReader(%s) with %s discard: got %d headers, want %d headers",
892 i, s1, s2, cnt, v.cnt)
893 }
894 }
895 }
896 }
897
898
899
900 func TestReadHeaderOnly(t *testing.T) {
901 f, err := os.Open("testdata/hdr-only.tar")
902 if err != nil {
903 t.Fatalf("unexpected error: %v", err)
904 }
905 defer f.Close()
906
907 var hdrs []*Header
908 tr := NewReader(f)
909 for {
910 hdr, err := tr.Next()
911 if err == io.EOF {
912 break
913 }
914 if err != nil {
915 t.Errorf("Next(): got %v, want %v", err, nil)
916 continue
917 }
918 hdrs = append(hdrs, hdr)
919
920
921 cnt, _ := io.ReadFull(tr, []byte{0})
922 if cnt > 0 && hdr.Typeflag != TypeReg {
923 t.Errorf("ReadFull(...): got %d bytes, want 0 bytes", cnt)
924 }
925 }
926
927
928
929 if len(hdrs) != 16 {
930 t.Fatalf("len(hdrs): got %d, want %d", len(hdrs), 16)
931 }
932 for i := 0; i < 8; i++ {
933 hdr1, hdr2 := hdrs[i+0], hdrs[i+8]
934 hdr1.Size, hdr2.Size = 0, 0
935 if !reflect.DeepEqual(*hdr1, *hdr2) {
936 t.Errorf("incorrect header:\ngot %+v\nwant %+v", *hdr1, *hdr2)
937 }
938 }
939 }
940
941 func TestMergePAX(t *testing.T) {
942 vectors := []struct {
943 in map[string]string
944 want *Header
945 ok bool
946 }{{
947 in: map[string]string{
948 "path": "a/b/c",
949 "uid": "1000",
950 "mtime": "1350244992.023960108",
951 },
952 want: &Header{
953 Name: "a/b/c",
954 Uid: 1000,
955 ModTime: time.Unix(1350244992, 23960108),
956 PAXRecords: map[string]string{
957 "path": "a/b/c",
958 "uid": "1000",
959 "mtime": "1350244992.023960108",
960 },
961 },
962 ok: true,
963 }, {
964 in: map[string]string{
965 "gid": "gtgergergersagersgers",
966 },
967 ok: false,
968 }, {
969 in: map[string]string{
970 "missing": "missing",
971 "SCHILY.xattr.key": "value",
972 },
973 want: &Header{
974 Xattrs: map[string]string{"key": "value"},
975 PAXRecords: map[string]string{
976 "missing": "missing",
977 "SCHILY.xattr.key": "value",
978 },
979 },
980 ok: true,
981 }}
982
983 for i, v := range vectors {
984 got := new(Header)
985 err := mergePAX(got, v.in)
986 if v.ok && !reflect.DeepEqual(*got, *v.want) {
987 t.Errorf("test %d, mergePAX(...):\ngot %+v\nwant %+v", i, *got, *v.want)
988 }
989 if ok := err == nil; ok != v.ok {
990 t.Errorf("test %d, mergePAX(...): got %v, want %v", i, ok, v.ok)
991 }
992 }
993 }
994
995 func TestParsePAX(t *testing.T) {
996 vectors := []struct {
997 in string
998 want map[string]string
999 ok bool
1000 }{
1001 {"", nil, true},
1002 {"6 k=1\n", map[string]string{"k": "1"}, true},
1003 {"10 a=name\n", map[string]string{"a": "name"}, true},
1004 {"9 a=name\n", map[string]string{"a": "name"}, true},
1005 {"30 mtime=1350244992.023960108\n", map[string]string{"mtime": "1350244992.023960108"}, true},
1006 {"3 somelongkey=\n", nil, false},
1007 {"50 tooshort=\n", nil, false},
1008 {"13 key1=haha\n13 key2=nana\n13 key3=kaka\n",
1009 map[string]string{"key1": "haha", "key2": "nana", "key3": "kaka"}, true},
1010 {"13 key1=val1\n13 key2=val2\n8 key1=\n",
1011 map[string]string{"key1": "", "key2": "val2"}, true},
1012 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=2\n" +
1013 "23 GNU.sparse.offset=1\n25 GNU.sparse.numbytes=2\n" +
1014 "23 GNU.sparse.offset=3\n25 GNU.sparse.numbytes=4\n",
1015 map[string]string{paxGNUSparseSize: "10", paxGNUSparseNumBlocks: "2", paxGNUSparseMap: "1,2,3,4"}, true},
1016 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" +
1017 "25 GNU.sparse.numbytes=2\n23 GNU.sparse.offset=1\n",
1018 nil, false},
1019 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" +
1020 "25 GNU.sparse.offset=1,2\n25 GNU.sparse.numbytes=2\n",
1021 nil, false},
1022 }
1023
1024 for i, v := range vectors {
1025 r := strings.NewReader(v.in)
1026 got, err := parsePAX(r)
1027 if !maps.Equal(got, v.want) && !(len(got) == 0 && len(v.want) == 0) {
1028 t.Errorf("test %d, parsePAX():\ngot %v\nwant %v", i, got, v.want)
1029 }
1030 if ok := err == nil; ok != v.ok {
1031 t.Errorf("test %d, parsePAX(): got %v, want %v", i, ok, v.ok)
1032 }
1033 }
1034 }
1035
1036 func TestReadOldGNUSparseMap(t *testing.T) {
1037 populateSparseMap := func(sa sparseArray, sps []string) []string {
1038 for i := 0; len(sps) > 0 && i < sa.maxEntries(); i++ {
1039 copy(sa.entry(i), sps[0])
1040 sps = sps[1:]
1041 }
1042 if len(sps) > 0 {
1043 copy(sa.isExtended(), "\x80")
1044 }
1045 return sps
1046 }
1047
1048 makeInput := func(format Format, size string, sps ...string) (out []byte) {
1049
1050 var blk block
1051 gnu := blk.toGNU()
1052 sparse := gnu.sparse()
1053 copy(gnu.realSize(), size)
1054 sps = populateSparseMap(sparse, sps)
1055 if format != FormatUnknown {
1056 blk.setFormat(format)
1057 }
1058 out = append(out, blk[:]...)
1059
1060
1061 for len(sps) > 0 {
1062 var blk block
1063 sps = populateSparseMap(blk.toSparse(), sps)
1064 out = append(out, blk[:]...)
1065 }
1066 return out
1067 }
1068
1069 makeSparseStrings := func(sp []sparseEntry) (out []string) {
1070 var f formatter
1071 for _, s := range sp {
1072 var b [24]byte
1073 f.formatNumeric(b[:12], s.Offset)
1074 f.formatNumeric(b[12:], s.Length)
1075 out = append(out, string(b[:]))
1076 }
1077 return out
1078 }
1079
1080 vectors := []struct {
1081 input []byte
1082 wantMap sparseDatas
1083 wantSize int64
1084 wantErr error
1085 }{{
1086 input: makeInput(FormatUnknown, ""),
1087 wantErr: ErrHeader,
1088 }, {
1089 input: makeInput(FormatGNU, "1234", "fewa"),
1090 wantSize: 01234,
1091 wantErr: ErrHeader,
1092 }, {
1093 input: makeInput(FormatGNU, "0031"),
1094 wantSize: 031,
1095 }, {
1096 input: makeInput(FormatGNU, "80"),
1097 wantErr: ErrHeader,
1098 }, {
1099 input: makeInput(FormatGNU, "1234",
1100 makeSparseStrings(sparseDatas{{0, 0}, {1, 1}})...),
1101 wantMap: sparseDatas{{0, 0}, {1, 1}},
1102 wantSize: 01234,
1103 }, {
1104 input: makeInput(FormatGNU, "1234",
1105 append(makeSparseStrings(sparseDatas{{0, 0}, {1, 1}}), []string{"", "blah"}...)...),
1106 wantMap: sparseDatas{{0, 0}, {1, 1}},
1107 wantSize: 01234,
1108 }, {
1109 input: makeInput(FormatGNU, "3333",
1110 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}})...),
1111 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}},
1112 wantSize: 03333,
1113 }, {
1114 input: makeInput(FormatGNU, "",
1115 append(append(
1116 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}}),
1117 []string{"", ""}...),
1118 makeSparseStrings(sparseDatas{{4, 1}, {6, 1}})...)...),
1119 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}},
1120 }, {
1121 input: makeInput(FormatGNU, "",
1122 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:blockSize],
1123 wantErr: io.ErrUnexpectedEOF,
1124 }, {
1125 input: makeInput(FormatGNU, "",
1126 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:3*blockSize/2],
1127 wantErr: io.ErrUnexpectedEOF,
1128 }, {
1129 input: makeInput(FormatGNU, "",
1130 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...),
1131 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}},
1132 }, {
1133 input: makeInput(FormatGNU, "",
1134 makeSparseStrings(sparseDatas{{10 << 30, 512}, {20 << 30, 512}})...),
1135 wantMap: sparseDatas{{10 << 30, 512}, {20 << 30, 512}},
1136 }}
1137
1138 for i, v := range vectors {
1139 var blk block
1140 var hdr Header
1141 v.input = v.input[copy(blk[:], v.input):]
1142 tr := Reader{r: bytes.NewReader(v.input)}
1143 got, err := tr.readOldGNUSparseMap(&hdr, &blk)
1144 if !slices.Equal(got, v.wantMap) {
1145 t.Errorf("test %d, readOldGNUSparseMap(): got %v, want %v", i, got, v.wantMap)
1146 }
1147 if err != v.wantErr {
1148 t.Errorf("test %d, readOldGNUSparseMap() = %v, want %v", i, err, v.wantErr)
1149 }
1150 if hdr.Size != v.wantSize {
1151 t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize)
1152 }
1153 }
1154 }
1155
1156 func TestReadGNUSparsePAXHeaders(t *testing.T) {
1157 padInput := func(s string) string {
1158 return s + string(zeroBlock[:blockPadding(int64(len(s)))])
1159 }
1160
1161 vectors := []struct {
1162 inputData string
1163 inputHdrs map[string]string
1164 wantMap sparseDatas
1165 wantSize int64
1166 wantName string
1167 wantErr error
1168 }{{
1169 inputHdrs: nil,
1170 wantErr: nil,
1171 }, {
1172 inputHdrs: map[string]string{
1173 paxGNUSparseNumBlocks: strconv.FormatInt(math.MaxInt64, 10),
1174 paxGNUSparseMap: "0,1,2,3",
1175 },
1176 wantErr: ErrHeader,
1177 }, {
1178 inputHdrs: map[string]string{
1179 paxGNUSparseNumBlocks: "4\x00",
1180 paxGNUSparseMap: "0,1,2,3",
1181 },
1182 wantErr: ErrHeader,
1183 }, {
1184 inputHdrs: map[string]string{
1185 paxGNUSparseNumBlocks: "4",
1186 paxGNUSparseMap: "0,1,2,3",
1187 },
1188 wantErr: ErrHeader,
1189 }, {
1190 inputHdrs: map[string]string{
1191 paxGNUSparseNumBlocks: "2",
1192 paxGNUSparseMap: "0,1,2,3",
1193 },
1194 wantMap: sparseDatas{{0, 1}, {2, 3}},
1195 }, {
1196 inputHdrs: map[string]string{
1197 paxGNUSparseNumBlocks: "2",
1198 paxGNUSparseMap: "0, 1,2,3",
1199 },
1200 wantErr: ErrHeader,
1201 }, {
1202 inputHdrs: map[string]string{
1203 paxGNUSparseNumBlocks: "2",
1204 paxGNUSparseMap: "0,1,02,3",
1205 paxGNUSparseRealSize: "4321",
1206 },
1207 wantMap: sparseDatas{{0, 1}, {2, 3}},
1208 wantSize: 4321,
1209 }, {
1210 inputHdrs: map[string]string{
1211 paxGNUSparseNumBlocks: "2",
1212 paxGNUSparseMap: "0,one1,2,3",
1213 },
1214 wantErr: ErrHeader,
1215 }, {
1216 inputHdrs: map[string]string{
1217 paxGNUSparseMajor: "0",
1218 paxGNUSparseMinor: "0",
1219 paxGNUSparseNumBlocks: "2",
1220 paxGNUSparseMap: "0,1,2,3",
1221 paxGNUSparseSize: "1234",
1222 paxGNUSparseRealSize: "4321",
1223 paxGNUSparseName: "realname",
1224 },
1225 wantMap: sparseDatas{{0, 1}, {2, 3}},
1226 wantSize: 1234,
1227 wantName: "realname",
1228 }, {
1229 inputHdrs: map[string]string{
1230 paxGNUSparseMajor: "0",
1231 paxGNUSparseMinor: "0",
1232 paxGNUSparseNumBlocks: "1",
1233 paxGNUSparseMap: "10737418240,512",
1234 paxGNUSparseSize: "10737418240",
1235 paxGNUSparseName: "realname",
1236 },
1237 wantMap: sparseDatas{{10737418240, 512}},
1238 wantSize: 10737418240,
1239 wantName: "realname",
1240 }, {
1241 inputHdrs: map[string]string{
1242 paxGNUSparseMajor: "0",
1243 paxGNUSparseMinor: "0",
1244 paxGNUSparseNumBlocks: "0",
1245 paxGNUSparseMap: "",
1246 },
1247 wantMap: sparseDatas{},
1248 }, {
1249 inputHdrs: map[string]string{
1250 paxGNUSparseMajor: "0",
1251 paxGNUSparseMinor: "1",
1252 paxGNUSparseNumBlocks: "4",
1253 paxGNUSparseMap: "0,5,10,5,20,5,30,5",
1254 },
1255 wantMap: sparseDatas{{0, 5}, {10, 5}, {20, 5}, {30, 5}},
1256 }, {
1257 inputHdrs: map[string]string{
1258 paxGNUSparseMajor: "1",
1259 paxGNUSparseMinor: "0",
1260 paxGNUSparseNumBlocks: "4",
1261 paxGNUSparseMap: "0,5,10,5,20,5,30,5",
1262 },
1263 wantErr: io.ErrUnexpectedEOF,
1264 }, {
1265 inputData: padInput("0\n"),
1266 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1267 wantMap: sparseDatas{},
1268 }, {
1269 inputData: padInput("0\n")[:blockSize-1] + "#",
1270 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1271 wantMap: sparseDatas{},
1272 }, {
1273 inputData: padInput("0"),
1274 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1275 wantErr: io.ErrUnexpectedEOF,
1276 }, {
1277 inputData: padInput("ab\n"),
1278 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1279 wantErr: ErrHeader,
1280 }, {
1281 inputData: padInput("1\n2\n3\n"),
1282 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1283 wantMap: sparseDatas{{2, 3}},
1284 }, {
1285 inputData: padInput("1\n2\n"),
1286 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1287 wantErr: io.ErrUnexpectedEOF,
1288 }, {
1289 inputData: padInput("1\n2\n\n"),
1290 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1291 wantErr: ErrHeader,
1292 }, {
1293 inputData: string(zeroBlock[:]) + padInput("0\n"),
1294 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1295 wantErr: ErrHeader,
1296 }, {
1297 inputData: strings.Repeat("0", blockSize) + padInput("1\n5\n1\n"),
1298 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1299 wantMap: sparseDatas{{5, 1}},
1300 }, {
1301 inputData: padInput(fmt.Sprintf("%d\n", int64(math.MaxInt64))),
1302 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1303 wantErr: ErrHeader,
1304 }, {
1305 inputData: padInput(strings.Repeat("0", 300) + "1\n" + strings.Repeat("0", 1000) + "5\n" + strings.Repeat("0", 800) + "2\n"),
1306 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1307 wantMap: sparseDatas{{5, 2}},
1308 }, {
1309 inputData: padInput("2\n10737418240\n512\n21474836480\n512\n"),
1310 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1311 wantMap: sparseDatas{{10737418240, 512}, {21474836480, 512}},
1312 }, {
1313 inputData: padInput("100\n" + func() string {
1314 var ss []string
1315 for i := 0; i < 100; i++ {
1316 ss = append(ss, fmt.Sprintf("%d\n%d\n", int64(i)<<30, 512))
1317 }
1318 return strings.Join(ss, "")
1319 }()),
1320 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},
1321 wantMap: func() (spd sparseDatas) {
1322 for i := 0; i < 100; i++ {
1323 spd = append(spd, sparseEntry{int64(i) << 30, 512})
1324 }
1325 return spd
1326 }(),
1327 }}
1328
1329 for i, v := range vectors {
1330 var hdr Header
1331 hdr.PAXRecords = v.inputHdrs
1332 r := strings.NewReader(v.inputData + "#")
1333 tr := Reader{curr: ®FileReader{r, int64(r.Len())}}
1334 got, err := tr.readGNUSparsePAXHeaders(&hdr)
1335 if !slices.Equal(got, v.wantMap) {
1336 t.Errorf("test %d, readGNUSparsePAXHeaders(): got %v, want %v", i, got, v.wantMap)
1337 }
1338 if err != v.wantErr {
1339 t.Errorf("test %d, readGNUSparsePAXHeaders() = %v, want %v", i, err, v.wantErr)
1340 }
1341 if hdr.Size != v.wantSize {
1342 t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize)
1343 }
1344 if hdr.Name != v.wantName {
1345 t.Errorf("test %d, Header.Name = %s, want %s", i, hdr.Name, v.wantName)
1346 }
1347 if v.wantErr == nil && r.Len() == 0 {
1348 t.Errorf("test %d, canary byte unexpectedly consumed", i)
1349 }
1350 }
1351 }
1352
1353
1354
1355 type testNonEmptyReader struct{ io.Reader }
1356
1357 func (r testNonEmptyReader) Read(b []byte) (int, error) {
1358 if len(b) == 0 {
1359 return 0, errors.New("unexpected empty Read call")
1360 }
1361 return r.Reader.Read(b)
1362 }
1363
1364 func TestFileReader(t *testing.T) {
1365 type (
1366 testRead struct {
1367 cnt int
1368 wantStr string
1369 wantErr error
1370 }
1371 testWriteTo struct {
1372 ops fileOps
1373 wantCnt int64
1374 wantErr error
1375 }
1376 testRemaining struct {
1377 wantLCnt int64
1378 wantPCnt int64
1379 }
1380 testFnc any
1381 )
1382
1383 type (
1384 makeReg struct {
1385 str string
1386 size int64
1387 }
1388 makeSparse struct {
1389 makeReg makeReg
1390 spd sparseDatas
1391 size int64
1392 }
1393 fileMaker any
1394 )
1395
1396 vectors := []struct {
1397 maker fileMaker
1398 tests []testFnc
1399 }{{
1400 maker: makeReg{"", 0},
1401 tests: []testFnc{
1402 testRemaining{0, 0},
1403 testRead{0, "", io.EOF},
1404 testRead{1, "", io.EOF},
1405 testWriteTo{nil, 0, nil},
1406 testRemaining{0, 0},
1407 },
1408 }, {
1409 maker: makeReg{"", 1},
1410 tests: []testFnc{
1411 testRemaining{1, 1},
1412 testRead{5, "", io.ErrUnexpectedEOF},
1413 testWriteTo{nil, 0, io.ErrUnexpectedEOF},
1414 testRemaining{1, 1},
1415 },
1416 }, {
1417 maker: makeReg{"hello", 5},
1418 tests: []testFnc{
1419 testRemaining{5, 5},
1420 testRead{5, "hello", io.EOF},
1421 testRemaining{0, 0},
1422 },
1423 }, {
1424 maker: makeReg{"hello, world", 50},
1425 tests: []testFnc{
1426 testRemaining{50, 50},
1427 testRead{7, "hello, ", nil},
1428 testRemaining{43, 43},
1429 testRead{5, "world", nil},
1430 testRemaining{38, 38},
1431 testWriteTo{nil, 0, io.ErrUnexpectedEOF},
1432 testRead{1, "", io.ErrUnexpectedEOF},
1433 testRemaining{38, 38},
1434 },
1435 }, {
1436 maker: makeReg{"hello, world", 5},
1437 tests: []testFnc{
1438 testRemaining{5, 5},
1439 testRead{0, "", nil},
1440 testRead{4, "hell", nil},
1441 testRemaining{1, 1},
1442 testWriteTo{fileOps{"o"}, 1, nil},
1443 testRemaining{0, 0},
1444 testWriteTo{nil, 0, nil},
1445 testRead{0, "", io.EOF},
1446 },
1447 }, {
1448 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8},
1449 tests: []testFnc{
1450 testRemaining{8, 5},
1451 testRead{3, "ab\x00", nil},
1452 testRead{10, "\x00\x00cde", io.EOF},
1453 testRemaining{0, 0},
1454 },
1455 }, {
1456 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8},
1457 tests: []testFnc{
1458 testRemaining{8, 5},
1459 testWriteTo{fileOps{"ab", int64(3), "cde"}, 8, nil},
1460 testRemaining{0, 0},
1461 },
1462 }, {
1463 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 10},
1464 tests: []testFnc{
1465 testRemaining{10, 5},
1466 testRead{100, "ab\x00\x00\x00cde\x00\x00", io.EOF},
1467 testRemaining{0, 0},
1468 },
1469 }, {
1470 maker: makeSparse{makeReg{"abc", 5}, sparseDatas{{0, 2}, {5, 3}}, 10},
1471 tests: []testFnc{
1472 testRemaining{10, 5},
1473 testRead{100, "ab\x00\x00\x00c", io.ErrUnexpectedEOF},
1474 testRemaining{4, 2},
1475 },
1476 }, {
1477 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {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 testRead{8, "\x00abc\x00\x00de", io.EOF},
1488 testRemaining{0, 0},
1489 },
1490 }, {
1491 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8},
1492 tests: []testFnc{
1493 testRemaining{8, 5},
1494 testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, nil},
1495 testRemaining{0, 0},
1496 },
1497 }, {
1498 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10},
1499 tests: []testFnc{
1500 testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF},
1501 },
1502 }, {
1503 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10},
1504 tests: []testFnc{
1505 testWriteTo{fileOps{int64(1), "abc", int64(2), "de", int64(1), "\x00"}, 10, nil},
1506 },
1507 }, {
1508 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}, {8, 0}, {8, 0}, {8, 0}, {8, 0}}, 10},
1509 tests: []testFnc{
1510 testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF},
1511 },
1512 }, {
1513 maker: makeSparse{makeReg{"", 0}, sparseDatas{}, 2},
1514 tests: []testFnc{
1515 testRead{100, "\x00\x00", io.EOF},
1516 },
1517 }, {
1518 maker: makeSparse{makeReg{"", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
1519 tests: []testFnc{
1520 testRead{100, "\x00", io.ErrUnexpectedEOF},
1521 },
1522 }, {
1523 maker: makeSparse{makeReg{"ab", 2}, sparseDatas{{1, 3}, {6, 5}}, 15},
1524 tests: []testFnc{
1525 testRead{100, "\x00ab", errMissData},
1526 },
1527 }, {
1528 maker: makeSparse{makeReg{"ab", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
1529 tests: []testFnc{
1530 testRead{100, "\x00ab", io.ErrUnexpectedEOF},
1531 },
1532 }, {
1533 maker: makeSparse{makeReg{"abc", 3}, sparseDatas{{1, 3}, {6, 5}}, 15},
1534 tests: []testFnc{
1535 testRead{100, "\x00abc\x00\x00", errMissData},
1536 },
1537 }, {
1538 maker: makeSparse{makeReg{"abc", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
1539 tests: []testFnc{
1540 testRead{100, "\x00abc\x00\x00", io.ErrUnexpectedEOF},
1541 },
1542 }, {
1543 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15},
1544 tests: []testFnc{
1545 testRead{100, "\x00abc\x00\x00de", errMissData},
1546 },
1547 }, {
1548 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15},
1549 tests: []testFnc{
1550 testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, errMissData},
1551 },
1552 }, {
1553 maker: makeSparse{makeReg{"abcde", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},
1554 tests: []testFnc{
1555 testRead{100, "\x00abc\x00\x00de", io.ErrUnexpectedEOF},
1556 },
1557 }, {
1558 maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15},
1559 tests: []testFnc{
1560 testRemaining{15, 13},
1561 testRead{100, "\x00abc\x00\x00defgh\x00\x00\x00\x00", errUnrefData},
1562 testWriteTo{nil, 0, errUnrefData},
1563 testRemaining{0, 5},
1564 },
1565 }, {
1566 maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15},
1567 tests: []testFnc{
1568 testRemaining{15, 13},
1569 testWriteTo{fileOps{int64(1), "abc", int64(2), "defgh", int64(4)}, 15, errUnrefData},
1570 testRead{100, "", errUnrefData},
1571 testRemaining{0, 5},
1572 },
1573 }}
1574
1575 for i, v := range vectors {
1576 var fr fileReader
1577 switch maker := v.maker.(type) {
1578 case makeReg:
1579 r := testNonEmptyReader{strings.NewReader(maker.str)}
1580 fr = ®FileReader{r, maker.size}
1581 case makeSparse:
1582 if !validateSparseEntries(maker.spd, maker.size) {
1583 t.Fatalf("invalid sparse map: %v", maker.spd)
1584 }
1585 sph := invertSparseEntries(maker.spd, maker.size)
1586 r := testNonEmptyReader{strings.NewReader(maker.makeReg.str)}
1587 fr = ®FileReader{r, maker.makeReg.size}
1588 fr = &sparseFileReader{fr, sph, 0}
1589 default:
1590 t.Fatalf("test %d, unknown make operation: %T", i, maker)
1591 }
1592
1593 for j, tf := range v.tests {
1594 switch tf := tf.(type) {
1595 case testRead:
1596 b := make([]byte, tf.cnt)
1597 n, err := fr.Read(b)
1598 if got := string(b[:n]); got != tf.wantStr || err != tf.wantErr {
1599 t.Errorf("test %d.%d, Read(%d):\ngot (%q, %v)\nwant (%q, %v)", i, j, tf.cnt, got, err, tf.wantStr, tf.wantErr)
1600 }
1601 case testWriteTo:
1602 f := &testFile{ops: tf.ops}
1603 got, err := fr.WriteTo(f)
1604 if _, ok := err.(testError); ok {
1605 t.Errorf("test %d.%d, WriteTo(): %v", i, j, err)
1606 } else if got != tf.wantCnt || err != tf.wantErr {
1607 t.Errorf("test %d.%d, WriteTo() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr)
1608 }
1609 if len(f.ops) > 0 {
1610 t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))
1611 }
1612 case testRemaining:
1613 if got := fr.logicalRemaining(); got != tf.wantLCnt {
1614 t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)
1615 }
1616 if got := fr.physicalRemaining(); got != tf.wantPCnt {
1617 t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)
1618 }
1619 default:
1620 t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)
1621 }
1622 }
1623 }
1624 }
1625
1626 func TestInsecurePaths(t *testing.T) {
1627 t.Setenv("GODEBUG", "tarinsecurepath=0")
1628 for _, path := range []string{
1629 "../foo",
1630 "/foo",
1631 "a/b/../../../c",
1632 } {
1633 var buf bytes.Buffer
1634 tw := NewWriter(&buf)
1635 tw.WriteHeader(&Header{
1636 Name: path,
1637 })
1638 const securePath = "secure"
1639 tw.WriteHeader(&Header{
1640 Name: securePath,
1641 })
1642 tw.Close()
1643
1644 tr := NewReader(&buf)
1645 h, err := tr.Next()
1646 if err != ErrInsecurePath {
1647 t.Errorf("tr.Next for file %q: got err %v, want ErrInsecurePath", path, err)
1648 continue
1649 }
1650 if h.Name != path {
1651 t.Errorf("tr.Next for file %q: got name %q, want %q", path, h.Name, path)
1652 }
1653
1654 h, err = tr.Next()
1655 if err != nil {
1656 t.Errorf("tr.Next for file %q: got err %v, want nil", securePath, err)
1657 }
1658 if h.Name != securePath {
1659 t.Errorf("tr.Next for file %q: got name %q, want %q", securePath, h.Name, securePath)
1660 }
1661 }
1662 }
1663
1664 func TestDisableInsecurePathCheck(t *testing.T) {
1665 t.Setenv("GODEBUG", "tarinsecurepath=1")
1666 var buf bytes.Buffer
1667 tw := NewWriter(&buf)
1668 const name = "/foo"
1669 tw.WriteHeader(&Header{
1670 Name: name,
1671 })
1672 tw.Close()
1673 tr := NewReader(&buf)
1674 h, err := tr.Next()
1675 if err != nil {
1676 t.Fatalf("tr.Next with tarinsecurepath=1: got err %v, want nil", err)
1677 }
1678 if h.Name != name {
1679 t.Fatalf("tr.Next with tarinsecurepath=1: got name %q, want %q", h.Name, name)
1680 }
1681 }
1682
View as plain text