1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.tools.build.apkzlib.zip; 18 19 import static com.android.tools.build.apkzlib.utils.ApkZFileTestUtils.readSegment; 20 import static org.junit.Assert.assertArrayEquals; 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotEquals; 24 import static org.junit.Assert.assertNotNull; 25 import static org.junit.Assert.assertNotSame; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assert.assertSame; 28 import static org.junit.Assert.assertTrue; 29 import static org.junit.Assert.fail; 30 31 import com.android.tools.build.apkzlib.zip.compress.DeflateExecutionCompressor; 32 import com.android.tools.build.apkzlib.zip.utils.CloseableByteSource; 33 import com.android.tools.build.apkzlib.zip.utils.RandomAccessFileUtils; 34 import com.google.common.base.Charsets; 35 import com.google.common.base.Strings; 36 import com.google.common.base.Throwables; 37 import com.google.common.hash.Hashing; 38 import com.google.common.io.ByteStreams; 39 import com.google.common.io.Closer; 40 import com.google.common.io.Files; 41 import java.io.ByteArrayInputStream; 42 import java.io.ByteArrayOutputStream; 43 import java.io.File; 44 import java.io.FileInputStream; 45 import java.io.FileOutputStream; 46 import java.io.IOException; 47 import java.io.InputStream; 48 import java.io.RandomAccessFile; 49 import java.util.Locale; 50 import java.util.Random; 51 import java.util.concurrent.ExecutorService; 52 import java.util.concurrent.Executors; 53 import java.util.zip.Deflater; 54 import java.util.zip.ZipEntry; 55 import java.util.zip.ZipFile; 56 import java.util.zip.ZipInputStream; 57 import java.util.zip.ZipOutputStream; 58 import javax.annotation.Nonnull; 59 import org.junit.Rule; 60 import org.junit.Test; 61 import org.junit.rules.TemporaryFolder; 62 63 public class ZFileTest { 64 @Rule 65 public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); 66 67 @Test getZipPath()68 public void getZipPath() throws Exception { 69 File temporaryDir = mTemporaryFolder.getRoot(); 70 File zpath = new File(temporaryDir, "a"); 71 try (ZFile zf = new ZFile(zpath)) { 72 assertEquals(zpath, zf.getFile()); 73 } 74 } 75 76 @Test readNonExistingFile()77 public void readNonExistingFile() throws Exception { 78 File temporaryDir = mTemporaryFolder.getRoot(); 79 File zf = new File(temporaryDir, "a"); 80 try (ZFile azf = new ZFile(zf)) { 81 azf.touch(); 82 } 83 84 assertTrue(zf.exists()); 85 } 86 87 @Test(expected = IOException.class) readExistingEmptyFile()88 public void readExistingEmptyFile() throws Exception { 89 File temporaryDir = mTemporaryFolder.getRoot(); 90 File zf = new File(temporaryDir, "a"); 91 Files.write(new byte[0], zf); 92 try (ZFile azf = new ZFile(zf)) { 93 /* 94 * Just open and close. 95 */ 96 } 97 } 98 99 @Test readAlmostEmptyZip()100 public void readAlmostEmptyZip() throws Exception { 101 File zf = ZipTestUtils.cloneRsrc("empty-zip.zip", mTemporaryFolder); 102 103 try (ZFile azf = new ZFile(zf)) { 104 assertEquals(1, azf.entries().size()); 105 106 StoredEntry z = azf.get("z/"); 107 assertNotNull(z); 108 assertSame(StoredEntryType.DIRECTORY, z.getType()); 109 } 110 } 111 112 @Test readZipWithTwoFilesOneDirectory()113 public void readZipWithTwoFilesOneDirectory() throws Exception { 114 File zf = ZipTestUtils.cloneRsrc("simple-zip.zip", mTemporaryFolder); 115 116 try (ZFile azf = new ZFile(zf)) { 117 assertEquals(3, azf.entries().size()); 118 119 StoredEntry e0 = azf.get("dir/"); 120 assertNotNull(e0); 121 assertSame(StoredEntryType.DIRECTORY, e0.getType()); 122 123 StoredEntry e1 = azf.get("dir/inside"); 124 assertNotNull(e1); 125 assertSame(StoredEntryType.FILE, e1.getType()); 126 ByteArrayOutputStream e1BytesOut = new ByteArrayOutputStream(); 127 ByteStreams.copy(e1.open(), e1BytesOut); 128 byte e1Bytes[] = e1BytesOut.toByteArray(); 129 String e1Txt = new String(e1Bytes, Charsets.US_ASCII); 130 assertEquals("inside", e1Txt); 131 132 StoredEntry e2 = azf.get("file.txt"); 133 assertNotNull(e2); 134 assertSame(StoredEntryType.FILE, e2.getType()); 135 ByteArrayOutputStream e2BytesOut = new ByteArrayOutputStream(); 136 ByteStreams.copy(e2.open(), e2BytesOut); 137 byte e2Bytes[] = e2BytesOut.toByteArray(); 138 String e2Txt = new String(e2Bytes, Charsets.US_ASCII); 139 assertEquals("file with more text to allow deflating to be useful", e2Txt); 140 } 141 } 142 143 @Test readOnlyZipSupport()144 public void readOnlyZipSupport() throws Exception { 145 File testZip = ZipTestUtils.cloneRsrc("empty-zip.zip", mTemporaryFolder); 146 147 assertTrue(testZip.setWritable(false)); 148 149 try (ZFile zf = new ZFile(testZip)) { 150 assertEquals(1, zf.entries().size()); 151 assertTrue(zf.getCentralDirectoryOffset() > 0); 152 assertTrue(zf.getEocdOffset() > 0); 153 } 154 } 155 156 @Test readOnlyV2SignedApkSupport()157 public void readOnlyV2SignedApkSupport() throws Exception { 158 File testZip = ZipTestUtils.cloneRsrc("v2-signed.apk", mTemporaryFolder); 159 160 assertTrue(testZip.setWritable(false)); 161 162 try (ZFile zf = new ZFile(testZip)) { 163 assertEquals(416, zf.entries().size()); 164 assertTrue(zf.getCentralDirectoryOffset() > 0); 165 assertTrue(zf.getEocdOffset() > 0); 166 } 167 } 168 169 @Test compressedFilesReadableByJavaZip()170 public void compressedFilesReadableByJavaZip() throws Exception { 171 File testZip = new File(mTemporaryFolder.getRoot(), "t.zip"); 172 173 File wiki = ZipTestUtils 174 .cloneRsrc("text-files/wikipedia.html", mTemporaryFolder, "wiki"); 175 File rfc = ZipTestUtils.cloneRsrc("text-files/rfc2460.txt", mTemporaryFolder, "rfc"); 176 File lena = ZipTestUtils.cloneRsrc("images/lena.png", mTemporaryFolder, "lena"); 177 byte[] wikiData = Files.toByteArray(wiki); 178 byte[] rfcData = Files.toByteArray(rfc); 179 byte[] lenaData = Files.toByteArray(lena); 180 181 try (ZFile zf = new ZFile(testZip)) { 182 zf.add("wiki", new ByteArrayInputStream(wikiData)); 183 zf.add("rfc", new ByteArrayInputStream(rfcData)); 184 zf.add("lena", new ByteArrayInputStream(lenaData)); 185 } 186 187 try(ZipFile jz = new ZipFile(testZip)) { 188 ZipEntry ze = jz.getEntry("wiki"); 189 assertNotNull(ze); 190 assertEquals(ZipEntry.DEFLATED, ze.getMethod()); 191 assertTrue(ze.getCompressedSize() < wikiData.length); 192 InputStream zeis = jz.getInputStream(ze); 193 assertArrayEquals(wikiData, ByteStreams.toByteArray(zeis)); 194 zeis.close(); 195 196 ze = jz.getEntry("rfc"); 197 assertNotNull(ze); 198 assertEquals(ZipEntry.DEFLATED, ze.getMethod()); 199 assertTrue(ze.getCompressedSize() < rfcData.length); 200 zeis = jz.getInputStream(ze); 201 assertArrayEquals(rfcData, ByteStreams.toByteArray(zeis)); 202 zeis.close(); 203 204 ze = jz.getEntry("lena"); 205 assertNotNull(ze); 206 assertEquals(ZipEntry.STORED, ze.getMethod()); 207 assertTrue(ze.getCompressedSize() == lenaData.length); 208 zeis = jz.getInputStream(ze); 209 assertArrayEquals(lenaData, ByteStreams.toByteArray(zeis)); 210 zeis.close(); 211 } 212 } 213 214 @Test 215 public void removeFileFromZip() throws Exception { 216 File zipFile = mTemporaryFolder.newFile("test.zip"); 217 218 try(ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 219 ZipEntry entry = new ZipEntry("foo/"); 220 entry.setMethod(ZipEntry.STORED); 221 entry.setSize(0); 222 entry.setCompressedSize(0); 223 entry.setCrc(0); 224 zos.putNextEntry(entry); 225 zos.putNextEntry(new ZipEntry("foo/bar")); 226 zos.write(new byte[] { 1, 2, 3, 4 }); 227 zos.closeEntry(); 228 } 229 230 try (ZFile zf = new ZFile(zipFile)) { 231 assertEquals(2, zf.entries().size()); 232 for (StoredEntry e : zf.entries()) { 233 if (e.getType() == StoredEntryType.FILE) { 234 e.delete(); 235 } 236 } 237 238 zf.update(); 239 240 try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) { 241 ZipEntry e1 = zis.getNextEntry(); 242 assertNotNull(e1); 243 244 assertEquals("foo/", e1.getName()); 245 246 ZipEntry e2 = zis.getNextEntry(); 247 assertNull(e2); 248 } 249 } 250 } 251 252 @Test 253 public void addFileToZip() throws Exception { 254 File zipFile = mTemporaryFolder.newFile("test.zip"); 255 256 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 257 ZipEntry fooDir = new ZipEntry("foo/"); 258 fooDir.setCrc(0); 259 fooDir.setCompressedSize(0); 260 fooDir.setSize(0); 261 fooDir.setMethod(ZipEntry.STORED); 262 zos.putNextEntry(fooDir); 263 zos.closeEntry(); 264 } 265 266 ZFile zf = new ZFile(zipFile); 267 assertEquals(1, zf.entries().size()); 268 269 zf.update(); 270 271 try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFile))) { 272 ZipEntry e1 = zis.getNextEntry(); 273 assertNotNull(e1); 274 275 assertEquals("foo/", e1.getName()); 276 277 ZipEntry e2 = zis.getNextEntry(); 278 assertNull(e2); 279 } 280 } 281 282 @Test 283 public void createNewZip() throws Exception { 284 File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip"); 285 286 ZFile zf = new ZFile(zipFile); 287 zf.add("foo", new ByteArrayInputStream(new byte[] { 0, 1 })); 288 zf.close(); 289 290 try (ZipFile jzf = new ZipFile(zipFile)) { 291 assertEquals(1, jzf.size()); 292 293 ZipEntry fooEntry = jzf.getEntry("foo"); 294 assertNotNull(fooEntry); 295 assertEquals("foo", fooEntry.getName()); 296 assertEquals(2, fooEntry.getSize()); 297 298 InputStream is = jzf.getInputStream(fooEntry); 299 assertEquals(0, is.read()); 300 assertEquals(1, is.read()); 301 assertEquals(-1, is.read()); 302 303 is.close(); 304 } 305 } 306 307 @Test 308 public void replaceFileWithSmallerInMiddle() throws Exception { 309 File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip"); 310 311 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 312 zos.putNextEntry(new ZipEntry("file1")); 313 zos.write(new byte[] { 1, 2, 3, 4, 5 }); 314 zos.putNextEntry(new ZipEntry("file2")); 315 zos.write(new byte[] { 6, 7, 8 }); 316 zos.putNextEntry(new ZipEntry("file3")); 317 zos.write(new byte[] { 9, 0, 1, 2, 3, 4 }); 318 } 319 320 int totalSize = (int) zipFile.length(); 321 322 try (ZFile zf = new ZFile(zipFile)) { 323 assertEquals(3, zf.entries().size()); 324 325 StoredEntry file2 = zf.get("file2"); 326 assertNotNull(file2); 327 assertEquals(3, file2.getCentralDirectoryHeader().getUncompressedSize()); 328 329 assertArrayEquals(new byte[] { 6, 7, 8 }, file2.read()); 330 331 zf.add("file2", new ByteArrayInputStream(new byte[] { 11, 12 })); 332 333 int newTotalSize = (int) zipFile.length(); 334 assertTrue(newTotalSize + " == " + totalSize, newTotalSize == totalSize); 335 336 file2 = zf.get("file2"); 337 assertNotNull(file2); 338 assertArrayEquals(new byte[] { 11, 12 }, file2.read()); 339 } 340 341 try (ZFile zf2 = new ZFile(zipFile)) { 342 StoredEntry file2 = zf2.get("file2"); 343 assertNotNull(file2); 344 assertArrayEquals(new byte[] { 11, 12 }, file2.read()); 345 } 346 } 347 348 @Test 349 public void replaceFileWithSmallerAtEnd() throws Exception { 350 File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip"); 351 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 352 zos.putNextEntry(new ZipEntry("file1")); 353 zos.write(new byte[]{1, 2, 3, 4, 5}); 354 zos.putNextEntry(new ZipEntry("file2")); 355 zos.write(new byte[]{6, 7, 8}); 356 zos.putNextEntry(new ZipEntry("file3")); 357 zos.write(new byte[]{9, 0, 1, 2, 3, 4}); 358 } 359 360 int totalSize = (int) zipFile.length(); 361 362 try (ZFile zf = new ZFile(zipFile)) { 363 assertEquals(3, zf.entries().size()); 364 365 StoredEntry file3 = zf.get("file3"); 366 assertNotNull(file3); 367 assertEquals(6, file3.getCentralDirectoryHeader().getUncompressedSize()); 368 369 assertArrayEquals(new byte[]{9, 0, 1, 2, 3, 4}, file3.read()); 370 371 zf.add("file3", new ByteArrayInputStream(new byte[]{11, 12})); 372 zf.close(); 373 374 int newTotalSize = (int) zipFile.length(); 375 assertTrue(newTotalSize + " < " + totalSize, newTotalSize < totalSize); 376 377 file3 = zf.get("file3"); 378 assertNotNull(file3); 379 assertArrayEquals(new byte[]{11, 12,}, file3.read()); 380 } 381 382 try (ZFile zf2 = new ZFile(zipFile)) { 383 StoredEntry file3 = zf2.get("file3"); 384 assertNotNull(file3); 385 assertArrayEquals(new byte[]{11, 12,}, file3.read()); 386 } 387 } 388 389 @Test 390 public void replaceFileWithBiggerAtBegin() throws Exception { 391 File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip"); 392 393 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 394 zos.putNextEntry(new ZipEntry("file1")); 395 zos.write(new byte[]{1, 2, 3, 4, 5}); 396 zos.putNextEntry(new ZipEntry("file2")); 397 zos.write(new byte[]{6, 7, 8}); 398 zos.putNextEntry(new ZipEntry("file3")); 399 zos.write(new byte[]{9, 0, 1, 2, 3, 4}); 400 } 401 402 int totalSize = (int) zipFile.length(); 403 byte[] newData = new byte[100]; 404 405 try (ZFile zf = new ZFile(zipFile)) { 406 assertEquals(3, zf.entries().size()); 407 408 StoredEntry file1 = zf.get("file1"); 409 assertNotNull(file1); 410 assertEquals(5, file1.getCentralDirectoryHeader().getUncompressedSize()); 411 412 assertArrayEquals(new byte[]{1, 2, 3, 4, 5}, file1.read()); 413 414 /* 415 * Need some data because java zip API uses data descriptors which we don't and makes 416 * the entries bigger (meaning just adding a couple of bytes would still fit in the 417 * same place). 418 */ 419 Random r = new Random(); 420 r.nextBytes(newData); 421 422 zf.add("file1", new ByteArrayInputStream(newData)); 423 zf.close(); 424 425 int newTotalSize = (int) zipFile.length(); 426 assertTrue(newTotalSize + " > " + totalSize, newTotalSize > totalSize); 427 428 file1 = zf.get("file1"); 429 assertNotNull(file1); 430 assertArrayEquals(newData, file1.read()); 431 } 432 433 try (ZFile zf2 = new ZFile(zipFile)) { 434 StoredEntry file1 = zf2.get("file1"); 435 assertNotNull(file1); 436 assertArrayEquals(newData, file1.read()); 437 } 438 } 439 440 @Test 441 public void replaceFileWithBiggerAtEnd() throws Exception { 442 File zipFile = new File(mTemporaryFolder.getRoot(), "test.zip"); 443 444 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 445 zos.putNextEntry(new ZipEntry("file1")); 446 zos.write(new byte[]{1, 2, 3, 4, 5}); 447 zos.putNextEntry(new ZipEntry("file2")); 448 zos.write(new byte[]{6, 7, 8}); 449 zos.putNextEntry(new ZipEntry("file3")); 450 zos.write(new byte[]{9, 0, 1, 2, 3, 4}); 451 } 452 453 int totalSize = (int) zipFile.length(); 454 byte[] newData = new byte[100]; 455 456 try (ZFile zf = new ZFile(zipFile)) { 457 assertEquals(3, zf.entries().size()); 458 459 StoredEntry file3 = zf.get("file3"); 460 assertNotNull(file3); 461 assertEquals(6, file3.getCentralDirectoryHeader().getUncompressedSize()); 462 463 assertArrayEquals(new byte[]{9, 0, 1, 2, 3, 4}, file3.read()); 464 465 /* 466 * Need some data because java zip API uses data descriptors which we don't and makes 467 * the entries bigger (meaning just adding a couple of bytes would still fit in the 468 * same place). 469 */ 470 Random r = new Random(); 471 r.nextBytes(newData); 472 473 zf.add("file3", new ByteArrayInputStream(newData)); 474 zf.close(); 475 476 int newTotalSize = (int) zipFile.length(); 477 assertTrue(newTotalSize + " > " + totalSize, newTotalSize > totalSize); 478 479 file3 = zf.get("file3"); 480 assertNotNull(file3); 481 assertArrayEquals(newData, file3.read()); 482 } 483 484 try (ZFile zf2 = new ZFile(zipFile)) { 485 StoredEntry file3 = zf2.get("file3"); 486 assertNotNull(file3); 487 assertArrayEquals(newData, file3.read()); 488 } 489 } 490 491 @Test 492 public void ignoredFilesDuringMerge() throws Exception { 493 File zip1 = mTemporaryFolder.newFile("t1.zip"); 494 495 try (ZipOutputStream zos1 = new ZipOutputStream(new FileOutputStream(zip1))) { 496 zos1.putNextEntry(new ZipEntry("only_in_1")); 497 zos1.write(new byte[] { 1, 2 }); 498 zos1.putNextEntry(new ZipEntry("overridden_by_2")); 499 zos1.write(new byte[] { 2, 3 }); 500 zos1.putNextEntry(new ZipEntry("not_overridden_by_2")); 501 zos1.write(new byte[] { 3, 4 }); 502 } 503 504 File zip2 = mTemporaryFolder.newFile("t2.zip"); 505 try (ZipOutputStream zos2 = new ZipOutputStream(new FileOutputStream(zip2))) { 506 zos2.putNextEntry(new ZipEntry("only_in_2")); 507 zos2.write(new byte[] { 4, 5 }); 508 zos2.putNextEntry(new ZipEntry("overridden_by_2")); 509 zos2.write(new byte[] { 5, 6 }); 510 zos2.putNextEntry(new ZipEntry("not_overridden_by_2")); 511 zos2.write(new byte[] { 6, 7 }); 512 zos2.putNextEntry(new ZipEntry("ignored_in_2")); 513 zos2.write(new byte[] { 7, 8 }); 514 } 515 516 try ( 517 ZFile zf1 = new ZFile(zip1); 518 ZFile zf2 = new ZFile(zip2)) { 519 zf1.mergeFrom(zf2, (input) -> input.matches("not.*") || input.matches(".*gnored.*")); 520 521 StoredEntry only_in_1 = zf1.get("only_in_1"); 522 assertNotNull(only_in_1); 523 assertArrayEquals(new byte[]{1, 2}, only_in_1.read()); 524 525 StoredEntry overridden_by_2 = zf1.get("overridden_by_2"); 526 assertNotNull(overridden_by_2); 527 assertArrayEquals(new byte[]{5, 6}, overridden_by_2.read()); 528 529 StoredEntry not_overridden_by_2 = zf1.get("not_overridden_by_2"); 530 assertNotNull(not_overridden_by_2); 531 assertArrayEquals(new byte[]{3, 4}, not_overridden_by_2.read()); 532 533 StoredEntry only_in_2 = zf1.get("only_in_2"); 534 assertNotNull(only_in_2); 535 assertArrayEquals(new byte[]{4, 5}, only_in_2.read()); 536 537 StoredEntry ignored_in_2 = zf1.get("ignored_in_2"); 538 assertNull(ignored_in_2); 539 } 540 } 541 542 @Test addingFileDoesNotAddDirectoriesAutomatically()543 public void addingFileDoesNotAddDirectoriesAutomatically() throws Exception { 544 File zip = new File(mTemporaryFolder.getRoot(), "z.zip"); 545 try (ZFile zf = new ZFile(zip)) { 546 zf.add("a/b/c", new ByteArrayInputStream(new byte[]{1, 2, 3})); 547 zf.update(); 548 assertEquals(1, zf.entries().size()); 549 550 StoredEntry c = zf.get("a/b/c"); 551 assertNotNull(c); 552 assertEquals(3, c.read().length); 553 } 554 } 555 556 @Test zipFileWithEocdSignatureInComment()557 public void zipFileWithEocdSignatureInComment() throws Exception { 558 File zip = mTemporaryFolder.newFile("f.zip"); 559 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zip))) { 560 zos.putNextEntry(new ZipEntry("a")); 561 zos.write(new byte[] { 1, 2, 3 }); 562 zos.setComment("Random comment with XXXX weird characters. There must be enough " 563 + "characters to survive skipping back the EOCD size."); 564 } 565 566 byte zipBytes[] = Files.toByteArray(zip); 567 boolean didX4 = false; 568 for (int i = 0; i < zipBytes.length - 3; i++) { 569 boolean x4 = true; 570 for (int j = 0; j < 4; j++) { 571 if (zipBytes[i + j] != 'X') { 572 x4 = false; 573 break; 574 } 575 } 576 577 if (x4) { 578 zipBytes[i] = (byte) 0x50; 579 zipBytes[i + 1] = (byte) 0x4b; 580 zipBytes[i + 2] = (byte) 0x05; 581 zipBytes[i + 3] = (byte) 0x06; 582 didX4 = true; 583 break; 584 } 585 } 586 587 assertTrue(didX4); 588 589 Files.write(zipBytes, zip); 590 591 try (ZFile zf = new ZFile(zip)) { 592 assertEquals(1, zf.entries().size()); 593 StoredEntry a = zf.get("a"); 594 assertNotNull(a); 595 assertArrayEquals(new byte[]{1, 2, 3}, a.read()); 596 } 597 } 598 599 @Test addFileRecursively()600 public void addFileRecursively() throws Exception { 601 File tdir = mTemporaryFolder.newFolder(); 602 File tfile = new File(tdir, "blah-blah"); 603 Files.write("blah", tfile, Charsets.US_ASCII); 604 605 File zip = new File(tdir, "f.zip"); 606 try (ZFile zf = new ZFile(zip)) { 607 zf.addAllRecursively(tfile); 608 609 StoredEntry blahEntry = zf.get("blah-blah"); 610 assertNotNull(blahEntry); 611 String contents = new String(blahEntry.read(), Charsets.US_ASCII); 612 assertEquals("blah", contents); 613 } 614 } 615 616 @Test addDirectoryRecursively()617 public void addDirectoryRecursively() throws Exception { 618 File tdir = mTemporaryFolder.newFolder(); 619 620 String boom = Strings.repeat("BOOM!", 100); 621 String kaboom = Strings.repeat("KABOOM!", 100); 622 623 Files.write(boom, new File(tdir, "danger"), Charsets.US_ASCII); 624 Files.write(kaboom, new File(tdir, "do not touch"), Charsets.US_ASCII); 625 File safeDir = new File(tdir, "safe"); 626 assertTrue(safeDir.mkdir()); 627 628 String iLoveChocolate = Strings.repeat("I love chocolate! ", 200); 629 String iLoveOrange = Strings.repeat("I love orange! ", 50); 630 String loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean vitae " 631 + "turpis quis justo scelerisque vulputate in et magna. Suspendisse eleifend " 632 + "ultricies nisi, placerat consequat risus accumsan et. Pellentesque habitant " 633 + "morbi tristique senectus et netus et malesuada fames ac turpis egestas. " 634 + "Integer vitae leo purus. Nulla facilisi. Duis ligula libero, lacinia a " 635 + "malesuada a, viverra tempor sapien. Donec eget consequat sapien, ultrices" 636 + "interdum diam. Maecenas ipsum erat, suscipit at iaculis a, mollis nec risus. " 637 + "Quisque tristique ac velit sed auctor. Nulla lacus diam, tristique id sem non, " 638 + "pellentesque commodo mauris."; 639 640 Files.write(iLoveChocolate, new File(safeDir, "eat.sweet"), Charsets.US_ASCII); 641 Files.write(iLoveOrange, new File(safeDir, "eat.fruit"), Charsets.US_ASCII); 642 Files.write(loremIpsum, new File(safeDir, "bedtime.reading.txt"), Charsets.US_ASCII); 643 644 File zip = new File(tdir, "f.zip"); 645 try (ZFile zf = new ZFile(zip)) { 646 zf.addAllRecursively(tdir, (f) -> !f.getName().startsWith("eat.")); 647 648 assertEquals(6, zf.entries().size()); 649 650 StoredEntry boomEntry = zf.get("danger"); 651 assertNotNull(boomEntry); 652 assertEquals(CompressionMethod.DEFLATE, 653 boomEntry.getCentralDirectoryHeader().getCompressionInfoWithWait().getMethod()); 654 assertEquals(boom, new String(boomEntry.read(), Charsets.US_ASCII)); 655 656 StoredEntry kaboomEntry = zf.get("do not touch"); 657 assertNotNull(kaboomEntry); 658 assertEquals(CompressionMethod.DEFLATE, 659 kaboomEntry 660 .getCentralDirectoryHeader() 661 .getCompressionInfoWithWait() 662 .getMethod()); 663 assertEquals(kaboom, new String(kaboomEntry.read(), Charsets.US_ASCII)); 664 665 StoredEntry safeEntry = zf.get("safe/"); 666 assertNotNull(safeEntry); 667 assertEquals(0, safeEntry.read().length); 668 669 StoredEntry choc = zf.get("safe/eat.sweet"); 670 assertNotNull(choc); 671 assertEquals(CompressionMethod.STORE, 672 choc.getCentralDirectoryHeader().getCompressionInfoWithWait().getMethod()); 673 assertEquals(iLoveChocolate, new String(choc.read(), Charsets.US_ASCII)); 674 675 StoredEntry orangeEntry = zf.get("safe/eat.fruit"); 676 assertNotNull(orangeEntry); 677 assertEquals(CompressionMethod.STORE, 678 orangeEntry 679 .getCentralDirectoryHeader() 680 .getCompressionInfoWithWait() 681 .getMethod()); 682 assertEquals(iLoveOrange, new String(orangeEntry.read(), Charsets.US_ASCII)); 683 684 StoredEntry loremEntry = zf.get("safe/bedtime.reading.txt"); 685 assertNotNull(loremEntry); 686 assertEquals(CompressionMethod.DEFLATE, 687 loremEntry 688 .getCentralDirectoryHeader() 689 .getCompressionInfoWithWait() 690 .getMethod()); 691 assertEquals(loremIpsum, new String(loremEntry.read(), Charsets.US_ASCII)); 692 } 693 } 694 695 @Test extraDirectoryOffsetEmptyFile()696 public void extraDirectoryOffsetEmptyFile() throws Exception { 697 File zipNoOffsetFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 698 File zipWithOffsetFile = new File(mTemporaryFolder.getRoot(), "b.zip"); 699 700 int offset = 31; 701 702 long zipNoOffsetSize; 703 try ( 704 ZFile zipNoOffset = new ZFile(zipNoOffsetFile); 705 ZFile zipWithOffset = new ZFile(zipWithOffsetFile)) { 706 zipWithOffset.setExtraDirectoryOffset(offset); 707 708 zipNoOffset.close(); 709 zipWithOffset.close(); 710 711 zipNoOffsetSize = zipNoOffsetFile.length(); 712 long zipWithOffsetSize = zipWithOffsetFile.length(); 713 714 assertEquals(zipNoOffsetSize + offset, zipWithOffsetSize); 715 716 /* 717 * EOCD with no comment has 22 bytes. 718 */ 719 assertEquals(0, zipNoOffset.getCentralDirectoryOffset()); 720 assertEquals(0, zipNoOffset.getCentralDirectorySize()); 721 assertEquals(0, zipNoOffset.getEocdOffset()); 722 assertEquals(ZFileTestConstants.EOCD_SIZE, zipNoOffset.getEocdSize()); 723 assertEquals(offset, zipWithOffset.getCentralDirectoryOffset()); 724 assertEquals(0, zipWithOffset.getCentralDirectorySize()); 725 assertEquals(offset, zipWithOffset.getEocdOffset()); 726 assertEquals(ZFileTestConstants.EOCD_SIZE, zipWithOffset.getEocdSize()); 727 } 728 729 /* 730 * The EOCDs should not differ up until the end of the Central Directory size and should 731 * not differ after the offset 732 */ 733 int p1Start = 0; 734 int p1Size = Eocd.F_CD_SIZE.endOffset(); 735 int p2Start = Eocd.F_CD_OFFSET.endOffset(); 736 int p2Size = (int) zipNoOffsetSize - p2Start; 737 738 byte[] noOffsetData1 = readSegment(zipNoOffsetFile, p1Start, p1Size); 739 byte[] noOffsetData2 = readSegment(zipNoOffsetFile, p2Start, p2Size); 740 byte[] withOffsetData1 = readSegment(zipWithOffsetFile, offset, p1Size); 741 byte[] withOffsetData2 = readSegment(zipWithOffsetFile, offset + p2Start, p2Size); 742 743 assertArrayEquals(noOffsetData1, withOffsetData1); 744 assertArrayEquals(noOffsetData2, withOffsetData2); 745 746 try (ZFile readWithOffset = new ZFile(zipWithOffsetFile)) { 747 assertEquals(0, readWithOffset.entries().size()); 748 } 749 } 750 751 @Test extraDirectoryOffsetNonEmptyFile()752 public void extraDirectoryOffsetNonEmptyFile() throws Exception { 753 File zipNoOffsetFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 754 File zipWithOffsetFile = new File(mTemporaryFolder.getRoot(), "b.zip"); 755 756 int cdSize; 757 758 // The byte arrays below are larger when compressed, so we end up storing them uncompressed, 759 // which would normally cause them to be 4-aligned. Disable that, to make calculations 760 // easier. 761 ZFileOptions options = new ZFileOptions(); 762 options.setAlignmentRule(AlignmentRules.constant(AlignmentRule.NO_ALIGNMENT)); 763 764 try (ZFile zipNoOffset = new ZFile(zipNoOffsetFile, options); 765 ZFile zipWithOffset = new ZFile(zipWithOffsetFile, options)) { 766 zipWithOffset.setExtraDirectoryOffset(37); 767 768 zipNoOffset.add("x", new ByteArrayInputStream(new byte[]{1, 2})); 769 zipWithOffset.add("x", new ByteArrayInputStream(new byte[]{1, 2})); 770 771 zipNoOffset.close(); 772 zipWithOffset.close(); 773 774 long zipNoOffsetSize = zipNoOffsetFile.length(); 775 long zipWithOffsetSize = zipWithOffsetFile.length(); 776 777 assertEquals(zipNoOffsetSize + 37, zipWithOffsetSize); 778 779 /* 780 * Local file header has 30 bytes + name. 781 * Central directory entry has 46 bytes + name 782 * EOCD with no comment has 22 bytes. 783 */ 784 assertEquals(ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2, 785 zipNoOffset.getCentralDirectoryOffset()); 786 cdSize = (int) zipNoOffset.getCentralDirectorySize(); 787 assertEquals(ZFileTestConstants.CENTRAL_DIRECTORY_ENTRY_SIZE + 1, cdSize); 788 assertEquals(ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2 + cdSize, 789 zipNoOffset.getEocdOffset()); 790 assertEquals(ZFileTestConstants.EOCD_SIZE, zipNoOffset.getEocdSize()); 791 assertEquals(ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2 + 37, 792 zipWithOffset.getCentralDirectoryOffset()); 793 assertEquals(cdSize, zipWithOffset.getCentralDirectorySize()); 794 assertEquals(ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2 + 37 + cdSize, 795 zipWithOffset.getEocdOffset()); 796 assertEquals(ZFileTestConstants.EOCD_SIZE, zipWithOffset.getEocdSize()); 797 } 798 799 /* 800 * The files should be equal: until the end of the first entry, from the beginning of the 801 * central directory until the offset field in the EOCD and after the offset field. 802 */ 803 int p1Start = 0; 804 int p1Size = ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2; 805 int p2Start = ZFileTestConstants.LOCAL_HEADER_SIZE + 1 + 2; 806 int p2Size = cdSize + Eocd.F_CD_SIZE.endOffset(); 807 int p3Start = p2Start + cdSize + Eocd.F_CD_OFFSET.endOffset(); 808 int p3Size = ZFileTestConstants.EOCD_SIZE - Eocd.F_CD_OFFSET.endOffset(); 809 810 byte[] noOffsetData1 = readSegment(zipNoOffsetFile, p1Start, p1Size); 811 byte[] noOffsetData2 = readSegment(zipNoOffsetFile, p2Start, p2Size); 812 byte[] noOffsetData3 = readSegment(zipNoOffsetFile, p3Start, p3Size); 813 byte[] withOffsetData1 = readSegment(zipWithOffsetFile, p1Start, p1Size); 814 byte[] withOffsetData2 = readSegment(zipWithOffsetFile, 37 + p2Start, p2Size); 815 byte[] withOffsetData3 = readSegment(zipWithOffsetFile, 37 + p3Start, p3Size); 816 817 assertArrayEquals(noOffsetData1, withOffsetData1); 818 assertArrayEquals(noOffsetData2, withOffsetData2); 819 assertArrayEquals(noOffsetData3, withOffsetData3); 820 821 try (ZFile readWithOffset = new ZFile(zipWithOffsetFile)) { 822 assertEquals(1, readWithOffset.entries().size()); 823 } 824 } 825 826 @Test changeExtraDirectoryOffset()827 public void changeExtraDirectoryOffset() throws Exception { 828 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 829 830 try (ZFile zip = new ZFile(zipFile)) { 831 zip.add("x", new ByteArrayInputStream(new byte[]{1, 2})); 832 zip.close(); 833 834 long noOffsetSize = zipFile.length(); 835 836 zip.setExtraDirectoryOffset(177); 837 zip.close(); 838 839 long withOffsetSize = zipFile.length(); 840 841 assertEquals(noOffsetSize + 177, withOffsetSize); 842 } 843 } 844 845 @Test computeOffsetWhenReadingEmptyFile()846 public void computeOffsetWhenReadingEmptyFile() throws Exception { 847 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 848 849 try (ZFile zip = new ZFile(zipFile)) { 850 zip.setExtraDirectoryOffset(18); 851 } 852 853 try (ZFile zip = new ZFile(zipFile)) { 854 assertEquals(18, zip.getExtraDirectoryOffset()); 855 } 856 } 857 858 @Test computeOffsetWhenReadingNonEmptyFile()859 public void computeOffsetWhenReadingNonEmptyFile() throws Exception { 860 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 861 862 try (ZFile zip = new ZFile(zipFile)) { 863 zip.setExtraDirectoryOffset(287); 864 zip.add("x", new ByteArrayInputStream(new byte[]{1, 2})); 865 } 866 867 try (ZFile zip = new ZFile(zipFile)) { 868 assertEquals(287, zip.getExtraDirectoryOffset()); 869 } 870 } 871 872 @Test obtainingCDAndEocdWhenEntriesWrittenOnEmptyZip()873 public void obtainingCDAndEocdWhenEntriesWrittenOnEmptyZip() throws Exception { 874 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 875 876 byte[][] cd = new byte[1][]; 877 byte[][] eocd = new byte[1][]; 878 879 try (ZFile zip = new ZFile(zipFile)) { 880 zip.addZFileExtension(new ZFileExtension() { 881 @Override 882 public void entriesWritten() throws IOException { 883 cd[0] = zip.getCentralDirectoryBytes(); 884 eocd[0] = zip.getEocdBytes(); 885 } 886 }); 887 } 888 889 assertNotNull(cd[0]); 890 assertEquals(0, cd[0].length); 891 assertNotNull(eocd[0]); 892 assertEquals(22, eocd[0].length); 893 } 894 895 @Test obtainingCDAndEocdWhenEntriesWrittenOnNonEmptyZip()896 public void obtainingCDAndEocdWhenEntriesWrittenOnNonEmptyZip() throws Exception { 897 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 898 899 byte[][] cd = new byte[1][]; 900 byte[][] eocd = new byte[1][]; 901 902 try (ZFile zip = new ZFile(zipFile)) { 903 zip.add("foo", new ByteArrayInputStream(new byte[0])); 904 zip.addZFileExtension(new ZFileExtension() { 905 @Override 906 public void entriesWritten() throws IOException { 907 cd[0] = zip.getCentralDirectoryBytes(); 908 eocd[0] = zip.getEocdBytes(); 909 } 910 }); 911 } 912 913 /* 914 * Central directory entry has 46 bytes + name 915 * EOCD with no comment has 22 bytes. 916 */ 917 assertNotNull(cd[0]); 918 assertEquals(46 + 3, cd[0].length); 919 assertNotNull(eocd[0]); 920 assertEquals(22, eocd[0].length); 921 } 922 923 @Test java7JarSupported()924 public void java7JarSupported() throws Exception { 925 File jar = ZipTestUtils.cloneRsrc("j7.jar", mTemporaryFolder); 926 927 try (ZFile j = new ZFile(jar)) { 928 assertEquals(8, j.entries().size()); 929 } 930 } 931 932 @Test java8JarSupported()933 public void java8JarSupported() throws Exception { 934 File jar = ZipTestUtils.cloneRsrc("j8.jar", mTemporaryFolder); 935 936 try (ZFile j = new ZFile(jar)) { 937 assertEquals(8, j.entries().size()); 938 } 939 } 940 941 @Test utf8NamesSupportedOnReading()942 public void utf8NamesSupportedOnReading() throws Exception { 943 File zip = ZipTestUtils.cloneRsrc("zip-with-utf8-filename.zip", mTemporaryFolder); 944 945 try (ZFile f = new ZFile(zip)) { 946 assertEquals(1, f.entries().size()); 947 948 StoredEntry entry = f.entries().iterator().next(); 949 String filetMignonKorean = "\uc548\uc2eC \uc694\ub9ac"; 950 String isGoodJapanese = "\u3068\u3066\u3082\u826f\u3044"; 951 952 assertEquals( 953 filetMignonKorean + " " + isGoodJapanese, 954 entry.getCentralDirectoryHeader().getName()); 955 assertArrayEquals( 956 "Stuff about food is good.\n".getBytes(Charsets.US_ASCII), entry.read()); 957 } 958 } 959 960 @Test utf8NamesSupportedOnReadingWithoutUtf8Flag()961 public void utf8NamesSupportedOnReadingWithoutUtf8Flag() throws Exception { 962 File zip = ZipTestUtils.cloneRsrc("zip-with-utf8-filename.zip", mTemporaryFolder); 963 964 // Reset bytes 7 and 122 that have the flag in the local header and central directory. 965 byte[] data = Files.toByteArray(zip); 966 data[7] = 0; 967 data[122] = 0; 968 Files.write(data, zip); 969 970 try (ZFile f = new ZFile(zip)) { 971 assertEquals(1, f.entries().size()); 972 973 StoredEntry entry = f.entries().iterator().next(); 974 String filetMignonKorean = "\uc548\uc2eC \uc694\ub9ac"; 975 String isGoodJapanese = "\u3068\u3066\u3082\u826f\u3044"; 976 977 assertEquals( 978 filetMignonKorean + " " + isGoodJapanese, 979 entry.getCentralDirectoryHeader().getName()); 980 assertArrayEquals( 981 "Stuff about food is good.\n".getBytes(Charsets.US_ASCII), 982 entry.read()); 983 } 984 } 985 986 @Test utf8NamesSupportedOnWriting()987 public void utf8NamesSupportedOnWriting() throws Exception { 988 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 989 String lettuceIsHealthyArmenian = "\u0533\u0561\u0566\u0561\u0580\u0020\u0561\u057C" 990 + "\u0578\u0572\u057B"; 991 992 try (ZFile zip = new ZFile(zipFile)) { 993 zip.add(lettuceIsHealthyArmenian, new ByteArrayInputStream(new byte[]{0})); 994 } 995 996 try (ZFile zip2 = new ZFile(zipFile)) { 997 assertEquals(1, zip2.entries().size()); 998 StoredEntry entry = zip2.entries().iterator().next(); 999 assertEquals(lettuceIsHealthyArmenian, entry.getCentralDirectoryHeader().getName()); 1000 } 1001 } 1002 1003 @Test zipMemoryUsageIsZeroAfterClose()1004 public void zipMemoryUsageIsZeroAfterClose() throws Exception { 1005 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1006 1007 ZFileOptions options = new ZFileOptions(); 1008 long used; 1009 try (ZFile zip = new ZFile(zipFile, options)) { 1010 1011 assertEquals(0, options.getTracker().getBytesUsed()); 1012 assertEquals(0, options.getTracker().getMaxBytesUsed()); 1013 1014 zip.add("Blah", new ByteArrayInputStream(new byte[500])); 1015 used = options.getTracker().getBytesUsed(); 1016 assertTrue(used > 500); 1017 assertEquals(used, options.getTracker().getMaxBytesUsed()); 1018 } 1019 1020 assertEquals(0, options.getTracker().getBytesUsed()); 1021 assertEquals(used, options.getTracker().getMaxBytesUsed()); 1022 } 1023 1024 @Test unusedZipAreasAreClearedOnWrite()1025 public void unusedZipAreasAreClearedOnWrite() throws Exception { 1026 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1027 ZFileOptions options = new ZFileOptions(); 1028 options.setAlignmentRule(AlignmentRules.constantForSuffix(".txt", 1000)); 1029 try (ZFile zf = new ZFile(zipFile, options)) { 1030 zf.add("test1.txt", new ByteArrayInputStream(new byte[]{1}), false); 1031 } 1032 1033 /* 1034 * Write dummy data in some unused portion of the file. 1035 */ 1036 try (RandomAccessFile raf = new RandomAccessFile(zipFile, "rw")) { 1037 1038 raf.seek(500); 1039 byte[] dummyData = "Dummy".getBytes(Charsets.US_ASCII); 1040 raf.write(dummyData); 1041 } 1042 1043 try (ZFile zf = new ZFile(zipFile)) { 1044 zf.touch(); 1045 } 1046 1047 try (RandomAccessFile raf = new RandomAccessFile(zipFile, "r")) { 1048 1049 /* 1050 * test1.txt won't take more than 200 bytes. Additionally, the header for 1051 */ 1052 byte[] data = new byte[900]; 1053 RandomAccessFileUtils.fullyRead(raf, data); 1054 1055 byte[] zeroData = new byte[data.length]; 1056 assertArrayEquals(zeroData, data); 1057 } 1058 } 1059 1060 @Test deferredCompression()1061 public void deferredCompression() throws Exception { 1062 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1063 1064 ExecutorService executor = Executors.newSingleThreadExecutor(); 1065 1066 ZFileOptions options = new ZFileOptions(); 1067 boolean[] done = new boolean[1]; 1068 options.setCompressor(new DeflateExecutionCompressor(executor, options.getTracker(), 1069 Deflater.BEST_COMPRESSION) { 1070 @Nonnull 1071 @Override 1072 protected CompressionResult immediateCompress(@Nonnull CloseableByteSource source) 1073 throws Exception { 1074 Thread.sleep(500); 1075 CompressionResult cr = super.immediateCompress(source); 1076 done[0] = true; 1077 return cr; 1078 } 1079 }); 1080 1081 try (ZFile zip = new ZFile(zipFile, options)) { 1082 byte sequences = 100; 1083 int seqCount = 1000; 1084 byte[] compressableData = new byte[sequences * seqCount]; 1085 for (byte i = 0; i < sequences; i++) { 1086 for (int j = 0; j < seqCount; j++) { 1087 compressableData[i * seqCount + j] = i; 1088 } 1089 } 1090 1091 zip.add("compressedFile", new ByteArrayInputStream(compressableData)); 1092 assertFalse(done[0]); 1093 1094 /* 1095 * Even before closing, eventually all the stream will be read. 1096 */ 1097 long tooLong = System.currentTimeMillis() + 10000; 1098 while (!done[0] && System.currentTimeMillis() < tooLong) { 1099 Thread.sleep(10); 1100 } 1101 1102 assertTrue(done[0]); 1103 } 1104 1105 executor.shutdownNow(); 1106 } 1107 1108 @Test zipFileWithEocdMarkerInComment()1109 public void zipFileWithEocdMarkerInComment() throws Exception { 1110 File zipFile = new File(mTemporaryFolder.getRoot(), "x"); 1111 1112 try (Closer closer = Closer.create()) { 1113 ZipOutputStream zos = closer.register( 1114 new ZipOutputStream(new FileOutputStream(zipFile))); 1115 zos.setComment("\u0065\u4b50"); 1116 zos.putNextEntry(new ZipEntry("foo")); 1117 zos.write(new byte[] { 1, 2, 3, 4 }); 1118 zos.close(); 1119 1120 ZFile zf = closer.register(new ZFile(zipFile)); 1121 StoredEntry entry = zf.get("foo"); 1122 assertNotNull(entry); 1123 assertEquals(4, entry.getCentralDirectoryHeader().getUncompressedSize()); 1124 } 1125 } 1126 1127 @Test zipFileWithEocdMarkerInFileName()1128 public void zipFileWithEocdMarkerInFileName() throws Exception { 1129 File zipFile = new File(mTemporaryFolder.getRoot(), "x"); 1130 1131 String fname = "tricky-\u0050\u004b\u0005\u0006"; 1132 byte[] bytes = new byte[] { 1, 2, 3, 4 }; 1133 1134 try (Closer closer = Closer.create()) { 1135 ZipOutputStream zos = closer.register( 1136 new ZipOutputStream(new FileOutputStream(zipFile))); 1137 zos.putNextEntry(new ZipEntry(fname)); 1138 zos.write(bytes); 1139 zos.close(); 1140 1141 ZFile zf = closer.register(new ZFile(zipFile)); 1142 StoredEntry entry = zf.get(fname); 1143 assertNotNull(entry); 1144 assertEquals(4, entry.getCentralDirectoryHeader().getUncompressedSize()); 1145 } 1146 } 1147 1148 @Test zipFileWithEocdMarkerInFileContents()1149 public void zipFileWithEocdMarkerInFileContents() throws Exception { 1150 File zipFile = new File(mTemporaryFolder.getRoot(), "x"); 1151 1152 byte[] bytes = new byte[] { 0x50, 0x4b, 0x05, 0x06 }; 1153 1154 try (Closer closer = Closer.create()) { 1155 ZipOutputStream zos = closer.register( 1156 new ZipOutputStream(new FileOutputStream(zipFile))); 1157 ZipEntry zipEntry = new ZipEntry("file"); 1158 zipEntry.setMethod(ZipEntry.STORED); 1159 zipEntry.setCompressedSize(4); 1160 zipEntry.setSize(4); 1161 zipEntry.setCrc(Hashing.crc32().hashBytes(bytes).padToLong()); 1162 zos.putNextEntry(zipEntry); 1163 zos.write(bytes); 1164 zos.close(); 1165 1166 ZFile zf = closer.register(new ZFile(zipFile)); 1167 StoredEntry entry = zf.get("file"); 1168 assertNotNull(entry); 1169 assertEquals(4, entry.getCentralDirectoryHeader().getUncompressedSize()); 1170 } 1171 } 1172 1173 @Test replaceVeryLargeFileWithBiggerInMiddleOfZip()1174 public void replaceVeryLargeFileWithBiggerInMiddleOfZip() throws Exception { 1175 File zipFile = new File(mTemporaryFolder.getRoot(), "x"); 1176 1177 long small1Offset; 1178 long small2Offset; 1179 ZFileOptions coverOptions = new ZFileOptions(); 1180 coverOptions.setCoverEmptySpaceUsingExtraField(true); 1181 try (ZFile zf = new ZFile(zipFile, coverOptions)) { 1182 zf.add("small1", new ByteArrayInputStream(new byte[] { 0, 1 })); 1183 } 1184 1185 try (ZFile zf = new ZFile(zipFile, coverOptions)) { 1186 zf.add("verybig", new ByteArrayInputStream(new byte[100_000]), false); 1187 } 1188 1189 try (ZFile zf = new ZFile(zipFile, coverOptions)) { 1190 zf.add("small2", new ByteArrayInputStream(new byte[] { 0, 1 })); 1191 } 1192 1193 try (ZFile zf = new ZFile(zipFile, coverOptions)) { 1194 StoredEntry se = zf.get("small1"); 1195 assertNotNull(se); 1196 small1Offset = se.getCentralDirectoryHeader().getOffset(); 1197 1198 se = zf.get("small2"); 1199 assertNotNull(se); 1200 small2Offset = se.getCentralDirectoryHeader().getOffset(); 1201 1202 se = zf.get("verybig"); 1203 assertNotNull(se); 1204 se.delete(); 1205 1206 zf.add("evenbigger", new ByteArrayInputStream(new byte[110_000]), false); 1207 } 1208 1209 try (ZFile zf = new ZFile(zipFile, coverOptions)) { 1210 StoredEntry se = zf.get("small1"); 1211 assertNotNull(se); 1212 assertEquals(se.getCentralDirectoryHeader().getOffset(), small1Offset); 1213 1214 se = zf.get("small2"); 1215 assertNotNull(se); 1216 assertNotEquals(se.getCentralDirectoryHeader().getOffset(), small2Offset); 1217 } 1218 } 1219 1220 @Test regressionRepackingDoesNotFail()1221 public void regressionRepackingDoesNotFail() throws Exception { 1222 File zipFile = new File(mTemporaryFolder.getRoot(), "x"); 1223 1224 ZFileOptions coverOptions = new ZFileOptions(); 1225 coverOptions.setCoverEmptySpaceUsingExtraField(true); 1226 try (ZFile zf = new ZFile(zipFile, coverOptions)) { 1227 zf.add("small_1", new ByteArrayInputStream(new byte[] { 0, 1 })); 1228 zf.add("very_big", new ByteArrayInputStream(new byte[100_000]), false); 1229 zf.add("small_2", new ByteArrayInputStream(new byte[] { 0, 1 })); 1230 zf.add("big", new ByteArrayInputStream(new byte[10_000]), false); 1231 zf.add("small_3", new ByteArrayInputStream(new byte[] { 0, 1 })); 1232 } 1233 1234 /* 1235 * Regression we're covering is that small_2 cannot be extended to cover up for the space 1236 * taken by very_big and needs to be repositioned. However, the algorithm to reposition 1237 * will put it in the best-fitting block, which is the one in "big", failing to actually 1238 * move it backwards in the file. 1239 */ 1240 try (ZFile zf = new ZFile(zipFile, coverOptions)) { 1241 StoredEntry se = zf.get("big"); 1242 assertNotNull(se); 1243 se.delete(); 1244 1245 se = zf.get("very_big"); 1246 assertNotNull(se); 1247 se.delete(); 1248 } 1249 } 1250 1251 @Test cannotAddMoreThan0x7fffExtraField()1252 public void cannotAddMoreThan0x7fffExtraField() throws Exception { 1253 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1254 1255 ZFileOptions zfo = new ZFileOptions(); 1256 zfo.setCoverEmptySpaceUsingExtraField(true); 1257 1258 /* 1259 * Create a zip file with: 1260 * 1261 * [small file][large file with exactly 0x8000 bytes][small file 2] 1262 */ 1263 long smallFile1Offset; 1264 long smallFile2Offset; 1265 long largeFileOffset; 1266 String largeFileName = "Large file"; 1267 try (ZFile zf = new ZFile(zipFile, zfo)) { 1268 zf.add("Small file", new ByteArrayInputStream(new byte[] { 0, 1 })); 1269 1270 int largeFileTotalSize = 0x8000; 1271 int largeFileContentsSize = 1272 largeFileTotalSize 1273 - ZFileTestConstants.LOCAL_HEADER_SIZE 1274 - largeFileName.length(); 1275 1276 zf.add(largeFileName, new ByteArrayInputStream(new byte[largeFileContentsSize]), false); 1277 zf.add("Small file 2", new ByteArrayInputStream(new byte[] { 0, 1 })); 1278 1279 zf.update(); 1280 1281 StoredEntry sfEntry = zf.get("Small file"); 1282 assertNotNull(sfEntry); 1283 smallFile1Offset = sfEntry.getCentralDirectoryHeader().getOffset(); 1284 assertEquals(0, smallFile1Offset); 1285 1286 StoredEntry lfEntry = zf.get(largeFileName); 1287 assertNotNull(lfEntry); 1288 largeFileOffset = lfEntry.getCentralDirectoryHeader().getOffset(); 1289 1290 StoredEntry sf2Entry = zf.get("Small file 2"); 1291 assertNotNull(sf2Entry); 1292 smallFile2Offset = sf2Entry.getCentralDirectoryHeader().getOffset(); 1293 1294 assertEquals(largeFileTotalSize, smallFile2Offset - largeFileOffset); 1295 } 1296 1297 /* 1298 * Remove the large file from the zip file and check that small file 2 has been moved, but 1299 * no extra field has been added. 1300 */ 1301 try (ZFile zf = new ZFile(zipFile, zfo)) { 1302 StoredEntry lfEntry = zf.get(largeFileName); 1303 assertNotNull(lfEntry); 1304 lfEntry.delete(); 1305 1306 zf.update(); 1307 1308 StoredEntry sfEntry = zf.get("Small file"); 1309 assertNotNull(sfEntry); 1310 smallFile1Offset = sfEntry.getCentralDirectoryHeader().getOffset(); 1311 assertEquals(0, smallFile1Offset); 1312 1313 StoredEntry sf2Entry = zf.get("Small file 2"); 1314 assertNotNull(sf2Entry); 1315 long newSmallFile2Offset = sf2Entry.getCentralDirectoryHeader().getOffset(); 1316 assertEquals(largeFileOffset, newSmallFile2Offset); 1317 1318 assertEquals(0, sf2Entry.getLocalExtra().size()); 1319 } 1320 } 1321 1322 @Test canAddMoreThan0x7fffExtraField()1323 public void canAddMoreThan0x7fffExtraField() throws Exception { 1324 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1325 1326 ZFileOptions zfo = new ZFileOptions(); 1327 zfo.setCoverEmptySpaceUsingExtraField(true); 1328 1329 /* 1330 * Create a zip file with: 1331 * 1332 * [small file][large file with exactly 0x7fff bytes][small file 2] 1333 */ 1334 long smallFile1Offset; 1335 long smallFile2Offset; 1336 long largeFileOffset; 1337 String largeFileName = "Large file"; 1338 int largeFileTotalSize = 0x7fff; 1339 try (ZFile zf = new ZFile(zipFile, zfo)) { 1340 zf.add("Small file", new ByteArrayInputStream(new byte[] { 0, 1 })); 1341 1342 int largeFileContentsSize = 1343 largeFileTotalSize 1344 - ZFileTestConstants.LOCAL_HEADER_SIZE 1345 - largeFileName.length(); 1346 1347 zf.add(largeFileName, new ByteArrayInputStream(new byte[largeFileContentsSize]), false); 1348 zf.add("Small file 2", new ByteArrayInputStream(new byte[] { 0, 1 })); 1349 1350 zf.update(); 1351 1352 StoredEntry sfEntry = zf.get("Small file"); 1353 assertNotNull(sfEntry); 1354 smallFile1Offset = sfEntry.getCentralDirectoryHeader().getOffset(); 1355 assertEquals(0, smallFile1Offset); 1356 1357 StoredEntry lfEntry = zf.get(largeFileName); 1358 assertNotNull(lfEntry); 1359 largeFileOffset = lfEntry.getCentralDirectoryHeader().getOffset(); 1360 1361 StoredEntry sf2Entry = zf.get("Small file 2"); 1362 assertNotNull(sf2Entry); 1363 smallFile2Offset = sf2Entry.getCentralDirectoryHeader().getOffset(); 1364 1365 assertEquals(largeFileTotalSize, smallFile2Offset - largeFileOffset); 1366 } 1367 1368 /* 1369 * Remove the large file from the zip file and check that small file 2 has been moved back 1370 * but with 0x7fff extra space added. 1371 */ 1372 try (ZFile zf = new ZFile(zipFile, zfo)) { 1373 StoredEntry lfEntry = zf.get(largeFileName); 1374 assertNotNull(lfEntry); 1375 lfEntry.delete(); 1376 1377 zf.update(); 1378 1379 StoredEntry sfEntry = zf.get("Small file"); 1380 assertNotNull(sfEntry); 1381 smallFile1Offset = sfEntry.getCentralDirectoryHeader().getOffset(); 1382 assertEquals(0, smallFile1Offset); 1383 1384 StoredEntry sf2Entry = zf.get("Small file 2"); 1385 assertNotNull(sf2Entry); 1386 long newSmallFile2Offset = sf2Entry.getCentralDirectoryHeader().getOffset(); 1387 1388 assertEquals(largeFileOffset, newSmallFile2Offset); 1389 assertEquals(largeFileTotalSize, sf2Entry.getLocalExtra().size()); 1390 } 1391 } 1392 1393 @Test detectIncorrectCRC32InLocalHeader()1394 public void detectIncorrectCRC32InLocalHeader() throws Exception { 1395 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1396 1397 /* 1398 * Zip files created by ZFile never have data descriptors so we need to create one using 1399 * java's zip. 1400 */ 1401 try ( 1402 FileOutputStream fos = new FileOutputStream(zipFile); 1403 ZipOutputStream zos = new ZipOutputStream(fos)) { 1404 ZipEntry ze = new ZipEntry("foo"); 1405 zos.putNextEntry(ze); 1406 byte[] randomBytes = new byte[512]; 1407 new Random().nextBytes(randomBytes); 1408 zos.write(randomBytes); 1409 } 1410 1411 /* 1412 * Open the zip file and compute where the local header CRC32 is. 1413 */ 1414 long crcOffset; 1415 try (ZFile zf = new ZFile(zipFile)) { 1416 StoredEntry se = zf.get("foo"); 1417 assertNotNull(se); 1418 long cdOffset = zf.getCentralDirectoryOffset(); 1419 1420 /* 1421 * Twelve bytes from the CD offset, we have the start of the CRC32 of the zip entry. 1422 */ 1423 crcOffset = cdOffset - 12; 1424 } 1425 1426 /* 1427 * Corrupt the CRC32. 1428 */ 1429 byte[] crc = readSegment(zipFile, crcOffset, 4); 1430 crc[0]++; 1431 try (RandomAccessFile raf = new RandomAccessFile(zipFile, "rw")) { 1432 raf.seek(crcOffset); 1433 raf.write(crc); 1434 } 1435 1436 /* 1437 * Now open the zip file and it should write a message in the log. 1438 */ 1439 ZFileOptions options = new ZFileOptions(); 1440 options.setVerifyLogFactory(VerifyLogs::unlimited); 1441 try (ZFile zf = new ZFile(zipFile, options)) { 1442 VerifyLog vl = zf.getVerifyLog(); 1443 assertTrue(vl.getLogs().isEmpty()); 1444 StoredEntry fooEntry = zf.get("foo"); 1445 vl = fooEntry.getVerifyLog(); 1446 assertEquals(1, vl.getLogs().size()); 1447 assertTrue(vl.getLogs().get(0).contains("CRC32")); 1448 } 1449 } 1450 1451 @Test detectIncorrectVersionToExtractInCentralDirectory()1452 public void detectIncorrectVersionToExtractInCentralDirectory() throws Exception { 1453 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1454 1455 /* 1456 * Create a valid zip file. 1457 */ 1458 try (ZFile zf = new ZFile(zipFile)) { 1459 zf.add("foo", new ByteArrayInputStream(new byte[0])); 1460 } 1461 1462 /* 1463 * Change the "version to extract" in the central directory to 0x7777. 1464 */ 1465 int versionToExtractOffset = 1466 ZFileTestConstants.LOCAL_HEADER_SIZE 1467 + 3 1468 + CentralDirectory.F_VERSION_EXTRACT.offset(); 1469 byte[] allZipBytes = Files.toByteArray(zipFile); 1470 allZipBytes[versionToExtractOffset] = 0x77; 1471 allZipBytes[versionToExtractOffset + 1] = 0x77; 1472 Files.write(allZipBytes, zipFile); 1473 1474 /* 1475 * Opening the file and it should write a message in the log. The entry has the right 1476 * version to extract (20), but it issues a warning because it is not equal to the one 1477 * in the central directory. 1478 */ 1479 ZFileOptions options = new ZFileOptions(); 1480 options.setVerifyLogFactory(VerifyLogs::unlimited); 1481 try (ZFile zf = new ZFile(zipFile, options)) { 1482 VerifyLog vl = zf.getVerifyLog(); 1483 assertEquals(1, vl.getLogs().size()); 1484 assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("version")); 1485 assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("extract")); 1486 StoredEntry fooEntry = zf.get("foo"); 1487 vl = fooEntry.getVerifyLog(); 1488 assertEquals(1, vl.getLogs().size()); 1489 assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("version")); 1490 assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("extract")); 1491 } 1492 } 1493 1494 @Test detectIncorrectVersionToExtractInLocalHeader()1495 public void detectIncorrectVersionToExtractInLocalHeader() throws Exception { 1496 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1497 1498 /* 1499 * Create a valid zip file. 1500 */ 1501 try (ZFile zf = new ZFile(zipFile)) { 1502 zf.add("foo", new ByteArrayInputStream(new byte[0])); 1503 } 1504 1505 /* 1506 * Change the "version to extract" in the local header to 0x7777. 1507 */ 1508 int versionToExtractOffset = StoredEntry.F_VERSION_EXTRACT.offset(); 1509 byte[] allZipBytes = Files.toByteArray(zipFile); 1510 allZipBytes[versionToExtractOffset] = 0x77; 1511 allZipBytes[versionToExtractOffset + 1] = 0x77; 1512 Files.write(allZipBytes, zipFile); 1513 1514 /* 1515 * Opening the file should log an error message. 1516 */ 1517 ZFileOptions options = new ZFileOptions(); 1518 options.setVerifyLogFactory(VerifyLogs::unlimited); 1519 try (ZFile zf = new ZFile(zipFile, options)) { 1520 VerifyLog vl = zf.getVerifyLog(); 1521 assertTrue(vl.getLogs().isEmpty()); 1522 StoredEntry fooEntry = zf.get("foo"); 1523 vl = fooEntry.getVerifyLog(); 1524 assertEquals(1, vl.getLogs().size()); 1525 assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("version")); 1526 assertTrue(vl.getLogs().get(0).toLowerCase(Locale.US).contains("extract")); 1527 } 1528 } 1529 1530 @Test sortZipContentsWithDeferredCrc32()1531 public void sortZipContentsWithDeferredCrc32() throws Exception { 1532 /* 1533 * Create a zip file with deferred CRC32 and files in non-alphabetical order. 1534 * ZipOutputStream always creates deferred CRC32 entries. 1535 */ 1536 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1537 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 1538 zos.putNextEntry(new ZipEntry("b")); 1539 zos.write(new byte[1000]); 1540 zos.putNextEntry(new ZipEntry("a")); 1541 zos.write(new byte[1000]); 1542 } 1543 1544 /* 1545 * Now open the zip using a ZFile and sort the contents and check that the deferred CRC32 1546 * bits were reset. 1547 */ 1548 try (ZFile zf = new ZFile(zipFile)) { 1549 StoredEntry a = zf.get("a"); 1550 assertNotNull(a); 1551 assertNotSame(DataDescriptorType.NO_DATA_DESCRIPTOR, a.getDataDescriptorType()); 1552 StoredEntry b = zf.get("b"); 1553 assertNotNull(b); 1554 assertNotSame(DataDescriptorType.NO_DATA_DESCRIPTOR, b.getDataDescriptorType()); 1555 assertTrue( 1556 a.getCentralDirectoryHeader().getOffset() 1557 > b.getCentralDirectoryHeader().getOffset()); 1558 1559 zf.sortZipContents(); 1560 zf.update(); 1561 1562 a = zf.get("a"); 1563 assertNotNull(a); 1564 assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, a.getDataDescriptorType()); 1565 b = zf.get("b"); 1566 assertNotNull(b); 1567 assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, b.getDataDescriptorType()); 1568 1569 assertTrue( 1570 a.getCentralDirectoryHeader().getOffset() 1571 < b.getCentralDirectoryHeader().getOffset()); 1572 } 1573 1574 /* 1575 * Open the file again and check there are no warnings. 1576 */ 1577 try (ZFile zf = new ZFile(zipFile)) { 1578 VerifyLog vl = zf.getVerifyLog(); 1579 assertEquals(0, vl.getLogs().size()); 1580 1581 StoredEntry a = zf.get("a"); 1582 assertNotNull(a); 1583 vl = a.getVerifyLog(); 1584 assertEquals(0, vl.getLogs().size()); 1585 1586 StoredEntry b = zf.get("b"); 1587 assertNotNull(b); 1588 vl = b.getVerifyLog(); 1589 assertEquals(0, vl.getLogs().size()); 1590 } 1591 } 1592 1593 @Test 1594 public void alignZipContentsWithDeferredCrc32() throws Exception { 1595 /* 1596 * Create an unaligned zip file with deferred CRC32 and files in non-alphabetical order. 1597 * We need an uncompressed file to make realigning have any effect. 1598 */ 1599 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1600 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 1601 zos.putNextEntry(new ZipEntry("x")); 1602 zos.write(new byte[1000]); 1603 zos.putNextEntry(new ZipEntry("y")); 1604 zos.write(new byte[1000]); 1605 ZipEntry zEntry = new ZipEntry("z"); 1606 zEntry.setSize(1000); 1607 zEntry.setMethod(ZipEntry.STORED); 1608 zEntry.setCrc(Hashing.crc32().hashBytes(new byte[1000]).asInt()); 1609 zos.putNextEntry(zEntry); 1610 zos.write(new byte[1000]); 1611 } 1612 1613 /* 1614 * Now open the zip using a ZFile and realign the contents and check that the deferred CRC32 1615 * bits were reset. 1616 */ 1617 ZFileOptions options = new ZFileOptions(); 1618 options.setAlignmentRule(AlignmentRules.constant(2000)); 1619 try (ZFile zf = new ZFile(zipFile, options)) { 1620 StoredEntry x = zf.get("x"); 1621 assertNotNull(x); 1622 assertNotSame(DataDescriptorType.NO_DATA_DESCRIPTOR, x.getDataDescriptorType()); 1623 StoredEntry y = zf.get("y"); 1624 assertNotNull(y); 1625 assertNotSame(DataDescriptorType.NO_DATA_DESCRIPTOR, y.getDataDescriptorType()); 1626 StoredEntry z = zf.get("z"); 1627 assertNotNull(z); 1628 assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, z.getDataDescriptorType()); 1629 1630 zf.realign(); 1631 zf.update(); 1632 1633 x = zf.get("x"); 1634 assertNotNull(x); 1635 assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, x.getDataDescriptorType()); 1636 y = zf.get("y"); 1637 assertNotNull(y); 1638 assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, y.getDataDescriptorType()); 1639 z = zf.get("z"); 1640 assertNotNull(z); 1641 assertSame(DataDescriptorType.NO_DATA_DESCRIPTOR, z.getDataDescriptorType()); 1642 } 1643 } 1644 1645 @Test 1646 public void openingZFileDoesNotRemoveDataDescriptors() throws Exception { 1647 /* 1648 * Create a zip file with deferred CRC32. 1649 */ 1650 File zipFile = new File(mTemporaryFolder.getRoot(), "a.zip"); 1651 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { 1652 zos.putNextEntry(new ZipEntry("a")); 1653 zos.write(new byte[1000]); 1654 } 1655 1656 /* 1657 * Open using ZFile and check that the deferred CRC32 is there. 1658 */ 1659 try (ZFile zf = new ZFile(zipFile)) { 1660 StoredEntry se = zf.get("a"); 1661 assertNotNull(se); 1662 assertNotEquals(DataDescriptorType.NO_DATA_DESCRIPTOR, se.getDataDescriptorType()); 1663 } 1664 1665 /* 1666 * Open using ZFile (again) and check that the deferred CRC32 is there. 1667 */ 1668 try (ZFile zf = new ZFile(zipFile)) { 1669 StoredEntry se = zf.get("a"); 1670 assertNotNull(se); 1671 assertNotEquals(DataDescriptorType.NO_DATA_DESCRIPTOR, se.getDataDescriptorType()); 1672 } 1673 } 1674 1675 @Test 1676 public void zipCommentsAreSaved() throws Exception { 1677 File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); 1678 try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFileWithComments))) { 1679 zos.setComment("foo"); 1680 } 1681 1682 /* 1683 * Open the zip file and check the comment is there. 1684 */ 1685 try (ZFile zf = new ZFile(zipFileWithComments)) { 1686 byte[] comment = zf.getEocdComment(); 1687 assertArrayEquals(new byte[] { 'f', 'o', 'o' }, comment); 1688 1689 /* 1690 * Modify the comment and write the file. 1691 */ 1692 zf.setEocdComment(new byte[] { 'b', 'a', 'r', 'r' }); 1693 } 1694 1695 /* 1696 * Open the file and see that the comment is there (both with java and zfile). 1697 */ 1698 try (ZipFile zf2 = new ZipFile(zipFileWithComments)) { 1699 assertEquals("barr", zf2.getComment()); 1700 } 1701 1702 try (ZFile zf3 = new ZFile(zipFileWithComments)) { 1703 assertArrayEquals(new byte[] { 'b', 'a', 'r', 'r' }, zf3.getEocdComment()); 1704 } 1705 } 1706 1707 @Test 1708 public void eocdCommentsWithMoreThan64kNotAllowed() throws Exception { 1709 File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); 1710 try (ZFile zf = new ZFile(zipFileWithComments)) { 1711 try { 1712 zf.setEocdComment(new byte[65536]); 1713 fail(); 1714 } catch (IllegalArgumentException e) { 1715 // Expected. 1716 } 1717 1718 zf.setEocdComment(new byte[65535]); 1719 } 1720 } 1721 1722 @Test 1723 public void eocdCommentsWithTheEocdMarkerAreAllowed() throws Exception { 1724 File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); 1725 byte[] data = new byte[100]; 1726 data[50] = 0x50; // Signature 1727 data[51] = 0x4b; 1728 data[52] = 0x05; 1729 data[53] = 0x06; 1730 data[54] = 0x00; // Number of disk 1731 data[55] = 0x00; 1732 data[56] = 0x00; // Disk CD start 1733 data[57] = 0x00; 1734 data[54] = 0x01; // Total records 1 1735 data[55] = 0x00; 1736 data[56] = 0x02; // Total records 2, must be = to total records 1 1737 data[57] = 0x00; 1738 1739 try (ZFile zf = new ZFile(zipFileWithComments)) { 1740 zf.setEocdComment(data); 1741 } 1742 1743 try (ZFile zf = new ZFile(zipFileWithComments)) { 1744 assertArrayEquals(data, zf.getEocdComment()); 1745 } 1746 } 1747 1748 @Test 1749 public void eocdCommentsWithTheEocdMarkerThatAreInvalidAreNotAllowed() throws Exception { 1750 File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); 1751 byte[] data = new byte[100]; 1752 data[50] = 0x50; 1753 data[51] = 0x4b; 1754 data[52] = 0x05; 1755 data[53] = 0x06; 1756 data[67] = 0x00; 1757 1758 try (ZFile zf = new ZFile(zipFileWithComments)) { 1759 try { 1760 zf.setEocdComment(data); 1761 fail(); 1762 } catch (IllegalArgumentException e) { 1763 // Expected. 1764 } 1765 } 1766 } 1767 1768 @Test 1769 public void zipCommentsArePreservedWithFileChanges() throws Exception { 1770 File zipFileWithComments = new File(mTemporaryFolder.getRoot(), "a.zip"); 1771 byte[] comment = new byte[] { 1, 3, 4 }; 1772 try (ZFile zf = new ZFile(zipFileWithComments)) { 1773 zf.add("foo", new ByteArrayInputStream(new byte[50])); 1774 zf.setEocdComment(comment); 1775 } 1776 1777 try (ZFile zf = new ZFile(zipFileWithComments)) { 1778 assertArrayEquals(comment, zf.getEocdComment()); 1779 zf.add("bar", new ByteArrayInputStream(new byte[100])); 1780 } 1781 1782 try (ZFile zf = new ZFile(zipFileWithComments)) { 1783 assertArrayEquals(comment, zf.getEocdComment()); 1784 } 1785 } 1786 1787 @Test 1788 public void overlappingZipEntries() throws Exception { 1789 File myZip = ZipTestUtils.cloneRsrc("overlapping.zip", mTemporaryFolder); 1790 try (ZFile zf = new ZFile(myZip)) { 1791 fail(); 1792 } catch (IOException e) { 1793 assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/bbb")); 1794 assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/ddd")); 1795 assertFalse(Throwables.getStackTraceAsString(e).contains("Central Directory")); 1796 } 1797 } 1798 1799 @Test 1800 public void overlappingZipEntryWithCentralDirectory() throws Exception { 1801 File myZip = ZipTestUtils.cloneRsrc("overlapping2.zip", mTemporaryFolder); 1802 try (ZFile zf = new ZFile(myZip)) { 1803 fail(); 1804 } catch (IOException e) { 1805 assertFalse(Throwables.getStackTraceAsString(e).contains("overlapping/bbb")); 1806 assertTrue(Throwables.getStackTraceAsString(e).contains("overlapping/ddd")); 1807 assertTrue(Throwables.getStackTraceAsString(e).contains("Central Directory")); 1808 } 1809 } 1810 1811 @Test 1812 public void readFileWithOffsetBeyondFileEnd() throws Exception { 1813 File myZip = ZipTestUtils.cloneRsrc("entry-outside-file.zip", mTemporaryFolder); 1814 try (ZFile zf = new ZFile(myZip)) { 1815 fail(); 1816 } catch (IOException e) { 1817 assertTrue(Throwables.getStackTraceAsString(e).contains("entry-outside-file/foo")); 1818 assertTrue(Throwables.getStackTraceAsString(e).contains("EOF")); 1819 } 1820 } 1821 } 1822