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