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