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