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  package org.apache.commons.compress.archivers.zip;
19  
20  import java.io.File;
21  import java.io.FileOutputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.io.RandomAccessFile;
25  import java.nio.ByteBuffer;
26  import java.util.HashMap;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.zip.CRC32;
31  import java.util.zip.Deflater;
32  import java.util.zip.ZipException;
33  
34  import org.apache.commons.compress.archivers.ArchiveEntry;
35  import org.apache.commons.compress.archivers.ArchiveOutputStream;
36  
37  import static org.apache.commons.compress.archivers.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
38  import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
39  import static org.apache.commons.compress.archivers.zip.ZipConstants.INITIAL_VERSION;
40  import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
41  import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
42  import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
43  import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT;
44  import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MIN_VERSION;
45  
46  /**
47   * Reimplementation of {@link java.util.zip.ZipOutputStream
48   * java.util.zip.ZipOutputStream} that does handle the extended
49   * functionality of this package, especially internal/external file
50   * attributes and extra fields with different layouts for local file
51   * data and central directory entries.
52   *
53   * <p>This class will try to use {@link java.io.RandomAccessFile
54   * RandomAccessFile} when you know that the output is going to go to a
55   * file.</p>
56   *
57   * <p>If RandomAccessFile cannot be used, this implementation will use
58   * a Data Descriptor to store size and CRC information for {@link
59   * #DEFLATED DEFLATED} entries, this means, you don't need to
60   * calculate them yourself.  Unfortunately this is not possible for
61   * the {@link #STORED STORED} method, here setting the CRC and
62   * uncompressed size information is required before {@link
63   * #putArchiveEntry(ArchiveEntry)} can be called.</p>
64   *
65   * <p>As of Apache Commons Compress 1.3 it transparently supports Zip64
66   * extensions and thus individual entries and archives larger than 4
67   * GB or with more than 65536 entries in most cases but explicit
68   * control is provided via {@link #setUseZip64}.  If the stream can not
69   * user RandomAccessFile and you try to write a ZipArchiveEntry of
70   * unknown size then Zip64 extensions will be disabled by default.</p>
71   *
72   * @NotThreadSafe
73   */
74  public class ZipArchiveOutputStream extends ArchiveOutputStream {
75  
76      static final int BUFFER_SIZE = 512;
77  
78      /** indicates if this archive is finished. protected for use in Jar implementation */
79      protected boolean finished = false;
80  
81      /* 
82       * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs
83       * when it gets handed a really big buffer.  See
84       * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396
85       *
86       * Using a buffer size of 8 kB proved to be a good compromise
87       */
88      private static final int DEFLATER_BLOCK_SIZE = 8192;
89  
90      /**
91       * Compression method for deflated entries.
92       */
93      public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
94  
95      /**
96       * Default compression level for deflated entries.
97       */
98      public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
99  
100     /**
101      * Compression method for stored entries.
102      */
103     public static final int STORED = java.util.zip.ZipEntry.STORED;
104 
105     /**
106      * default encoding for file names and comment.
107      */
108     static final String DEFAULT_ENCODING = ZipEncodingHelper.UTF8;
109 
110     /**
111      * General purpose flag, which indicates that filenames are
112      * written in utf-8.
113      * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead
114      */
115     @Deprecated
116     public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG;
117 
118     private static final byte[] EMPTY = new byte[0];
119 
120     /**
121      * Current entry.
122      */
123     private CurrentEntry entry;
124 
125     /**
126      * The file comment.
127      */
128     private String comment = "";
129 
130     /**
131      * Compression level for next entry.
132      */
133     private int level = DEFAULT_COMPRESSION;
134 
135     /**
136      * Has the compression level changed when compared to the last
137      * entry?
138      */
139     private boolean hasCompressionLevelChanged = false;
140 
141     /**
142      * Default compression method for next entry.
143      */
144     private int method = java.util.zip.ZipEntry.DEFLATED;
145 
146     /**
147      * List of ZipArchiveEntries written so far.
148      */
149     private final List<ZipArchiveEntry> entries =
150         new LinkedList<ZipArchiveEntry>();
151 
152     /**
153      * CRC instance to avoid parsing DEFLATED data twice.
154      */
155     private final CRC32 crc = new CRC32();
156 
157     /**
158      * Count the bytes written to out.
159      */
160     private long written = 0;
161 
162     /**
163      * Start of central directory.
164      */
165     private long cdOffset = 0;
166 
167     /**
168      * Length of central directory.
169      */
170     private long cdLength = 0;
171 
172     /**
173      * Helper, a 0 as ZipShort.
174      */
175     private static final byte[] ZERO = {0, 0};
176 
177     /**
178      * Helper, a 0 as ZipLong.
179      */
180     private static final byte[] LZERO = {0, 0, 0, 0};
181 
182     /**
183      * Holds the offsets of the LFH starts for each entry.
184      */
185     private final Map<ZipArchiveEntry, Long> offsets =
186         new HashMap<ZipArchiveEntry, Long>();
187 
188     /**
189      * The encoding to use for filenames and the file comment.
190      *
191      * <p>For a list of possible values see <a
192      * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
193      * Defaults to UTF-8.</p>
194      */
195     private String encoding = DEFAULT_ENCODING;
196 
197     /**
198      * The zip encoding to use for filenames and the file comment.
199      *
200      * This field is of internal use and will be set in {@link
201      * #setEncoding(String)}.
202      */
203     private ZipEncoding zipEncoding =
204         ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING);
205 
206     /**
207      * This Deflater object is used for output.
208      *
209      */
210     protected final Deflater def = new Deflater(level, true);
211 
212     /**
213      * This buffer serves as a Deflater.
214      *
215      */
216     private final byte[] buf = new byte[BUFFER_SIZE];
217 
218     /**
219      * Optional random access output.
220      */
221     private final RandomAccessFile raf;
222 
223     private final OutputStream out;
224 
225     /**
226      * whether to use the general purpose bit flag when writing UTF-8
227      * filenames or not.
228      */
229     private boolean useUTF8Flag = true; 
230 
231     /**
232      * Whether to encode non-encodable file names as UTF-8.
233      */
234     private boolean fallbackToUTF8 = false;
235 
236     /**
237      * whether to create UnicodePathExtraField-s for each entry.
238      */
239     private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
240 
241     /**
242      * Whether anything inside this archive has used a ZIP64 feature.
243      *
244      * @since 1.3
245      */
246     private boolean hasUsedZip64 = false;
247 
248     private Zip64Mode zip64Mode = Zip64Mode.AsNeeded;
249 
250     /**
251      * Creates a new ZIP OutputStream filtering the underlying stream.
252      * @param out the outputstream to zip
253      */
254     public ZipArchiveOutputStream(OutputStream out) {
255         this.out = out;
256         this.raf = null;
257     }
258 
259     /**
260      * Creates a new ZIP OutputStream writing to a File.  Will use
261      * random access if possible.
262      * @param file the file to zip to
263      * @throws IOException on error
264      */
265     public ZipArchiveOutputStream(File file) throws IOException {
266         OutputStream o = null;
267         RandomAccessFile _raf = null;
268         try {
269             _raf = new RandomAccessFile(file, "rw");
270             _raf.setLength(0);
271         } catch (IOException e) {
272             if (_raf != null) {
273                 try {
274                     _raf.close();
275                 } catch (IOException inner) { // NOPMD
276                     // ignore
277                 }
278                 _raf = null;
279             }
280             o = new FileOutputStream(file);
281         }
282         out = o;
283         raf = _raf;
284     }
285 
286     /**
287      * This method indicates whether this archive is writing to a
288      * seekable stream (i.e., to a random access file).
289      *
290      * <p>For seekable streams, you don't need to calculate the CRC or
291      * uncompressed size for {@link #STORED} entries before
292      * invoking {@link #putArchiveEntry(ArchiveEntry)}.
293      * @return true if seekable
294      */
295     public boolean isSeekable() {
296         return raf != null;
297     }
298 
299     /**
300      * The encoding to use for filenames and the file comment.
301      *
302      * <p>For a list of possible values see <a
303      * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>.
304      * Defaults to UTF-8.</p>
305      * @param encoding the encoding to use for file names, use null
306      * for the platform's default encoding
307      */
308     public void setEncoding(final String encoding) {
309         this.encoding = encoding;
310         this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
311         if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) {
312             useUTF8Flag = false;
313         }
314     }
315 
316     /**
317      * The encoding to use for filenames and the file comment.
318      *
319      * @return null if using the platform's default character encoding.
320      */
321     public String getEncoding() {
322         return encoding;
323     }
324 
325     /**
326      * Whether to set the language encoding flag if the file name
327      * encoding is UTF-8.
328      *
329      * <p>Defaults to true.</p>
330      */
331     public void setUseLanguageEncodingFlag(boolean b) {
332         useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding);
333     }
334 
335     /**
336      * Whether to create Unicode Extra Fields.
337      *
338      * <p>Defaults to NEVER.</p>
339      */
340     public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {
341         createUnicodeExtraFields = b;
342     }
343 
344     /**
345      * Whether to fall back to UTF and the language encoding flag if
346      * the file name cannot be encoded using the specified encoding.
347      *
348      * <p>Defaults to false.</p>
349      */
350     public void setFallbackToUTF8(boolean b) {
351         fallbackToUTF8 = b;
352     }
353 
354     /**
355      * Whether Zip64 extensions will be used.
356      *
357      * <p>When setting the mode to {@link Zip64Mode#Never Never},
358      * {@link #putArchiveEntry}, {@link #closeArchiveEntry}, {@link
359      * #finish} or {@link #close} may throw a {@link
360      * Zip64RequiredException} if the entry's size or the total size
361      * of the archive exceeds 4GB or there are more than 65536 entries
362      * inside the archive.  Any archive created in this mode will be
363      * readable by implementations that don't support Zip64.</p>
364      *
365      * <p>When setting the mode to {@link Zip64Mode#Always Always},
366      * Zip64 extensions will be used for all entries.  Any archive
367      * created in this mode may be unreadable by implementations that
368      * don't support Zip64 even if all its contents would be.</p>
369      *
370      * <p>When setting the mode to {@link Zip64Mode#AsNeeded
371      * AsNeeded}, Zip64 extensions will transparently be used for
372      * those entries that require them.  This mode can only be used if
373      * the uncompressed size of the {@link ZipArchiveEntry} is known
374      * when calling {@link #putArchiveEntry} or the archive is written
375      * to a seekable output (i.e. you have used the {@link
376      * #ZipArchiveOutputStream(java.io.File) File-arg constructor}) -
377      * this mode is not valid when the output stream is not seekable
378      * and the uncompressed size is unknown when {@link
379      * #putArchiveEntry} is called.</p>
380      * 
381      * <p>If no entry inside the resulting archive requires Zip64
382      * extensions then {@link Zip64Mode#Never Never} will create the
383      * smallest archive.  {@link Zip64Mode#AsNeeded AsNeeded} will
384      * create a slightly bigger archive if the uncompressed size of
385      * any entry has initially been unknown and create an archive
386      * identical to {@link Zip64Mode#Never Never} otherwise.  {@link
387      * Zip64Mode#Always Always} will create an archive that is at
388      * least 24 bytes per entry bigger than the one {@link
389      * Zip64Mode#Never Never} would create.</p>
390      *
391      * <p>Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless
392      * {@link #putArchiveEntry} is called with an entry of unknown
393      * size and data is written to a non-seekable stream - in this
394      * case the default is {@link Zip64Mode#Never Never}.</p>
395      *
396      * @since 1.3
397      */
398     public void setUseZip64(Zip64Mode mode) {
399         zip64Mode = mode;
400     }
401 
402     /**
403      * {@inheritDoc}
404      * @throws Zip64RequiredException if the archive's size exceeds 4
405      * GByte or there are more than 65535 entries inside the archive
406      * and {@link #setUseZip64} is {@link Zip64Mode#Never}.
407      */
408     @Override
409     public void finish() throws IOException {
410         if (finished) {
411             throw new IOException("This archive has already been finished");
412         }
413 
414         if (entry != null) {
415             throw new IOException("This archive contains unclosed entries.");
416         }
417 
418         cdOffset = written;
419         for (ZipArchiveEntry ze : entries) {
420             writeCentralFileHeader(ze);
421         }
422         cdLength = written - cdOffset;
423         writeZip64CentralDirectory();
424         writeCentralDirectoryEnd();
425         offsets.clear();
426         entries.clear();
427         def.end();
428         finished = true;
429     }
430 
431     /**
432      * Writes all necessary data for this entry.
433      * @throws IOException on error
434      * @throws Zip64RequiredException if the entry's uncompressed or
435      * compressed size exceeds 4 GByte and {@link #setUseZip64} 
436      * is {@link Zip64Mode#Never}.
437      */
438     @Override
439     public void closeArchiveEntry() throws IOException {
440         if (finished) {
441             throw new IOException("Stream has already been finished");
442         }
443 
444         if (entry == null) {
445             throw new IOException("No current entry to close");
446         }
447 
448         if (!entry.hasWritten) {
449             write(EMPTY, 0, 0);
450         }
451 
452         flushDeflater();
453 
454         final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
455         long bytesWritten = written - entry.dataStart;
456         long realCrc = crc.getValue();
457         crc.reset();
458 
459         final boolean actuallyNeedsZip64 =
460             handleSizesAndCrc(bytesWritten, realCrc, effectiveMode);
461 
462         if (raf != null) {
463             rewriteSizesAndCrc(actuallyNeedsZip64);
464         }
465 
466         writeDataDescriptor(entry.entry);
467         entry = null;
468     }
469 
470     /**
471      * Ensures all bytes sent to the deflater are written to the stream.
472      */
473     private void flushDeflater() throws IOException {
474         if (entry.entry.getMethod() == DEFLATED) {
475             def.finish();
476             while (!def.finished()) {
477                 deflate();
478             }
479         }
480     }
481 
482     /**
483      * Ensures the current entry's size and CRC information is set to
484      * the values just written, verifies it isn't too big in the
485      * Zip64Mode.Never case and returns whether the entry would
486      * require a Zip64 extra field.
487      */
488     private boolean handleSizesAndCrc(long bytesWritten, long crc,
489                                       Zip64Mode effectiveMode)
490         throws ZipException {
491         if (entry.entry.getMethod() == DEFLATED) {
492             /* It turns out def.getBytesRead() returns wrong values if
493              * the size exceeds 4 GB on Java < Java7
494             entry.entry.setSize(def.getBytesRead());
495             */
496             entry.entry.setSize(entry.bytesRead);
497             entry.entry.setCompressedSize(bytesWritten);
498             entry.entry.setCrc(crc);
499 
500             def.reset();
501         } else if (raf == null) {
502             if (entry.entry.getCrc() != crc) {
503                 throw new ZipException("bad CRC checksum for entry "
504                                        + entry.entry.getName() + ": "
505                                        + Long.toHexString(entry.entry.getCrc())
506                                        + " instead of "
507                                        + Long.toHexString(crc));
508             }
509 
510             if (entry.entry.getSize() != bytesWritten) {
511                 throw new ZipException("bad size for entry "
512                                        + entry.entry.getName() + ": "
513                                        + entry.entry.getSize()
514                                        + " instead of "
515                                        + bytesWritten);
516             }
517         } else { /* method is STORED and we used RandomAccessFile */
518             entry.entry.setSize(bytesWritten);
519             entry.entry.setCompressedSize(bytesWritten);
520             entry.entry.setCrc(crc);
521         }
522 
523         final boolean actuallyNeedsZip64 = effectiveMode == Zip64Mode.Always
524             || entry.entry.getSize() >= ZIP64_MAGIC
525             || entry.entry.getCompressedSize() >= ZIP64_MAGIC;
526         if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) {
527             throw new Zip64RequiredException(Zip64RequiredException
528                                              .getEntryTooBigMessage(entry.entry));
529         }
530         return actuallyNeedsZip64;
531     }
532 
533     /**
534      * When using random access output, write the local file header
535      * and potentiall the ZIP64 extra containing the correct CRC and
536      * compressed/uncompressed sizes.
537      */
538     private void rewriteSizesAndCrc(boolean actuallyNeedsZip64)
539         throws IOException {
540         long save = raf.getFilePointer();
541 
542         raf.seek(entry.localDataStart);
543         writeOut(ZipLong.getBytes(entry.entry.getCrc()));
544         if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) {
545             writeOut(ZipLong.getBytes(entry.entry.getCompressedSize()));
546             writeOut(ZipLong.getBytes(entry.entry.getSize()));
547         } else {
548             writeOut(ZipLong.ZIP64_MAGIC.getBytes());
549             writeOut(ZipLong.ZIP64_MAGIC.getBytes());
550         }
551 
552         if (hasZip64Extra(entry.entry)) {
553             // seek to ZIP64 extra, skip header and size information
554             raf.seek(entry.localDataStart + 3 * WORD + 2 * SHORT
555                      + getName(entry.entry).limit() + 2 * SHORT);
556             // inside the ZIP64 extra uncompressed size comes
557             // first, unlike the LFH, CD or data descriptor
558             writeOut(ZipEightByteInteger.getBytes(entry.entry.getSize()));
559             writeOut(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize()));
560 
561             if (!actuallyNeedsZip64) {
562                 // do some cleanup:
563                 // * rewrite version needed to extract
564                 raf.seek(entry.localDataStart  - 5 * SHORT);
565                 writeOut(ZipShort.getBytes(INITIAL_VERSION));
566 
567                 // * remove ZIP64 extra so it doesn't get written
568                 //   to the central directory
569                 entry.entry.removeExtraField(Zip64ExtendedInformationExtraField
570                                              .HEADER_ID);
571                 entry.entry.setExtra();
572 
573                 // * reset hasUsedZip64 if it has been set because
574                 //   of this entry
575                 if (entry.causedUseOfZip64) {
576                     hasUsedZip64 = false;
577                 }
578             }
579         }
580         raf.seek(save);
581     }
582 
583     /**
584      * {@inheritDoc} 
585      * @throws ClassCastException if entry is not an instance of ZipArchiveEntry
586      * @throws Zip64RequiredException if the entry's uncompressed or
587      * compressed size is known to exceed 4 GByte and {@link #setUseZip64} 
588      * is {@link Zip64Mode#Never}.
589      */
590     @Override
591     public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException {
592         if (finished) {
593             throw new IOException("Stream has already been finished");
594         }
595 
596         if (entry != null) {
597             closeArchiveEntry();
598         }
599 
600         entry = new CurrentEntry((ZipArchiveEntry) archiveEntry);
601         entries.add(entry.entry);
602 
603         setDefaults(entry.entry);
604 
605         final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
606         validateSizeInformation(effectiveMode);
607 
608         if (shouldAddZip64Extra(entry.entry, effectiveMode)) {
609 
610             Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry);
611 
612             // just a placeholder, real data will be in data
613             // descriptor or inserted later via RandomAccessFile
614             ZipEightByteInteger size = ZipEightByteInteger.ZERO;
615             if (entry.entry.getMethod() == STORED
616                 && entry.entry.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
617                 // actually, we already know the sizes
618                 size = new ZipEightByteInteger(entry.entry.getSize());
619             }
620             z64.setSize(size);
621             z64.setCompressedSize(size);
622             entry.entry.setExtra();
623         }
624 
625         if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
626             def.setLevel(level);
627             hasCompressionLevelChanged = false;
628         }
629         writeLocalFileHeader(entry.entry);
630     }
631 
632     /**
633      * Provides default values for compression method and last
634      * modification time.
635      */
636     private void setDefaults(ZipArchiveEntry entry) {
637         if (entry.getMethod() == -1) { // not specified
638             entry.setMethod(method);
639         }
640 
641         if (entry.getTime() == -1) { // not specified
642             entry.setTime(System.currentTimeMillis());
643         }
644     }
645 
646     /**
647      * Throws an exception if the size is unknown for a stored entry
648      * that is written to a non-seekable output or the entry is too
649      * big to be written without Zip64 extra but the mode has been set
650      * to Never.
651      */
652     private void validateSizeInformation(Zip64Mode effectiveMode)
653         throws ZipException {
654         // Size/CRC not required if RandomAccessFile is used
655         if (entry.entry.getMethod() == STORED && raf == null) {
656             if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) {
657                 throw new ZipException("uncompressed size is required for"
658                                        + " STORED method when not writing to a"
659                                        + " file");
660             }
661             if (entry.entry.getCrc() == -1) {
662                 throw new ZipException("crc checksum is required for STORED"
663                                        + " method when not writing to a file");
664             }
665             entry.entry.setCompressedSize(entry.entry.getSize());
666         }
667 
668         if ((entry.entry.getSize() >= ZIP64_MAGIC
669              || entry.entry.getCompressedSize() >= ZIP64_MAGIC)
670             && effectiveMode == Zip64Mode.Never) {
671             throw new Zip64RequiredException(Zip64RequiredException
672                                              .getEntryTooBigMessage(entry.entry));
673         }
674     }
675 
676     /**
677      * Whether to addd a Zip64 extended information extra field to the
678      * local file header.
679      *
680      * <p>Returns true if</p>
681      *
682      * <ul>
683      * <li>mode is Always</li>
684      * <li>or we already know it is going to be needed</li>
685      * <li>or the size is unknown and we can ensure it won't hurt
686      * other implementations if we add it (i.e. we can erase its
687      * usage</li>
688      * </ul>
689      */
690     private boolean shouldAddZip64Extra(ZipArchiveEntry entry, Zip64Mode mode) {
691         return mode == Zip64Mode.Always
692             || entry.getSize() >= ZIP64_MAGIC
693             || entry.getCompressedSize() >= ZIP64_MAGIC
694             || (entry.getSize() == ArchiveEntry.SIZE_UNKNOWN
695                 && raf != null && mode != Zip64Mode.Never);
696     }
697 
698     /**
699      * Set the file comment.
700      * @param comment the comment
701      */
702     public void setComment(String comment) {
703         this.comment = comment;
704     }
705 
706     /**
707      * Sets the compression level for subsequent entries.
708      *
709      * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
710      * @param level the compression level.
711      * @throws IllegalArgumentException if an invalid compression
712      * level is specified.
713      */
714     public void setLevel(int level) {
715         if (level < Deflater.DEFAULT_COMPRESSION
716             || level > Deflater.BEST_COMPRESSION) {
717             throw new IllegalArgumentException("Invalid compression level: "
718                                                + level);
719         }
720         hasCompressionLevelChanged = (this.level != level);
721         this.level = level;
722     }
723 
724     /**
725      * Sets the default compression method for subsequent entries.
726      *
727      * <p>Default is DEFLATED.</p>
728      * @param method an <code>int</code> from java.util.zip.ZipEntry
729      */
730     public void setMethod(int method) {
731         this.method = method;
732     }
733 
734     /**
735      * Whether this stream is able to write the given entry.
736      *
737      * <p>May return false if it is set up to use encryption or a
738      * compression method that hasn't been implemented yet.</p>
739      * @since 1.1
740      */
741     @Override
742     public boolean canWriteEntryData(ArchiveEntry ae) {
743         if (ae instanceof ZipArchiveEntry) {
744             return ZipUtil.canHandleEntryData((ZipArchiveEntry) ae);
745         }
746         return false;
747     }
748 
749     /**
750      * Writes bytes to ZIP entry.
751      * @param b the byte array to write
752      * @param offset the start position to write from
753      * @param length the number of bytes to write
754      * @throws IOException on error
755      */
756     @Override
757     public void write(byte[] b, int offset, int length) throws IOException {
758         ZipUtil.checkRequestedFeatures(entry.entry);
759         entry.hasWritten = true;
760         if (entry.entry.getMethod() == DEFLATED) {
761             writeDeflated(b, offset, length);
762         } else {
763             writeOut(b, offset, length);
764             written += length;
765         }
766         crc.update(b, offset, length);
767         count(length);
768     }
769 
770     /**
771      * write implementation for DEFLATED entries.
772      */
773     private void writeDeflated(byte[]b, int offset, int length)
774         throws IOException {
775         if (length > 0 && !def.finished()) {
776             entry.bytesRead += length;
777             if (length <= DEFLATER_BLOCK_SIZE) {
778                 def.setInput(b, offset, length);
779                 deflateUntilInputIsNeeded();
780             } else {
781                 final int fullblocks = length / DEFLATER_BLOCK_SIZE;
782                 for (int i = 0; i < fullblocks; i++) {
783                     def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
784                                  DEFLATER_BLOCK_SIZE);
785                     deflateUntilInputIsNeeded();
786                 }
787                 final int done = fullblocks * DEFLATER_BLOCK_SIZE;
788                 if (done < length) {
789                     def.setInput(b, offset + done, length - done);
790                     deflateUntilInputIsNeeded();
791                 }
792             }
793         }
794     }
795 
796     /**
797      * Closes this output stream and releases any system resources
798      * associated with the stream.
799      *
800      * @exception  IOException  if an I/O error occurs.
801      * @throws Zip64RequiredException if the archive's size exceeds 4
802      * GByte or there are more than 65535 entries inside the archive
803      * and {@link #setUseZip64} is {@link Zip64Mode#Never}.
804      */
805     @Override
806     public void close() throws IOException {
807         if (!finished) {
808             finish();
809         }
810         destroy();
811     }
812 
813     /**
814      * Flushes this output stream and forces any buffered output bytes
815      * to be written out to the stream.
816      *
817      * @exception  IOException  if an I/O error occurs.
818      */
819     @Override
820     public void flush() throws IOException {
821         if (out != null) {
822             out.flush();
823         }
824     }
825 
826     /*
827      * Various ZIP constants
828      */
829     /**
830      * local file header signature
831      */
832     static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
833     /**
834      * data descriptor signature
835      */
836     static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes();
837     /**
838      * central file header signature
839      */
840     static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
841     /**
842      * end of central dir signature
843      */
844     static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
845     /**
846      * ZIP64 end of central dir signature
847      */
848     static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L);
849     /**
850      * ZIP64 end of central dir locator signature
851      */
852     static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L);
853 
854     /**
855      * Writes next block of compressed data to the output stream.
856      * @throws IOException on error
857      */
858     protected final void deflate() throws IOException {
859         int len = def.deflate(buf, 0, buf.length);
860         if (len > 0) {
861             writeOut(buf, 0, len);
862             written += len;
863         }
864     }
865 
866     /**
867      * Writes the local file header entry
868      * @param ze the entry to write
869      * @throws IOException on error
870      */
871     protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException {
872 
873         boolean encodable = zipEncoding.canEncode(ze.getName());
874         ByteBuffer name = getName(ze);
875 
876         if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
877             addUnicodeExtraFields(ze, encodable, name);
878         }
879 
880         offsets.put(ze, Long.valueOf(written));
881 
882         writeOut(LFH_SIG);
883         written += WORD;
884 
885         //store method in local variable to prevent multiple method calls
886         final int zipMethod = ze.getMethod();
887 
888         writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
889                                                          !encodable
890                                                          && fallbackToUTF8,
891                                                          hasZip64Extra(ze));
892         written += WORD;
893 
894         // compression method
895         writeOut(ZipShort.getBytes(zipMethod));
896         written += SHORT;
897 
898         // last mod. time and date
899         writeOut(ZipUtil.toDosTime(ze.getTime()));
900         written += WORD;
901 
902         // CRC
903         // compressed length
904         // uncompressed length
905         entry.localDataStart = written;
906         if (zipMethod == DEFLATED || raf != null) {
907             writeOut(LZERO);
908             if (hasZip64Extra(entry.entry)) {
909                 // point to ZIP64 extended information extra field for
910                 // sizes, may get rewritten once sizes are known if
911                 // stream is seekable
912                 writeOut(ZipLong.ZIP64_MAGIC.getBytes());
913                 writeOut(ZipLong.ZIP64_MAGIC.getBytes());
914             } else {
915                 writeOut(LZERO);
916                 writeOut(LZERO);
917             }
918         } else {
919             writeOut(ZipLong.getBytes(ze.getCrc()));
920             byte[] size = ZipLong.ZIP64_MAGIC.getBytes();
921             if (!hasZip64Extra(ze)) {
922                 size = ZipLong.getBytes(ze.getSize());
923             }
924             writeOut(size);
925             writeOut(size);
926         }
927         // CheckStyle:MagicNumber OFF
928         written += 12;
929         // CheckStyle:MagicNumber ON
930 
931         // file name length
932         writeOut(ZipShort.getBytes(name.limit()));
933         written += SHORT;
934 
935         // extra field length
936         byte[] extra = ze.getLocalFileDataExtra();
937         writeOut(ZipShort.getBytes(extra.length));
938         written += SHORT;
939 
940         // file name
941         writeOut(name.array(), name.arrayOffset(),
942                  name.limit() - name.position());
943         written += name.limit();
944 
945         // extra field
946         writeOut(extra);
947         written += extra.length;
948 
949         entry.dataStart = written;
950     }
951 
952     /**
953      * Adds UnicodeExtra fields for name and file comment if mode is
954      * ALWAYS or the data cannot be encoded using the configured
955      * encoding.
956      */
957     private void addUnicodeExtraFields(ZipArchiveEntry ze, boolean encodable,
958                                        ByteBuffer name)
959         throws IOException {
960         if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
961             || !encodable) {
962             ze.addExtraField(new UnicodePathExtraField(ze.getName(),
963                                                        name.array(),
964                                                        name.arrayOffset(),
965                                                        name.limit()
966                                                        - name.position()));
967         }
968 
969         String comm = ze.getComment();
970         if (comm != null && !"".equals(comm)) {
971 
972             boolean commentEncodable = zipEncoding.canEncode(comm);
973 
974             if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
975                 || !commentEncodable) {
976                 ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
977                 ze.addExtraField(new UnicodeCommentExtraField(comm,
978                                                               commentB.array(),
979                                                               commentB.arrayOffset(),
980                                                               commentB.limit()
981                                                               - commentB.position())
982                                  );
983             }
984         }
985     }
986 
987     /**
988      * Writes the data descriptor entry.
989      * @param ze the entry to write
990      * @throws IOException on error
991      */
992     protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException {
993         if (ze.getMethod() != DEFLATED || raf != null) {
994             return;
995         }
996         writeOut(DD_SIG);
997         writeOut(ZipLong.getBytes(ze.getCrc()));
998         int sizeFieldSize = WORD;
999         if (!hasZip64Extra(ze)) {
1000             writeOut(ZipLong.getBytes(ze.getCompressedSize()));
1001             writeOut(ZipLong.getBytes(ze.getSize()));
1002         } else {
1003             sizeFieldSize = DWORD;
1004             writeOut(ZipEightByteInteger.getBytes(ze.getCompressedSize()));
1005             writeOut(ZipEightByteInteger.getBytes(ze.getSize()));
1006         }
1007         written += 2 * WORD + 2 * sizeFieldSize;
1008     }
1009 
1010     /**
1011      * Writes the central file header entry.
1012      * @param ze the entry to write
1013      * @throws IOException on error
1014      * @throws Zip64RequiredException if the archive's size exceeds 4
1015      * GByte and {@link Zip64Mode #setUseZip64} is {@link
1016      * Zip64Mode#Never}.
1017      */
1018     protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException {
1019         writeOut(CFH_SIG);
1020         written += WORD;
1021 
1022         final long lfhOffset = offsets.get(ze).longValue();
1023         final boolean needsZip64Extra = hasZip64Extra(ze)
1024             || ze.getCompressedSize() >= ZIP64_MAGIC
1025             || ze.getSize() >= ZIP64_MAGIC
1026             || lfhOffset >= ZIP64_MAGIC;
1027 
1028         if (needsZip64Extra && zip64Mode == Zip64Mode.Never) {
1029             // must be the offset that is too big, otherwise an
1030             // exception would have been throw in putArchiveEntry or
1031             // closeArchiveEntry
1032             throw new Zip64RequiredException(Zip64RequiredException
1033                                              .ARCHIVE_TOO_BIG_MESSAGE);
1034         }
1035 
1036         handleZip64Extra(ze, lfhOffset, needsZip64Extra);
1037 
1038         // version made by
1039         // CheckStyle:MagicNumber OFF
1040         writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 
1041                                    (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION
1042                                                   : ZIP64_MIN_VERSION)));
1043         written += SHORT;
1044 
1045         final int zipMethod = ze.getMethod();
1046         final boolean encodable = zipEncoding.canEncode(ze.getName());
1047         writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
1048                                                          !encodable
1049                                                          && fallbackToUTF8,
1050                                                          needsZip64Extra);
1051         written += WORD;
1052 
1053         // compression method
1054         writeOut(ZipShort.getBytes(zipMethod));
1055         written += SHORT;
1056 
1057         // last mod. time and date
1058         writeOut(ZipUtil.toDosTime(ze.getTime()));
1059         written += WORD;
1060 
1061         // CRC
1062         // compressed length
1063         // uncompressed length
1064         writeOut(ZipLong.getBytes(ze.getCrc()));
1065         if (ze.getCompressedSize() >= ZIP64_MAGIC
1066             || ze.getSize() >= ZIP64_MAGIC) {
1067             writeOut(ZipLong.ZIP64_MAGIC.getBytes());
1068             writeOut(ZipLong.ZIP64_MAGIC.getBytes());
1069         } else {
1070             writeOut(ZipLong.getBytes(ze.getCompressedSize()));
1071             writeOut(ZipLong.getBytes(ze.getSize()));
1072         }
1073         // CheckStyle:MagicNumber OFF
1074         written += 12;
1075         // CheckStyle:MagicNumber ON
1076 
1077         ByteBuffer name = getName(ze);
1078 
1079         writeOut(ZipShort.getBytes(name.limit()));
1080         written += SHORT;
1081 
1082         // extra field length
1083         byte[] extra = ze.getCentralDirectoryExtra();
1084         writeOut(ZipShort.getBytes(extra.length));
1085         written += SHORT;
1086 
1087         // file comment length
1088         String comm = ze.getComment();
1089         if (comm == null) {
1090             comm = "";
1091         }
1092 
1093         ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
1094 
1095         writeOut(ZipShort.getBytes(commentB.limit()));
1096         written += SHORT;
1097 
1098         // disk number start
1099         writeOut(ZERO);
1100         written += SHORT;
1101 
1102         // internal file attributes
1103         writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
1104         written += SHORT;
1105 
1106         // external file attributes
1107         writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
1108         written += WORD;
1109 
1110         // relative offset of LFH
1111         writeOut(ZipLong.getBytes(Math.min(lfhOffset, ZIP64_MAGIC)));
1112         written += WORD;
1113 
1114         // file name
1115         writeOut(name.array(), name.arrayOffset(),
1116                  name.limit() - name.position());
1117         written += name.limit();
1118 
1119         // extra field
1120         writeOut(extra);
1121         written += extra.length;
1122 
1123         // file comment
1124         writeOut(commentB.array(), commentB.arrayOffset(),
1125                  commentB.limit() - commentB.position());
1126         written += commentB.limit();
1127     }
1128 
1129     /**
1130      * If the entry needs Zip64 extra information inside the central
1131      * directory then configure its data.
1132      */
1133     private void handleZip64Extra(ZipArchiveEntry ze, long lfhOffset,
1134                                   boolean needsZip64Extra) {
1135         if (needsZip64Extra) {
1136             Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze);
1137             if (ze.getCompressedSize() >= ZIP64_MAGIC
1138                 || ze.getSize() >= ZIP64_MAGIC) {
1139                 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
1140                 z64.setSize(new ZipEightByteInteger(ze.getSize()));
1141             } else {
1142                 // reset value that may have been set for LFH
1143                 z64.setCompressedSize(null);
1144                 z64.setSize(null);
1145             }
1146             if (lfhOffset >= ZIP64_MAGIC) {
1147                 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset));
1148             }
1149             ze.setExtra();
1150         }
1151     }
1152 
1153     /**
1154      * Writes the &quot;End of central dir record&quot;.
1155      * @throws IOException on error
1156      * @throws Zip64RequiredException if the archive's size exceeds 4
1157      * GByte or there are more than 65535 entries inside the archive
1158      * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}.
1159      */
1160     protected void writeCentralDirectoryEnd() throws IOException {
1161         writeOut(EOCD_SIG);
1162 
1163         // disk numbers
1164         writeOut(ZERO);
1165         writeOut(ZERO);
1166 
1167         // number of entries
1168         int numberOfEntries = entries.size();
1169         if (numberOfEntries > ZIP64_MAGIC_SHORT
1170             && zip64Mode == Zip64Mode.Never) {
1171             throw new Zip64RequiredException(Zip64RequiredException
1172                                              .TOO_MANY_ENTRIES_MESSAGE);
1173         }
1174         if (cdOffset > ZIP64_MAGIC && zip64Mode == Zip64Mode.Never) {
1175             throw new Zip64RequiredException(Zip64RequiredException
1176                                              .ARCHIVE_TOO_BIG_MESSAGE);
1177         }
1178 
1179         byte[] num = ZipShort.getBytes(Math.min(numberOfEntries,
1180                                                 ZIP64_MAGIC_SHORT));
1181         writeOut(num);
1182         writeOut(num);
1183 
1184         // length and location of CD
1185         writeOut(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC)));
1186         writeOut(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC)));
1187 
1188         // ZIP file comment
1189         ByteBuffer data = this.zipEncoding.encode(comment);
1190         writeOut(ZipShort.getBytes(data.limit()));
1191         writeOut(data.array(), data.arrayOffset(),
1192                  data.limit() - data.position());
1193     }
1194 
1195     private static final byte[] ONE = ZipLong.getBytes(1L);
1196 
1197     /**
1198      * Writes the &quot;ZIP64 End of central dir record&quot; and
1199      * &quot;ZIP64 End of central dir locator&quot;.
1200      * @throws IOException on error
1201      * @since 1.3
1202      */
1203     protected void writeZip64CentralDirectory() throws IOException {
1204         if (zip64Mode == Zip64Mode.Never) {
1205             return;
1206         }
1207 
1208         if (!hasUsedZip64
1209             && (cdOffset >= ZIP64_MAGIC || cdLength >= ZIP64_MAGIC
1210                 || entries.size() >= ZIP64_MAGIC_SHORT)) {
1211             // actually "will use"
1212             hasUsedZip64 = true;
1213         }
1214 
1215         if (!hasUsedZip64) {
1216             return;
1217         }
1218 
1219         long offset = written;
1220 
1221         writeOut(ZIP64_EOCD_SIG);
1222         // size, we don't have any variable length as we don't support
1223         // the extensible data sector, yet
1224         writeOut(ZipEightByteInteger
1225                  .getBytes(SHORT   /* version made by */
1226                            + SHORT /* version needed to extract */
1227                            + WORD  /* disk number */
1228                            + WORD  /* disk with central directory */
1229                            + DWORD /* number of entries in CD on this disk */
1230                            + DWORD /* total number of entries */
1231                            + DWORD /* size of CD */
1232                            + DWORD /* offset of CD */
1233                            ));
1234 
1235         // version made by and version needed to extract
1236         writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION));
1237         writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION));
1238 
1239         // disk numbers - four bytes this time
1240         writeOut(LZERO);
1241         writeOut(LZERO);
1242 
1243         // number of entries
1244         byte[] num = ZipEightByteInteger.getBytes(entries.size());
1245         writeOut(num);
1246         writeOut(num);
1247 
1248         // length and location of CD
1249         writeOut(ZipEightByteInteger.getBytes(cdLength));
1250         writeOut(ZipEightByteInteger.getBytes(cdOffset));
1251 
1252         // no "zip64 extensible data sector" for now
1253 
1254         // and now the "ZIP64 end of central directory locator"
1255         writeOut(ZIP64_EOCD_LOC_SIG);
1256 
1257         // disk number holding the ZIP64 EOCD record
1258         writeOut(LZERO);
1259         // relative offset of ZIP64 EOCD record
1260         writeOut(ZipEightByteInteger.getBytes(offset));
1261         // total number of disks
1262         writeOut(ONE);
1263     }
1264 
1265     /**
1266      * Write bytes to output or random access file.
1267      * @param data the byte array to write
1268      * @throws IOException on error
1269      */
1270     protected final void writeOut(byte[] data) throws IOException {
1271         writeOut(data, 0, data.length);
1272     }
1273 
1274     /**
1275      * Write bytes to output or random access file.
1276      * @param data the byte array to write
1277      * @param offset the start position to write from
1278      * @param length the number of bytes to write
1279      * @throws IOException on error
1280      */
1281     protected final void writeOut(byte[] data, int offset, int length)
1282         throws IOException {
1283         if (raf != null) {
1284             raf.write(data, offset, length);
1285         } else {
1286             out.write(data, offset, length);
1287         }
1288     }
1289 
1290     private void deflateUntilInputIsNeeded() throws IOException {
1291         while (!def.needsInput()) {
1292             deflate();
1293         }
1294     }
1295 
1296     private void writeVersionNeededToExtractAndGeneralPurposeBits(final int
1297                                                                   zipMethod,
1298                                                                   final boolean
1299                                                                   utfFallback,
1300                                                                   final boolean
1301                                                                   zip64)
1302         throws IOException {
1303 
1304         // CheckStyle:MagicNumber OFF
1305         int versionNeededToExtract = INITIAL_VERSION;
1306         GeneralPurposeBit b = new GeneralPurposeBit();
1307         b.useUTF8ForNames(useUTF8Flag || utfFallback);
1308         if (zipMethod == DEFLATED && raf == null) {
1309             // requires version 2 as we are going to store length info
1310             // in the data descriptor
1311             versionNeededToExtract = DATA_DESCRIPTOR_MIN_VERSION;
1312             b.useDataDescriptor(true);
1313         }
1314         if (zip64) {
1315             versionNeededToExtract = ZIP64_MIN_VERSION;
1316         }
1317         // CheckStyle:MagicNumber ON
1318 
1319         // version needed to extract
1320         writeOut(ZipShort.getBytes(versionNeededToExtract));
1321         // general purpose bit flag
1322         writeOut(b.encode());
1323     }
1324 
1325     /**
1326      * Creates a new zip entry taking some information from the given
1327      * file and using the provided name.
1328      *
1329      * <p>The name will be adjusted to end with a forward slash "/" if
1330      * the file is a directory.  If the file is not a directory a
1331      * potential trailing forward slash will be stripped from the
1332      * entry name.</p>
1333      *
1334      * <p>Must not be used if the stream has already been closed.</p>
1335      */
1336     @Override
1337     public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
1338         throws IOException {
1339         if (finished) {
1340             throw new IOException("Stream has already been finished");
1341         }
1342         return new ZipArchiveEntry(inputFile, entryName);
1343     }
1344 
1345     /**
1346      * Get the existing ZIP64 extended information extra field or
1347      * create a new one and add it to the entry.
1348      *
1349      * @since 1.3
1350      */
1351     private Zip64ExtendedInformationExtraField
1352         getZip64Extra(ZipArchiveEntry ze) {
1353         if (entry != null) {
1354             entry.causedUseOfZip64 = !hasUsedZip64;
1355         }
1356         hasUsedZip64 = true;
1357         Zip64ExtendedInformationExtraField z64 =
1358             (Zip64ExtendedInformationExtraField)
1359             ze.getExtraField(Zip64ExtendedInformationExtraField
1360                              .HEADER_ID);
1361         if (z64 == null) {
1362             /*
1363               System.err.println("Adding z64 for " + ze.getName()
1364               + ", method: " + ze.getMethod()
1365               + " (" + (ze.getMethod() == STORED) + ")"
1366               + ", raf: " + (raf != null));
1367             */
1368             z64 = new Zip64ExtendedInformationExtraField();
1369         }
1370 
1371         // even if the field is there already, make sure it is the first one
1372         ze.addAsFirstExtraField(z64);
1373 
1374         return z64;
1375     }
1376 
1377     /**
1378      * Is there a ZIP64 extended information extra field for the
1379      * entry?
1380      *
1381      * @since 1.3
1382      */
1383     private boolean hasZip64Extra(ZipArchiveEntry ze) {
1384         return ze.getExtraField(Zip64ExtendedInformationExtraField
1385                                 .HEADER_ID)
1386             != null;
1387     }
1388 
1389     /**
1390      * If the mode is AsNeeded and the entry is a compressed entry of
1391      * unknown size that gets written to a non-seekable stream the
1392      * change the default to Never.
1393      *
1394      * @since 1.3
1395      */
1396     private Zip64Mode getEffectiveZip64Mode(ZipArchiveEntry ze) {
1397         if (zip64Mode != Zip64Mode.AsNeeded
1398             || raf != null
1399             || ze.getMethod() != DEFLATED
1400             || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
1401             return zip64Mode;
1402         }
1403         return Zip64Mode.Never;
1404     }
1405 
1406     private ZipEncoding getEntryEncoding(ZipArchiveEntry ze) {
1407         boolean encodable = zipEncoding.canEncode(ze.getName());
1408         return !encodable && fallbackToUTF8
1409             ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
1410     }
1411 
1412     private ByteBuffer getName(ZipArchiveEntry ze) throws IOException {
1413         return getEntryEncoding(ze).encode(ze.getName());
1414     }
1415 
1416     /**
1417      * Closes the underlying stream/file without finishing the
1418      * archive, the result will likely be a corrupt archive.
1419      *
1420      * <p>This method only exists to support tests that generate
1421      * corrupt archives so they can clean up any temporary files.</p>
1422      */
1423     void destroy() throws IOException {
1424         if (raf != null) {
1425             raf.close();
1426         }
1427         if (out != null) {
1428             out.close();
1429         }
1430     }
1431 
1432     /**
1433      * enum that represents the possible policies for creating Unicode
1434      * extra fields.
1435      */
1436     public static final class UnicodeExtraFieldPolicy {
1437         /**
1438          * Always create Unicode extra fields.
1439          */
1440         public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
1441         /**
1442          * Never create Unicode extra fields.
1443          */
1444         public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
1445         /**
1446          * Create Unicode extra fields for filenames that cannot be
1447          * encoded using the specified encoding.
1448          */
1449         public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
1450             new UnicodeExtraFieldPolicy("not encodeable");
1451 
1452         private final String name;
1453         private UnicodeExtraFieldPolicy(String n) {
1454             name = n;
1455         }
1456         @Override
1457         public String toString() {
1458             return name;
1459         }
1460     }
1461 
1462     /**
1463      * Structure collecting information for the entry that is
1464      * currently being written.
1465      */
1466     private static final class CurrentEntry {
1467         private CurrentEntry(ZipArchiveEntry entry) {
1468             this.entry = entry;
1469         }
1470         /**
1471          * Current ZIP entry.
1472          */
1473         private final ZipArchiveEntry entry;
1474         /**
1475          * Offset for CRC entry in the local file header data for the
1476          * current entry starts here.
1477          */
1478         private long localDataStart = 0;
1479         /**
1480          * Data for local header data
1481          */
1482         private long dataStart = 0;
1483         /**
1484          * Number of bytes read for the current entry (can't rely on
1485          * Deflater#getBytesRead) when using DEFLATED.
1486          */
1487         private long bytesRead = 0;
1488         /**
1489          * Whether current entry was the first one using ZIP64 features.
1490          */
1491         private boolean causedUseOfZip64 = false;
1492         /**
1493          * Whether write() has been called at all.
1494          *
1495          * <p>In order to create a valid archive {@link
1496          * #closeArchiveEntry closeArchiveEntry} will write an empty
1497          * array to get the CRC right if nothing has been written to
1498          * the stream at all.</p>
1499          */
1500         private boolean hasWritten;
1501     }
1502 
1503 }