1// Copyright 2018 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package zip 16 17import ( 18 "bytes" 19 "hash/crc32" 20 "io" 21 "os" 22 "reflect" 23 "syscall" 24 "testing" 25 26 "android/soong/third_party/zip" 27 28 "github.com/google/blueprint/pathtools" 29) 30 31var ( 32 fileA = []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") 33 fileB = []byte("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB") 34 fileC = []byte("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC") 35 fileEmpty = []byte("") 36 fileManifest = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\n\n") 37 38 fileCustomManifest = []byte("Custom manifest: true\n") 39 customManifestAfter = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\nCustom manifest: true\n\n") 40) 41 42var mockFs = pathtools.MockFs(map[string][]byte{ 43 "a/a/a": fileA, 44 "a/a/b": fileB, 45 "a/a/c -> ../../c": nil, 46 "dangling -> missing": nil, 47 "a/a/d -> b": nil, 48 "c": fileC, 49 "l_nl": []byte("a/a/a\na/a/b\nc\n"), 50 "l_sp": []byte("a/a/a a/a/b c"), 51 "l2": []byte("missing\n"), 52 "manifest.txt": fileCustomManifest, 53}) 54 55func fh(name string, contents []byte, method uint16) zip.FileHeader { 56 return zip.FileHeader{ 57 Name: name, 58 Method: method, 59 CRC32: crc32.ChecksumIEEE(contents), 60 UncompressedSize64: uint64(len(contents)), 61 ExternalAttrs: 0, 62 } 63} 64 65func fhManifest(contents []byte) zip.FileHeader { 66 return zip.FileHeader{ 67 Name: "META-INF/MANIFEST.MF", 68 Method: zip.Store, 69 CRC32: crc32.ChecksumIEEE(contents), 70 UncompressedSize64: uint64(len(contents)), 71 ExternalAttrs: (syscall.S_IFREG | 0700) << 16, 72 } 73} 74 75func fhLink(name string, to string) zip.FileHeader { 76 return zip.FileHeader{ 77 Name: name, 78 Method: zip.Store, 79 CRC32: crc32.ChecksumIEEE([]byte(to)), 80 UncompressedSize64: uint64(len(to)), 81 ExternalAttrs: (syscall.S_IFLNK | 0777) << 16, 82 } 83} 84 85func fhDir(name string) zip.FileHeader { 86 return zip.FileHeader{ 87 Name: name, 88 Method: zip.Store, 89 CRC32: crc32.ChecksumIEEE(nil), 90 UncompressedSize64: 0, 91 ExternalAttrs: (syscall.S_IFDIR|0700)<<16 | 0x10, 92 } 93} 94 95func fileArgsBuilder() *FileArgsBuilder { 96 return &FileArgsBuilder{ 97 fs: mockFs, 98 } 99} 100 101func TestZip(t *testing.T) { 102 testCases := []struct { 103 name string 104 args *FileArgsBuilder 105 compressionLevel int 106 emulateJar bool 107 nonDeflatedFiles map[string]bool 108 dirEntries bool 109 manifest string 110 storeSymlinks bool 111 ignoreMissingFiles bool 112 113 files []zip.FileHeader 114 err error 115 }{ 116 { 117 name: "empty args", 118 args: fileArgsBuilder(), 119 120 files: []zip.FileHeader{}, 121 }, 122 { 123 name: "files", 124 args: fileArgsBuilder(). 125 File("a/a/a"). 126 File("a/a/b"). 127 File("c"), 128 compressionLevel: 9, 129 130 files: []zip.FileHeader{ 131 fh("a/a/a", fileA, zip.Deflate), 132 fh("a/a/b", fileB, zip.Deflate), 133 fh("c", fileC, zip.Deflate), 134 }, 135 }, 136 { 137 name: "files glob", 138 args: fileArgsBuilder(). 139 SourcePrefixToStrip("a"). 140 File("a/**/*"), 141 compressionLevel: 9, 142 storeSymlinks: true, 143 144 files: []zip.FileHeader{ 145 fh("a/a", fileA, zip.Deflate), 146 fh("a/b", fileB, zip.Deflate), 147 fhLink("a/c", "../../c"), 148 fhLink("a/d", "b"), 149 }, 150 }, 151 { 152 name: "dir", 153 args: fileArgsBuilder(). 154 SourcePrefixToStrip("a"). 155 Dir("a"), 156 compressionLevel: 9, 157 storeSymlinks: true, 158 159 files: []zip.FileHeader{ 160 fh("a/a", fileA, zip.Deflate), 161 fh("a/b", fileB, zip.Deflate), 162 fhLink("a/c", "../../c"), 163 fhLink("a/d", "b"), 164 }, 165 }, 166 { 167 name: "stored files", 168 args: fileArgsBuilder(). 169 File("a/a/a"). 170 File("a/a/b"). 171 File("c"), 172 compressionLevel: 0, 173 174 files: []zip.FileHeader{ 175 fh("a/a/a", fileA, zip.Store), 176 fh("a/a/b", fileB, zip.Store), 177 fh("c", fileC, zip.Store), 178 }, 179 }, 180 { 181 name: "symlinks in zip", 182 args: fileArgsBuilder(). 183 File("a/a/a"). 184 File("a/a/b"). 185 File("a/a/c"). 186 File("a/a/d"), 187 compressionLevel: 9, 188 storeSymlinks: true, 189 190 files: []zip.FileHeader{ 191 fh("a/a/a", fileA, zip.Deflate), 192 fh("a/a/b", fileB, zip.Deflate), 193 fhLink("a/a/c", "../../c"), 194 fhLink("a/a/d", "b"), 195 }, 196 }, 197 { 198 name: "follow symlinks", 199 args: fileArgsBuilder(). 200 File("a/a/a"). 201 File("a/a/b"). 202 File("a/a/c"). 203 File("a/a/d"), 204 compressionLevel: 9, 205 storeSymlinks: false, 206 207 files: []zip.FileHeader{ 208 fh("a/a/a", fileA, zip.Deflate), 209 fh("a/a/b", fileB, zip.Deflate), 210 fh("a/a/c", fileC, zip.Deflate), 211 fh("a/a/d", fileB, zip.Deflate), 212 }, 213 }, 214 { 215 name: "dangling symlinks", 216 args: fileArgsBuilder(). 217 File("dangling"), 218 compressionLevel: 9, 219 storeSymlinks: true, 220 221 files: []zip.FileHeader{ 222 fhLink("dangling", "missing"), 223 }, 224 }, 225 { 226 name: "list", 227 args: fileArgsBuilder(). 228 List("l_nl"), 229 compressionLevel: 9, 230 231 files: []zip.FileHeader{ 232 fh("a/a/a", fileA, zip.Deflate), 233 fh("a/a/b", fileB, zip.Deflate), 234 fh("c", fileC, zip.Deflate), 235 }, 236 }, 237 { 238 name: "list", 239 args: fileArgsBuilder(). 240 List("l_sp"), 241 compressionLevel: 9, 242 243 files: []zip.FileHeader{ 244 fh("a/a/a", fileA, zip.Deflate), 245 fh("a/a/b", fileB, zip.Deflate), 246 fh("c", fileC, zip.Deflate), 247 }, 248 }, 249 { 250 name: "prefix in zip", 251 args: fileArgsBuilder(). 252 PathPrefixInZip("foo"). 253 File("a/a/a"). 254 File("a/a/b"). 255 File("c"), 256 compressionLevel: 9, 257 258 files: []zip.FileHeader{ 259 fh("foo/a/a/a", fileA, zip.Deflate), 260 fh("foo/a/a/b", fileB, zip.Deflate), 261 fh("foo/c", fileC, zip.Deflate), 262 }, 263 }, 264 { 265 name: "relative root", 266 args: fileArgsBuilder(). 267 SourcePrefixToStrip("a"). 268 File("a/a/a"). 269 File("a/a/b"), 270 compressionLevel: 9, 271 272 files: []zip.FileHeader{ 273 fh("a/a", fileA, zip.Deflate), 274 fh("a/b", fileB, zip.Deflate), 275 }, 276 }, 277 { 278 name: "multiple relative root", 279 args: fileArgsBuilder(). 280 SourcePrefixToStrip("a"). 281 File("a/a/a"). 282 SourcePrefixToStrip("a/a"). 283 File("a/a/b"), 284 compressionLevel: 9, 285 286 files: []zip.FileHeader{ 287 fh("a/a", fileA, zip.Deflate), 288 fh("b", fileB, zip.Deflate), 289 }, 290 }, 291 { 292 name: "emulate jar", 293 args: fileArgsBuilder(). 294 File("a/a/a"). 295 File("a/a/b"), 296 compressionLevel: 9, 297 emulateJar: true, 298 299 files: []zip.FileHeader{ 300 fhDir("META-INF/"), 301 fhManifest(fileManifest), 302 fhDir("a/"), 303 fhDir("a/a/"), 304 fh("a/a/a", fileA, zip.Deflate), 305 fh("a/a/b", fileB, zip.Deflate), 306 }, 307 }, 308 { 309 name: "emulate jar with manifest", 310 args: fileArgsBuilder(). 311 File("a/a/a"). 312 File("a/a/b"), 313 compressionLevel: 9, 314 emulateJar: true, 315 manifest: "manifest.txt", 316 317 files: []zip.FileHeader{ 318 fhDir("META-INF/"), 319 fhManifest(customManifestAfter), 320 fhDir("a/"), 321 fhDir("a/a/"), 322 fh("a/a/a", fileA, zip.Deflate), 323 fh("a/a/b", fileB, zip.Deflate), 324 }, 325 }, 326 { 327 name: "dir entries", 328 args: fileArgsBuilder(). 329 File("a/a/a"). 330 File("a/a/b"), 331 compressionLevel: 9, 332 dirEntries: true, 333 334 files: []zip.FileHeader{ 335 fhDir("a/"), 336 fhDir("a/a/"), 337 fh("a/a/a", fileA, zip.Deflate), 338 fh("a/a/b", fileB, zip.Deflate), 339 }, 340 }, 341 { 342 name: "junk paths", 343 args: fileArgsBuilder(). 344 JunkPaths(true). 345 File("a/a/a"). 346 File("a/a/b"), 347 compressionLevel: 9, 348 349 files: []zip.FileHeader{ 350 fh("a", fileA, zip.Deflate), 351 fh("b", fileB, zip.Deflate), 352 }, 353 }, 354 { 355 name: "non deflated files", 356 args: fileArgsBuilder(). 357 File("a/a/a"). 358 File("a/a/b"), 359 compressionLevel: 9, 360 nonDeflatedFiles: map[string]bool{"a/a/a": true}, 361 362 files: []zip.FileHeader{ 363 fh("a/a/a", fileA, zip.Store), 364 fh("a/a/b", fileB, zip.Deflate), 365 }, 366 }, 367 { 368 name: "ignore missing files", 369 args: fileArgsBuilder(). 370 File("a/a/a"). 371 File("a/a/b"). 372 File("missing"), 373 compressionLevel: 9, 374 ignoreMissingFiles: true, 375 376 files: []zip.FileHeader{ 377 fh("a/a/a", fileA, zip.Deflate), 378 fh("a/a/b", fileB, zip.Deflate), 379 }, 380 }, 381 382 // errors 383 { 384 name: "error missing file", 385 args: fileArgsBuilder(). 386 File("missing"), 387 err: os.ErrNotExist, 388 }, 389 { 390 name: "error missing dir", 391 args: fileArgsBuilder(). 392 Dir("missing"), 393 err: os.ErrNotExist, 394 }, 395 { 396 name: "error missing file in list", 397 args: fileArgsBuilder(). 398 List("l2"), 399 err: os.ErrNotExist, 400 }, 401 { 402 name: "error incorrect relative root", 403 args: fileArgsBuilder(). 404 SourcePrefixToStrip("b"). 405 File("a/a/a"), 406 err: IncorrectRelativeRootError{}, 407 }, 408 } 409 410 for _, test := range testCases { 411 t.Run(test.name, func(t *testing.T) { 412 if test.args.Error() != nil { 413 t.Fatal(test.args.Error()) 414 } 415 416 args := ZipArgs{} 417 args.FileArgs = test.args.FileArgs() 418 args.CompressionLevel = test.compressionLevel 419 args.EmulateJar = test.emulateJar 420 args.AddDirectoryEntriesToZip = test.dirEntries 421 args.NonDeflatedFiles = test.nonDeflatedFiles 422 args.ManifestSourcePath = test.manifest 423 args.StoreSymlinks = test.storeSymlinks 424 args.IgnoreMissingFiles = test.ignoreMissingFiles 425 args.Filesystem = mockFs 426 args.Stderr = &bytes.Buffer{} 427 428 buf := &bytes.Buffer{} 429 err := ZipTo(args, buf) 430 431 if (err != nil) != (test.err != nil) { 432 t.Fatalf("want error %v, got %v", test.err, err) 433 } else if test.err != nil { 434 if os.IsNotExist(test.err) { 435 if !os.IsNotExist(test.err) { 436 t.Fatalf("want error %v, got %v", test.err, err) 437 } 438 } else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr { 439 if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr { 440 t.Fatalf("want error %v, got %v", test.err, err) 441 } 442 } else { 443 t.Fatalf("want error %v, got %v", test.err, err) 444 } 445 return 446 } 447 448 br := bytes.NewReader(buf.Bytes()) 449 zr, err := zip.NewReader(br, int64(br.Len())) 450 if err != nil { 451 t.Fatal(err) 452 } 453 454 var files []zip.FileHeader 455 for _, f := range zr.File { 456 r, err := f.Open() 457 if err != nil { 458 t.Fatalf("error when opening %s: %s", f.Name, err) 459 } 460 461 crc := crc32.NewIEEE() 462 len, err := io.Copy(crc, r) 463 r.Close() 464 if err != nil { 465 t.Fatalf("error when reading %s: %s", f.Name, err) 466 } 467 468 if uint64(len) != f.UncompressedSize64 { 469 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len) 470 } 471 472 if crc.Sum32() != f.CRC32 { 473 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc) 474 } 475 476 files = append(files, f.FileHeader) 477 } 478 479 if len(files) != len(test.files) { 480 t.Fatalf("want %d files, got %d", len(test.files), len(files)) 481 } 482 483 for i := range files { 484 want := test.files[i] 485 got := files[i] 486 487 if want.Name != got.Name { 488 t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name) 489 continue 490 } 491 492 if want.UncompressedSize64 != got.UncompressedSize64 { 493 t.Errorf("incorrect file %s length want %v got %v", want.Name, 494 want.UncompressedSize64, got.UncompressedSize64) 495 } 496 497 if want.ExternalAttrs != got.ExternalAttrs { 498 t.Errorf("incorrect file %s attrs want %x got %x", want.Name, 499 want.ExternalAttrs, got.ExternalAttrs) 500 } 501 502 if want.CRC32 != got.CRC32 { 503 t.Errorf("incorrect file %s crc want %v got %v", want.Name, 504 want.CRC32, got.CRC32) 505 } 506 507 if want.Method != got.Method { 508 t.Errorf("incorrect file %s method want %v got %v", want.Name, 509 want.Method, got.Method) 510 } 511 } 512 }) 513 } 514} 515 516func TestReadRespFile(t *testing.T) { 517 testCases := []struct { 518 name, in string 519 out []string 520 }{ 521 { 522 name: "single quoting test case 1", 523 in: `./cmd '"'-C`, 524 out: []string{"./cmd", `"-C`}, 525 }, 526 { 527 name: "single quoting test case 2", 528 in: `./cmd '-C`, 529 out: []string{"./cmd", `-C`}, 530 }, 531 { 532 name: "single quoting test case 3", 533 in: `./cmd '\"'-C`, 534 out: []string{"./cmd", `\"-C`}, 535 }, 536 { 537 name: "single quoting test case 4", 538 in: `./cmd '\\'-C`, 539 out: []string{"./cmd", `\\-C`}, 540 }, 541 { 542 name: "none quoting test case 1", 543 in: `./cmd \'-C`, 544 out: []string{"./cmd", `'-C`}, 545 }, 546 { 547 name: "none quoting test case 2", 548 in: `./cmd \\-C`, 549 out: []string{"./cmd", `\-C`}, 550 }, 551 { 552 name: "none quoting test case 3", 553 in: `./cmd \"-C`, 554 out: []string{"./cmd", `"-C`}, 555 }, 556 { 557 name: "double quoting test case 1", 558 in: `./cmd "'"-C`, 559 out: []string{"./cmd", `'-C`}, 560 }, 561 { 562 name: "double quoting test case 2", 563 in: `./cmd "\\"-C`, 564 out: []string{"./cmd", `\-C`}, 565 }, 566 { 567 name: "double quoting test case 3", 568 in: `./cmd "\""-C`, 569 out: []string{"./cmd", `"-C`}, 570 }, 571 } 572 573 for _, testCase := range testCases { 574 t.Run(testCase.name, func(t *testing.T) { 575 got := ReadRespFile([]byte(testCase.in)) 576 if !reflect.DeepEqual(got, testCase.out) { 577 t.Errorf("expected %q got %q", testCase.out, got) 578 } 579 }) 580 } 581} 582 583func TestSrcJar(t *testing.T) { 584 mockFs := pathtools.MockFs(map[string][]byte{ 585 "wrong_package.java": []byte("package foo;"), 586 "foo/correct_package.java": []byte("package foo;"), 587 "src/no_package.java": nil, 588 "src2/parse_error.java": []byte("error"), 589 }) 590 591 want := []string{ 592 "foo/", 593 "foo/wrong_package.java", 594 "foo/correct_package.java", 595 "no_package.java", 596 "src2/", 597 "src2/parse_error.java", 598 } 599 600 args := ZipArgs{} 601 args.FileArgs = NewFileArgsBuilder().File("**/*.java").FileArgs() 602 603 args.SrcJar = true 604 args.AddDirectoryEntriesToZip = true 605 args.Filesystem = mockFs 606 args.Stderr = &bytes.Buffer{} 607 608 buf := &bytes.Buffer{} 609 err := ZipTo(args, buf) 610 if err != nil { 611 t.Fatalf("got error %v", err) 612 } 613 614 br := bytes.NewReader(buf.Bytes()) 615 zr, err := zip.NewReader(br, int64(br.Len())) 616 if err != nil { 617 t.Fatal(err) 618 } 619 620 var got []string 621 for _, f := range zr.File { 622 r, err := f.Open() 623 if err != nil { 624 t.Fatalf("error when opening %s: %s", f.Name, err) 625 } 626 627 crc := crc32.NewIEEE() 628 len, err := io.Copy(crc, r) 629 r.Close() 630 if err != nil { 631 t.Fatalf("error when reading %s: %s", f.Name, err) 632 } 633 634 if uint64(len) != f.UncompressedSize64 { 635 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len) 636 } 637 638 if crc.Sum32() != f.CRC32 { 639 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc) 640 } 641 642 got = append(got, f.Name) 643 } 644 645 if !reflect.DeepEqual(want, got) { 646 t.Errorf("want files %q, got %q", want, got) 647 } 648} 649