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