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