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