github.com/eun/go@v0.0.0-20170811110501-92cfd07a6cfd/src/archive/tar/writer_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 "encoding/hex" 10 "io" 11 "io/ioutil" 12 "os" 13 "path" 14 "reflect" 15 "sort" 16 "strings" 17 "testing" 18 "testing/iotest" 19 "time" 20 ) 21 22 // Render a pseudo-diff between two blocks of bytes. 23 func bytediff(a []byte, b []byte) (s string) { 24 var ax = strings.Split(hex.Dump(a), "\n") 25 var bx = strings.Split(hex.Dump(b), "\n") 26 for i := 0; i < len(ax) || i < len(bx); i++ { 27 var sa, sb = "", "" 28 if i < len(ax) { 29 sa = ax[i] 30 } 31 if i < len(bx) { 32 sb = bx[i] 33 } 34 if sa != sb { 35 if len(sa) > 0 { 36 s += "+" + sa + "\n" 37 } 38 if len(sb) > 0 { 39 s += "-" + sb + "\n" 40 } 41 } 42 } 43 return s 44 } 45 46 func TestWriter(t *testing.T) { 47 type entry struct { 48 header *Header 49 contents string 50 } 51 52 vectors := []struct { 53 file string // filename of expected output 54 entries []*entry 55 err error // expected error on WriteHeader 56 }{{ 57 // The writer test file was produced with this command: 58 // tar (GNU tar) 1.26 59 // ln -s small.txt link.txt 60 // tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt 61 file: "testdata/writer.tar", 62 entries: []*entry{{ 63 header: &Header{ 64 Name: "small.txt", 65 Mode: 0640, 66 Uid: 73025, 67 Gid: 5000, 68 Size: 5, 69 ModTime: time.Unix(1246508266, 0), 70 Typeflag: '0', 71 Uname: "dsymonds", 72 Gname: "eng", 73 }, 74 contents: "Kilts", 75 }, { 76 header: &Header{ 77 Name: "small2.txt", 78 Mode: 0640, 79 Uid: 73025, 80 Gid: 5000, 81 Size: 11, 82 ModTime: time.Unix(1245217492, 0), 83 Typeflag: '0', 84 Uname: "dsymonds", 85 Gname: "eng", 86 }, 87 contents: "Google.com\n", 88 }, { 89 header: &Header{ 90 Name: "link.txt", 91 Mode: 0777, 92 Uid: 1000, 93 Gid: 1000, 94 Size: 0, 95 ModTime: time.Unix(1314603082, 0), 96 Typeflag: '2', 97 Linkname: "small.txt", 98 Uname: "strings", 99 Gname: "strings", 100 }, 101 // no contents 102 }}, 103 }, { 104 // The truncated test file was produced using these commands: 105 // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt 106 // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar 107 file: "testdata/writer-big.tar", 108 entries: []*entry{{ 109 header: &Header{ 110 Name: "tmp/16gig.txt", 111 Mode: 0640, 112 Uid: 73025, 113 Gid: 5000, 114 Size: 16 << 30, 115 ModTime: time.Unix(1254699560, 0), 116 Typeflag: '0', 117 Uname: "dsymonds", 118 Gname: "eng", 119 }, 120 // fake contents 121 contents: strings.Repeat("\x00", 4<<10), 122 }}, 123 }, { 124 // This truncated file was produced using this library. 125 // It was verified to work with GNU tar 1.27.1 and BSD tar 3.1.2. 126 // dd if=/dev/zero bs=1G count=16 >> writer-big-long.tar 127 // gnutar -xvf writer-big-long.tar 128 // bsdtar -xvf writer-big-long.tar 129 // 130 // This file is in PAX format. 131 file: "testdata/writer-big-long.tar", 132 entries: []*entry{{ 133 header: &Header{ 134 Name: strings.Repeat("longname/", 15) + "16gig.txt", 135 Mode: 0644, 136 Uid: 1000, 137 Gid: 1000, 138 Size: 16 << 30, 139 ModTime: time.Unix(1399583047, 0), 140 Typeflag: '0', 141 Uname: "guillaume", 142 Gname: "guillaume", 143 }, 144 // fake contents 145 contents: strings.Repeat("\x00", 4<<10), 146 }}, 147 }, { 148 // TODO(dsnet): The Writer output should match the following file. 149 // To fix an issue (see https://golang.org/issue/12594), we disabled 150 // prefix support, which alters the generated output. 151 /* 152 // This file was produced using gnu tar 1.17 153 // gnutar -b 4 --format=ustar (longname/)*15 + file.txt 154 file: "testdata/ustar.tar" 155 */ 156 file: "testdata/ustar.issue12594.tar", // This is a valid tar file, but not expected 157 entries: []*entry{{ 158 header: &Header{ 159 Name: strings.Repeat("longname/", 15) + "file.txt", 160 Mode: 0644, 161 Uid: 0765, 162 Gid: 024, 163 Size: 06, 164 ModTime: time.Unix(1360135598, 0), 165 Typeflag: '0', 166 Uname: "shane", 167 Gname: "staff", 168 }, 169 contents: "hello\n", 170 }}, 171 }, { 172 // This file was produced using gnu tar 1.26 173 // echo "Slartibartfast" > file.txt 174 // ln file.txt hard.txt 175 // tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt 176 file: "testdata/hardlink.tar", 177 entries: []*entry{{ 178 header: &Header{ 179 Name: "file.txt", 180 Mode: 0644, 181 Uid: 1000, 182 Gid: 100, 183 Size: 15, 184 ModTime: time.Unix(1425484303, 0), 185 Typeflag: '0', 186 Uname: "vbatts", 187 Gname: "users", 188 }, 189 contents: "Slartibartfast\n", 190 }, { 191 header: &Header{ 192 Name: "hard.txt", 193 Mode: 0644, 194 Uid: 1000, 195 Gid: 100, 196 Size: 0, 197 ModTime: time.Unix(1425484303, 0), 198 Typeflag: '1', 199 Linkname: "file.txt", 200 Uname: "vbatts", 201 Gname: "users", 202 }, 203 // no contents 204 }}, 205 }, { 206 entries: []*entry{{ 207 header: &Header{ 208 Name: "bad-null.txt", 209 Typeflag: '0', 210 Xattrs: map[string]string{"null\x00null\x00": "fizzbuzz"}, 211 }, 212 }}, 213 err: ErrHeader, 214 }, { 215 entries: []*entry{{ 216 header: &Header{ 217 Name: "null\x00.txt", 218 Typeflag: '0', 219 }, 220 }}, 221 err: ErrHeader, 222 }} 223 224 for _, v := range vectors { 225 t.Run(path.Base(v.file), func(t *testing.T) { 226 buf := new(bytes.Buffer) 227 tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB 228 canFail := false 229 for i, entry := range v.entries { 230 canFail = canFail || entry.header.Size > 1<<10 || v.err != nil 231 232 err := tw.WriteHeader(entry.header) 233 if err != v.err { 234 t.Fatalf("entry %d: WriteHeader() = %v, want %v", i, err, v.err) 235 } 236 if _, err := io.WriteString(tw, entry.contents); err != nil { 237 t.Fatalf("entry %d: WriteString() = %v, want nil", i, err) 238 } 239 } 240 // Only interested in Close failures for the small tests. 241 if err := tw.Close(); err != nil && !canFail { 242 t.Fatalf("Close() = %v, want nil", err) 243 } 244 245 if v.file != "" { 246 want, err := ioutil.ReadFile(v.file) 247 if err != nil { 248 t.Fatalf("ReadFile() = %v, want nil", err) 249 } 250 got := buf.Bytes() 251 if !bytes.Equal(want, got) { 252 t.Fatalf("incorrect result: (-=want, +=got)\n%v", bytediff(want, got)) 253 } 254 } 255 }) 256 } 257 } 258 259 func TestPax(t *testing.T) { 260 // Create an archive with a large name 261 fileinfo, err := os.Stat("testdata/small.txt") 262 if err != nil { 263 t.Fatal(err) 264 } 265 hdr, err := FileInfoHeader(fileinfo, "") 266 if err != nil { 267 t.Fatalf("os.Stat: %v", err) 268 } 269 // Force a PAX long name to be written 270 longName := strings.Repeat("ab", 100) 271 contents := strings.Repeat(" ", int(hdr.Size)) 272 hdr.Name = longName 273 var buf bytes.Buffer 274 writer := NewWriter(&buf) 275 if err := writer.WriteHeader(hdr); err != nil { 276 t.Fatal(err) 277 } 278 if _, err = writer.Write([]byte(contents)); err != nil { 279 t.Fatal(err) 280 } 281 if err := writer.Close(); err != nil { 282 t.Fatal(err) 283 } 284 // Simple test to make sure PAX extensions are in effect 285 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 286 t.Fatal("Expected at least one PAX header to be written.") 287 } 288 // Test that we can get a long name back out of the archive. 289 reader := NewReader(&buf) 290 hdr, err = reader.Next() 291 if err != nil { 292 t.Fatal(err) 293 } 294 if hdr.Name != longName { 295 t.Fatal("Couldn't recover long file name") 296 } 297 } 298 299 func TestPaxSymlink(t *testing.T) { 300 // Create an archive with a large linkname 301 fileinfo, err := os.Stat("testdata/small.txt") 302 if err != nil { 303 t.Fatal(err) 304 } 305 hdr, err := FileInfoHeader(fileinfo, "") 306 hdr.Typeflag = TypeSymlink 307 if err != nil { 308 t.Fatalf("os.Stat:1 %v", err) 309 } 310 // Force a PAX long linkname to be written 311 longLinkname := strings.Repeat("1234567890/1234567890", 10) 312 hdr.Linkname = longLinkname 313 314 hdr.Size = 0 315 var buf bytes.Buffer 316 writer := NewWriter(&buf) 317 if err := writer.WriteHeader(hdr); err != nil { 318 t.Fatal(err) 319 } 320 if err := writer.Close(); err != nil { 321 t.Fatal(err) 322 } 323 // Simple test to make sure PAX extensions are in effect 324 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 325 t.Fatal("Expected at least one PAX header to be written.") 326 } 327 // Test that we can get a long name back out of the archive. 328 reader := NewReader(&buf) 329 hdr, err = reader.Next() 330 if err != nil { 331 t.Fatal(err) 332 } 333 if hdr.Linkname != longLinkname { 334 t.Fatal("Couldn't recover long link name") 335 } 336 } 337 338 func TestPaxNonAscii(t *testing.T) { 339 // Create an archive with non ascii. These should trigger a pax header 340 // because pax headers have a defined utf-8 encoding. 341 fileinfo, err := os.Stat("testdata/small.txt") 342 if err != nil { 343 t.Fatal(err) 344 } 345 346 hdr, err := FileInfoHeader(fileinfo, "") 347 if err != nil { 348 t.Fatalf("os.Stat:1 %v", err) 349 } 350 351 // some sample data 352 chineseFilename := "文件名" 353 chineseGroupname := "組" 354 chineseUsername := "用戶名" 355 356 hdr.Name = chineseFilename 357 hdr.Gname = chineseGroupname 358 hdr.Uname = chineseUsername 359 360 contents := strings.Repeat(" ", int(hdr.Size)) 361 362 var buf bytes.Buffer 363 writer := NewWriter(&buf) 364 if err := writer.WriteHeader(hdr); err != nil { 365 t.Fatal(err) 366 } 367 if _, err = writer.Write([]byte(contents)); err != nil { 368 t.Fatal(err) 369 } 370 if err := writer.Close(); err != nil { 371 t.Fatal(err) 372 } 373 // Simple test to make sure PAX extensions are in effect 374 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 375 t.Fatal("Expected at least one PAX header to be written.") 376 } 377 // Test that we can get a long name back out of the archive. 378 reader := NewReader(&buf) 379 hdr, err = reader.Next() 380 if err != nil { 381 t.Fatal(err) 382 } 383 if hdr.Name != chineseFilename { 384 t.Fatal("Couldn't recover unicode name") 385 } 386 if hdr.Gname != chineseGroupname { 387 t.Fatal("Couldn't recover unicode group") 388 } 389 if hdr.Uname != chineseUsername { 390 t.Fatal("Couldn't recover unicode user") 391 } 392 } 393 394 func TestPaxXattrs(t *testing.T) { 395 xattrs := map[string]string{ 396 "user.key": "value", 397 } 398 399 // Create an archive with an xattr 400 fileinfo, err := os.Stat("testdata/small.txt") 401 if err != nil { 402 t.Fatal(err) 403 } 404 hdr, err := FileInfoHeader(fileinfo, "") 405 if err != nil { 406 t.Fatalf("os.Stat: %v", err) 407 } 408 contents := "Kilts" 409 hdr.Xattrs = xattrs 410 var buf bytes.Buffer 411 writer := NewWriter(&buf) 412 if err := writer.WriteHeader(hdr); err != nil { 413 t.Fatal(err) 414 } 415 if _, err = writer.Write([]byte(contents)); err != nil { 416 t.Fatal(err) 417 } 418 if err := writer.Close(); err != nil { 419 t.Fatal(err) 420 } 421 // Test that we can get the xattrs back out of the archive. 422 reader := NewReader(&buf) 423 hdr, err = reader.Next() 424 if err != nil { 425 t.Fatal(err) 426 } 427 if !reflect.DeepEqual(hdr.Xattrs, xattrs) { 428 t.Fatalf("xattrs did not survive round trip: got %+v, want %+v", 429 hdr.Xattrs, xattrs) 430 } 431 } 432 433 func TestPaxHeadersSorted(t *testing.T) { 434 fileinfo, err := os.Stat("testdata/small.txt") 435 if err != nil { 436 t.Fatal(err) 437 } 438 hdr, err := FileInfoHeader(fileinfo, "") 439 if err != nil { 440 t.Fatalf("os.Stat: %v", err) 441 } 442 contents := strings.Repeat(" ", int(hdr.Size)) 443 444 hdr.Xattrs = map[string]string{ 445 "foo": "foo", 446 "bar": "bar", 447 "baz": "baz", 448 "qux": "qux", 449 } 450 451 var buf bytes.Buffer 452 writer := NewWriter(&buf) 453 if err := writer.WriteHeader(hdr); err != nil { 454 t.Fatal(err) 455 } 456 if _, err = writer.Write([]byte(contents)); err != nil { 457 t.Fatal(err) 458 } 459 if err := writer.Close(); err != nil { 460 t.Fatal(err) 461 } 462 // Simple test to make sure PAX extensions are in effect 463 if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) { 464 t.Fatal("Expected at least one PAX header to be written.") 465 } 466 467 // xattr bar should always appear before others 468 indices := []int{ 469 bytes.Index(buf.Bytes(), []byte("bar=bar")), 470 bytes.Index(buf.Bytes(), []byte("baz=baz")), 471 bytes.Index(buf.Bytes(), []byte("foo=foo")), 472 bytes.Index(buf.Bytes(), []byte("qux=qux")), 473 } 474 if !sort.IntsAreSorted(indices) { 475 t.Fatal("PAX headers are not sorted") 476 } 477 } 478 479 func TestUSTARLongName(t *testing.T) { 480 // Create an archive with a path that failed to split with USTAR extension in previous versions. 481 fileinfo, err := os.Stat("testdata/small.txt") 482 if err != nil { 483 t.Fatal(err) 484 } 485 hdr, err := FileInfoHeader(fileinfo, "") 486 hdr.Typeflag = TypeDir 487 if err != nil { 488 t.Fatalf("os.Stat:1 %v", err) 489 } 490 // Force a PAX long name to be written. The name was taken from a practical example 491 // that fails and replaced ever char through numbers to anonymize the sample. 492 longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/" 493 hdr.Name = longName 494 495 hdr.Size = 0 496 var buf bytes.Buffer 497 writer := NewWriter(&buf) 498 if err := writer.WriteHeader(hdr); err != nil { 499 t.Fatal(err) 500 } 501 if err := writer.Close(); err != nil { 502 t.Fatal(err) 503 } 504 // Test that we can get a long name back out of the archive. 505 reader := NewReader(&buf) 506 hdr, err = reader.Next() 507 if err != nil { 508 t.Fatal(err) 509 } 510 if hdr.Name != longName { 511 t.Fatal("Couldn't recover long name") 512 } 513 } 514 515 func TestValidTypeflagWithPAXHeader(t *testing.T) { 516 var buffer bytes.Buffer 517 tw := NewWriter(&buffer) 518 519 fileName := strings.Repeat("ab", 100) 520 521 hdr := &Header{ 522 Name: fileName, 523 Size: 4, 524 Typeflag: 0, 525 } 526 if err := tw.WriteHeader(hdr); err != nil { 527 t.Fatalf("Failed to write header: %s", err) 528 } 529 if _, err := tw.Write([]byte("fooo")); err != nil { 530 t.Fatalf("Failed to write the file's data: %s", err) 531 } 532 tw.Close() 533 534 tr := NewReader(&buffer) 535 536 for { 537 header, err := tr.Next() 538 if err == io.EOF { 539 break 540 } 541 if err != nil { 542 t.Fatalf("Failed to read header: %s", err) 543 } 544 if header.Typeflag != 0 { 545 t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag) 546 } 547 } 548 } 549 550 func TestWriteAfterClose(t *testing.T) { 551 var buffer bytes.Buffer 552 tw := NewWriter(&buffer) 553 554 hdr := &Header{ 555 Name: "small.txt", 556 Size: 5, 557 } 558 if err := tw.WriteHeader(hdr); err != nil { 559 t.Fatalf("Failed to write header: %s", err) 560 } 561 tw.Close() 562 if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose { 563 t.Fatalf("Write: got %v; want ErrWriteAfterClose", err) 564 } 565 } 566 567 func TestSplitUSTARPath(t *testing.T) { 568 sr := strings.Repeat 569 570 vectors := []struct { 571 input string // Input path 572 prefix string // Expected output prefix 573 suffix string // Expected output suffix 574 ok bool // Split success? 575 }{ 576 {"", "", "", false}, 577 {"abc", "", "", false}, 578 {"用戶名", "", "", false}, 579 {sr("a", nameSize), "", "", false}, 580 {sr("a", nameSize) + "/", "", "", false}, 581 {sr("a", nameSize) + "/a", sr("a", nameSize), "a", true}, 582 {sr("a", prefixSize) + "/", "", "", false}, 583 {sr("a", prefixSize) + "/a", sr("a", prefixSize), "a", true}, 584 {sr("a", nameSize+1), "", "", false}, 585 {sr("/", nameSize+1), sr("/", nameSize-1), "/", true}, 586 {sr("a", prefixSize) + "/" + sr("b", nameSize), 587 sr("a", prefixSize), sr("b", nameSize), true}, 588 {sr("a", prefixSize) + "//" + sr("b", nameSize), "", "", false}, 589 {sr("a/", nameSize), sr("a/", 77) + "a", sr("a/", 22), true}, 590 } 591 592 for _, v := range vectors { 593 prefix, suffix, ok := splitUSTARPath(v.input) 594 if prefix != v.prefix || suffix != v.suffix || ok != v.ok { 595 t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)", 596 v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok) 597 } 598 } 599 } 600 601 // TestIssue12594 tests that the Writer does not attempt to populate the prefix 602 // field when encoding a header in the GNU format. The prefix field is valid 603 // in USTAR and PAX, but not GNU. 604 func TestIssue12594(t *testing.T) { 605 names := []string{ 606 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/file.txt", 607 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/file.txt", 608 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/333/file.txt", 609 "0/1/2/3/4/5/6/7/8/9/10/11/12/13/14/15/16/17/18/19/20/21/22/23/24/25/26/27/28/29/30/31/32/33/34/35/36/37/38/39/40/file.txt", 610 "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/file.txt", 611 "/home/support/.openoffice.org/3/user/uno_packages/cache/registry/com.sun.star.comp.deployment.executable.PackageRegistryBackend", 612 } 613 614 for i, name := range names { 615 var b bytes.Buffer 616 617 tw := NewWriter(&b) 618 if err := tw.WriteHeader(&Header{ 619 Name: name, 620 Uid: 1 << 25, // Prevent USTAR format 621 }); err != nil { 622 t.Errorf("test %d, unexpected WriteHeader error: %v", i, err) 623 } 624 if err := tw.Close(); err != nil { 625 t.Errorf("test %d, unexpected Close error: %v", i, err) 626 } 627 628 // The prefix field should never appear in the GNU format. 629 var blk block 630 copy(blk[:], b.Bytes()) 631 prefix := string(blk.USTAR().Prefix()) 632 if i := strings.IndexByte(prefix, 0); i >= 0 { 633 prefix = prefix[:i] // Truncate at the NUL terminator 634 } 635 if blk.GetFormat() == formatGNU && len(prefix) > 0 && strings.HasPrefix(name, prefix) { 636 t.Errorf("test %d, found prefix in GNU format: %s", i, prefix) 637 } 638 639 tr := NewReader(&b) 640 hdr, err := tr.Next() 641 if err != nil { 642 t.Errorf("test %d, unexpected Next error: %v", i, err) 643 } 644 if hdr.Name != name { 645 t.Errorf("test %d, hdr.Name = %s, want %s", i, hdr.Name, name) 646 } 647 } 648 }