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