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