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