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