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