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