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