Ensure errors are handled or logged
if err != nil {
1// Copyright 2009 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package tar67import (8 "bytes"9 "compress/bzip2"10 "errors"11 "fmt"12 "hash/crc32"13 "internal/obscuretestdata"14 "io"15 "maps"16 "math"17 "os"18 "path"19 "reflect"20 "slices"21 "strconv"22 "strings"23 "testing"24 "time"25)2627func TestReader(t *testing.T) {28 vectors := []struct {29 file string // Test input file30 obscured bool // Obscured with obscuretestdata package31 headers []*Header // Expected output headers32 chksums []string // CRC32 checksum of files, leave as nil if not checked33 err error // Expected error to occur34 }{{35 file: "testdata/gnu.tar",36 headers: []*Header{{37 Name: "small.txt",38 Mode: 0640,39 Uid: 73025,40 Gid: 5000,41 Size: 5,42 ModTime: time.Unix(1244428340, 0),43 Typeflag: '0',44 Uname: "dsymonds",45 Gname: "eng",46 Format: FormatGNU,47 }, {48 Name: "small2.txt",49 Mode: 0640,50 Uid: 73025,51 Gid: 5000,52 Size: 11,53 ModTime: time.Unix(1244436044, 0),54 Typeflag: '0',55 Uname: "dsymonds",56 Gname: "eng",57 Format: FormatGNU,58 }},59 chksums: []string{60 "6cbd88fc",61 "ddac04b3",62 },63 }, {64 file: "testdata/sparse-formats.tar",65 headers: []*Header{{66 Name: "sparse-gnu",67 Mode: 420,68 Uid: 1000,69 Gid: 1000,70 Size: 200,71 ModTime: time.Unix(1392395740, 0),72 Typeflag: 0x53,73 Linkname: "",74 Uname: "david",75 Gname: "david",76 Devmajor: 0,77 Devminor: 0,78 Format: FormatGNU,79 }, {80 Name: "sparse-posix-0.0",81 Mode: 420,82 Uid: 1000,83 Gid: 1000,84 Size: 200,85 ModTime: time.Unix(1392342187, 0),86 Typeflag: 0x30,87 Linkname: "",88 Uname: "david",89 Gname: "david",90 Devmajor: 0,91 Devminor: 0,92 PAXRecords: map[string]string{93 "GNU.sparse.size": "200",94 "GNU.sparse.numblocks": "95",95 "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",96 },97 Format: FormatPAX,98 }, {99 Name: "sparse-posix-0.1",100 Mode: 420,101 Uid: 1000,102 Gid: 1000,103 Size: 200,104 ModTime: time.Unix(1392340456, 0),105 Typeflag: 0x30,106 Linkname: "",107 Uname: "david",108 Gname: "david",109 Devmajor: 0,110 Devminor: 0,111 PAXRecords: map[string]string{112 "GNU.sparse.size": "200",113 "GNU.sparse.numblocks": "95",114 "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",115 "GNU.sparse.name": "sparse-posix-0.1",116 },117 Format: FormatPAX,118 }, {119 Name: "sparse-posix-1.0",120 Mode: 420,121 Uid: 1000,122 Gid: 1000,123 Size: 200,124 ModTime: time.Unix(1392337404, 0),125 Typeflag: 0x30,126 Linkname: "",127 Uname: "david",128 Gname: "david",129 Devmajor: 0,130 Devminor: 0,131 PAXRecords: map[string]string{132 "GNU.sparse.major": "1",133 "GNU.sparse.minor": "0",134 "GNU.sparse.realsize": "200",135 "GNU.sparse.name": "sparse-posix-1.0",136 },137 Format: FormatPAX,138 }, {139 Name: "end",140 Mode: 420,141 Uid: 1000,142 Gid: 1000,143 Size: 4,144 ModTime: time.Unix(1392398319, 0),145 Typeflag: 0x30,146 Linkname: "",147 Uname: "david",148 Gname: "david",149 Devmajor: 0,150 Devminor: 0,151 Format: FormatGNU,152 }},153 chksums: []string{154 "5375e1d2",155 "5375e1d2",156 "5375e1d2",157 "5375e1d2",158 "8eb179ba",159 },160 }, {161 file: "testdata/star.tar",162 headers: []*Header{{163 Name: "small.txt",164 Mode: 0640,165 Uid: 73025,166 Gid: 5000,167 Size: 5,168 ModTime: time.Unix(1244592783, 0),169 Typeflag: '0',170 Uname: "dsymonds",171 Gname: "eng",172 AccessTime: time.Unix(1244592783, 0),173 ChangeTime: time.Unix(1244592783, 0),174 }, {175 Name: "small2.txt",176 Mode: 0640,177 Uid: 73025,178 Gid: 5000,179 Size: 11,180 ModTime: time.Unix(1244592783, 0),181 Typeflag: '0',182 Uname: "dsymonds",183 Gname: "eng",184 AccessTime: time.Unix(1244592783, 0),185 ChangeTime: time.Unix(1244592783, 0),186 }},187 }, {188 file: "testdata/v7.tar",189 headers: []*Header{{190 Name: "small.txt",191 Mode: 0444,192 Uid: 73025,193 Gid: 5000,194 Size: 5,195 ModTime: time.Unix(1244593104, 0),196 Typeflag: '0',197 }, {198 Name: "small2.txt",199 Mode: 0444,200 Uid: 73025,201 Gid: 5000,202 Size: 11,203 ModTime: time.Unix(1244593104, 0),204 Typeflag: '0',205 }},206 }, {207 file: "testdata/pax.tar",208 headers: []*Header{{209 Name: "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",210 Mode: 0664,211 Uid: 1000,212 Gid: 1000,213 Uname: "shane",214 Gname: "shane",215 Size: 7,216 ModTime: time.Unix(1350244992, 23960108),217 ChangeTime: time.Unix(1350244992, 23960108),218 AccessTime: time.Unix(1350244992, 23960108),219 Typeflag: TypeReg,220 PAXRecords: map[string]string{221 "path": "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",222 "mtime": "1350244992.023960108",223 "atime": "1350244992.023960108",224 "ctime": "1350244992.023960108",225 },226 Format: FormatPAX,227 }, {228 Name: "a/b",229 Mode: 0777,230 Uid: 1000,231 Gid: 1000,232 Uname: "shane",233 Gname: "shane",234 Size: 0,235 ModTime: time.Unix(1350266320, 910238425),236 ChangeTime: time.Unix(1350266320, 910238425),237 AccessTime: time.Unix(1350266320, 910238425),238 Typeflag: TypeSymlink,239 Linkname: "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",240 PAXRecords: map[string]string{241 "linkpath": "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100",242 "mtime": "1350266320.910238425",243 "atime": "1350266320.910238425",244 "ctime": "1350266320.910238425",245 },246 Format: FormatPAX,247 }},248 }, {249 file: "testdata/pax-bad-hdr-file.tar",250 err: ErrHeader,251 }, {252 file: "testdata/pax-bad-hdr-large.tar.bz2",253 err: ErrFieldTooLong,254 }, {255 file: "testdata/pax-bad-mtime-file.tar",256 err: ErrHeader,257 }, {258 file: "testdata/pax-pos-size-file.tar",259 headers: []*Header{{260 Name: "foo",261 Mode: 0640,262 Uid: 319973,263 Gid: 5000,264 Size: 999,265 ModTime: time.Unix(1442282516, 0),266 Typeflag: '0',267 Uname: "joetsai",268 Gname: "eng",269 PAXRecords: map[string]string{270 "size": "000000000000000000000999",271 },272 Format: FormatPAX,273 }},274 chksums: []string{275 "5fd7e86a",276 },277 }, {278 file: "testdata/pax-records.tar",279 headers: []*Header{{280 Typeflag: TypeReg,281 Name: "file",282 Uname: strings.Repeat("long", 10),283 ModTime: time.Unix(0, 0),284 PAXRecords: map[string]string{285 "GOLANG.pkg": "tar",286 "comment": "Hello, 世界",287 "uname": strings.Repeat("long", 10),288 },289 Format: FormatPAX,290 }},291 }, {292 file: "testdata/pax-global-records.tar",293 headers: []*Header{{294 Typeflag: TypeXGlobalHeader,295 Name: "global1",296 PAXRecords: map[string]string{"path": "global1", "mtime": "1500000000.0"},297 Format: FormatPAX,298 }, {299 Typeflag: TypeReg,300 Name: "file1",301 ModTime: time.Unix(0, 0),302 Format: FormatUSTAR,303 }, {304 Typeflag: TypeReg,305 Name: "file2",306 PAXRecords: map[string]string{"path": "file2"},307 ModTime: time.Unix(0, 0),308 Format: FormatPAX,309 }, {310 Typeflag: TypeXGlobalHeader,311 Name: "GlobalHead.0.0",312 PAXRecords: map[string]string{"path": ""},313 Format: FormatPAX,314 }, {315 Typeflag: TypeReg,316 Name: "file3",317 ModTime: time.Unix(0, 0),318 Format: FormatUSTAR,319 }, {320 Typeflag: TypeReg,321 Name: "file4",322 ModTime: time.Unix(1400000000, 0),323 PAXRecords: map[string]string{"mtime": "1400000000"},324 Format: FormatPAX,325 }},326 }, {327 file: "testdata/nil-uid.tar", // golang.org/issue/5290328 headers: []*Header{{329 Name: "P1050238.JPG.log",330 Mode: 0664,331 Uid: 0,332 Gid: 0,333 Size: 14,334 ModTime: time.Unix(1365454838, 0),335 Typeflag: TypeReg,336 Linkname: "",337 Uname: "eyefi",338 Gname: "eyefi",339 Devmajor: 0,340 Devminor: 0,341 Format: FormatGNU,342 }},343 }, {344 file: "testdata/xattrs.tar",345 headers: []*Header{{346 Name: "small.txt",347 Mode: 0644,348 Uid: 1000,349 Gid: 10,350 Size: 5,351 ModTime: time.Unix(1386065770, 448252320),352 Typeflag: '0',353 Uname: "alex",354 Gname: "wheel",355 AccessTime: time.Unix(1389782991, 419875220),356 ChangeTime: time.Unix(1389782956, 794414986),357 Xattrs: map[string]string{358 "user.key": "value",359 "user.key2": "value2",360 // Interestingly, selinux encodes the terminating null inside the xattr361 "security.selinux": "unconfined_u:object_r:default_t:s0\x00",362 },363 PAXRecords: map[string]string{364 "mtime": "1386065770.44825232",365 "atime": "1389782991.41987522",366 "ctime": "1389782956.794414986",367 "SCHILY.xattr.user.key": "value",368 "SCHILY.xattr.user.key2": "value2",369 "SCHILY.xattr.security.selinux": "unconfined_u:object_r:default_t:s0\x00",370 },371 Format: FormatPAX,372 }, {373 Name: "small2.txt",374 Mode: 0644,375 Uid: 1000,376 Gid: 10,377 Size: 11,378 ModTime: time.Unix(1386065770, 449252304),379 Typeflag: '0',380 Uname: "alex",381 Gname: "wheel",382 AccessTime: time.Unix(1389782991, 419875220),383 ChangeTime: time.Unix(1386065770, 449252304),384 Xattrs: map[string]string{385 "security.selinux": "unconfined_u:object_r:default_t:s0\x00",386 },387 PAXRecords: map[string]string{388 "mtime": "1386065770.449252304",389 "atime": "1389782991.41987522",390 "ctime": "1386065770.449252304",391 "SCHILY.xattr.security.selinux": "unconfined_u:object_r:default_t:s0\x00",392 },393 Format: FormatPAX,394 }},395 }, {396 // Matches the behavior of GNU, BSD, and STAR tar utilities.397 file: "testdata/gnu-multi-hdrs.tar",398 headers: []*Header{{399 Name: "GNU2/GNU2/long-path-name",400 Linkname: "GNU4/GNU4/long-linkpath-name",401 ModTime: time.Unix(0, 0),402 Typeflag: '2',403 Format: FormatGNU,404 }},405 }, {406 // GNU tar file with atime and ctime fields set.407 // Created with the GNU tar v1.27.1.408 // tar --incremental -S -cvf gnu-incremental.tar test2409 file: "testdata/gnu-incremental.tar",410 headers: []*Header{{411 Name: "test2/",412 Mode: 16877,413 Uid: 1000,414 Gid: 1000,415 Size: 14,416 ModTime: time.Unix(1441973427, 0),417 Typeflag: 'D',418 Uname: "rawr",419 Gname: "dsnet",420 AccessTime: time.Unix(1441974501, 0),421 ChangeTime: time.Unix(1441973436, 0),422 Format: FormatGNU,423 }, {424 Name: "test2/foo",425 Mode: 33188,426 Uid: 1000,427 Gid: 1000,428 Size: 64,429 ModTime: time.Unix(1441973363, 0),430 Typeflag: '0',431 Uname: "rawr",432 Gname: "dsnet",433 AccessTime: time.Unix(1441974501, 0),434 ChangeTime: time.Unix(1441973436, 0),435 Format: FormatGNU,436 }, {437 Name: "test2/sparse",438 Mode: 33188,439 Uid: 1000,440 Gid: 1000,441 Size: 536870912,442 ModTime: time.Unix(1441973427, 0),443 Typeflag: 'S',444 Uname: "rawr",445 Gname: "dsnet",446 AccessTime: time.Unix(1441991948, 0),447 ChangeTime: time.Unix(1441973436, 0),448 Format: FormatGNU,449 }},450 }, {451 // Matches the behavior of GNU and BSD tar utilities.452 file: "testdata/pax-multi-hdrs.tar",453 headers: []*Header{{454 Name: "bar",455 Linkname: "PAX4/PAX4/long-linkpath-name",456 ModTime: time.Unix(0, 0),457 Typeflag: '2',458 PAXRecords: map[string]string{459 "linkpath": "PAX4/PAX4/long-linkpath-name",460 },461 Format: FormatPAX,462 }},463 }, {464 // Both BSD and GNU tar truncate long names at first NUL even465 // if there is data following that NUL character.466 // This is reasonable as GNU long names are C-strings.467 file: "testdata/gnu-long-nul.tar",468 headers: []*Header{{469 Name: "0123456789",470 Mode: 0644,471 Uid: 1000,472 Gid: 1000,473 ModTime: time.Unix(1486082191, 0),474 Typeflag: '0',475 Uname: "rawr",476 Gname: "dsnet",477 Format: FormatGNU,478 }},479 }, {480 // This archive was generated by Writer but is readable by both481 // GNU and BSD tar utilities.482 // The archive generated by GNU is nearly byte-for-byte identical483 // to the Go version except the Go version sets a negative Devminor484 // just to force the GNU format.485 file: "testdata/gnu-utf8.tar",486 headers: []*Header{{487 Name: "☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹",488 Mode: 0644,489 Uid: 1000, Gid: 1000,490 ModTime: time.Unix(0, 0),491 Typeflag: '0',492 Uname: "☺",493 Gname: "⚹",494 Format: FormatGNU,495 }},496 }, {497 // This archive was generated by Writer but is readable by both498 // GNU and BSD tar utilities.499 // The archive generated by GNU is nearly byte-for-byte identical500 // to the Go version except the Go version sets a negative Devminor501 // just to force the GNU format.502 file: "testdata/gnu-not-utf8.tar",503 headers: []*Header{{504 Name: "hi\x80\x81\x82\x83bye",505 Mode: 0644,506 Uid: 1000,507 Gid: 1000,508 ModTime: time.Unix(0, 0),509 Typeflag: '0',510 Uname: "rawr",511 Gname: "dsnet",512 Format: FormatGNU,513 }},514 }, {515 // BSD tar v3.1.2 and GNU tar v1.27.1 both rejects PAX records516 // with NULs in the key.517 file: "testdata/pax-nul-xattrs.tar",518 err: ErrHeader,519 }, {520 // BSD tar v3.1.2 rejects a PAX path with NUL in the value, while521 // GNU tar v1.27.1 simply truncates at first NUL.522 // We emulate the behavior of BSD since it is strange doing NUL523 // truncations since PAX records are length-prefix strings instead524 // of NUL-terminated C-strings.525 file: "testdata/pax-nul-path.tar",526 err: ErrHeader,527 }, {528 file: "testdata/neg-size.tar.base64",529 obscured: true,530 err: ErrHeader,531 }, {532 file: "testdata/issue10968.tar",533 err: ErrHeader,534 }, {535 file: "testdata/issue11169.tar",536 err: ErrHeader,537 }, {538 file: "testdata/issue12435.tar",539 err: ErrHeader,540 }, {541 // Ensure that we can read back the original Header as written with542 // a buggy pre-Go1.8 tar.Writer.543 file: "testdata/invalid-go17.tar",544 headers: []*Header{{545 Name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/foo",546 Uid: 010000000,547 ModTime: time.Unix(0, 0),548 Typeflag: '0',549 }},550 }, {551 // USTAR archive with a regular entry with non-zero device numbers.552 file: "testdata/ustar-file-devs.tar",553 headers: []*Header{{554 Name: "file",555 Mode: 0644,556 Typeflag: '0',557 ModTime: time.Unix(0, 0),558 Devmajor: 1,559 Devminor: 1,560 Format: FormatUSTAR,561 }},562 }, {563 // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.564 file: "testdata/gnu-nil-sparse-data.tar",565 headers: []*Header{{566 Name: "sparse.db",567 Typeflag: TypeGNUSparse,568 Size: 1000,569 ModTime: time.Unix(0, 0),570 Format: FormatGNU,571 }},572 }, {573 // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.574 file: "testdata/gnu-nil-sparse-hole.tar",575 headers: []*Header{{576 Name: "sparse.db",577 Typeflag: TypeGNUSparse,578 Size: 1000,579 ModTime: time.Unix(0, 0),580 Format: FormatGNU,581 }},582 }, {583 // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.584 file: "testdata/pax-nil-sparse-data.tar",585 headers: []*Header{{586 Name: "sparse.db",587 Typeflag: TypeReg,588 Size: 1000,589 ModTime: time.Unix(0, 0),590 PAXRecords: map[string]string{591 "size": "1512",592 "GNU.sparse.major": "1",593 "GNU.sparse.minor": "0",594 "GNU.sparse.realsize": "1000",595 "GNU.sparse.name": "sparse.db",596 },597 Format: FormatPAX,598 }},599 }, {600 // Generated by Go, works on BSD tar v3.1.2 and GNU tar v.1.27.1.601 file: "testdata/pax-nil-sparse-hole.tar",602 headers: []*Header{{603 Name: "sparse.db",604 Typeflag: TypeReg,605 Size: 1000,606 ModTime: time.Unix(0, 0),607 PAXRecords: map[string]string{608 "size": "512",609 "GNU.sparse.major": "1",610 "GNU.sparse.minor": "0",611 "GNU.sparse.realsize": "1000",612 "GNU.sparse.name": "sparse.db",613 },614 Format: FormatPAX,615 }},616 }, {617 file: "testdata/trailing-slash.tar",618 headers: []*Header{{619 Typeflag: TypeDir,620 Name: strings.Repeat("123456789/", 30),621 ModTime: time.Unix(0, 0),622 PAXRecords: map[string]string{623 "path": strings.Repeat("123456789/", 30),624 },625 Format: FormatPAX,626 }},627 }, {628 // Small compressed file that uncompresses to629 // a file with a very large GNU 1.0 sparse map.630 file: "testdata/gnu-sparse-many-zeros.tar.bz2",631 err: errSparseTooLong,632 }}633634 for _, v := range vectors {635 t.Run(strings.TrimSuffix(path.Base(v.file), ".base64"), func(t *testing.T) {636 path := v.file637 if v.obscured {638 tf, err := obscuretestdata.DecodeToTempFile(path)639 if err != nil {640 t.Fatalf("obscuredtestdata.DecodeToTempFile(%s): %v", path, err)641 }642 path = tf643 }644645 f, err := os.Open(path)646 if err != nil {647 t.Fatalf("unexpected error: %v", err)648 }649 defer f.Close()650651 var fr io.Reader = f652 if strings.HasSuffix(v.file, ".bz2") || strings.HasSuffix(v.file, ".bz2.base64") {653 fr = bzip2.NewReader(fr)654 }655656 // Capture all headers and checksums.657 var (658 tr = NewReader(fr)659 hdrs []*Header660 chksums []string661 rdbuf = make([]byte, 8)662 )663 for {664 var hdr *Header665 hdr, err = tr.Next()666 if err != nil {667 if err == io.EOF {668 err = nil // Expected error669 }670 break671 }672 hdrs = append(hdrs, hdr)673674 if v.chksums == nil {675 continue676 }677 h := crc32.NewIEEE()678 _, err = io.CopyBuffer(h, tr, rdbuf) // Effectively an incremental read679 if err != nil {680 break681 }682 chksums = append(chksums, fmt.Sprintf("%x", h.Sum(nil)))683 }684685 for i, hdr := range hdrs {686 if i >= len(v.headers) {687 t.Fatalf("entry %d: unexpected header:\ngot %+v", i, *hdr)688 }689 if !reflect.DeepEqual(*hdr, *v.headers[i]) {690 t.Fatalf("entry %d: incorrect header:\ngot %+v\nwant %+v", i, *hdr, *v.headers[i])691 }692 }693 if len(hdrs) != len(v.headers) {694 t.Fatalf("got %d headers, want %d headers", len(hdrs), len(v.headers))695 }696697 for i, sum := range chksums {698 if i >= len(v.chksums) {699 t.Fatalf("entry %d: unexpected sum: got %s", i, sum)700 }701 if sum != v.chksums[i] {702 t.Fatalf("entry %d: incorrect checksum: got %s, want %s", i, sum, v.chksums[i])703 }704 }705706 if err != v.err {707 t.Fatalf("unexpected error: got %v, want %v", err, v.err)708 }709 f.Close()710 })711 }712}713714func TestPartialRead(t *testing.T) {715 type testCase struct {716 cnt int // Number of bytes to read717 output string // Expected value of string read718 }719 vectors := []struct {720 file string721 cases []testCase722 }{{723 file: "testdata/gnu.tar",724 cases: []testCase{725 {4, "Kilt"},726 {6, "Google"},727 },728 }, {729 file: "testdata/sparse-formats.tar",730 cases: []testCase{731 {2, "\x00G"},732 {4, "\x00G\x00o"},733 {6, "\x00G\x00o\x00G"},734 {8, "\x00G\x00o\x00G\x00o"},735 {4, "end\n"},736 },737 }}738739 for _, v := range vectors {740 t.Run(path.Base(v.file), func(t *testing.T) {741 f, err := os.Open(v.file)742 if err != nil {743 t.Fatalf("Open() error: %v", err)744 }745 defer f.Close()746747 tr := NewReader(f)748 for i, tc := range v.cases {749 hdr, err := tr.Next()750 if err != nil || hdr == nil {751 t.Fatalf("entry %d, Next(): got %v, want %v", i, err, nil)752 }753 buf := make([]byte, tc.cnt)754 if _, err := io.ReadFull(tr, buf); err != nil {755 t.Fatalf("entry %d, ReadFull(): got %v, want %v", i, err, nil)756 }757 if string(buf) != tc.output {758 t.Fatalf("entry %d, ReadFull(): got %q, want %q", i, string(buf), tc.output)759 }760 }761762 if _, err := tr.Next(); err != io.EOF {763 t.Fatalf("Next(): got %v, want EOF", err)764 }765 })766 }767}768769func TestUninitializedRead(t *testing.T) {770 f, err := os.Open("testdata/gnu.tar")771 if err != nil {772 t.Fatalf("Unexpected error: %v", err)773 }774 defer f.Close()775776 tr := NewReader(f)777 _, err = tr.Read([]byte{})778 if err == nil || err != io.EOF {779 t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF)780 }781782}783784type reader struct{ io.Reader }785type readSeeker struct{ io.ReadSeeker }786type readBadSeeker struct{ io.ReadSeeker }787788func (rbs *readBadSeeker) Seek(int64, int) (int64, error) { return 0, fmt.Errorf("illegal seek") }789790// TestReadTruncation tests the ending condition on various truncated files and791// that truncated files are still detected even if the underlying io.Reader792// satisfies io.Seeker.793func TestReadTruncation(t *testing.T) {794 var ss []string795 for _, p := range []string{796 "testdata/gnu.tar",797 "testdata/ustar-file-reg.tar",798 "testdata/pax-path-hdr.tar",799 "testdata/sparse-formats.tar",800 } {801 buf, err := os.ReadFile(p)802 if err != nil {803 t.Fatalf("unexpected error: %v", err)804 }805 ss = append(ss, string(buf))806 }807808 data1, data2, pax, sparse := ss[0], ss[1], ss[2], ss[3]809 data2 += strings.Repeat("\x00", 10*512)810 trash := strings.Repeat("garbage ", 64) // Exactly 512 bytes811812 vectors := []struct {813 input string // Input stream814 cnt int // Expected number of headers read815 err error // Expected error outcome816 }{817 {"", 0, io.EOF}, // Empty file is a "valid" tar file818 {data1[:511], 0, io.ErrUnexpectedEOF},819 {data1[:512], 1, io.ErrUnexpectedEOF},820 {data1[:1024], 1, io.EOF},821 {data1[:1536], 2, io.ErrUnexpectedEOF},822 {data1[:2048], 2, io.EOF},823 {data1, 2, io.EOF},824 {data1[:2048] + data2[:1536], 3, io.EOF},825 {data2[:511], 0, io.ErrUnexpectedEOF},826 {data2[:512], 1, io.ErrUnexpectedEOF},827 {data2[:1195], 1, io.ErrUnexpectedEOF},828 {data2[:1196], 1, io.EOF}, // Exact end of data and start of padding829 {data2[:1200], 1, io.EOF},830 {data2[:1535], 1, io.EOF},831 {data2[:1536], 1, io.EOF}, // Exact end of padding832 {data2[:1536] + trash[:1], 1, io.ErrUnexpectedEOF},833 {data2[:1536] + trash[:511], 1, io.ErrUnexpectedEOF},834 {data2[:1536] + trash, 1, ErrHeader},835 {data2[:2048], 1, io.EOF}, // Exactly 1 empty block836 {data2[:2048] + trash[:1], 1, io.ErrUnexpectedEOF},837 {data2[:2048] + trash[:511], 1, io.ErrUnexpectedEOF},838 {data2[:2048] + trash, 1, ErrHeader},839 {data2[:2560], 1, io.EOF}, // Exactly 2 empty blocks (normal end-of-stream)840 {data2[:2560] + trash[:1], 1, io.EOF},841 {data2[:2560] + trash[:511], 1, io.EOF},842 {data2[:2560] + trash, 1, io.EOF},843 {data2[:3072], 1, io.EOF},844 {pax, 0, io.EOF}, // PAX header without data is a "valid" tar file845 {pax + trash[:1], 0, io.ErrUnexpectedEOF},846 {pax + trash[:511], 0, io.ErrUnexpectedEOF},847 {sparse[:511], 0, io.ErrUnexpectedEOF},848 {sparse[:512], 0, io.ErrUnexpectedEOF},849 {sparse[:3584], 1, io.EOF},850 {sparse[:9200], 1, io.EOF}, // Terminate in padding of sparse header851 {sparse[:9216], 1, io.EOF},852 {sparse[:9728], 2, io.ErrUnexpectedEOF},853 {sparse[:10240], 2, io.EOF},854 {sparse[:11264], 2, io.ErrUnexpectedEOF},855 {sparse, 5, io.EOF},856 {sparse + trash, 5, io.EOF},857 }858859 for i, v := range vectors {860 for j := 0; j < 6; j++ {861 var tr *Reader862 var s1, s2 string863864 switch j {865 case 0:866 tr = NewReader(&reader{strings.NewReader(v.input)})867 s1, s2 = "io.Reader", "auto"868 case 1:869 tr = NewReader(&reader{strings.NewReader(v.input)})870 s1, s2 = "io.Reader", "manual"871 case 2:872 tr = NewReader(&readSeeker{strings.NewReader(v.input)})873 s1, s2 = "io.ReadSeeker", "auto"874 case 3:875 tr = NewReader(&readSeeker{strings.NewReader(v.input)})876 s1, s2 = "io.ReadSeeker", "manual"877 case 4:878 tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})879 s1, s2 = "ReadBadSeeker", "auto"880 case 5:881 tr = NewReader(&readBadSeeker{strings.NewReader(v.input)})882 s1, s2 = "ReadBadSeeker", "manual"883 }884885 var cnt int886 var err error887 for {888 if _, err = tr.Next(); err != nil {889 break890 }891 cnt++892 if s2 == "manual" {893 if _, err = tr.writeTo(io.Discard); err != nil {894 break895 }896 }897 }898 if err != v.err {899 t.Errorf("test %d, NewReader(%s) with %s discard: got %v, want %v",900 i, s1, s2, err, v.err)901 }902 if cnt != v.cnt {903 t.Errorf("test %d, NewReader(%s) with %s discard: got %d headers, want %d headers",904 i, s1, s2, cnt, v.cnt)905 }906 }907 }908}909910// TestReadHeaderOnly tests that Reader does not attempt to read special911// header-only files.912func TestReadHeaderOnly(t *testing.T) {913 f, err := os.Open("testdata/hdr-only.tar")914 if err != nil {915 t.Fatalf("unexpected error: %v", err)916 }917 defer f.Close()918919 var hdrs []*Header920 tr := NewReader(f)921 for {922 hdr, err := tr.Next()923 if err == io.EOF {924 break925 }926 if err != nil {927 t.Errorf("Next(): got %v, want %v", err, nil)928 continue929 }930 hdrs = append(hdrs, hdr)931932 // If a special flag, we should read nothing.933 cnt, _ := io.ReadFull(tr, []byte{0})934 if cnt > 0 && hdr.Typeflag != TypeReg {935 t.Errorf("ReadFull(...): got %d bytes, want 0 bytes", cnt)936 }937 }938939 // File is crafted with 16 entries. The later 8 are identical to the first940 // 8 except that the size is set.941 if len(hdrs) != 16 {942 t.Fatalf("len(hdrs): got %d, want %d", len(hdrs), 16)943 }944 for i := 0; i < 8; i++ {945 hdr1, hdr2 := hdrs[i+0], hdrs[i+8]946 hdr1.Size, hdr2.Size = 0, 0947 if !reflect.DeepEqual(*hdr1, *hdr2) {948 t.Errorf("incorrect header:\ngot %+v\nwant %+v", *hdr1, *hdr2)949 }950 }951}952953func TestMergePAX(t *testing.T) {954 vectors := []struct {955 in map[string]string956 want *Header957 ok bool958 }{{959 in: map[string]string{960 "path": "a/b/c",961 "uid": "1000",962 "mtime": "1350244992.023960108",963 },964 want: &Header{965 Name: "a/b/c",966 Uid: 1000,967 ModTime: time.Unix(1350244992, 23960108),968 PAXRecords: map[string]string{969 "path": "a/b/c",970 "uid": "1000",971 "mtime": "1350244992.023960108",972 },973 },974 ok: true,975 }, {976 in: map[string]string{977 "gid": "gtgergergersagersgers",978 },979 ok: false,980 }, {981 in: map[string]string{982 "missing": "missing",983 "SCHILY.xattr.key": "value",984 },985 want: &Header{986 Xattrs: map[string]string{"key": "value"},987 PAXRecords: map[string]string{988 "missing": "missing",989 "SCHILY.xattr.key": "value",990 },991 },992 ok: true,993 }}994995 for i, v := range vectors {996 got := new(Header)997 err := mergePAX(got, v.in)998 if v.ok && !reflect.DeepEqual(*got, *v.want) {999 t.Errorf("test %d, mergePAX(...):\ngot %+v\nwant %+v", i, *got, *v.want)1000 }1001 if ok := err == nil; ok != v.ok {1002 t.Errorf("test %d, mergePAX(...): got %v, want %v", i, ok, v.ok)1003 }1004 }1005}10061007func TestParsePAX(t *testing.T) {1008 vectors := []struct {1009 in string1010 want map[string]string1011 ok bool1012 }{1013 {"", nil, true},1014 {"6 k=1\n", map[string]string{"k": "1"}, true},1015 {"10 a=name\n", map[string]string{"a": "name"}, true},1016 {"9 a=name\n", map[string]string{"a": "name"}, true},1017 {"30 mtime=1350244992.023960108\n", map[string]string{"mtime": "1350244992.023960108"}, true},1018 {"3 somelongkey=\n", nil, false},1019 {"50 tooshort=\n", nil, false},1020 {"13 key1=haha\n13 key2=nana\n13 key3=kaka\n",1021 map[string]string{"key1": "haha", "key2": "nana", "key3": "kaka"}, true},1022 {"13 key1=val1\n13 key2=val2\n8 key1=\n",1023 map[string]string{"key1": "", "key2": "val2"}, true},1024 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=2\n" +1025 "23 GNU.sparse.offset=1\n25 GNU.sparse.numbytes=2\n" +1026 "23 GNU.sparse.offset=3\n25 GNU.sparse.numbytes=4\n",1027 map[string]string{paxGNUSparseSize: "10", paxGNUSparseNumBlocks: "2", paxGNUSparseMap: "1,2,3,4"}, true},1028 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" +1029 "25 GNU.sparse.numbytes=2\n23 GNU.sparse.offset=1\n",1030 nil, false},1031 {"22 GNU.sparse.size=10\n26 GNU.sparse.numblocks=1\n" +1032 "25 GNU.sparse.offset=1,2\n25 GNU.sparse.numbytes=2\n",1033 nil, false},1034 }10351036 for i, v := range vectors {1037 r := strings.NewReader(v.in)1038 got, err := parsePAX(r)1039 if !maps.Equal(got, v.want) && !(len(got) == 0 && len(v.want) == 0) {1040 t.Errorf("test %d, parsePAX():\ngot %v\nwant %v", i, got, v.want)1041 }1042 if ok := err == nil; ok != v.ok {1043 t.Errorf("test %d, parsePAX(): got %v, want %v", i, ok, v.ok)1044 }1045 }1046}10471048func TestReadOldGNUSparseMap(t *testing.T) {1049 populateSparseMap := func(sa sparseArray, sps []string) []string {1050 for i := 0; len(sps) > 0 && i < sa.maxEntries(); i++ {1051 copy(sa.entry(i), sps[0])1052 sps = sps[1:]1053 }1054 if len(sps) > 0 {1055 copy(sa.isExtended(), "\x80")1056 }1057 return sps1058 }10591060 makeInput := func(format Format, size string, sps ...string) (out []byte) {1061 // Write the initial GNU header.1062 var blk block1063 gnu := blk.toGNU()1064 sparse := gnu.sparse()1065 copy(gnu.realSize(), size)1066 sps = populateSparseMap(sparse, sps)1067 if format != FormatUnknown {1068 blk.setFormat(format)1069 }1070 out = append(out, blk[:]...)10711072 // Write extended sparse blocks.1073 for len(sps) > 0 {1074 var blk block1075 sps = populateSparseMap(blk.toSparse(), sps)1076 out = append(out, blk[:]...)1077 }1078 return out1079 }10801081 makeSparseStrings := func(sp []sparseEntry) (out []string) {1082 var f formatter1083 for _, s := range sp {1084 var b [24]byte1085 f.formatNumeric(b[:12], s.Offset)1086 f.formatNumeric(b[12:], s.Length)1087 out = append(out, string(b[:]))1088 }1089 return out1090 }10911092 vectors := []struct {1093 input []byte1094 wantMap sparseDatas1095 wantSize int641096 wantErr error1097 }{{1098 input: makeInput(FormatUnknown, ""),1099 wantErr: ErrHeader,1100 }, {1101 input: makeInput(FormatGNU, "1234", "fewa"),1102 wantSize: 01234,1103 wantErr: ErrHeader,1104 }, {1105 input: makeInput(FormatGNU, "0031"),1106 wantSize: 031,1107 }, {1108 input: makeInput(FormatGNU, "80"),1109 wantErr: ErrHeader,1110 }, {1111 input: makeInput(FormatGNU, "1234",1112 makeSparseStrings(sparseDatas{{0, 0}, {1, 1}})...),1113 wantMap: sparseDatas{{0, 0}, {1, 1}},1114 wantSize: 01234,1115 }, {1116 input: makeInput(FormatGNU, "1234",1117 append(makeSparseStrings(sparseDatas{{0, 0}, {1, 1}}), []string{"", "blah"}...)...),1118 wantMap: sparseDatas{{0, 0}, {1, 1}},1119 wantSize: 01234,1120 }, {1121 input: makeInput(FormatGNU, "3333",1122 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}})...),1123 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}},1124 wantSize: 03333,1125 }, {1126 input: makeInput(FormatGNU, "",1127 append(append(1128 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}}),1129 []string{"", ""}...),1130 makeSparseStrings(sparseDatas{{4, 1}, {6, 1}})...)...),1131 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}},1132 }, {1133 input: makeInput(FormatGNU, "",1134 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:blockSize],1135 wantErr: io.ErrUnexpectedEOF,1136 }, {1137 input: makeInput(FormatGNU, "",1138 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...)[:3*blockSize/2],1139 wantErr: io.ErrUnexpectedEOF,1140 }, {1141 input: makeInput(FormatGNU, "",1142 makeSparseStrings(sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}})...),1143 wantMap: sparseDatas{{0, 1}, {2, 1}, {4, 1}, {6, 1}, {8, 1}, {10, 1}},1144 }, {1145 input: makeInput(FormatGNU, "",1146 makeSparseStrings(sparseDatas{{10 << 30, 512}, {20 << 30, 512}})...),1147 wantMap: sparseDatas{{10 << 30, 512}, {20 << 30, 512}},1148 }, {1149 input: makeInput(FormatGNU, "",1150 makeSparseStrings(func() sparseDatas {1151 var datas sparseDatas1152 // This is more than enough entries to exceed our limit.1153 for i := range int64(1 << 20) {1154 datas = append(datas, sparseEntry{i * 2, (i * 2) + 1})1155 }1156 return datas1157 }())...),1158 wantErr: errSparseTooLong,1159 }}11601161 for i, v := range vectors {1162 var blk block1163 var hdr Header1164 v.input = v.input[copy(blk[:], v.input):]1165 tr := Reader{r: bytes.NewReader(v.input)}1166 got, err := tr.readOldGNUSparseMap(&hdr, &blk)1167 if !slices.Equal(got, v.wantMap) {1168 t.Errorf("test %d, readOldGNUSparseMap(): got %v, want %v", i, got, v.wantMap)1169 }1170 if err != v.wantErr {1171 t.Errorf("test %d, readOldGNUSparseMap() = %v, want %v", i, err, v.wantErr)1172 }1173 if hdr.Size != v.wantSize {1174 t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize)1175 }1176 }1177}11781179func TestReadGNUSparsePAXHeaders(t *testing.T) {1180 padInput := func(s string) string {1181 return s + string(zeroBlock[:blockPadding(int64(len(s)))])1182 }11831184 vectors := []struct {1185 inputData string1186 inputHdrs map[string]string1187 wantMap sparseDatas1188 wantSize int641189 wantName string1190 wantErr error1191 }{{1192 inputHdrs: nil,1193 wantErr: nil,1194 }, {1195 inputHdrs: map[string]string{1196 paxGNUSparseNumBlocks: strconv.FormatInt(math.MaxInt64, 10),1197 paxGNUSparseMap: "0,1,2,3",1198 },1199 wantErr: ErrHeader,1200 }, {1201 inputHdrs: map[string]string{1202 paxGNUSparseNumBlocks: "4\x00",1203 paxGNUSparseMap: "0,1,2,3",1204 },1205 wantErr: ErrHeader,1206 }, {1207 inputHdrs: map[string]string{1208 paxGNUSparseNumBlocks: "4",1209 paxGNUSparseMap: "0,1,2,3",1210 },1211 wantErr: ErrHeader,1212 }, {1213 inputHdrs: map[string]string{1214 paxGNUSparseNumBlocks: "2",1215 paxGNUSparseMap: "0,1,2,3",1216 },1217 wantMap: sparseDatas{{0, 1}, {2, 3}},1218 }, {1219 inputHdrs: map[string]string{1220 paxGNUSparseNumBlocks: "2",1221 paxGNUSparseMap: "0, 1,2,3",1222 },1223 wantErr: ErrHeader,1224 }, {1225 inputHdrs: map[string]string{1226 paxGNUSparseNumBlocks: "2",1227 paxGNUSparseMap: "0,1,02,3",1228 paxGNUSparseRealSize: "4321",1229 },1230 wantMap: sparseDatas{{0, 1}, {2, 3}},1231 wantSize: 4321,1232 }, {1233 inputHdrs: map[string]string{1234 paxGNUSparseNumBlocks: "2",1235 paxGNUSparseMap: "0,one1,2,3",1236 },1237 wantErr: ErrHeader,1238 }, {1239 inputHdrs: map[string]string{1240 paxGNUSparseMajor: "0",1241 paxGNUSparseMinor: "0",1242 paxGNUSparseNumBlocks: "2",1243 paxGNUSparseMap: "0,1,2,3",1244 paxGNUSparseSize: "1234",1245 paxGNUSparseRealSize: "4321",1246 paxGNUSparseName: "realname",1247 },1248 wantMap: sparseDatas{{0, 1}, {2, 3}},1249 wantSize: 1234,1250 wantName: "realname",1251 }, {1252 inputHdrs: map[string]string{1253 paxGNUSparseMajor: "0",1254 paxGNUSparseMinor: "0",1255 paxGNUSparseNumBlocks: "1",1256 paxGNUSparseMap: "10737418240,512",1257 paxGNUSparseSize: "10737418240",1258 paxGNUSparseName: "realname",1259 },1260 wantMap: sparseDatas{{10737418240, 512}},1261 wantSize: 10737418240,1262 wantName: "realname",1263 }, {1264 inputHdrs: map[string]string{1265 paxGNUSparseMajor: "0",1266 paxGNUSparseMinor: "0",1267 paxGNUSparseNumBlocks: "0",1268 paxGNUSparseMap: "",1269 },1270 wantMap: sparseDatas{},1271 }, {1272 inputHdrs: map[string]string{1273 paxGNUSparseMajor: "0",1274 paxGNUSparseMinor: "1",1275 paxGNUSparseNumBlocks: "4",1276 paxGNUSparseMap: "0,5,10,5,20,5,30,5",1277 },1278 wantMap: sparseDatas{{0, 5}, {10, 5}, {20, 5}, {30, 5}},1279 }, {1280 inputHdrs: map[string]string{1281 paxGNUSparseMajor: "1",1282 paxGNUSparseMinor: "0",1283 paxGNUSparseNumBlocks: "4",1284 paxGNUSparseMap: "0,5,10,5,20,5,30,5",1285 },1286 wantErr: io.ErrUnexpectedEOF,1287 }, {1288 inputData: padInput("0\n"),1289 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1290 wantMap: sparseDatas{},1291 }, {1292 inputData: padInput("0\n")[:blockSize-1] + "#",1293 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1294 wantMap: sparseDatas{},1295 }, {1296 inputData: padInput("0"),1297 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1298 wantErr: io.ErrUnexpectedEOF,1299 }, {1300 inputData: padInput("ab\n"),1301 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1302 wantErr: ErrHeader,1303 }, {1304 inputData: padInput("1\n2\n3\n"),1305 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1306 wantMap: sparseDatas{{2, 3}},1307 }, {1308 inputData: padInput("1\n2\n"),1309 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1310 wantErr: io.ErrUnexpectedEOF,1311 }, {1312 inputData: padInput("1\n2\n\n"),1313 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1314 wantErr: ErrHeader,1315 }, {1316 inputData: string(zeroBlock[:]) + padInput("0\n"),1317 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1318 wantErr: ErrHeader,1319 }, {1320 inputData: strings.Repeat("0", blockSize) + padInput("1\n5\n1\n"),1321 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1322 wantMap: sparseDatas{{5, 1}},1323 }, {1324 inputData: padInput(fmt.Sprintf("%d\n", int64(math.MaxInt64))),1325 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1326 wantErr: ErrHeader,1327 }, {1328 inputData: padInput(strings.Repeat("0", 300) + "1\n" + strings.Repeat("0", 1000) + "5\n" + strings.Repeat("0", 800) + "2\n"),1329 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1330 wantMap: sparseDatas{{5, 2}},1331 }, {1332 inputData: padInput("2\n10737418240\n512\n21474836480\n512\n"),1333 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1334 wantMap: sparseDatas{{10737418240, 512}, {21474836480, 512}},1335 }, {1336 inputData: padInput("100\n" + func() string {1337 var ss []string1338 for i := 0; i < 100; i++ {1339 ss = append(ss, fmt.Sprintf("%d\n%d\n", int64(i)<<30, 512))1340 }1341 return strings.Join(ss, "")1342 }()),1343 inputHdrs: map[string]string{paxGNUSparseMajor: "1", paxGNUSparseMinor: "0"},1344 wantMap: func() (spd sparseDatas) {1345 for i := 0; i < 100; i++ {1346 spd = append(spd, sparseEntry{int64(i) << 30, 512})1347 }1348 return spd1349 }(),1350 }}13511352 for i, v := range vectors {1353 var hdr Header1354 hdr.PAXRecords = v.inputHdrs1355 r := strings.NewReader(v.inputData + "#") // Add canary byte1356 tr := Reader{curr: ®FileReader{r, int64(r.Len())}}1357 got, err := tr.readGNUSparsePAXHeaders(&hdr)1358 if !slices.Equal(got, v.wantMap) {1359 t.Errorf("test %d, readGNUSparsePAXHeaders(): got %v, want %v", i, got, v.wantMap)1360 }1361 if err != v.wantErr {1362 t.Errorf("test %d, readGNUSparsePAXHeaders() = %v, want %v", i, err, v.wantErr)1363 }1364 if hdr.Size != v.wantSize {1365 t.Errorf("test %d, Header.Size = %d, want %d", i, hdr.Size, v.wantSize)1366 }1367 if hdr.Name != v.wantName {1368 t.Errorf("test %d, Header.Name = %s, want %s", i, hdr.Name, v.wantName)1369 }1370 if v.wantErr == nil && r.Len() == 0 {1371 t.Errorf("test %d, canary byte unexpectedly consumed", i)1372 }1373 }1374}13751376// testNonEmptyReader wraps an io.Reader and ensures that1377// Read is never called with an empty buffer.1378type testNonEmptyReader struct{ io.Reader }13791380func (r testNonEmptyReader) Read(b []byte) (int, error) {1381 if len(b) == 0 {1382 return 0, errors.New("unexpected empty Read call")1383 }1384 return r.Reader.Read(b)1385}13861387func TestFileReader(t *testing.T) {1388 type (1389 testRead struct { // Read(cnt) == (wantStr, wantErr)1390 cnt int1391 wantStr string1392 wantErr error1393 }1394 testWriteTo struct { // WriteTo(testFile{ops}) == (wantCnt, wantErr)1395 ops fileOps1396 wantCnt int641397 wantErr error1398 }1399 testRemaining struct { // logicalRemaining() == wantLCnt, physicalRemaining() == wantPCnt1400 wantLCnt int641401 wantPCnt int641402 }1403 testFnc any // testRead | testWriteTo | testRemaining1404 )14051406 type (1407 makeReg struct {1408 str string1409 size int641410 }1411 makeSparse struct {1412 makeReg makeReg1413 spd sparseDatas1414 size int641415 }1416 fileMaker any // makeReg | makeSparse1417 )14181419 vectors := []struct {1420 maker fileMaker1421 tests []testFnc1422 }{{1423 maker: makeReg{"", 0},1424 tests: []testFnc{1425 testRemaining{0, 0},1426 testRead{0, "", io.EOF},1427 testRead{1, "", io.EOF},1428 testWriteTo{nil, 0, nil},1429 testRemaining{0, 0},1430 },1431 }, {1432 maker: makeReg{"", 1},1433 tests: []testFnc{1434 testRemaining{1, 1},1435 testRead{5, "", io.ErrUnexpectedEOF},1436 testWriteTo{nil, 0, io.ErrUnexpectedEOF},1437 testRemaining{1, 1},1438 },1439 }, {1440 maker: makeReg{"hello", 5},1441 tests: []testFnc{1442 testRemaining{5, 5},1443 testRead{5, "hello", io.EOF},1444 testRemaining{0, 0},1445 },1446 }, {1447 maker: makeReg{"hello, world", 50},1448 tests: []testFnc{1449 testRemaining{50, 50},1450 testRead{7, "hello, ", nil},1451 testRemaining{43, 43},1452 testRead{5, "world", nil},1453 testRemaining{38, 38},1454 testWriteTo{nil, 0, io.ErrUnexpectedEOF},1455 testRead{1, "", io.ErrUnexpectedEOF},1456 testRemaining{38, 38},1457 },1458 }, {1459 maker: makeReg{"hello, world", 5},1460 tests: []testFnc{1461 testRemaining{5, 5},1462 testRead{0, "", nil},1463 testRead{4, "hell", nil},1464 testRemaining{1, 1},1465 testWriteTo{fileOps{"o"}, 1, nil},1466 testRemaining{0, 0},1467 testWriteTo{nil, 0, nil},1468 testRead{0, "", io.EOF},1469 },1470 }, {1471 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8},1472 tests: []testFnc{1473 testRemaining{8, 5},1474 testRead{3, "ab\x00", nil},1475 testRead{10, "\x00\x00cde", io.EOF},1476 testRemaining{0, 0},1477 },1478 }, {1479 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 8},1480 tests: []testFnc{1481 testRemaining{8, 5},1482 testWriteTo{fileOps{"ab", int64(3), "cde"}, 8, nil},1483 testRemaining{0, 0},1484 },1485 }, {1486 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{0, 2}, {5, 3}}, 10},1487 tests: []testFnc{1488 testRemaining{10, 5},1489 testRead{100, "ab\x00\x00\x00cde\x00\x00", io.EOF},1490 testRemaining{0, 0},1491 },1492 }, {1493 maker: makeSparse{makeReg{"abc", 5}, sparseDatas{{0, 2}, {5, 3}}, 10},1494 tests: []testFnc{1495 testRemaining{10, 5},1496 testRead{100, "ab\x00\x00\x00c", io.ErrUnexpectedEOF},1497 testRemaining{4, 2},1498 },1499 }, {1500 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 8},1501 tests: []testFnc{1502 testRemaining{8, 5},1503 testRead{8, "\x00abc\x00\x00de", io.EOF},1504 testRemaining{0, 0},1505 },1506 }, {1507 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8},1508 tests: []testFnc{1509 testRemaining{8, 5},1510 testRead{8, "\x00abc\x00\x00de", io.EOF},1511 testRemaining{0, 0},1512 },1513 }, {1514 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 0}, {6, 0}, {6, 2}}, 8},1515 tests: []testFnc{1516 testRemaining{8, 5},1517 testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, nil},1518 testRemaining{0, 0},1519 },1520 }, {1521 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10},1522 tests: []testFnc{1523 testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF},1524 },1525 }, {1526 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}}, 10},1527 tests: []testFnc{1528 testWriteTo{fileOps{int64(1), "abc", int64(2), "de", int64(1), "\x00"}, 10, nil},1529 },1530 }, {1531 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 2}, {8, 0}, {8, 0}, {8, 0}, {8, 0}}, 10},1532 tests: []testFnc{1533 testRead{100, "\x00abc\x00\x00de\x00\x00", io.EOF},1534 },1535 }, {1536 maker: makeSparse{makeReg{"", 0}, sparseDatas{}, 2},1537 tests: []testFnc{1538 testRead{100, "\x00\x00", io.EOF},1539 },1540 }, {1541 maker: makeSparse{makeReg{"", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},1542 tests: []testFnc{1543 testRead{100, "\x00", io.ErrUnexpectedEOF},1544 },1545 }, {1546 maker: makeSparse{makeReg{"ab", 2}, sparseDatas{{1, 3}, {6, 5}}, 15},1547 tests: []testFnc{1548 testRead{100, "\x00ab", errMissData},1549 },1550 }, {1551 maker: makeSparse{makeReg{"ab", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},1552 tests: []testFnc{1553 testRead{100, "\x00ab", io.ErrUnexpectedEOF},1554 },1555 }, {1556 maker: makeSparse{makeReg{"abc", 3}, sparseDatas{{1, 3}, {6, 5}}, 15},1557 tests: []testFnc{1558 testRead{100, "\x00abc\x00\x00", errMissData},1559 },1560 }, {1561 maker: makeSparse{makeReg{"abc", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},1562 tests: []testFnc{1563 testRead{100, "\x00abc\x00\x00", io.ErrUnexpectedEOF},1564 },1565 }, {1566 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15},1567 tests: []testFnc{1568 testRead{100, "\x00abc\x00\x00de", errMissData},1569 },1570 }, {1571 maker: makeSparse{makeReg{"abcde", 5}, sparseDatas{{1, 3}, {6, 5}}, 15},1572 tests: []testFnc{1573 testWriteTo{fileOps{int64(1), "abc", int64(2), "de"}, 8, errMissData},1574 },1575 }, {1576 maker: makeSparse{makeReg{"abcde", 8}, sparseDatas{{1, 3}, {6, 5}}, 15},1577 tests: []testFnc{1578 testRead{100, "\x00abc\x00\x00de", io.ErrUnexpectedEOF},1579 },1580 }, {1581 maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15},1582 tests: []testFnc{1583 testRemaining{15, 13},1584 testRead{100, "\x00abc\x00\x00defgh\x00\x00\x00\x00", errUnrefData},1585 testWriteTo{nil, 0, errUnrefData},1586 testRemaining{0, 5},1587 },1588 }, {1589 maker: makeSparse{makeReg{"abcdefghEXTRA", 13}, sparseDatas{{1, 3}, {6, 5}}, 15},1590 tests: []testFnc{1591 testRemaining{15, 13},1592 testWriteTo{fileOps{int64(1), "abc", int64(2), "defgh", int64(4)}, 15, errUnrefData},1593 testRead{100, "", errUnrefData},1594 testRemaining{0, 5},1595 },1596 }}15971598 for i, v := range vectors {1599 var fr fileReader1600 switch maker := v.maker.(type) {1601 case makeReg:1602 r := testNonEmptyReader{strings.NewReader(maker.str)}1603 fr = ®FileReader{r, maker.size}1604 case makeSparse:1605 if !validateSparseEntries(maker.spd, maker.size) {1606 t.Fatalf("invalid sparse map: %v", maker.spd)1607 }1608 sph := invertSparseEntries(maker.spd, maker.size)1609 r := testNonEmptyReader{strings.NewReader(maker.makeReg.str)}1610 fr = ®FileReader{r, maker.makeReg.size}1611 fr = &sparseFileReader{fr, sph, 0}1612 default:1613 t.Fatalf("test %d, unknown make operation: %T", i, maker)1614 }16151616 for j, tf := range v.tests {1617 switch tf := tf.(type) {1618 case testRead:1619 b := make([]byte, tf.cnt)1620 n, err := fr.Read(b)1621 if got := string(b[:n]); got != tf.wantStr || err != tf.wantErr {1622 t.Errorf("test %d.%d, Read(%d):\ngot (%q, %v)\nwant (%q, %v)", i, j, tf.cnt, got, err, tf.wantStr, tf.wantErr)1623 }1624 case testWriteTo:1625 f := &testFile{ops: tf.ops}1626 got, err := fr.WriteTo(f)1627 if _, ok := err.(testError); ok {1628 t.Errorf("test %d.%d, WriteTo(): %v", i, j, err)1629 } else if got != tf.wantCnt || err != tf.wantErr {1630 t.Errorf("test %d.%d, WriteTo() = (%d, %v), want (%d, %v)", i, j, got, err, tf.wantCnt, tf.wantErr)1631 }1632 if len(f.ops) > 0 {1633 t.Errorf("test %d.%d, expected %d more operations", i, j, len(f.ops))1634 }1635 case testRemaining:1636 if got := fr.logicalRemaining(); got != tf.wantLCnt {1637 t.Errorf("test %d.%d, logicalRemaining() = %d, want %d", i, j, got, tf.wantLCnt)1638 }1639 if got := fr.physicalRemaining(); got != tf.wantPCnt {1640 t.Errorf("test %d.%d, physicalRemaining() = %d, want %d", i, j, got, tf.wantPCnt)1641 }1642 default:1643 t.Fatalf("test %d.%d, unknown test operation: %T", i, j, tf)1644 }1645 }1646 }1647}16481649func TestInsecurePaths(t *testing.T) {1650 t.Setenv("GODEBUG", "tarinsecurepath=0")1651 for _, path := range []string{1652 "../foo",1653 "/foo",1654 "a/b/../../../c",1655 } {1656 var buf bytes.Buffer1657 tw := NewWriter(&buf)1658 tw.WriteHeader(&Header{1659 Name: path,1660 })1661 const securePath = "secure"1662 tw.WriteHeader(&Header{1663 Name: securePath,1664 })1665 tw.Close()16661667 tr := NewReader(&buf)1668 h, err := tr.Next()1669 if err != ErrInsecurePath {1670 t.Errorf("tr.Next for file %q: got err %v, want ErrInsecurePath", path, err)1671 continue1672 }1673 if h.Name != path {1674 t.Errorf("tr.Next for file %q: got name %q, want %q", path, h.Name, path)1675 }1676 // Error should not be sticky.1677 h, err = tr.Next()1678 if err != nil {1679 t.Errorf("tr.Next for file %q: got err %v, want nil", securePath, err)1680 }1681 if h.Name != securePath {1682 t.Errorf("tr.Next for file %q: got name %q, want %q", securePath, h.Name, securePath)1683 }1684 }1685}16861687func TestDisableInsecurePathCheck(t *testing.T) {1688 t.Setenv("GODEBUG", "tarinsecurepath=1")1689 var buf bytes.Buffer1690 tw := NewWriter(&buf)1691 const name = "/foo"1692 tw.WriteHeader(&Header{1693 Name: name,1694 })1695 tw.Close()1696 tr := NewReader(&buf)1697 h, err := tr.Next()1698 if err != nil {1699 t.Fatalf("tr.Next with tarinsecurepath=1: got err %v, want nil", err)1700 }1701 if h.Name != name {1702 t.Fatalf("tr.Next with tarinsecurepath=1: got name %q, want %q", h.Name, name)1703 }1704}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.