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