001    /*
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     *
017     */
018    package org.apache.commons.compress.archivers.zip;
019    
020    import java.io.File;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.io.OutputStream;
024    import java.io.RandomAccessFile;
025    import java.nio.ByteBuffer;
026    import java.util.HashMap;
027    import java.util.LinkedList;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.zip.CRC32;
031    import java.util.zip.Deflater;
032    import java.util.zip.ZipException;
033    
034    import org.apache.commons.compress.archivers.ArchiveEntry;
035    import org.apache.commons.compress.archivers.ArchiveOutputStream;
036    
037    import static org.apache.commons.compress.archivers.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
038    import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD;
039    import static org.apache.commons.compress.archivers.zip.ZipConstants.INITIAL_VERSION;
040    import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT;
041    import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD;
042    import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC;
043    import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT;
044    import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MIN_VERSION;
045    
046    /**
047     * Reimplementation of {@link java.util.zip.ZipOutputStream
048     * java.util.zip.ZipOutputStream} that does handle the extended
049     * functionality of this package, especially internal/external file
050     * attributes and extra fields with different layouts for local file
051     * data and central directory entries.
052     *
053     * <p>This class will try to use {@link java.io.RandomAccessFile
054     * RandomAccessFile} when you know that the output is going to go to a
055     * file.</p>
056     *
057     * <p>If RandomAccessFile cannot be used, this implementation will use
058     * a Data Descriptor to store size and CRC information for {@link
059     * #DEFLATED DEFLATED} entries, this means, you don't need to
060     * calculate them yourself.  Unfortunately this is not possible for
061     * the {@link #STORED STORED} method, here setting the CRC and
062     * uncompressed size information is required before {@link
063     * #putArchiveEntry(ArchiveEntry)} can be called.</p>
064     *
065     * <p>As of Apache Commons Compress 1.3 it transparently supports Zip64
066     * extensions and thus individual entries and archives larger than 4
067     * GB or with more than 65536 entries in most cases but explicit
068     * control is provided via {@link #setUseZip64}.  If the stream can not
069     * user RandomAccessFile and you try to write a ZipArchiveEntry of
070     * unknown size then Zip64 extensions will be disabled by default.</p>
071     *
072     * @NotThreadSafe
073     */
074    public class ZipArchiveOutputStream extends ArchiveOutputStream {
075    
076        static final int BUFFER_SIZE = 512;
077    
078        /** indicates if this archive is finished. protected for use in Jar implementation */
079        protected boolean finished = false;
080    
081        /* 
082         * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs
083         * when it gets handed a really big buffer.  See
084         * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396
085         *
086         * Using a buffer size of 8 kB proved to be a good compromise
087         */
088        private static final int DEFLATER_BLOCK_SIZE = 8192;
089    
090        /**
091         * Compression method for deflated entries.
092         */
093        public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
094    
095        /**
096         * Default compression level for deflated entries.
097         */
098        public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
099    
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    }