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\\[\n"), 50 "l_sp": []byte("a/a/a a/a/b c \\["), 51 "l2": []byte("missing\n"), 52 "rsp": []byte("'a/a/a'\na/a/b\n'@'\n'foo'\\''bar'\n'['"), 53 "@ -> c": nil, 54 "foo'bar -> c": nil, 55 "manifest.txt": fileCustomManifest, 56 "[": fileEmpty, 57}) 58 59func fh(name string, contents []byte, method uint16) zip.FileHeader { 60 return zip.FileHeader{ 61 Name: name, 62 Method: method, 63 CRC32: crc32.ChecksumIEEE(contents), 64 UncompressedSize64: uint64(len(contents)), 65 ExternalAttrs: (syscall.S_IFREG | 0644) << 16, 66 } 67} 68 69func fhManifest(contents []byte) zip.FileHeader { 70 return zip.FileHeader{ 71 Name: "META-INF/MANIFEST.MF", 72 Method: zip.Store, 73 CRC32: crc32.ChecksumIEEE(contents), 74 UncompressedSize64: uint64(len(contents)), 75 ExternalAttrs: (syscall.S_IFREG | 0644) << 16, 76 } 77} 78 79func fhLink(name string, to string) zip.FileHeader { 80 return zip.FileHeader{ 81 Name: name, 82 Method: zip.Store, 83 CRC32: crc32.ChecksumIEEE([]byte(to)), 84 UncompressedSize64: uint64(len(to)), 85 ExternalAttrs: (syscall.S_IFLNK | 0777) << 16, 86 } 87} 88 89func fhDir(name string) zip.FileHeader { 90 return zip.FileHeader{ 91 Name: name, 92 Method: zip.Store, 93 CRC32: crc32.ChecksumIEEE(nil), 94 UncompressedSize64: 0, 95 ExternalAttrs: (syscall.S_IFDIR|0755)<<16 | 0x10, 96 } 97} 98 99func fileArgsBuilder() *FileArgsBuilder { 100 return &FileArgsBuilder{ 101 fs: mockFs, 102 } 103} 104 105func TestZip(t *testing.T) { 106 testCases := []struct { 107 name string 108 args *FileArgsBuilder 109 compressionLevel int 110 emulateJar bool 111 nonDeflatedFiles map[string]bool 112 dirEntries bool 113 manifest string 114 storeSymlinks bool 115 ignoreMissingFiles bool 116 117 files []zip.FileHeader 118 err error 119 }{ 120 { 121 name: "empty args", 122 args: fileArgsBuilder(), 123 124 files: []zip.FileHeader{}, 125 }, 126 { 127 name: "files", 128 args: fileArgsBuilder(). 129 File("a/a/a"). 130 File("a/a/b"). 131 File("c"). 132 File(`\[`), 133 compressionLevel: 9, 134 135 files: []zip.FileHeader{ 136 fh("a/a/a", fileA, zip.Deflate), 137 fh("a/a/b", fileB, zip.Deflate), 138 fh("c", fileC, zip.Deflate), 139 fh("[", fileEmpty, zip.Store), 140 }, 141 }, 142 { 143 name: "files glob", 144 args: fileArgsBuilder(). 145 SourcePrefixToStrip("a"). 146 File("a/**/*"), 147 compressionLevel: 9, 148 storeSymlinks: true, 149 150 files: []zip.FileHeader{ 151 fh("a/a", fileA, zip.Deflate), 152 fh("a/b", fileB, zip.Deflate), 153 fhLink("a/c", "../../c"), 154 fhLink("a/d", "b"), 155 }, 156 }, 157 { 158 name: "dir", 159 args: fileArgsBuilder(). 160 SourcePrefixToStrip("a"). 161 Dir("a"), 162 compressionLevel: 9, 163 storeSymlinks: true, 164 165 files: []zip.FileHeader{ 166 fh("a/a", fileA, zip.Deflate), 167 fh("a/b", fileB, zip.Deflate), 168 fhLink("a/c", "../../c"), 169 fhLink("a/d", "b"), 170 }, 171 }, 172 { 173 name: "stored files", 174 args: fileArgsBuilder(). 175 File("a/a/a"). 176 File("a/a/b"). 177 File("c"), 178 compressionLevel: 0, 179 180 files: []zip.FileHeader{ 181 fh("a/a/a", fileA, zip.Store), 182 fh("a/a/b", fileB, zip.Store), 183 fh("c", fileC, zip.Store), 184 }, 185 }, 186 { 187 name: "symlinks in zip", 188 args: fileArgsBuilder(). 189 File("a/a/a"). 190 File("a/a/b"). 191 File("a/a/c"). 192 File("a/a/d"), 193 compressionLevel: 9, 194 storeSymlinks: true, 195 196 files: []zip.FileHeader{ 197 fh("a/a/a", fileA, zip.Deflate), 198 fh("a/a/b", fileB, zip.Deflate), 199 fhLink("a/a/c", "../../c"), 200 fhLink("a/a/d", "b"), 201 }, 202 }, 203 { 204 name: "follow symlinks", 205 args: fileArgsBuilder(). 206 File("a/a/a"). 207 File("a/a/b"). 208 File("a/a/c"). 209 File("a/a/d"), 210 compressionLevel: 9, 211 storeSymlinks: false, 212 213 files: []zip.FileHeader{ 214 fh("a/a/a", fileA, zip.Deflate), 215 fh("a/a/b", fileB, zip.Deflate), 216 fh("a/a/c", fileC, zip.Deflate), 217 fh("a/a/d", fileB, zip.Deflate), 218 }, 219 }, 220 { 221 name: "dangling symlinks", 222 args: fileArgsBuilder(). 223 File("dangling"), 224 compressionLevel: 9, 225 storeSymlinks: true, 226 227 files: []zip.FileHeader{ 228 fhLink("dangling", "missing"), 229 }, 230 }, 231 { 232 name: "list", 233 args: fileArgsBuilder(). 234 List("l_nl"), 235 compressionLevel: 9, 236 237 files: []zip.FileHeader{ 238 fh("a/a/a", fileA, zip.Deflate), 239 fh("a/a/b", fileB, zip.Deflate), 240 fh("c", fileC, zip.Deflate), 241 fh("[", fileEmpty, zip.Store), 242 }, 243 }, 244 { 245 name: "list", 246 args: fileArgsBuilder(). 247 List("l_sp"), 248 compressionLevel: 9, 249 250 files: []zip.FileHeader{ 251 fh("a/a/a", fileA, zip.Deflate), 252 fh("a/a/b", fileB, zip.Deflate), 253 fh("c", fileC, zip.Deflate), 254 fh("[", fileEmpty, zip.Store), 255 }, 256 }, 257 { 258 name: "rsp", 259 args: fileArgsBuilder(). 260 RspFile("rsp"), 261 compressionLevel: 9, 262 263 files: []zip.FileHeader{ 264 fh("a/a/a", fileA, zip.Deflate), 265 fh("a/a/b", fileB, zip.Deflate), 266 fh("@", fileC, zip.Deflate), 267 fh("foo'bar", fileC, zip.Deflate), 268 fh("[", fileEmpty, zip.Store), 269 }, 270 }, 271 { 272 name: "prefix in zip", 273 args: fileArgsBuilder(). 274 PathPrefixInZip("foo"). 275 File("a/a/a"). 276 File("a/a/b"). 277 File("c"), 278 compressionLevel: 9, 279 280 files: []zip.FileHeader{ 281 fh("foo/a/a/a", fileA, zip.Deflate), 282 fh("foo/a/a/b", fileB, zip.Deflate), 283 fh("foo/c", fileC, zip.Deflate), 284 }, 285 }, 286 { 287 name: "relative root", 288 args: fileArgsBuilder(). 289 SourcePrefixToStrip("a"). 290 File("a/a/a"). 291 File("a/a/b"), 292 compressionLevel: 9, 293 294 files: []zip.FileHeader{ 295 fh("a/a", fileA, zip.Deflate), 296 fh("a/b", fileB, zip.Deflate), 297 }, 298 }, 299 { 300 name: "multiple relative root", 301 args: fileArgsBuilder(). 302 SourcePrefixToStrip("a"). 303 File("a/a/a"). 304 SourcePrefixToStrip("a/a"). 305 File("a/a/b"), 306 compressionLevel: 9, 307 308 files: []zip.FileHeader{ 309 fh("a/a", fileA, zip.Deflate), 310 fh("b", fileB, zip.Deflate), 311 }, 312 }, 313 { 314 name: "emulate jar", 315 args: fileArgsBuilder(). 316 File("a/a/a"). 317 File("a/a/b"), 318 compressionLevel: 9, 319 emulateJar: true, 320 321 files: []zip.FileHeader{ 322 fhDir("META-INF/"), 323 fhManifest(fileManifest), 324 fhDir("a/"), 325 fhDir("a/a/"), 326 fh("a/a/a", fileA, zip.Deflate), 327 fh("a/a/b", fileB, zip.Deflate), 328 }, 329 }, 330 { 331 name: "emulate jar with manifest", 332 args: fileArgsBuilder(). 333 File("a/a/a"). 334 File("a/a/b"), 335 compressionLevel: 9, 336 emulateJar: true, 337 manifest: "manifest.txt", 338 339 files: []zip.FileHeader{ 340 fhDir("META-INF/"), 341 fhManifest(customManifestAfter), 342 fhDir("a/"), 343 fhDir("a/a/"), 344 fh("a/a/a", fileA, zip.Deflate), 345 fh("a/a/b", fileB, zip.Deflate), 346 }, 347 }, 348 { 349 name: "dir entries", 350 args: fileArgsBuilder(). 351 File("a/a/a"). 352 File("a/a/b"), 353 compressionLevel: 9, 354 dirEntries: true, 355 356 files: []zip.FileHeader{ 357 fhDir("a/"), 358 fhDir("a/a/"), 359 fh("a/a/a", fileA, zip.Deflate), 360 fh("a/a/b", fileB, zip.Deflate), 361 }, 362 }, 363 { 364 name: "junk paths", 365 args: fileArgsBuilder(). 366 JunkPaths(true). 367 File("a/a/a"). 368 File("a/a/b"), 369 compressionLevel: 9, 370 371 files: []zip.FileHeader{ 372 fh("a", fileA, zip.Deflate), 373 fh("b", fileB, zip.Deflate), 374 }, 375 }, 376 { 377 name: "non deflated files", 378 args: fileArgsBuilder(). 379 File("a/a/a"). 380 File("a/a/b"), 381 compressionLevel: 9, 382 nonDeflatedFiles: map[string]bool{"a/a/a": true}, 383 384 files: []zip.FileHeader{ 385 fh("a/a/a", fileA, zip.Store), 386 fh("a/a/b", fileB, zip.Deflate), 387 }, 388 }, 389 { 390 name: "ignore missing files", 391 args: fileArgsBuilder(). 392 File("a/a/a"). 393 File("a/a/b"). 394 File("missing"), 395 compressionLevel: 9, 396 ignoreMissingFiles: true, 397 398 files: []zip.FileHeader{ 399 fh("a/a/a", fileA, zip.Deflate), 400 fh("a/a/b", fileB, zip.Deflate), 401 }, 402 }, 403 404 // errors 405 { 406 name: "error missing file", 407 args: fileArgsBuilder(). 408 File("missing"), 409 err: os.ErrNotExist, 410 }, 411 { 412 name: "error missing dir", 413 args: fileArgsBuilder(). 414 Dir("missing"), 415 err: os.ErrNotExist, 416 }, 417 { 418 name: "error missing file in list", 419 args: fileArgsBuilder(). 420 List("l2"), 421 err: os.ErrNotExist, 422 }, 423 { 424 name: "error incorrect relative root", 425 args: fileArgsBuilder(). 426 SourcePrefixToStrip("b"). 427 File("a/a/a"), 428 err: IncorrectRelativeRootError{}, 429 }, 430 } 431 432 for _, test := range testCases { 433 t.Run(test.name, func(t *testing.T) { 434 if test.args.Error() != nil { 435 t.Fatal(test.args.Error()) 436 } 437 438 args := ZipArgs{} 439 args.FileArgs = test.args.FileArgs() 440 args.CompressionLevel = test.compressionLevel 441 args.EmulateJar = test.emulateJar 442 args.AddDirectoryEntriesToZip = test.dirEntries 443 args.NonDeflatedFiles = test.nonDeflatedFiles 444 args.ManifestSourcePath = test.manifest 445 args.StoreSymlinks = test.storeSymlinks 446 args.IgnoreMissingFiles = test.ignoreMissingFiles 447 args.Filesystem = mockFs 448 args.Stderr = &bytes.Buffer{} 449 450 buf := &bytes.Buffer{} 451 err := zipTo(args, buf) 452 453 if (err != nil) != (test.err != nil) { 454 t.Fatalf("want error %v, got %v", test.err, err) 455 } else if test.err != nil { 456 if os.IsNotExist(test.err) { 457 if !os.IsNotExist(test.err) { 458 t.Fatalf("want error %v, got %v", test.err, err) 459 } 460 } else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr { 461 if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr { 462 t.Fatalf("want error %v, got %v", test.err, err) 463 } 464 } else { 465 t.Fatalf("want error %v, got %v", test.err, err) 466 } 467 return 468 } 469 470 br := bytes.NewReader(buf.Bytes()) 471 zr, err := zip.NewReader(br, int64(br.Len())) 472 if err != nil { 473 t.Fatal(err) 474 } 475 476 var files []zip.FileHeader 477 for _, f := range zr.File { 478 r, err := f.Open() 479 if err != nil { 480 t.Fatalf("error when opening %s: %s", f.Name, err) 481 } 482 483 crc := crc32.NewIEEE() 484 len, err := io.Copy(crc, r) 485 r.Close() 486 if err != nil { 487 t.Fatalf("error when reading %s: %s", f.Name, err) 488 } 489 490 if uint64(len) != f.UncompressedSize64 { 491 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len) 492 } 493 494 if crc.Sum32() != f.CRC32 { 495 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc) 496 } 497 498 files = append(files, f.FileHeader) 499 } 500 501 if len(files) != len(test.files) { 502 t.Fatalf("want %d files, got %d", len(test.files), len(files)) 503 } 504 505 for i := range files { 506 want := test.files[i] 507 got := files[i] 508 509 if want.Name != got.Name { 510 t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name) 511 continue 512 } 513 514 if want.UncompressedSize64 != got.UncompressedSize64 { 515 t.Errorf("incorrect file %s length want %v got %v", want.Name, 516 want.UncompressedSize64, got.UncompressedSize64) 517 } 518 519 if want.ExternalAttrs != got.ExternalAttrs { 520 t.Errorf("incorrect file %s attrs want %x got %x", want.Name, 521 want.ExternalAttrs, got.ExternalAttrs) 522 } 523 524 if want.CRC32 != got.CRC32 { 525 t.Errorf("incorrect file %s crc want %v got %v", want.Name, 526 want.CRC32, got.CRC32) 527 } 528 529 if want.Method != got.Method { 530 t.Errorf("incorrect file %s method want %v got %v", want.Name, 531 want.Method, got.Method) 532 } 533 } 534 }) 535 } 536} 537 538func TestSrcJar(t *testing.T) { 539 mockFs := pathtools.MockFs(map[string][]byte{ 540 "wrong_package.java": []byte("package foo;"), 541 "foo/correct_package.java": []byte("package foo;"), 542 "src/no_package.java": nil, 543 "src2/parse_error.java": []byte("error"), 544 }) 545 546 want := []string{ 547 "foo/", 548 "foo/wrong_package.java", 549 "foo/correct_package.java", 550 "no_package.java", 551 "src2/", 552 "src2/parse_error.java", 553 } 554 555 args := ZipArgs{} 556 args.FileArgs = NewFileArgsBuilder().File("**/*.java").FileArgs() 557 558 args.SrcJar = true 559 args.AddDirectoryEntriesToZip = true 560 args.Filesystem = mockFs 561 args.Stderr = &bytes.Buffer{} 562 563 buf := &bytes.Buffer{} 564 err := zipTo(args, buf) 565 if err != nil { 566 t.Fatalf("got error %v", err) 567 } 568 569 br := bytes.NewReader(buf.Bytes()) 570 zr, err := zip.NewReader(br, int64(br.Len())) 571 if err != nil { 572 t.Fatal(err) 573 } 574 575 var got []string 576 for _, f := range zr.File { 577 r, err := f.Open() 578 if err != nil { 579 t.Fatalf("error when opening %s: %s", f.Name, err) 580 } 581 582 crc := crc32.NewIEEE() 583 len, err := io.Copy(crc, r) 584 r.Close() 585 if err != nil { 586 t.Fatalf("error when reading %s: %s", f.Name, err) 587 } 588 589 if uint64(len) != f.UncompressedSize64 { 590 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len) 591 } 592 593 if crc.Sum32() != f.CRC32 { 594 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc) 595 } 596 597 got = append(got, f.Name) 598 } 599 600 if !reflect.DeepEqual(want, got) { 601 t.Errorf("want files %q, got %q", want, got) 602 } 603} 604