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