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