1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package org.apache.commons.compress.archivers.zip; 20 21 import static org.apache.commons.compress.AbstractTestCase.getFile; 22 import static org.junit.Assert.assertArrayEquals; 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 import static org.junit.Assume.assumeTrue; 30 31 import java.io.BufferedOutputStream; 32 import java.io.File; 33 import java.io.FileInputStream; 34 import java.io.FileOutputStream; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.io.RandomAccessFile; 38 import java.util.Enumeration; 39 import java.util.Random; 40 import java.util.zip.ZipEntry; 41 42 import org.apache.commons.compress.AbstractTestCase; 43 import org.junit.Test; 44 45 public class Zip64SupportIT { 46 47 private static final long FIVE_BILLION = 5000000000l; 48 private static final int ONE_MILLION = 1000000; 49 private static final int ONE_HUNDRED_THOUSAND = 100000; 50 51 @Test public void read5GBOfZerosUsingInputStream() throws Throwable { 52 read5GBOfZerosImpl(get5GBZerosFile(), "5GB_of_Zeros"); 53 } 54 55 @Test public void read5GBOfZerosGeneratedBy7ZIPUsingInputStream() 56 throws Throwable { 57 read5GBOfZerosImpl(get5GBZerosFileGeneratedBy7ZIP(), "5GB_of_Zeros"); 58 } 59 60 @Test public void read5GBOfZerosGeneratedByJava7JarUsingInputStream() 61 throws Throwable { 62 read5GBOfZerosImpl(get5GBZerosFileGeneratedByJava7Jar(), "5GB_of_Zeros"); 63 } 64 65 @Test public void read5GBOfZerosGeneratedByWinZIPUsingInputStream() 66 throws Throwable { 67 read5GBOfZerosImpl(get5GBZerosFileGeneratedByWinZIP(), "5GB_of_Zeros"); 68 } 69 70 @Test public void read5GBOfZerosGeneratedByPKZipUsingInputStream() 71 throws Throwable { 72 read5GBOfZerosImpl(get5GBZerosFileGeneratedByPKZip(), 73 "zip6/5GB_of_Zeros"); 74 } 75 76 @Test public void read100KFilesUsingInputStream() throws Throwable { 77 read100KFilesImpl(get100KFileFile()); 78 } 79 80 @Test public void read100KFilesGeneratedBy7ZIPUsingInputStream() 81 throws Throwable { 82 read100KFilesImpl(get100KFileFileGeneratedBy7ZIP()); 83 } 84 85 @Test public void read100KFilesGeneratedByWinCFUsingInputStream() 86 throws Throwable { 87 read100KFilesImpl(get100KFileFileGeneratedByWinCF()); 88 } 89 90 @Test public void read100KFilesGeneratedByJava7JarUsingInputStream() 91 throws Throwable { 92 read100KFilesImpl(get100KFileFileGeneratedByJava7Jar()); 93 } 94 95 @Test public void read100KFilesGeneratedByWinZIPUsingInputStream() 96 throws Throwable { 97 read100KFilesImpl(get100KFileFileGeneratedByWinZIP()); 98 } 99 100 @Test public void read100KFilesGeneratedByPKZipUsingInputStream() 101 throws Throwable { 102 read100KFilesImpl(get100KFileFileGeneratedByPKZip()); 103 } 104 105 @Test public void read5GBOfZerosUsingZipFile() throws Throwable { 106 read5GBOfZerosUsingZipFileImpl(get5GBZerosFile(), "5GB_of_Zeros"); 107 } 108 109 @Test public void read5GBOfZerosGeneratedBy7ZIPUsingZipFile() 110 throws Throwable { 111 read5GBOfZerosUsingZipFileImpl(get5GBZerosFileGeneratedBy7ZIP(), 112 "5GB_of_Zeros"); 113 } 114 115 @Test public void read5GBOfZerosGeneratedByJava7JarUsingZipFile() 116 throws Throwable { 117 read5GBOfZerosUsingZipFileImpl(get5GBZerosFileGeneratedByJava7Jar(), 118 "5GB_of_Zeros"); 119 } 120 121 @Test public void read5GBOfZerosGeneratedByWinZIPUsingZipFile() 122 throws Throwable { 123 read5GBOfZerosUsingZipFileImpl(get5GBZerosFileGeneratedByWinZIP(), 124 "5GB_of_Zeros"); 125 } 126 127 @Test public void read5GBOfZerosGeneratedByPKZipUsingZipFile() 128 throws Throwable { 129 read5GBOfZerosUsingZipFileImpl(get5GBZerosFileGeneratedByPKZip(), 130 "zip6/5GB_of_Zeros"); 131 } 132 133 @Test public void writeAndRead5GBOfZerosUsingZipFile() throws Throwable { 134 File f = null; 135 try { 136 f = write5GBZerosFile("writeAndRead5GBOfZerosUsingZipFile"); 137 read5GBOfZerosUsingZipFileImpl(f, "5GB_of_Zeros"); 138 } finally { 139 if (f != null) { 140 AbstractTestCase.tryHardToDelete(f); 141 } 142 } 143 } 144 145 private static File write5GBZerosFile(final String testName) throws Throwable { 146 final File f = getTempFile(testName); 147 final ZipArchiveOutputStream zos = new ZipArchiveOutputStream(f); 148 try { 149 zos.setUseZip64(Zip64Mode.Always); 150 final byte[] buf = new byte[ONE_MILLION]; 151 final ZipArchiveEntry zae = new ZipArchiveEntry("5GB_of_Zeros"); 152 zae.setSize(FIVE_BILLION); 153 zae.setMethod(ZipEntry.DEFLATED); 154 zae.setCrc(0x8a408f16L); 155 zos.putArchiveEntry(zae); 156 for (int j = 0; j < FIVE_BILLION / 1000 / 1000; j++) { 157 zos.write(buf); 158 } 159 zos.closeArchiveEntry(); 160 zos.close(); 161 } catch (final IOException ex) { 162 System.err.println("Failed to write archive because of: " 163 + ex.getMessage() 164 + " - likely not enough disk space."); 165 assumeTrue(false); 166 } finally { 167 zos.destroy(); 168 } 169 return f; 170 } 171 172 @Test public void read100KFilesUsingZipFile() throws Throwable { 173 read100KFilesUsingZipFileImpl(get100KFileFile()); 174 } 175 176 @Test public void read100KFilesGeneratedBy7ZIPUsingZipFile() 177 throws Throwable { 178 read100KFilesUsingZipFileImpl(get100KFileFileGeneratedBy7ZIP()); 179 } 180 181 @Test public void read100KFilesGeneratedByWinCFUsingZipFile() 182 throws Throwable { 183 read100KFilesUsingZipFileImpl(get100KFileFileGeneratedByWinCF()); 184 } 185 186 @Test public void read100KFilesGeneratedByJava7JarUsingZipFile() 187 throws Throwable { 188 read100KFilesUsingZipFileImpl(get100KFileFileGeneratedByJava7Jar()); 189 } 190 191 @Test public void read100KFilesGeneratedByWinZIPUsingZipFile() 192 throws Throwable { 193 read100KFilesUsingZipFileImpl(get100KFileFileGeneratedByWinZIP()); 194 } 195 196 @Test public void read100KFilesGeneratedByPKZipUsingZipFile() 197 throws Throwable { 198 read100KFilesUsingZipFileImpl(get100KFileFileGeneratedByPKZip()); 199 } 200 201 private static ZipOutputTest write100KFiles() { 202 return write100KFiles(Zip64Mode.AsNeeded); 203 } 204 205 private static ZipOutputTest write100KFiles(final Zip64Mode mode) { 206 return new ZipOutputTest() { 207 @Override 208 public void test(final File f, final ZipArchiveOutputStream zos) 209 throws IOException { 210 if (mode != Zip64Mode.AsNeeded) { 211 zos.setUseZip64(mode); 212 } 213 write100KFilesToStream(zos); 214 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 215 final long end = a.length(); 216 217 // validate "end of central directory" is at 218 // the end of the file and contains the magic 219 // value 0xFFFF as "number of entries". 220 a.seek(end 221 - 22 /* length of EOCD without file comment */); 222 final byte[] eocd = new byte[12]; 223 a.readFully(eocd); 224 assertArrayEquals(new byte[] { 225 // sig 226 (byte) 0x50, (byte) 0x4b, 5, 6, 227 // disk numbers 228 0, 0, 0, 0, 229 // entries 230 (byte) 0xff, (byte) 0xff, 231 (byte) 0xff, (byte) 0xff, 232 }, eocd); 233 234 // validate "Zip64 end of central directory 235 // locator" is right in front of the EOCD and 236 // the location of the "Zip64 end of central 237 // directory record" seems correct 238 final long expectedZ64EocdOffset = end - 22 /* eocd.length */ 239 - 20 /* z64 eocd locator.length */ 240 - 56 /* z64 eocd without extensible data sector */; 241 final byte[] loc = 242 ZipEightByteInteger.getBytes(expectedZ64EocdOffset); 243 a.seek(end - 22 - 20); 244 final byte[] z64EocdLoc = new byte[20]; 245 a.readFully(z64EocdLoc); 246 assertArrayEquals(new byte[] { 247 // sig 248 (byte) 0x50, (byte) 0x4b, 6, 7, 249 // disk numbers 250 0, 0, 0, 0, 251 // location of Zip64 EOCD, 252 loc[0], loc[1], loc[2], loc[3], 253 loc[4], loc[5], loc[6], loc[7], 254 // total number of disks 255 1, 0, 0, 0, 256 }, z64EocdLoc); 257 258 // validate "Zip64 end of central directory 259 // record" is where it is supposed to be, the 260 // known values are fine and read the location 261 // of the central directory from it 262 a.seek(expectedZ64EocdOffset); 263 final byte[] z64EocdStart = new byte[40]; 264 a.readFully(z64EocdStart); 265 assertArrayEquals(new byte[] { 266 // sig 267 (byte) 0x50, (byte) 0x4b, 6, 6, 268 // size of z64 EOCD 269 44, 0, 0, 0, 270 0, 0, 0, 0, 271 // version made by 272 45, 0, 273 // version needed to extract 274 45, 0, 275 // disk numbers 276 0, 0, 0, 0, 277 0, 0, 0, 0, 278 // number of entries 100k = 0x186A0 279 (byte) 0xA0, (byte) 0x86, 1, 0, 280 0, 0, 0, 0, 281 (byte) 0xA0, (byte) 0x86, 1, 0, 282 0, 0, 0, 0, 283 }, z64EocdStart); 284 a.seek(expectedZ64EocdOffset + 48 /* skip size */); 285 final byte[] cdOffset = new byte[8]; 286 a.readFully(cdOffset); 287 final long cdLoc = ZipEightByteInteger.getLongValue(cdOffset); 288 289 // finally verify there really is a central 290 // directory entry where the Zip64 EOCD claims 291 a.seek(cdLoc); 292 final byte[] sig = new byte[4]; 293 a.readFully(sig); 294 assertArrayEquals(new byte[] { 295 (byte) 0x50, (byte) 0x4b, 1, 2, 296 }, sig); 297 } 298 } 299 }; 300 } 301 302 @Test public void write100KFilesFile() throws Throwable { 303 withTemporaryArchive("write100KFilesFile", write100KFiles(), true); 304 } 305 306 @Test public void write100KFilesStream() throws Throwable { 307 withTemporaryArchive("write100KFilesStream", write100KFiles(), false); 308 } 309 310 @Test public void write100KFilesFileModeAlways() throws Throwable { 311 withTemporaryArchive("write100KFilesFileModeAlways", 312 write100KFiles(Zip64Mode.Always), true); 313 } 314 315 @Test public void write100KFilesStreamModeAlways() throws Throwable { 316 withTemporaryArchive("write100KFilesStreamModeAlways", 317 write100KFiles(Zip64Mode.Always), false); 318 } 319 320 private static final ZipOutputTest write100KFilesModeNever = 321 new ZipOutputTest() { 322 @Override 323 public void test(final File f, final ZipArchiveOutputStream zos) 324 throws IOException { 325 zos.setUseZip64(Zip64Mode.Never); 326 try { 327 write100KFilesToStream(zos); 328 fail("expected a Zip64RequiredException"); 329 } catch (final Zip64RequiredException ex) { 330 assertEquals(Zip64RequiredException.TOO_MANY_ENTRIES_MESSAGE, 331 ex.getMessage()); 332 } 333 } 334 }; 335 336 @Test public void write100KFilesFileModeNever() throws Throwable { 337 withTemporaryArchive("write100KFilesFileModeNever", 338 write100KFilesModeNever, true); 339 } 340 341 @Test public void write100KFilesStreamModeNever() throws Throwable { 342 withTemporaryArchive("write100KFilesStreamModeNever", 343 write100KFilesModeNever, false); 344 } 345 346 @Test public void readSelfGenerated100KFilesUsingZipFile() 347 throws Throwable { 348 withTemporaryArchive("readSelfGenerated100KFilesUsingZipFile()", 349 new ZipOutputTest() { 350 @Override 351 public void test(final File f, 352 final ZipArchiveOutputStream zos) 353 throws IOException { 354 write100KFilesToStream(zos); 355 read100KFilesUsingZipFileImpl(f); 356 } 357 }, 358 true); 359 } 360 361 private static ZipOutputTest write3EntriesCreatingBigArchive() { 362 return write3EntriesCreatingBigArchive(Zip64Mode.AsNeeded); 363 } 364 365 /* 366 * Individual sizes don't require ZIP64 but the offset of the 367 * third entry is bigger than 0xFFFFFFFF so a ZIP64 extended 368 * information is needed inside the central directory. 369 * 370 * Creates a temporary archive of approx 5GB in size 371 */ 372 private static ZipOutputTest 373 write3EntriesCreatingBigArchive(final Zip64Mode mode) { 374 return new ZipOutputTest() { 375 @Override 376 public void test(final File f, final ZipArchiveOutputStream zos) 377 throws IOException { 378 if (mode != Zip64Mode.AsNeeded) { 379 zos.setUseZip64(mode); 380 } 381 write3EntriesCreatingBigArchiveToStream(zos); 382 383 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 384 getLengthAndPositionAtCentralDirectory(a); 385 // skip first two entries 386 a.skipBytes(2 * 47 /* CD entry of file with 387 file name length 1 and no 388 extra data */ 389 + 2 * (mode == Zip64Mode.Always ? 28 : 0) 390 /* ZIP64 extra fields if mode is Always */ 391 ); 392 393 // grab third entry, verify offset is 394 // 0xFFFFFFFF and it has a ZIP64 extended 395 // information extra field 396 final byte[] header = new byte[12]; 397 a.readFully(header); 398 assertArrayEquals("CDH start", new byte[] { 399 // sig 400 (byte) 0x50, (byte) 0x4b, 1, 2, 401 // version made by 402 45, 0, 403 // version needed to extract 404 45, 0, 405 // GPB (EFS bit) 406 0, 8, 407 // method 408 0, 0 409 }, header); 410 // ignore timestamp, CRC, compressed size 411 a.skipBytes(12); 412 // Original Size 413 final byte[] originalSize = new byte[4]; 414 a.readFully(originalSize); 415 if (mode == Zip64Mode.Always) { 416 assertArrayEquals("CDH original size", new byte[] { 417 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 418 }, originalSize); 419 } else { 420 assertArrayEquals("CDH original size", new byte[] { 421 1, 0, 0, 0 422 }, originalSize); 423 } 424 final byte[] rest = new byte[19]; 425 a.readFully(rest); 426 assertArrayEquals("CDH rest", new byte[] { 427 // file name length 428 1, 0, 429 // extra field length 430 (byte) (mode == Zip64Mode.Always? 28 : 12), 0, 431 // comment length 432 0, 0, 433 // disk number 434 0, 0, 435 // attributes 436 0, 0, 437 0, 0, 0, 0, 438 // offset 439 (byte) 0xFF, (byte) 0xFF, 440 (byte) 0xFF, (byte) 0xFF, 441 // file name 442 (byte) '2' 443 }, rest); 444 if (mode == Zip64Mode.Always) { 445 final byte[] extra = new byte[12]; 446 a.readFully(extra); 447 assertArrayEquals("CDH extra", new byte[] { 448 // Header-ID 449 1, 0, 450 // size 451 24, 0, 452 // Original Size 453 1, 0, 0, 0, 0, 0, 0, 0, 454 }, extra); 455 // skip compressed size 456 a.skipBytes(8); 457 } else { 458 final byte[] extra = new byte[4]; 459 a.readFully(extra); 460 assertArrayEquals("CDH extra", new byte[] { 461 // Header-ID 462 1, 0, 463 // size 464 8, 0, 465 }, extra); 466 } 467 468 // read offset of LFH 469 final byte[] offset = new byte[8]; 470 a.readFully(offset); 471 // verify there is a LFH where the CD claims it 472 a.seek(ZipEightByteInteger.getLongValue(offset)); 473 final byte[] sig = new byte[4]; 474 a.readFully(sig); 475 assertArrayEquals("LFH signature", new byte[] { 476 (byte) 0x50, (byte) 0x4b, 3, 4, 477 }, sig); 478 } 479 } 480 }; 481 } 482 483 @Test public void write3EntriesCreatingBigArchiveFile() throws Throwable { 484 withTemporaryArchive("write3EntriesCreatingBigArchiveFile", 485 write3EntriesCreatingBigArchive(), 486 true); 487 } 488 489 @Test public void write3EntriesCreatingBigArchiveStream() throws Throwable { 490 withTemporaryArchive("write3EntriesCreatingBigArchiveStream", 491 write3EntriesCreatingBigArchive(), 492 false); 493 } 494 495 @Test public void write3EntriesCreatingBigArchiveFileModeAlways() 496 throws Throwable { 497 withTemporaryArchive("write3EntriesCreatingBigArchiveFileModeAlways", 498 write3EntriesCreatingBigArchive(Zip64Mode.Always), 499 true); 500 } 501 502 @Test public void write3EntriesCreatingBigArchiveStreamModeAlways() 503 throws Throwable { 504 withTemporaryArchive("write3EntriesCreatingBigArchiveStreamModeAlways", 505 write3EntriesCreatingBigArchive(Zip64Mode.Always), 506 false); 507 } 508 509 private static final ZipOutputTest write3EntriesCreatingBigArchiveModeNever = 510 new ZipOutputTest() { 511 @Override 512 public void test(final File f, final ZipArchiveOutputStream zos) 513 throws IOException { 514 zos.setUseZip64(Zip64Mode.Never); 515 try { 516 write3EntriesCreatingBigArchiveToStream(zos); 517 fail("expected a Zip64RequiredException"); 518 } catch (final Zip64RequiredException ex) { 519 assertEquals(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE, 520 ex.getMessage()); 521 } 522 } 523 }; 524 525 @Test public void write3EntriesCreatingBigArchiveFileModeNever() 526 throws Throwable { 527 withTemporaryArchive("write3EntriesCreatingBigArchiveFileModeNever", 528 write3EntriesCreatingBigArchiveModeNever, 529 true); 530 } 531 532 @Test public void write3EntriesCreatingBigArchiveStreamModeNever() 533 throws Throwable { 534 withTemporaryArchive("write3EntriesCreatingBigArchiveStreamModeNever", 535 write3EntriesCreatingBigArchiveModeNever, 536 false); 537 } 538 539 @Test public void read3EntriesCreatingBigArchiveFileUsingZipFile() 540 throws Throwable { 541 withTemporaryArchive("read3EntriesCreatingBigArchiveFileUsingZipFile", 542 new ZipOutputTest() { 543 @Override 544 public void test(final File f, 545 final ZipArchiveOutputStream zos) 546 throws IOException { 547 write3EntriesCreatingBigArchiveToStream(zos); 548 ZipFile zf = null; 549 try { 550 zf = new ZipFile(f); 551 int idx = 0; 552 for (final Enumeration<ZipArchiveEntry> e = 553 zf.getEntriesInPhysicalOrder(); 554 e.hasMoreElements(); ) { 555 final ZipArchiveEntry zae = e.nextElement(); 556 assertEquals(String.valueOf(idx), 557 zae.getName()); 558 if (idx++ < 2) { 559 assertEquals(FIVE_BILLION / 2, 560 zae.getSize()); 561 } else { 562 assertEquals(1, 563 zae.getSize()); 564 try (InputStream i = zf.getInputStream(zae)) { 565 assertNotNull(i); 566 assertEquals(42, i.read()); 567 } 568 } 569 } 570 } finally { 571 ZipFile.closeQuietly(zf); 572 } 573 } 574 }, 575 true); 576 } 577 578 private static ZipOutputTest writeBigStoredEntry(final boolean knownSize) { 579 return writeBigStoredEntry(knownSize, Zip64Mode.AsNeeded); 580 } 581 582 /* 583 * One entry of length 5 billion bytes, written without 584 * compression. 585 * 586 * No Compression => sizes are stored directly inside the LFH. No 587 * Data Descriptor at all. 588 * 589 * Creates a temporary archive of approx 5GB in size 590 */ 591 private static ZipOutputTest writeBigStoredEntry(final boolean knownSize, 592 final Zip64Mode mode) { 593 return new ZipOutputTest() { 594 @Override 595 public void test(final File f, final ZipArchiveOutputStream zos) 596 throws IOException { 597 if (mode != Zip64Mode.AsNeeded) { 598 zos.setUseZip64(mode); 599 } 600 final byte[] buf = new byte[ONE_MILLION]; 601 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 602 if (knownSize) { 603 zae.setSize(FIVE_BILLION); 604 zae.setCrc(0x5c316f50L); 605 } 606 zae.setMethod(ZipEntry.STORED); 607 zos.putArchiveEntry(zae); 608 for (int j = 0; j < FIVE_BILLION / 1000 / 1000; j++) { 609 zos.write(buf); 610 } 611 zos.closeArchiveEntry(); 612 zos.close(); 613 614 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 615 getLengthAndPositionAtCentralDirectory(a); 616 617 // grab first entry, verify sizes are 0xFFFFFFFF 618 // and it has a ZIP64 extended information extra 619 // field 620 byte[] header = new byte[12]; 621 a.readFully(header); 622 assertArrayEquals("CDH start", new byte[] { 623 // sig 624 (byte) 0x50, (byte) 0x4b, 1, 2, 625 // version made by 626 45, 0, 627 // version needed to extract 628 45, 0, 629 // GPB (EFS bit) 630 0, 8, 631 // method 632 0, 0 633 }, header); 634 // ignore timestamp 635 a.skipBytes(4); 636 byte[] rest = new byte[26]; 637 a.readFully(rest); 638 assertArrayEquals("CDH rest", new byte[] { 639 // CRC 640 (byte) 0x50, (byte) 0x6F, (byte) 0x31, (byte) 0x5c, 641 // Compressed Size 642 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 643 // Original Size 644 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 645 // file name length 646 1, 0, 647 // extra field length 648 (byte) (mode == Zip64Mode.Always? 28 : 20), 0, 649 // comment length 650 0, 0, 651 // disk number 652 0, 0, 653 // attributes 654 0, 0, 655 0, 0, 0, 0, 656 }, rest); 657 byte[] offset = new byte[4]; 658 a.readFully(offset); 659 if (mode == Zip64Mode.Always) { 660 assertArrayEquals("offset", new byte[] { 661 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 662 }, offset); 663 } else { 664 assertArrayEquals("offset", new byte[] { 665 0, 0, 0, 0, 666 }, offset); 667 } 668 assertEquals('0', a.read()); 669 final byte[] extra = new byte[20]; 670 a.readFully(extra); 671 // 5e9 == 0x12A05F200 672 assertArrayEquals("CDH extra", new byte[] { 673 // Header-ID 674 1, 0, 675 // size of extra 676 (byte) (mode == Zip64Mode.Always? 24 : 16), 0, 677 // original size 678 0, (byte) 0xF2, 5, (byte) 0x2A, 679 1, 0, 0, 0, 680 // compressed size 681 0, (byte) 0xF2, 5, (byte) 0x2A, 682 1, 0, 0, 0, 683 }, extra); 684 if (mode == Zip64Mode.Always) { 685 offset = new byte[8]; 686 a.readFully(offset); 687 assertArrayEquals("extra offset", new byte[] { 688 0, 0, 0, 0, 0, 0, 0, 0, 689 }, offset); 690 } 691 692 // and now validate local file header 693 a.seek(0); 694 header = new byte[10]; 695 a.readFully(header); 696 assertArrayEquals("LFH start", new byte[] { 697 // sig 698 (byte) 0x50, (byte) 0x4b, 3, 4, 699 // version needed to extract 700 45, 0, 701 // GPB (EFS bit) 702 0, 8, 703 // method 704 0, 0 705 }, header); 706 // ignore timestamp 707 a.skipBytes(4); 708 rest = new byte[17]; 709 a.readFully(rest); 710 assertArrayEquals("LFH rest", new byte[] { 711 // CRC 712 (byte) 0x50, (byte) 0x6F, (byte) 0x31, (byte) 0x5c, 713 // Compressed Size 714 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 715 // Original Size 716 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 717 // file name length 718 1, 0, 719 // extra field length 720 20, 0, 721 // file name 722 (byte) '0' 723 }, rest); 724 a.readFully(extra); 725 // 5e9 == 0x12A05F200 726 assertArrayEquals("LFH extra", new byte[] { 727 // Header-ID 728 1, 0, 729 // size of extra 730 16, 0, 731 // original size 732 0, (byte) 0xF2, 5, (byte) 0x2A, 733 1, 0, 0, 0, 734 // compressed size 735 0, (byte) 0xF2, 5, (byte) 0x2A, 736 1, 0, 0, 0, 737 }, extra); 738 } 739 } 740 }; 741 } 742 743 /* 744 * No Compression + Stream => sizes must be known before data is 745 * written. 746 */ 747 @Test public void writeBigStoredEntryToStream() throws Throwable { 748 withTemporaryArchive("writeBigStoredEntryToStream", 749 writeBigStoredEntry(true), 750 false); 751 } 752 753 @Test public void writeBigStoredEntryKnownSizeToFile() throws Throwable { 754 withTemporaryArchive("writeBigStoredEntryKnownSizeToFile", 755 writeBigStoredEntry(true), 756 true); 757 } 758 759 @Test public void writeBigStoredEntryUnnownSizeToFile() throws Throwable { 760 withTemporaryArchive("writeBigStoredEntryUnknownSizeToFile", 761 writeBigStoredEntry(false), 762 true); 763 } 764 765 @Test public void writeBigStoredEntryToStreamModeAlways() throws Throwable { 766 withTemporaryArchive("writeBigStoredEntryToStreamModeAlways", 767 writeBigStoredEntry(true, Zip64Mode.Always), 768 false); 769 } 770 771 @Test public void writeBigStoredEntryKnownSizeToFileModeAlways() 772 throws Throwable { 773 withTemporaryArchive("writeBigStoredEntryKnownSizeToFileModeAlways", 774 writeBigStoredEntry(true, Zip64Mode.Always), 775 true); 776 } 777 778 @Test public void writeBigStoredEntryUnnownSizeToFileModeAlways() 779 throws Throwable { 780 withTemporaryArchive("writeBigStoredEntryUnknownSizeToFileModeAlways", 781 writeBigStoredEntry(false, Zip64Mode.Always), 782 true); 783 } 784 785 private static ZipOutputTest 786 writeBigStoredEntryModeNever(final boolean knownSize) { 787 return new ZipOutputTest() { 788 @Override 789 public void test(final File f, final ZipArchiveOutputStream zos) 790 throws IOException { 791 zos.setUseZip64(Zip64Mode.Never); 792 try { 793 final byte[] buf = new byte[ONE_MILLION]; 794 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 795 if (knownSize) { 796 zae.setSize(FIVE_BILLION); 797 zae.setCrc(0x5c316f50L); 798 } 799 zae.setMethod(ZipEntry.STORED); 800 zos.putArchiveEntry(zae); 801 for (int j = 0; j < FIVE_BILLION / 1000 / 1000; j++) { 802 zos.write(buf); 803 } 804 zos.closeArchiveEntry(); 805 fail("expected a Zip64RequiredException"); 806 } catch (final Zip64RequiredException ex) { 807 assertTrue(ex.getMessage().startsWith("0's size")); 808 } 809 } 810 }; 811 } 812 813 @Test public void writeBigStoredEntryToStreamModeNever() throws Throwable { 814 withTemporaryArchive("writeBigStoredEntryToStreamModeNever", 815 writeBigStoredEntryModeNever(true), 816 false); 817 } 818 819 @Test public void writeBigStoredEntryKnownSizeToFileModeNever() 820 throws Throwable { 821 withTemporaryArchive("writeBigStoredEntryKnownSizeToFileModeNever", 822 writeBigStoredEntryModeNever(true), 823 true); 824 } 825 826 @Test public void writeBigStoredEntryUnnownSizeToFileModeNever() 827 throws Throwable { 828 withTemporaryArchive("writeBigStoredEntryUnknownSizeToFileModeNever", 829 writeBigStoredEntryModeNever(false), 830 true); 831 } 832 833 /* 834 * One entry of length 5 billion bytes, written with 835 * compression to a stream. 836 * 837 * Compression + Stream => sizes are set to 0 in LFH and ZIP64 838 * entry, real values are inside the data descriptor. 839 * 840 * Creates a temporary archive of approx 4MB in size 841 */ 842 private static ZipOutputTest 843 writeBigDeflatedEntryToStream(final boolean knownSize, 844 final Zip64Mode mode) { 845 return new ZipOutputTest() { 846 @Override 847 public void test(final File f, 848 final ZipArchiveOutputStream zos) 849 throws IOException { 850 if (mode != Zip64Mode.AsNeeded) { 851 zos.setUseZip64(mode); 852 } 853 final byte[] buf = new byte[ONE_MILLION]; 854 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 855 if (knownSize) { 856 zae.setSize(FIVE_BILLION); 857 } 858 zae.setMethod(ZipEntry.DEFLATED); 859 zos.putArchiveEntry(zae); 860 for (int j = 0; j < FIVE_BILLION / 1000 / 1000; j++) { 861 zos.write(buf); 862 } 863 zos.closeArchiveEntry(); 864 zos.close(); 865 866 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 867 getLengthAndPositionAtCentralDirectory(a); 868 869 final long cfhPos = a.getFilePointer(); 870 // grab first entry, verify 871 // sizes are 0xFFFFFFFF and 872 // it has a ZIP64 extended 873 // information extra field 874 byte[] header = new byte[12]; 875 a.readFully(header); 876 assertArrayEquals("CDH start", new byte[] { 877 // sig 878 (byte) 0x50, (byte) 0x4b, 1, 2, 879 // version made by 880 45, 0, 881 // version needed to extract 882 45, 0, 883 // GPB (EFS + Data Descriptor) 884 8, 8, 885 // method 886 8, 0, 887 }, header); 888 // ignore timestamp 889 a.skipBytes(4); 890 byte[] rest = new byte[26]; 891 a.readFully(rest); 892 assertArrayEquals("CDH rest", new byte[] { 893 // CRC 894 (byte) 0x50, (byte) 0x6F, (byte) 0x31, (byte) 0x5c, 895 // Compressed Size 896 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 897 // Original Size 898 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 899 // file name length 900 1, 0, 901 // extra field length 902 (byte) (mode == Zip64Mode.Always? 28 : 20), 0, 903 // comment length 904 0, 0, 905 // disk number 906 0, 0, 907 // attributes 908 0, 0, 909 0, 0, 0, 0, 910 }, rest); 911 byte[] offset = new byte[4]; 912 a.readFully(offset); 913 if (mode == Zip64Mode.Always) { 914 assertArrayEquals("offset", new byte[] { 915 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 916 }, offset); 917 } else { 918 assertArrayEquals("offset", new byte[] { 919 0, 0, 0, 0, 920 }, offset); 921 } 922 assertEquals('0', a.read()); 923 byte[] extra = new byte[12]; 924 a.readFully(extra); 925 // 5e9 == 0x12A05F200 926 assertArrayEquals("CDH extra", new byte[] { 927 // Header-ID 928 1, 0, 929 // size of extra 930 (byte) (mode == Zip64Mode.Always? 24 : 16), 0, 931 // original size 932 0, (byte) 0xF2, 5, (byte) 0x2A, 933 1, 0, 0, 0, 934 }, extra); 935 if (mode == Zip64Mode.Always) { 936 // skip compressed size 937 a.skipBytes(8); 938 offset = new byte[8]; 939 a.readFully(offset); 940 assertArrayEquals("extra offset", new byte[] { 941 0, 0, 0, 0, 0, 0, 0, 0, 942 }, offset); 943 } 944 945 // validate data descriptor 946 a.seek(cfhPos - 24); 947 byte[] dd = new byte[8]; 948 a.readFully(dd); 949 assertArrayEquals("DD", new byte[] { 950 // sig 951 (byte) 0x50, (byte) 0x4b, 7, 8, 952 // CRC 953 (byte) 0x50, (byte) 0x6F, (byte) 0x31, (byte) 0x5c, 954 }, dd); 955 // skip compressed size 956 a.skipBytes(8); 957 dd = new byte[8]; 958 a.readFully(dd); 959 assertArrayEquals("DD sizes", new byte[] { 960 // original size 961 0, (byte) 0xF2, 5, (byte) 0x2A, 962 1, 0, 0, 0, 963 }, dd); 964 965 // and now validate local file header 966 a.seek(0); 967 header = new byte[10]; 968 a.readFully(header); 969 assertArrayEquals("LFH start", new byte[] { 970 // sig 971 (byte) 0x50, (byte) 0x4b, 3, 4, 972 // version needed to extract 973 45, 0, 974 // GPB (EFS + Data Descriptor) 975 8, 8, 976 // method 977 8, 0, 978 }, header); 979 // ignore timestamp 980 a.skipBytes(4); 981 rest = new byte[17]; 982 a.readFully(rest); 983 assertArrayEquals("LFH rest", new byte[] { 984 // CRC 985 0, 0, 0, 0, 986 // Compressed Size 987 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 988 // Original Size 989 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 990 // file name length 991 1, 0, 992 // extra field length 993 20, 0, 994 // file name 995 (byte) '0' 996 }, rest); 997 extra = new byte[20]; 998 a.readFully(extra); 999 assertArrayEquals("LFH extra", new byte[] { 1000 // Header-ID 1001 1, 0, 1002 // size of extra 1003 16, 0, 1004 // original size 1005 0, 0, 0, 0, 1006 0, 0, 0, 0, 1007 // compressed size 1008 0, 0, 0, 0, 1009 0, 0, 0, 0, 1010 }, extra); 1011 } 1012 } 1013 }; 1014 } 1015 1016 @Test public void writeBigDeflatedEntryKnownSizeToStream() 1017 throws Throwable { 1018 withTemporaryArchive("writeBigDeflatedEntryKnownSizeToStream", 1019 writeBigDeflatedEntryToStream(true, 1020 Zip64Mode.AsNeeded), 1021 false); 1022 } 1023 1024 @Test public void writeBigDeflatedEntryKnownSizeToStreamModeAlways() 1025 throws Throwable { 1026 withTemporaryArchive("writeBigDeflatedEntryKnownSizeToStreamModeAlways", 1027 writeBigDeflatedEntryToStream(true, 1028 Zip64Mode.Always), 1029 false); 1030 } 1031 1032 @Test public void writeBigDeflatedEntryUnknownSizeToStreamModeAlways() 1033 throws Throwable { 1034 withTemporaryArchive("writeBigDeflatedEntryUnknownSizeToStreamModeAlways", 1035 writeBigDeflatedEntryToStream(false, 1036 Zip64Mode.Always), 1037 false); 1038 } 1039 1040 private static ZipOutputTest 1041 writeBigDeflatedEntryUnknownSizeToStream(final Zip64Mode mode) { 1042 return new ZipOutputTest() { 1043 @Override 1044 public void test(final File f, final ZipArchiveOutputStream zos) 1045 throws IOException { 1046 try { 1047 if (mode != Zip64Mode.AsNeeded) { 1048 zos.setUseZip64(mode); 1049 } 1050 final byte[] buf = new byte[ONE_MILLION]; 1051 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 1052 zae.setMethod(ZipEntry.DEFLATED); 1053 zos.putArchiveEntry(zae); 1054 for (int j = 0; j < FIVE_BILLION / 1000 / 1000; j++) { 1055 zos.write(buf); 1056 } 1057 zos.closeArchiveEntry(); 1058 fail("expected a Zip64RequiredException"); 1059 } catch (final Zip64RequiredException ex) { 1060 assertTrue(ex.getMessage().startsWith("0's size")); 1061 } 1062 } 1063 }; 1064 } 1065 1066 @Test public void writeBigDeflatedEntryUnknownSizeToStream() 1067 throws Throwable { 1068 withTemporaryArchive("writeBigDeflatedEntryUnknownSizeToStream", 1069 writeBigDeflatedEntryUnknownSizeToStream(Zip64Mode 1070 .AsNeeded), 1071 false); 1072 } 1073 1074 @Test public void writeBigDeflatedEntryUnknownSizeToStreamModeNever() 1075 throws Throwable { 1076 withTemporaryArchive("writeBigDeflatedEntryUnknownSizeToStreamModeNever", 1077 writeBigDeflatedEntryUnknownSizeToStream(Zip64Mode 1078 .Never), 1079 false); 1080 } 1081 1082 private static ZipOutputTest 1083 writeBigDeflatedEntryToFile(final boolean knownSize) { 1084 return writeBigDeflatedEntryToFile(knownSize, Zip64Mode.AsNeeded); 1085 } 1086 1087 /* 1088 * One entry of length 5 billion bytes, written with 1089 * compression to a file. 1090 * 1091 * Writing to a file => sizes are stored directly inside the LFH. 1092 * No Data Descriptor at all. 1093 * 1094 * Creates a temporary archive of approx 4MB in size 1095 */ 1096 private static ZipOutputTest 1097 writeBigDeflatedEntryToFile(final boolean knownSize, 1098 final Zip64Mode mode) { 1099 return new ZipOutputTest() { 1100 @Override 1101 public void test(final File f, final ZipArchiveOutputStream zos) 1102 throws IOException { 1103 if (mode != Zip64Mode.AsNeeded) { 1104 zos.setUseZip64(mode); 1105 } 1106 final byte[] buf = new byte[ONE_MILLION]; 1107 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 1108 if (knownSize) { 1109 zae.setSize(FIVE_BILLION); 1110 } 1111 zae.setMethod(ZipEntry.DEFLATED); 1112 zos.putArchiveEntry(zae); 1113 for (int j = 0; 1114 j < FIVE_BILLION / 1000 / 1000; 1115 j++) { 1116 zos.write(buf); 1117 } 1118 zos.closeArchiveEntry(); 1119 zos.close(); 1120 1121 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 1122 getLengthAndPositionAtCentralDirectory(a); 1123 1124 // grab first entry, verify 1125 // sizes are 0xFFFFFFFF and 1126 // it has a ZIP64 extended 1127 // information extra field 1128 byte[] header = new byte[12]; 1129 a.readFully(header); 1130 assertArrayEquals("CDH start", new byte[] { 1131 // sig 1132 (byte) 0x50, (byte) 0x4b, 1, 2, 1133 // version made by 1134 45, 0, 1135 // version needed to extract 1136 45, 0, 1137 // GPB (EFS + *no* Data Descriptor) 1138 0, 8, 1139 // method 1140 8, 0, 1141 }, header); 1142 // ignore timestamp 1143 a.skipBytes(4); 1144 byte[] rest = new byte[26]; 1145 a.readFully(rest); 1146 assertArrayEquals("CDH rest", new byte[] { 1147 // CRC 1148 (byte) 0x50, (byte) 0x6F, (byte) 0x31, (byte) 0x5c, 1149 // Compressed Size 1150 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1151 // Original Size 1152 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1153 // file name length 1154 1, 0, 1155 // extra field length 1156 (byte) (mode == Zip64Mode.Always? 28 : 20), 0, 1157 // comment length 1158 0, 0, 1159 // disk number 1160 0, 0, 1161 // attributes 1162 0, 0, 1163 0, 0, 0, 0, 1164 }, rest); 1165 byte[] offset = new byte[4]; 1166 a.readFully(offset); 1167 if (mode == Zip64Mode.Always) { 1168 assertArrayEquals("offset", new byte[] { 1169 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1170 }, offset); 1171 } else { 1172 assertArrayEquals("offset", new byte[] { 1173 0, 0, 0, 0, 1174 }, offset); 1175 } 1176 assertEquals('0', a.read()); 1177 byte[] extra = new byte[12]; 1178 a.readFully(extra); 1179 // 5e9 == 0x12A05F200 1180 assertArrayEquals("CDH extra", new byte[] { 1181 // Header-ID 1182 1, 0, 1183 // size of extra 1184 (byte) (mode == Zip64Mode.Always? 24 : 16), 0, 1185 // original size 1186 0, (byte) 0xF2, 5, (byte) 0x2A, 1187 1, 0, 0, 0, 1188 }, extra); 1189 if (mode == Zip64Mode.Always) { 1190 // skip compressed size 1191 a.skipBytes(8); 1192 offset = new byte[8]; 1193 a.readFully(offset); 1194 assertArrayEquals("extra offset", new byte[] { 1195 0, 0, 0, 0, 0, 0, 0, 0, 1196 }, offset); 1197 } 1198 1199 // and now validate local file header 1200 a.seek(0); 1201 header = new byte[10]; 1202 a.readFully(header); 1203 assertArrayEquals("LFH start", new byte[] { 1204 // sig 1205 (byte) 0x50, (byte) 0x4b, 3, 4, 1206 // version needed to extract 1207 45, 0, 1208 // GPB (EFS bit, no DD) 1209 0, 8, 1210 // method 1211 8, 0, 1212 }, header); 1213 // ignore timestamp 1214 a.skipBytes(4); 1215 rest = new byte[17]; 1216 a.readFully(rest); 1217 assertArrayEquals(new byte[] { 1218 // CRC 1219 (byte) 0x50, (byte) 0x6F, (byte) 0x31, (byte) 0x5c, 1220 // Compressed Size 1221 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1222 // Original Size 1223 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1224 // file name length 1225 1, 0, 1226 // extra field length 1227 20, 0, 1228 // file name 1229 (byte) '0' 1230 }, rest); 1231 extra = new byte[12]; 1232 a.readFully(extra); 1233 assertArrayEquals(new byte[] { 1234 // Header-ID 1235 1, 0, 1236 // size of extra 1237 16, 0, 1238 // original size 1239 0, (byte) 0xF2, 5, (byte) 0x2A, 1240 1, 0, 0, 0, 1241 // skip compressed size 1242 }, extra); 1243 } 1244 } 1245 }; 1246 } 1247 1248 @Test public void writeBigDeflatedEntryKnownSizeToFile() 1249 throws Throwable { 1250 withTemporaryArchive("writeBigDeflatedEntryKnownSizeToFile", 1251 writeBigDeflatedEntryToFile(true), 1252 true); 1253 } 1254 1255 @Test public void writeBigDeflatedEntryUnknownSizeToFile() 1256 throws Throwable { 1257 withTemporaryArchive("writeBigDeflatedEntryUnknownSizeToFile", 1258 writeBigDeflatedEntryToFile(false), 1259 true); 1260 } 1261 1262 @Test public void writeBigDeflatedEntryKnownSizeToFileModeAlways() 1263 throws Throwable { 1264 withTemporaryArchive("writeBigDeflatedEntryKnownSizeToFileModeAlways", 1265 writeBigDeflatedEntryToFile(true, Zip64Mode.Always), 1266 true); 1267 } 1268 1269 @Test public void writeBigDeflatedEntryUnknownSizeToFileModeAlways() 1270 throws Throwable { 1271 withTemporaryArchive("writeBigDeflatedEntryUnknownSizeToFileModeAlways", 1272 writeBigDeflatedEntryToFile(false, 1273 Zip64Mode.Always), 1274 true); 1275 } 1276 1277 @Test public void writeBigDeflatedEntryKnownSizeToStreamModeNever() 1278 throws Throwable { 1279 withTemporaryArchive("writeBigDeflatedEntryKnownSizeToStreamModeNever", 1280 new ZipOutputTest() { 1281 @Override 1282 public void test(final File f, 1283 final ZipArchiveOutputStream zos) 1284 throws IOException { 1285 zos.setUseZip64(Zip64Mode.Never); 1286 try { 1287 final ZipArchiveEntry zae = 1288 new ZipArchiveEntry("0"); 1289 zae.setSize(FIVE_BILLION); 1290 zae.setMethod(ZipEntry.DEFLATED); 1291 zos.putArchiveEntry(zae); 1292 fail("expected a" 1293 + " Zip64RequiredException"); 1294 } catch (final Zip64RequiredException ex) { 1295 assertTrue(ex.getMessage() 1296 .startsWith("0's size")); 1297 } 1298 } 1299 }, 1300 false); 1301 } 1302 1303 /* 1304 * One entry of length 5 billion bytes, written with 1305 * compression to a file. 1306 * 1307 * Writing to a file => sizes are stored directly inside the LFH. 1308 * No Data Descriptor at all. 1309 * 1310 * Creates a temporary archive of approx 4MB in size 1311 */ 1312 private static ZipOutputTest 1313 writeBigDeflatedEntryToFileModeNever(final boolean knownSize) { 1314 return new ZipOutputTest() { 1315 @Override 1316 public void test(final File f, final ZipArchiveOutputStream zos) 1317 throws IOException { 1318 zos.setUseZip64(Zip64Mode.Never); 1319 try { 1320 final byte[] buf = new byte[ONE_MILLION]; 1321 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 1322 if (knownSize) { 1323 zae.setSize(FIVE_BILLION); 1324 } 1325 zae.setMethod(ZipEntry.DEFLATED); 1326 zos.putArchiveEntry(zae); 1327 for (int j = 0; 1328 j < FIVE_BILLION / 1000 / 1000; 1329 j++) { 1330 zos.write(buf); 1331 } 1332 zos.closeArchiveEntry(); 1333 fail("expected a Zip64RequiredException"); 1334 } catch (final Zip64RequiredException ex) { 1335 assertTrue(ex.getMessage().startsWith("0's size")); 1336 } 1337 } 1338 }; 1339 } 1340 1341 @Test public void writeBigDeflatedEntryKnownSizeToFileModeNever() 1342 throws Throwable { 1343 withTemporaryArchive("writeBigDeflatedEntryKnownSizeToFileModeNever", 1344 writeBigDeflatedEntryToFileModeNever(true), 1345 true); 1346 } 1347 1348 @Test public void writeBigDeflatedEntryUnknownSizeToFileModeNever() 1349 throws Throwable { 1350 withTemporaryArchive("writeBigDeflatedEntryUnknownSizeToFileModeNever", 1351 writeBigDeflatedEntryToFileModeNever(false), 1352 true); 1353 } 1354 1355 private static ZipOutputTest writeSmallStoredEntry(final boolean knownSize) { 1356 return writeSmallStoredEntry(knownSize, Zip64Mode.AsNeeded); 1357 } 1358 1359 /* 1360 * One entry of length 1 million bytes, written without compression. 1361 * 1362 * No Compression => sizes are stored directly inside the LFH. No 1363 * Data Descriptor at all. Shouldn't contain any ZIP64 extra 1364 * field if size was known. 1365 * 1366 * Creates a temporary archive of approx 1MB in size 1367 */ 1368 private static ZipOutputTest writeSmallStoredEntry(final boolean knownSize, 1369 final Zip64Mode mode) { 1370 return new ZipOutputTest() { 1371 @Override 1372 public void test(final File f, final ZipArchiveOutputStream zos) 1373 throws IOException { 1374 if (mode != Zip64Mode.AsNeeded) { 1375 zos.setUseZip64(mode); 1376 } 1377 final byte[] buf = new byte[ONE_MILLION]; 1378 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 1379 if (knownSize) { 1380 zae.setSize(ONE_MILLION); 1381 zae.setCrc(0x1279CB9EL); 1382 } 1383 zae.setMethod(ZipEntry.STORED); 1384 zos.putArchiveEntry(zae); 1385 zos.write(buf); 1386 zos.closeArchiveEntry(); 1387 zos.close(); 1388 1389 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 1390 getLengthAndPositionAtCentralDirectory(a); 1391 1392 // grab first CF entry, verify sizes are 1e6 and it 1393 // has no ZIP64 extended information extra field 1394 // at all 1395 byte[] header = new byte[12]; 1396 a.readFully(header); 1397 assertArrayEquals("CDH start", new byte[] { 1398 // sig 1399 (byte) 0x50, (byte) 0x4b, 1, 2, 1400 // version made by 1401 20, 0, 1402 // version needed to extract 1403 10, 0, 1404 // GPB (EFS bit) 1405 0, 8, 1406 // method 1407 0, 0 1408 }, header); 1409 // ignore timestamp 1410 a.skipBytes(4); 1411 byte[] rest = new byte[31]; 1412 a.readFully(rest); 1413 // 1e6 == 0xF4240 1414 assertArrayEquals("CDH rest", new byte[] { 1415 // CRC 1416 (byte) 0x9E, (byte) 0xCB, (byte) 0x79, (byte) 0x12, 1417 // Compressed Size 1418 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1419 // Original Size 1420 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1421 // file name length 1422 1, 0, 1423 // extra field length 1424 0, 0, 1425 // comment length 1426 0, 0, 1427 // disk number 1428 0, 0, 1429 // attributes 1430 0, 0, 1431 0, 0, 0, 0, 1432 // offset 1433 0, 0, 0, 0, 1434 // file name 1435 (byte) '0' 1436 }, rest); 1437 1438 // and now validate local file header: this one 1439 // has a ZIP64 extra field if and only if size was 1440 // unknown and mode was not Never or the mode was 1441 // Always (regardless of size) 1442 final boolean hasExtra = mode == Zip64Mode.Always 1443 || (mode == Zip64Mode.AsNeeded && !knownSize); 1444 a.seek(0); 1445 header = new byte[10]; 1446 a.readFully(header); 1447 assertArrayEquals("LFH start", new byte[] { 1448 // sig 1449 (byte) 0x50, (byte) 0x4b, 3, 4, 1450 // version needed to extract 1451 10, 0, 1452 // GPB (EFS bit) 1453 0, 8, 1454 // method 1455 0, 0 1456 }, header); 1457 // ignore timestamp 1458 a.skipBytes(4); 1459 rest = new byte[17]; 1460 a.readFully(rest); 1461 // 1e6 == 0xF4240 1462 assertArrayEquals("LFH rest", new byte[] { 1463 // CRC 1464 (byte) 0x9E, (byte) 0xCB, (byte) 0x79, (byte) 0x12, 1465 // Compressed Size 1466 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1467 // Original Size 1468 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1469 // file name length 1470 1, 0, 1471 // extra field length 1472 (byte) (!hasExtra ? 0 : 20), 0, 1473 // file name 1474 (byte) '0' 1475 }, rest); 1476 if (hasExtra) { 1477 final byte[] extra = new byte[20]; 1478 a.readFully(extra); 1479 assertArrayEquals("ZIP64 extra field", new byte[] { 1480 // Header-ID 1481 1, 0, 1482 // size of extra 1483 16, 0, 1484 // original size 1485 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1486 0, 0, 0, 0, 1487 // compressed size 1488 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1489 0, 0, 0, 0, 1490 }, extra); 1491 } 1492 } 1493 } 1494 }; 1495 } 1496 1497 @Test public void writeSmallStoredEntryToStream() throws Throwable { 1498 withTemporaryArchive("writeSmallStoredEntryToStream", 1499 writeSmallStoredEntry(true), 1500 false); 1501 } 1502 1503 @Test public void writeSmallStoredEntryKnownSizeToFile() throws Throwable { 1504 withTemporaryArchive("writeSmallStoredEntryKnownSizeToFile", 1505 writeSmallStoredEntry(true), 1506 true); 1507 } 1508 1509 @Test public void writeSmallStoredEntryUnnownSizeToFile() throws Throwable { 1510 withTemporaryArchive("writeSmallStoredEntryUnknownSizeToFile", 1511 writeSmallStoredEntry(false), 1512 true); 1513 } 1514 1515 @Test public void writeSmallStoredEntryToStreamModeNever() throws Throwable { 1516 withTemporaryArchive("writeSmallStoredEntryToStreamModeNever", 1517 writeSmallStoredEntry(true, Zip64Mode.Never), 1518 false); 1519 } 1520 1521 @Test public void writeSmallStoredEntryKnownSizeToFileModeNever() 1522 throws Throwable { 1523 withTemporaryArchive("writeSmallStoredEntryKnownSizeToFileModeNever", 1524 writeSmallStoredEntry(true, Zip64Mode.Never), 1525 true); 1526 } 1527 1528 @Test public void writeSmallStoredEntryUnnownSizeToFileModeNever() 1529 throws Throwable { 1530 withTemporaryArchive("writeSmallStoredEntryUnknownSizeToFileModeNever", 1531 writeSmallStoredEntry(false, Zip64Mode.Never), 1532 true); 1533 } 1534 1535 /* 1536 * One entry of length 1 million bytes, written without compression. 1537 * 1538 * No Compression => sizes are stored directly inside the LFH. No 1539 * Data Descriptor at all. Contains ZIP64 extra fields because 1540 * mode is Always 1541 * 1542 * Creates a temporary archive of approx 1MB in size 1543 */ 1544 private static ZipOutputTest 1545 writeSmallStoredEntryModeAlways(final boolean knownSize) { 1546 return new ZipOutputTest() { 1547 @Override 1548 public void test(final File f, final ZipArchiveOutputStream zos) 1549 throws IOException { 1550 zos.setUseZip64(Zip64Mode.Always); 1551 final byte[] buf = new byte[ONE_MILLION]; 1552 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 1553 if (knownSize) { 1554 zae.setSize(ONE_MILLION); 1555 zae.setCrc(0x1279CB9EL); 1556 } 1557 zae.setMethod(ZipEntry.STORED); 1558 zos.putArchiveEntry(zae); 1559 zos.write(buf); 1560 zos.closeArchiveEntry(); 1561 zos.close(); 1562 1563 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 1564 getLengthAndPositionAtCentralDirectory(a); 1565 1566 // grab first CF entry, verify sizes are 1e6 and it 1567 // has an empty ZIP64 extended information extra field 1568 byte[] header = new byte[12]; 1569 a.readFully(header); 1570 assertArrayEquals("CDH start", new byte[] { 1571 // sig 1572 (byte) 0x50, (byte) 0x4b, 1, 2, 1573 // version made by 1574 45, 0, 1575 // version needed to extract 1576 45, 0, 1577 // GPB (EFS bit) 1578 0, 8, 1579 // method 1580 0, 0 1581 }, header); 1582 // ignore timestamp 1583 a.skipBytes(4); 1584 byte[] rest = new byte[31]; 1585 a.readFully(rest); 1586 // 1e6 == 0xF4240 1587 assertArrayEquals("CDH rest", new byte[] { 1588 // CRC 1589 (byte) 0x9E, (byte) 0xCB, (byte) 0x79, (byte) 0x12, 1590 // Compressed Size 1591 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1592 // Original Size 1593 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1594 // file name length 1595 1, 0, 1596 // extra field length 1597 28, 0, 1598 // comment length 1599 0, 0, 1600 // disk number 1601 0, 0, 1602 // attributes 1603 0, 0, 1604 0, 0, 0, 0, 1605 // offset 1606 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1607 // file name 1608 (byte) '0' 1609 }, rest); 1610 1611 byte[] extra = new byte[28]; 1612 a.readFully(extra); 1613 assertArrayEquals("CDH extra", new byte[] { 1614 // Header-ID 1615 1, 0, 1616 // size of extra 1617 24, 0, 1618 // original size 1619 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1620 0, 0, 0, 0, 1621 // compressed size 1622 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1623 0, 0, 0, 0, 1624 0, 0, 0, 0, 0, 0, 0, 0, 1625 }, extra); 1626 1627 // and now validate local file header: this one 1628 // has a ZIP64 extra field as the mode was 1629 // Always 1630 a.seek(0); 1631 header = new byte[10]; 1632 a.readFully(header); 1633 assertArrayEquals("LFH start", new byte[] { 1634 // sig 1635 (byte) 0x50, (byte) 0x4b, 3, 4, 1636 // version needed to extract 1637 45, 0, 1638 // GPB (EFS bit) 1639 0, 8, 1640 // method 1641 0, 0 1642 }, header); 1643 // ignore timestamp 1644 a.skipBytes(4); 1645 rest = new byte[17]; 1646 a.readFully(rest); 1647 // 1e6 == 0xF4240 1648 assertArrayEquals("LFH rest", new byte[] { 1649 // CRC 1650 (byte) 0x9E, (byte) 0xCB, (byte) 0x79, (byte) 0x12, 1651 // Compressed Size 1652 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1653 // Original Size 1654 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1655 // file name length 1656 1, 0, 1657 // extra field length 1658 20, 0, 1659 // file name 1660 (byte) '0' 1661 }, rest); 1662 1663 extra = new byte[20]; 1664 a.readFully(extra); 1665 assertArrayEquals("LFH extra", new byte[] { 1666 // Header-ID 1667 1, 0, 1668 // size of extra 1669 16, 0, 1670 // original size 1671 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1672 0, 0, 0, 0, 1673 // compressed size 1674 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1675 0, 0, 0, 0, 1676 }, extra); 1677 } 1678 } 1679 }; 1680 } 1681 1682 @Test public void writeSmallStoredEntryToStreamModeAlways() 1683 throws Throwable { 1684 withTemporaryArchive("writeSmallStoredEntryToStreamModeAlways", 1685 writeSmallStoredEntryModeAlways(true), 1686 false); 1687 } 1688 1689 @Test public void writeSmallStoredEntryKnownSizeToFileModeAlways() 1690 throws Throwable { 1691 withTemporaryArchive("writeSmallStoredEntryKnownSizeToFileModeAlways", 1692 writeSmallStoredEntryModeAlways(true), 1693 true); 1694 } 1695 1696 @Test public void writeSmallStoredEntryUnnownSizeToFileModeAlways() 1697 throws Throwable { 1698 withTemporaryArchive("writeSmallStoredEntryUnknownSizeToFileModeAlways", 1699 writeSmallStoredEntryModeAlways(false), 1700 true); 1701 } 1702 1703 /* 1704 * One entry of length 1 million bytes, written with compression 1705 * to a stream. 1706 * 1707 * Compression + Stream => sizes are set to 0 in LFH, real values 1708 * are inside the data descriptor. No ZIP64 extra field at all. 1709 */ 1710 private static ZipOutputTest 1711 writeSmallDeflatedEntryToStream(final boolean knownSize, 1712 final Zip64Mode mode) { 1713 return new ZipOutputTest() { 1714 @Override 1715 public void test(final File f, final ZipArchiveOutputStream zos) 1716 throws IOException { 1717 if (mode != Zip64Mode.AsNeeded) { 1718 zos.setUseZip64(mode); 1719 } 1720 final byte[] buf = new byte[ONE_MILLION]; 1721 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 1722 if (knownSize) { 1723 zae.setSize(ONE_MILLION); 1724 } 1725 zae.setMethod(ZipEntry.DEFLATED); 1726 zos.putArchiveEntry(zae); 1727 zos.write(buf); 1728 zos.closeArchiveEntry(); 1729 zos.close(); 1730 1731 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 1732 getLengthAndPositionAtCentralDirectory(a); 1733 1734 final long cfhPos = a.getFilePointer(); 1735 // grab first entry, verify sizes are not 1736 // 0xFFFFFFF and it has no ZIP64 extended 1737 // information extra field 1738 byte[] header = new byte[12]; 1739 a.readFully(header); 1740 assertArrayEquals(new byte[] { 1741 // sig 1742 (byte) 0x50, (byte) 0x4b, 1, 2, 1743 // version made by 1744 20, 0, 1745 // version needed to extract 1746 20, 0, 1747 // GPB (EFS + Data Descriptor) 1748 8, 8, 1749 // method 1750 8, 0, 1751 }, header); 1752 // ignore timestamp 1753 a.skipBytes(4); 1754 final byte[] crc = new byte[4]; 1755 a.readFully(crc); 1756 assertArrayEquals(new byte[] { 1757 (byte) 0x9E, (byte) 0xCB, 1758 (byte) 0x79, (byte) 0x12, 1759 }, crc); 1760 // skip compressed size 1761 a.skipBytes(4); 1762 byte[] rest = new byte[23]; 1763 a.readFully(rest); 1764 assertArrayEquals(new byte[] { 1765 // Original Size 1766 (byte) 0x40, (byte) 0x42, 1767 (byte) 0x0F, 0, 1768 // file name length 1769 1, 0, 1770 // extra field length 1771 0, 0, 1772 // comment length 1773 0, 0, 1774 // disk number 1775 0, 0, 1776 // attributes 1777 0, 0, 1778 0, 0, 0, 0, 1779 // offset 1780 0, 0, 0, 0, 1781 // file name 1782 (byte) '0' 1783 }, rest); 1784 1785 // validate data descriptor 1786 a.seek(cfhPos - 16); 1787 byte[] dd = new byte[8]; 1788 a.readFully(dd); 1789 assertArrayEquals(new byte[] { 1790 // sig 1791 (byte) 0x50, (byte) 0x4b, 7, 8, 1792 // CRC 1793 (byte) 0x9E, (byte) 0xCB, (byte) 0x79, (byte) 0x12, 1794 }, dd); 1795 // skip uncompressed size 1796 a.skipBytes(4); 1797 dd = new byte[4]; 1798 a.readFully(dd); 1799 assertArrayEquals(new byte[] { 1800 // original size 1801 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1802 }, dd); 1803 1804 // and now validate local file header 1805 a.seek(0); 1806 header = new byte[10]; 1807 a.readFully(header); 1808 assertArrayEquals(new byte[] { 1809 // sig 1810 (byte) 0x50, (byte) 0x4b, 3, 4, 1811 // version needed to extract 1812 20, 0, 1813 // GPB (EFS + Data Descriptor) 1814 8, 8, 1815 // method 1816 8, 0, 1817 }, header); 1818 // ignore timestamp 1819 a.skipBytes(4); 1820 rest = new byte[17]; 1821 a.readFully(rest); 1822 assertArrayEquals(new byte[] { 1823 // CRC 1824 0, 0, 0, 0, 1825 // Compressed Size 1826 0, 0, 0, 0, 1827 // Original Size 1828 0, 0, 0, 0, 1829 // file name length 1830 1, 0, 1831 // extra field length 1832 0, 0, 1833 // file name 1834 (byte) '0' 1835 }, rest); 1836 } 1837 } 1838 }; 1839 1840 } 1841 1842 @Test public void writeSmallDeflatedEntryKnownSizeToStream() 1843 throws Throwable { 1844 withTemporaryArchive("writeSmallDeflatedEntryKnownSizeToStream", 1845 writeSmallDeflatedEntryToStream(true, 1846 Zip64Mode.AsNeeded), 1847 false); 1848 } 1849 1850 @Test public void writeSmallDeflatedEntryKnownSizeToStreamModeNever() 1851 throws Throwable { 1852 withTemporaryArchive("writeSmallDeflatedEntryKnownSizeToStreamModeNever", 1853 writeSmallDeflatedEntryToStream(true, 1854 Zip64Mode.Never), 1855 false); 1856 } 1857 1858 @Test public void writeSmallDeflatedEntryUnknownSizeToStream() 1859 throws Throwable { 1860 withTemporaryArchive("writeSmallDeflatedEntryUnknownSizeToStream", 1861 writeSmallDeflatedEntryToStream(false, 1862 Zip64Mode.AsNeeded), 1863 false); 1864 } 1865 1866 @Test public void writeSmallDeflatedEntryUnknownSizeToStreamModeNever() 1867 throws Throwable { 1868 withTemporaryArchive("writeSmallDeflatedEntryUnknownSizeToStreamModeNever", 1869 writeSmallDeflatedEntryToStream(false, 1870 Zip64Mode.Never), 1871 false); 1872 } 1873 1874 /* 1875 * One entry of length 1 million bytes, written with compression 1876 * to a stream. 1877 * 1878 * Compression + Stream => sizes are set to 0 in LFH, real values 1879 * are inside the data descriptor. ZIP64 extra field as mode is Always. 1880 */ 1881 private static ZipOutputTest 1882 writeSmallDeflatedEntryToStreamModeAlways(final boolean knownSize) { 1883 return new ZipOutputTest() { 1884 @Override 1885 public void test(final File f, final ZipArchiveOutputStream zos) 1886 throws IOException { 1887 zos.setUseZip64(Zip64Mode.Always); 1888 final byte[] buf = new byte[ONE_MILLION]; 1889 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 1890 if (knownSize) { 1891 zae.setSize(ONE_MILLION); 1892 } 1893 zae.setMethod(ZipEntry.DEFLATED); 1894 zos.putArchiveEntry(zae); 1895 zos.write(buf); 1896 zos.closeArchiveEntry(); 1897 zos.close(); 1898 1899 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 1900 getLengthAndPositionAtCentralDirectory(a); 1901 1902 final long cfhPos = a.getFilePointer(); 1903 // grab first entry, verify sizes are not 1904 // 0xFFFFFFF and it has an empty ZIP64 extended 1905 // information extra field 1906 byte[] header = new byte[12]; 1907 a.readFully(header); 1908 assertArrayEquals("CDH start", new byte[] { 1909 // sig 1910 (byte) 0x50, (byte) 0x4b, 1, 2, 1911 // version made by 1912 45, 0, 1913 // version needed to extract 1914 45, 0, 1915 // GPB (EFS + Data Descriptor) 1916 8, 8, 1917 // method 1918 8, 0, 1919 }, header); 1920 // ignore timestamp 1921 a.skipBytes(4); 1922 final byte[] crc = new byte[4]; 1923 a.readFully(crc); 1924 assertArrayEquals(new byte[] { 1925 (byte) 0x9E, (byte) 0xCB, 1926 (byte) 0x79, (byte) 0x12, 1927 }, crc); 1928 // skip compressed size 1929 a.skipBytes(4); 1930 byte[] rest = new byte[23]; 1931 a.readFully(rest); 1932 assertArrayEquals("CDH rest", new byte[] { 1933 // Original Size 1934 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1935 // file name length 1936 1, 0, 1937 // extra field length 1938 28, 0, 1939 // comment length 1940 0, 0, 1941 // disk number 1942 0, 0, 1943 // attributes 1944 0, 0, 1945 0, 0, 0, 0, 1946 // offset 1947 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 1948 // file name 1949 (byte) '0' 1950 }, rest); 1951 byte[] extra = new byte[12]; 1952 a.readFully(extra); 1953 assertArrayEquals("CDH extra", new byte[] { 1954 // Header-ID 1955 1, 0, 1956 // size of extra 1957 24, 0, 1958 // original size 1959 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1960 0, 0, 0, 0, 1961 }, extra); 1962 // skip compressed size 1963 a.skipBytes(8); 1964 byte[] offset = new byte[8]; 1965 a.readFully(offset); 1966 assertArrayEquals("extra offset", new byte[] { 1967 0, 0, 0, 0, 0, 0, 0, 0, 1968 }, offset); 1969 1970 // validate data descriptor 1971 a.seek(cfhPos - 24); 1972 byte[] dd = new byte[8]; 1973 a.readFully(dd); 1974 assertArrayEquals("DD", new byte[] { 1975 // sig 1976 (byte) 0x50, (byte) 0x4b, 7, 8, 1977 // CRC 1978 (byte) 0x9E, (byte) 0xCB, (byte) 0x79, (byte) 0x12, 1979 }, dd); 1980 // skip compressed size 1981 a.skipBytes(8); 1982 dd = new byte[8]; 1983 a.readFully(dd); 1984 assertArrayEquals("DD size", new byte[] { 1985 // original size 1986 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 1987 0, 0, 0, 0 1988 }, dd); 1989 1990 // and now validate local file header 1991 a.seek(0); 1992 header = new byte[10]; 1993 a.readFully(header); 1994 assertArrayEquals("LFH start", new byte[] { 1995 // sig 1996 (byte) 0x50, (byte) 0x4b, 3, 4, 1997 // version needed to extract 1998 45, 0, 1999 // GPB (EFS + Data Descriptor) 2000 8, 8, 2001 // method 2002 8, 0, 2003 }, header); 2004 // ignore timestamp 2005 a.skipBytes(4); 2006 rest = new byte[17]; 2007 a.readFully(rest); 2008 assertArrayEquals("LFH rest", new byte[] { 2009 // CRC 2010 0, 0, 0, 0, 2011 // Compressed Size 2012 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 2013 // Original Size 2014 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 2015 // file name length 2016 1, 0, 2017 // extra field length 2018 20, 0, 2019 // file name 2020 (byte) '0' 2021 }, rest); 2022 2023 extra = new byte[20]; 2024 a.readFully(extra); 2025 assertArrayEquals("LFH extra", new byte[] { 2026 // Header-ID 2027 1, 0, 2028 // size of extra 2029 16, 0, 2030 // original size 2031 0, 0, 0, 0, 2032 0, 0, 0, 0, 2033 // compressed size 2034 0, 0, 0, 0, 2035 0, 0, 0, 0, 2036 }, extra); 2037 } 2038 } 2039 }; 2040 2041 } 2042 2043 @Test public void writeSmallDeflatedEntryKnownSizeToStreamModeAlways() 2044 throws Throwable { 2045 withTemporaryArchive("writeSmallDeflatedEntryKnownSizeToStreamModeAlways", 2046 writeSmallDeflatedEntryToStreamModeAlways(true), 2047 false); 2048 } 2049 2050 @Test public void writeSmallDeflatedEntryUnknownSizeToStreamModeAlways() 2051 throws Throwable { 2052 withTemporaryArchive("writeSmallDeflatedEntryUnknownSizeToStreamModeAlways", 2053 writeSmallDeflatedEntryToStreamModeAlways(false), 2054 false); 2055 } 2056 2057 private static ZipOutputTest writeSmallDeflatedEntryToFile(final boolean knownSize) { 2058 return writeSmallDeflatedEntryToFile(knownSize, Zip64Mode.AsNeeded); 2059 } 2060 2061 /* 2062 * One entry of length 1 million bytes, written with compression 2063 * to a file. 2064 * 2065 * Writing to a file => sizes are stored directly inside the LFH. 2066 * No Data Descriptor at all. Shouldn't contain any ZIP64 extra 2067 * field if size was known. 2068 */ 2069 private static ZipOutputTest 2070 writeSmallDeflatedEntryToFile(final boolean knownSize, 2071 final Zip64Mode mode) { 2072 return new ZipOutputTest() { 2073 @Override 2074 public void test(final File f, final ZipArchiveOutputStream zos) 2075 throws IOException { 2076 if (mode != Zip64Mode.AsNeeded) { 2077 zos.setUseZip64(mode); 2078 } 2079 final byte[] buf = new byte[ONE_MILLION]; 2080 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 2081 if (knownSize) { 2082 zae.setSize(ONE_MILLION); 2083 } 2084 zae.setMethod(ZipEntry.DEFLATED); 2085 zos.putArchiveEntry(zae); 2086 zos.write(buf); 2087 zos.closeArchiveEntry(); 2088 zos.close(); 2089 2090 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 2091 getLengthAndPositionAtCentralDirectory(a); 2092 2093 // grab first CD entry, verify sizes are not 2094 // 0xFFFFFFFF and it has a no ZIP64 extended 2095 // information extra field 2096 byte[] header = new byte[12]; 2097 a.readFully(header); 2098 assertArrayEquals(new byte[] { 2099 // sig 2100 (byte) 0x50, (byte) 0x4b, 1, 2, 2101 // version made by 2102 20, 0, 2103 // version needed to extract 2104 20, 0, 2105 // GPB (EFS + *no* Data Descriptor) 2106 0, 8, 2107 // method 2108 8, 0, 2109 }, header); 2110 // ignore timestamp 2111 a.skipBytes(4); 2112 byte[] crc = new byte[4]; 2113 a.readFully(crc); 2114 assertArrayEquals(new byte[] { 2115 (byte) 0x9E, (byte) 0xCB, 2116 (byte) 0x79, (byte) 0x12, 2117 }, crc); 2118 // skip compressed size 2119 a.skipBytes(4); 2120 byte[] rest = new byte[23]; 2121 a.readFully(rest); 2122 assertArrayEquals(new byte[] { 2123 // Original Size 2124 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 2125 // file name length 2126 1, 0, 2127 // extra field length 2128 0, 0, 2129 // comment length 2130 0, 0, 2131 // disk number 2132 0, 0, 2133 // attributes 2134 0, 0, 2135 0, 0, 0, 0, 2136 // offset 2137 0, 0, 0, 0, 2138 // file name 2139 (byte) '0' 2140 }, rest); 2141 2142 // and now validate local file header 2143 a.seek(0); 2144 header = new byte[10]; 2145 a.readFully(header); 2146 assertArrayEquals(new byte[] { 2147 // sig 2148 (byte) 0x50, (byte) 0x4b, 3, 4, 2149 // version needed to extract 2150 20, 0, 2151 // GPB (EFS bit, no DD) 2152 0, 8, 2153 // method 2154 8, 0, 2155 }, header); 2156 // ignore timestamp 2157 a.skipBytes(4); 2158 crc = new byte[4]; 2159 a.readFully(crc); 2160 assertArrayEquals(new byte[] { 2161 (byte) 0x9E, (byte) 0xCB, 2162 (byte) 0x79, (byte) 0x12, 2163 }, crc); 2164 // skip compressed size 2165 a.skipBytes(4); 2166 rest = new byte[9]; 2167 a.readFully(rest); 2168 2169 final boolean hasExtra = 2170 mode == Zip64Mode.AsNeeded && !knownSize; 2171 2172 assertArrayEquals(new byte[] { 2173 // Original Size 2174 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 2175 // file name length 2176 1, 0, 2177 // extra field length 2178 (byte) (!hasExtra ? 0 : 20), 0, 2179 // file name 2180 (byte) '0' 2181 }, rest); 2182 if (hasExtra) { 2183 final byte[] extra = new byte[12]; 2184 a.readFully(extra); 2185 assertArrayEquals(new byte[] { 2186 // Header-ID 2187 1, 0, 2188 // size of extra 2189 16, 0, 2190 // original size 2191 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 2192 0, 0, 0, 0, 2193 // don't know the 2194 // compressed size, 2195 // don't want to 2196 // hard-code it 2197 }, extra); 2198 } 2199 } 2200 } 2201 }; 2202 } 2203 2204 @Test public void writeSmallDeflatedEntryKnownSizeToFile() 2205 throws Throwable { 2206 withTemporaryArchive("writeSmallDeflatedEntryKnownSizeToFile", 2207 writeSmallDeflatedEntryToFile(true), 2208 true); 2209 } 2210 2211 @Test public void writeSmallDeflatedEntryUnknownSizeToFile() 2212 throws Throwable { 2213 withTemporaryArchive("writeSmallDeflatedEntryUnknownSizeToFile", 2214 writeSmallDeflatedEntryToFile(false), 2215 true); 2216 } 2217 2218 @Test public void writeSmallDeflatedEntryKnownSizeToFileModeNever() 2219 throws Throwable { 2220 withTemporaryArchive("writeSmallDeflatedEntryKnownSizeToFileModeNever", 2221 writeSmallDeflatedEntryToFile(true, 2222 Zip64Mode.Never), 2223 true); 2224 } 2225 2226 @Test public void writeSmallDeflatedEntryUnknownSizeToFileModeNever() 2227 throws Throwable { 2228 withTemporaryArchive("writeSmallDeflatedEntryUnknownSizeToFileModeNever", 2229 writeSmallDeflatedEntryToFile(false, 2230 Zip64Mode.Never), 2231 true); 2232 } 2233 2234 /* 2235 * One entry of length 1 million bytes, written with compression 2236 * to a file. 2237 * 2238 * Writing to a file => sizes are stored directly inside the LFH. 2239 * No Data Descriptor at all. Must contain ZIP64 extra field as 2240 * mode is Always. 2241 */ 2242 private static ZipOutputTest 2243 writeSmallDeflatedEntryToFileModeAlways(final boolean knownSize) { 2244 return new ZipOutputTest() { 2245 @Override 2246 public void test(final File f, final ZipArchiveOutputStream zos) 2247 throws IOException { 2248 zos.setUseZip64(Zip64Mode.Always); 2249 final byte[] buf = new byte[ONE_MILLION]; 2250 final ZipArchiveEntry zae = new ZipArchiveEntry("0"); 2251 if (knownSize) { 2252 zae.setSize(ONE_MILLION); 2253 } 2254 zae.setMethod(ZipEntry.DEFLATED); 2255 zos.putArchiveEntry(zae); 2256 zos.write(buf); 2257 zos.closeArchiveEntry(); 2258 zos.close(); 2259 2260 try (RandomAccessFile a = new RandomAccessFile(f, "r")) { 2261 getLengthAndPositionAtCentralDirectory(a); 2262 2263 // grab first CD entry, verify sizes are not 2264 // 0xFFFFFFFF and it has a an empty ZIP64 extended 2265 // information extra field 2266 byte[] header = new byte[12]; 2267 a.readFully(header); 2268 assertArrayEquals("CDH start", new byte[] { 2269 // sig 2270 (byte) 0x50, (byte) 0x4b, 1, 2, 2271 // version made by 2272 45, 0, 2273 // version needed to extract 2274 45, 0, 2275 // GPB (EFS + *no* Data Descriptor) 2276 0, 8, 2277 // method 2278 8, 0, 2279 }, header); 2280 // ignore timestamp 2281 a.skipBytes(4); 2282 byte[] crc = new byte[4]; 2283 a.readFully(crc); 2284 assertArrayEquals("CDH CRC", new byte[] { 2285 (byte) 0x9E, (byte) 0xCB, (byte) 0x79, (byte) 0x12, 2286 }, crc); 2287 // skip compressed size 2288 a.skipBytes(4); 2289 byte[] rest = new byte[23]; 2290 a.readFully(rest); 2291 assertArrayEquals("CDH rest", new byte[] { 2292 // Original Size 2293 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 2294 // file name length 2295 1, 0, 2296 // extra field length 2297 28, 0, 2298 // comment length 2299 0, 0, 2300 // disk number 2301 0, 0, 2302 // attributes 2303 0, 0, 2304 0, 0, 0, 0, 2305 // offset 2306 (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 2307 // file name 2308 (byte) '0' 2309 }, rest); 2310 byte[] extra = new byte[12]; 2311 a.readFully(extra); 2312 assertArrayEquals("CDH extra", new byte[] { 2313 // Header-ID 2314 1, 0, 2315 // size of extra 2316 24, 0, 2317 // original size 2318 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 2319 0, 0, 0, 0, 2320 }, extra); 2321 // skip compressed size 2322 a.skipBytes(8); 2323 byte[] offset = new byte[8]; 2324 a.readFully(offset); 2325 assertArrayEquals("extra offset", new byte[] { 2326 0, 0, 0, 0, 0, 0, 0, 0, 2327 }, offset); 2328 2329 // and now validate local file header 2330 a.seek(0); 2331 header = new byte[10]; 2332 a.readFully(header); 2333 assertArrayEquals("LFH start", new byte[] { 2334 // sig 2335 (byte) 0x50, (byte) 0x4b, 3, 4, 2336 // version needed to extract 2337 45, 0, 2338 // GPB (EFS bit, no DD) 2339 0, 8, 2340 // method 2341 8, 0, 2342 }, header); 2343 // ignore timestamp 2344 a.skipBytes(4); 2345 crc = new byte[4]; 2346 a.readFully(crc); 2347 assertArrayEquals("LFH CRC", new byte[] { 2348 (byte) 0x9E, (byte) 0xCB, 2349 (byte) 0x79, (byte) 0x12, 2350 }, crc); 2351 rest = new byte[13]; 2352 a.readFully(rest); 2353 2354 assertArrayEquals("LFH rest", new byte[] { 2355 // Compressed Size 2356 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 2357 // Original Size 2358 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 2359 // file name length 2360 1, 0, 2361 // extra field length 2362 20, 0, 2363 // file name 2364 (byte) '0' 2365 }, rest); 2366 2367 extra = new byte[12]; 2368 a.readFully(extra); 2369 assertArrayEquals("LFH extra", new byte[] { 2370 // Header-ID 2371 1, 0, 2372 // size of extra 2373 16, 0, 2374 // original size 2375 (byte) 0x40, (byte) 0x42, (byte) 0x0F, 0, 2376 0, 0, 0, 0, 2377 // don't know the 2378 // compressed size, 2379 // don't want to 2380 // hard-code it 2381 }, extra); 2382 } 2383 } 2384 }; 2385 } 2386 2387 @Test public void writeSmallDeflatedEntryKnownSizeToFileModeAlways() 2388 throws Throwable { 2389 withTemporaryArchive("writeSmallDeflatedEntryKnownSizeToFileModeAlways", 2390 writeSmallDeflatedEntryToFileModeAlways(true), 2391 true); 2392 } 2393 2394 @Test public void writeSmallDeflatedEntryUnknownSizeToFileModeAlways() 2395 throws Throwable { 2396 withTemporaryArchive("writeSmallDeflatedEntryUnknownSizeToFileModeAlways", 2397 writeSmallDeflatedEntryToFileModeAlways(false), 2398 true); 2399 } 2400 2401 static interface ZipOutputTest { 2402 void test(File f, ZipArchiveOutputStream zos) throws IOException; 2403 } 2404 2405 private static void withTemporaryArchive(final String testName, 2406 final ZipOutputTest test, 2407 final boolean useRandomAccessFile) 2408 throws Throwable { 2409 final File f = getTempFile(testName); 2410 BufferedOutputStream os = null; 2411 final ZipArchiveOutputStream zos = useRandomAccessFile 2412 ? new ZipArchiveOutputStream(f) 2413 : new ZipArchiveOutputStream(os = new BufferedOutputStream(new FileOutputStream(f))); 2414 try { 2415 test.test(f, zos); 2416 } catch (final IOException ex) { 2417 System.err.println("Failed to write archive because of: " 2418 + ex.getMessage() 2419 + " - likely not enough disk space."); 2420 assumeTrue(false); 2421 } finally { 2422 try { 2423 zos.destroy(); 2424 } finally { 2425 if (os != null) { 2426 os.close(); 2427 } 2428 AbstractTestCase.tryHardToDelete(f); 2429 } 2430 } 2431 } 2432 2433 private static File get5GBZerosFile() throws Throwable { 2434 return getFile("5GB_of_Zeros.zip"); 2435 } 2436 2437 private static File get5GBZerosFileGeneratedBy7ZIP() throws Throwable { 2438 return getFile("5GB_of_Zeros_7ZIP.zip"); 2439 } 2440 2441 private static File get5GBZerosFileGeneratedByJava7Jar() throws Throwable { 2442 return getFile("5GB_of_Zeros_jar.zip"); 2443 } 2444 2445 private static File get5GBZerosFileGeneratedByWinZIP() throws Throwable { 2446 return getFile("5GB_of_Zeros_WinZip.zip"); 2447 } 2448 2449 private static File get5GBZerosFileGeneratedByPKZip() throws Throwable { 2450 return getFile("5GB_of_Zeros_PKZip.zip"); 2451 } 2452 2453 private static File get100KFileFile() throws Throwable { 2454 return getFile("100k_Files.zip"); 2455 } 2456 2457 private static File get100KFileFileGeneratedBy7ZIP() throws Throwable { 2458 return getFile("100k_Files_7ZIP.zip"); 2459 } 2460 2461 private static File get100KFileFileGeneratedByWinCF() throws Throwable { 2462 return getFile("100k_Files_WindowsCompressedFolders.zip"); 2463 } 2464 2465 private static File get100KFileFileGeneratedByJava7Jar() throws Throwable { 2466 return getFile("100k_Files_jar.zip"); 2467 } 2468 2469 private static File get100KFileFileGeneratedByWinZIP() throws Throwable { 2470 return getFile("100k_Files_WinZIP.zip"); 2471 } 2472 2473 private static File get100KFileFileGeneratedByPKZip() throws Throwable { 2474 return getFile("100k_Files_PKZip.zip"); 2475 } 2476 2477 private static File getTempFile(final String testName) throws Throwable { 2478 final File f = File.createTempFile("commons-compress-" + testName, ".zip"); 2479 f.deleteOnExit(); 2480 return f; 2481 } 2482 2483 private static void read5GBOfZerosImpl(final File f, final String expectedName) 2484 throws IOException { 2485 final FileInputStream fin = new FileInputStream(f); 2486 ZipArchiveInputStream zin = null; 2487 try { 2488 zin = new ZipArchiveInputStream(fin); 2489 ZipArchiveEntry zae = zin.getNextZipEntry(); 2490 while (zae.isDirectory()) { 2491 zae = zin.getNextZipEntry(); 2492 } 2493 assertEquals(expectedName, zae.getName()); 2494 final byte[] buf = new byte[1024 * 1024]; 2495 long read = 0; 2496 final Random r = new Random(System.currentTimeMillis()); 2497 int readNow; 2498 while ((readNow = zin.read(buf, 0, buf.length)) > 0) { 2499 // testing all bytes for a value of 0 is going to take 2500 // too long, just pick a few ones randomly 2501 for (int i = 0; i < 1024; i++) { 2502 final int idx = r.nextInt(readNow); 2503 assertEquals("testing byte " + (read + idx), 0, buf[idx]); 2504 } 2505 read += readNow; 2506 } 2507 assertEquals(FIVE_BILLION, read); 2508 assertNull(zin.getNextZipEntry()); 2509 assertEquals(FIVE_BILLION, zae.getSize()); 2510 } finally { 2511 if (zin != null) { 2512 zin.close(); 2513 } 2514 fin.close(); // fin cannot be null here 2515 } 2516 } 2517 2518 private static void read5GBOfZerosUsingZipFileImpl(final File f, 2519 final String expectedName) 2520 throws IOException { 2521 ZipFile zf = null; 2522 try { 2523 zf = new ZipFile(f); 2524 final Enumeration<ZipArchiveEntry> e = zf.getEntries(); 2525 assertTrue(e.hasMoreElements()); 2526 ZipArchiveEntry zae = e.nextElement(); 2527 while (zae.isDirectory()) { 2528 zae = e.nextElement(); 2529 } 2530 assertEquals(expectedName, zae.getName()); 2531 assertEquals(FIVE_BILLION, zae.getSize()); 2532 final byte[] buf = new byte[1024 * 1024]; 2533 long read = 0; 2534 final Random r = new Random(System.currentTimeMillis()); 2535 int readNow; 2536 try (InputStream zin = zf.getInputStream(zae)) { 2537 while ((readNow = zin.read(buf, 0, buf.length)) > 0) { 2538 // testing all bytes for a value of 0 is going to take 2539 // too long, just pick a few ones randomly 2540 for (int i = 0; i < 1024; i++) { 2541 final int idx = r.nextInt(readNow); 2542 assertEquals("testing byte " + (read + idx), 0, buf[idx]); 2543 } 2544 read += readNow; 2545 } 2546 } 2547 assertEquals(FIVE_BILLION, read); 2548 assertFalse(e.hasMoreElements()); 2549 } finally { 2550 ZipFile.closeQuietly(zf); 2551 } 2552 } 2553 2554 private static void read100KFilesImpl(final File f) throws IOException { 2555 final FileInputStream fin = new FileInputStream(f); 2556 ZipArchiveInputStream zin = null; 2557 try { 2558 zin = new ZipArchiveInputStream(fin); 2559 int files = 0; 2560 ZipArchiveEntry zae = null; 2561 while ((zae = zin.getNextZipEntry()) != null) { 2562 if (!zae.isDirectory()) { 2563 files++; 2564 assertEquals(0, zae.getSize()); 2565 } 2566 } 2567 assertEquals(ONE_HUNDRED_THOUSAND, files); 2568 } finally { 2569 if (zin != null) { 2570 zin.close(); 2571 } 2572 fin.close(); 2573 } 2574 } 2575 2576 private static void read100KFilesUsingZipFileImpl(final File f) 2577 throws IOException { 2578 ZipFile zf = null; 2579 try { 2580 zf = new ZipFile(f); 2581 int files = 0; 2582 for (final Enumeration<ZipArchiveEntry> e = zf.getEntries(); e.hasMoreElements(); ) { 2583 final ZipArchiveEntry zae = e.nextElement(); 2584 if (!zae.isDirectory()) { 2585 files++; 2586 assertEquals(0, zae.getSize()); 2587 } 2588 } 2589 assertEquals(ONE_HUNDRED_THOUSAND, files); 2590 } finally { 2591 ZipFile.closeQuietly(zf); 2592 } 2593 } 2594 2595 private static long getLengthAndPositionAtCentralDirectory(final RandomAccessFile a) 2596 throws IOException { 2597 final long end = a.length(); 2598 a.seek(end - 22 - 20); 2599 final byte[] sig = new byte[4]; 2600 a.readFully(sig); 2601 if (sig[0] != (byte) 0x50 || sig[1] != (byte) 0x4b 2602 || sig[2] != 6 || sig[3] != 7) { 2603 // not a ZIP64 archive 2604 return getLengthAndPositionAtCentralDirectory32(a, end); 2605 } 2606 2607 final long cdOffsetLoc = end - 22 - 20 - 56 + 48; 2608 // seek to central directory locator 2609 a.seek(cdOffsetLoc); 2610 final byte[] cdOffset = new byte[8]; 2611 a.readFully(cdOffset); 2612 a.seek(ZipEightByteInteger.getLongValue(cdOffset)); 2613 return end; 2614 } 2615 2616 private static long getLengthAndPositionAtCentralDirectory32(final RandomAccessFile a, final long end) 2617 throws IOException { 2618 a.seek(end - 22 + 16); 2619 final byte[] cdOffset = new byte[4]; 2620 a.readFully(cdOffset); 2621 a.seek(ZipLong.getValue(cdOffset)); 2622 return end; 2623 } 2624 2625 private static void write100KFilesToStream(final ZipArchiveOutputStream zos) 2626 throws IOException { 2627 for (int i = 0; i < ONE_HUNDRED_THOUSAND; i++) { 2628 final ZipArchiveEntry zae = new ZipArchiveEntry(String.valueOf(i)); 2629 zae.setSize(0); 2630 zos.putArchiveEntry(zae); 2631 zos.closeArchiveEntry(); 2632 } 2633 zos.close(); 2634 } 2635 2636 private static void 2637 write3EntriesCreatingBigArchiveToStream(final ZipArchiveOutputStream zos) 2638 throws IOException { 2639 final byte[] buf = new byte[ONE_MILLION]; 2640 ZipArchiveEntry zae = null; 2641 for (int i = 0; i < 2; i++) { 2642 zae = new ZipArchiveEntry(String.valueOf(i)); 2643 zae.setSize(FIVE_BILLION / 2); 2644 zae.setMethod(ZipEntry.STORED); 2645 zae.setCrc(0x8a408f16L); 2646 zos.putArchiveEntry(zae); 2647 for (int j = 0; j < FIVE_BILLION / 2 / 1000 / 1000; 2648 j++) { 2649 zos.write(buf); 2650 } 2651 zos.closeArchiveEntry(); 2652 } 2653 zae = new ZipArchiveEntry(String.valueOf(2)); 2654 zae.setSize(1); 2655 zae.setMethod(ZipEntry.STORED); 2656 zae.setCrc(0x9b9265bL); 2657 zos.putArchiveEntry(zae); 2658 zos.write(new byte[] { 42 }); 2659 zos.closeArchiveEntry(); 2660 zos.close(); 2661 } 2662 } 2663