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