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