    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 Apache Commons Compress 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 Apache Commons Compress 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 Apache Commons Compress 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 Apache Commons Compress 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 Apache Commons Compress 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 Apache Commons Compress 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 Apache Commons Compress 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    }