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