ZipArchiveOutputStream.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.archivers.zip;

  18. import java.io.ByteArrayOutputStream;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.nio.ByteBuffer;
  24. import java.nio.channels.SeekableByteChannel;
  25. import java.nio.charset.Charset;
  26. import java.nio.charset.StandardCharsets;
  27. import java.nio.file.LinkOption;
  28. import java.nio.file.OpenOption;
  29. import java.nio.file.Path;
  30. import java.util.HashMap;
  31. import java.util.LinkedList;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.zip.Deflater;
  35. import java.util.zip.ZipException;

  36. import org.apache.commons.compress.archivers.ArchiveEntry;
  37. import org.apache.commons.compress.archivers.ArchiveOutputStream;
  38. import org.apache.commons.compress.utils.ByteUtils;
  39. import org.apache.commons.io.Charsets;

  40. /**
  41.  * Reimplementation of {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} to handle the extended functionality of this package, especially
  42.  * internal/external file attributes and extra fields with different layouts for local file data and central directory entries.
  43.  * <p>
  44.  * 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
  45.  * created.
  46.  * </p>
  47.  * <p>
  48.  * If SeekableByteChannel cannot be used, this implementation will use a Data Descriptor to store size and CRC information for {@link #DEFLATED DEFLATED}
  49.  * 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
  50.  * uncompressed size information is required before {@link #putArchiveEntry(ZipArchiveEntry)} can be called.
  51.  * </p>
  52.  * <p>
  53.  * 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
  54.  * than 65,536 entries in most cases but explicit control is provided via {@link #setUseZip64}. If the stream can not use SeekableByteChannel and you try to
  55.  * write a ZipArchiveEntry of unknown size, then Zip64 extensions will be disabled by default.
  56.  * </p>
  57.  *
  58.  * @NotThreadSafe
  59.  */
  60. public class ZipArchiveOutputStream extends ArchiveOutputStream<ZipArchiveEntry> {

  61.     /**
  62.      * Structure collecting information for the entry that is currently being written.
  63.      */
  64.     private static final class CurrentEntry {

  65.         /**
  66.          * Current ZIP entry.
  67.          */
  68.         private final ZipArchiveEntry entry;

  69.         /**
  70.          * Offset for CRC entry in the local file header data for the current entry starts here.
  71.          */
  72.         private long localDataStart;

  73.         /**
  74.          * Data for local header data
  75.          */
  76.         private long dataStart;

  77.         /**
  78.          * Number of bytes read for the current entry (can't rely on Deflater#getBytesRead) when using DEFLATED.
  79.          */
  80.         private long bytesRead;

  81.         /**
  82.          * Whether current entry was the first one using ZIP64 features.
  83.          */
  84.         private boolean causedUseOfZip64;

  85.         /**
  86.          * Whether write() has been called at all.
  87.          *
  88.          * <p>
  89.          * In order to create a valid archive {@link #closeArchiveEntry closeArchiveEntry} will write an empty array to get the CRC right if nothing has been
  90.          * written to the stream at all.
  91.          * </p>
  92.          */
  93.         private boolean hasWritten;

  94.         private CurrentEntry(final ZipArchiveEntry entry) {
  95.             this.entry = entry;
  96.         }
  97.     }

  98.     private static final class EntryMetaData {
  99.         private final long offset;
  100.         private final boolean usesDataDescriptor;

  101.         private EntryMetaData(final long offset, final boolean usesDataDescriptor) {
  102.             this.offset = offset;
  103.             this.usesDataDescriptor = usesDataDescriptor;
  104.         }
  105.     }

  106.     /**
  107.      * enum that represents the possible policies for creating Unicode extra fields.
  108.      */
  109.     public static final class UnicodeExtraFieldPolicy {

  110.         /**
  111.          * Always create Unicode extra fields.
  112.          */
  113.         public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");

  114.         /**
  115.          * Never create Unicode extra fields.
  116.          */
  117.         public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");

  118.         /**
  119.          * Creates Unicode extra fields for file names that cannot be encoded using the specified encoding.
  120.          */
  121.         public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = new UnicodeExtraFieldPolicy("not encodeable");

  122.         private final String name;

  123.         private UnicodeExtraFieldPolicy(final String n) {
  124.             name = n;
  125.         }

  126.         @Override
  127.         public String toString() {
  128.             return name;
  129.         }
  130.     }

  131.     static final int BUFFER_SIZE = 512;
  132.     private static final int LFH_SIG_OFFSET = 0;
  133.     private static final int LFH_VERSION_NEEDED_OFFSET = 4;
  134.     private static final int LFH_GPB_OFFSET = 6;
  135.     private static final int LFH_METHOD_OFFSET = 8;
  136.     private static final int LFH_TIME_OFFSET = 10;
  137.     private static final int LFH_CRC_OFFSET = 14;
  138.     private static final int LFH_COMPRESSED_SIZE_OFFSET = 18;
  139.     private static final int LFH_ORIGINAL_SIZE_OFFSET = 22;
  140.     private static final int LFH_FILENAME_LENGTH_OFFSET = 26;
  141.     private static final int LFH_EXTRA_LENGTH_OFFSET = 28;
  142.     private static final int LFH_FILENAME_OFFSET = 30;
  143.     private static final int CFH_SIG_OFFSET = 0;
  144.     private static final int CFH_VERSION_MADE_BY_OFFSET = 4;
  145.     private static final int CFH_VERSION_NEEDED_OFFSET = 6;
  146.     private static final int CFH_GPB_OFFSET = 8;
  147.     private static final int CFH_METHOD_OFFSET = 10;
  148.     private static final int CFH_TIME_OFFSET = 12;
  149.     private static final int CFH_CRC_OFFSET = 16;
  150.     private static final int CFH_COMPRESSED_SIZE_OFFSET = 20;
  151.     private static final int CFH_ORIGINAL_SIZE_OFFSET = 24;
  152.     private static final int CFH_FILENAME_LENGTH_OFFSET = 28;
  153.     private static final int CFH_EXTRA_LENGTH_OFFSET = 30;
  154.     private static final int CFH_COMMENT_LENGTH_OFFSET = 32;
  155.     private static final int CFH_DISK_NUMBER_OFFSET = 34;
  156.     private static final int CFH_INTERNAL_ATTRIBUTES_OFFSET = 36;

  157.     private static final int CFH_EXTERNAL_ATTRIBUTES_OFFSET = 38;

  158.     private static final int CFH_LFH_OFFSET = 42;

  159.     private static final int CFH_FILENAME_OFFSET = 46;

  160.     /**
  161.      * Compression method for deflated entries.
  162.      */
  163.     public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;

  164.     /**
  165.      * Default compression level for deflated entries.
  166.      */
  167.     public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;

  168.     /**
  169.      * Compression method for stored entries.
  170.      */
  171.     public static final int STORED = java.util.zip.ZipEntry.STORED;

  172.     /**
  173.      * Default encoding for file names and comment.
  174.      */
  175.     static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

  176.     /**
  177.      * General purpose flag, which indicates that file names are written in UTF-8.
  178.      *
  179.      * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead
  180.      */
  181.     @Deprecated
  182.     public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG;

  183.     /**
  184.      * Helper, a 0 as ZipShort.
  185.      */
  186.     private static final byte[] ZERO = { 0, 0 };

  187.     /**
  188.      * Helper, a 0 as ZipLong.
  189.      */
  190.     private static final byte[] LZERO = { 0, 0, 0, 0 };

  191.     private static final byte[] ONE = ZipLong.getBytes(1L);

  192.     /*
  193.      * Various ZIP constants shared between this class, ZipArchiveInputStream and ZipFile
  194.      */
  195.     /**
  196.      * local file header signature
  197.      */
  198.     static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); // NOSONAR

  199.     /**
  200.      * data descriptor signature
  201.      */
  202.     static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes(); // NOSONAR

  203.     /**
  204.      * central file header signature
  205.      */
  206.     static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); // NOSONAR

  207.     /**
  208.      * end of central dir signature
  209.      */
  210.     static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); // NOSONAR

  211.     /**
  212.      * ZIP64 end of central dir signature
  213.      */
  214.     static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L); // NOSONAR

  215.     /**
  216.      * ZIP64 end of central dir locator signature
  217.      */
  218.     static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L); // NOSONAR

  219.     /**
  220.      * Indicates if this archive is finished. protected for use in Jar implementation.
  221.      *
  222.      * @deprecated See {@link #isFinished()} and {@link #finish()}.
  223.      */
  224.     @Deprecated
  225.     protected boolean finished;

  226.     /**
  227.      * Current entry.
  228.      */
  229.     private CurrentEntry entry;

  230.     /**
  231.      * The file comment.
  232.      */
  233.     private String comment = "";

  234.     /**
  235.      * Compression level for next entry.
  236.      */
  237.     private int level = DEFAULT_COMPRESSION;

  238.     /**
  239.      * Has the compression level changed when compared to the last entry?
  240.      */
  241.     private boolean hasCompressionLevelChanged;

  242.     /**
  243.      * Default compression method for next entry.
  244.      */
  245.     private int method = java.util.zip.ZipEntry.DEFLATED;

  246.     /**
  247.      * List of ZipArchiveEntries written so far.
  248.      */
  249.     private final List<ZipArchiveEntry> entries = new LinkedList<>();

  250.     private final StreamCompressor streamCompressor;

  251.     /**
  252.      * Start of central directory.
  253.      */
  254.     private long cdOffset;

  255.     /**
  256.      * Length of central directory.
  257.      */
  258.     private long cdLength;

  259.     /**
  260.      * Disk number start of central directory.
  261.      */
  262.     private long cdDiskNumberStart;

  263.     /**
  264.      * Length of end of central directory
  265.      */
  266.     private long eocdLength;

  267.     /**
  268.      * Holds some book-keeping data for each entry.
  269.      */
  270.     private final Map<ZipArchiveEntry, EntryMetaData> metaData = new HashMap<>();

  271.     /**
  272.      * The encoding to use for file names and the file comment.
  273.      *
  274.      * <p>
  275.      * 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>.
  276.      * Defaults to UTF-8.
  277.      * </p>
  278.      */
  279.     private Charset charset = DEFAULT_CHARSET;

  280.     /**
  281.      * The ZIP encoding to use for file names and the file comment.
  282.      *
  283.      * This field is of internal use and will be set in {@link #setEncoding(String)}.
  284.      */
  285.     private ZipEncoding zipEncoding = ZipEncodingHelper.getZipEncoding(DEFAULT_CHARSET);

  286.     /**
  287.      * This Deflater object is used for output.
  288.      */
  289.     protected final Deflater def;

  290.     /**
  291.      * whether to use the general purpose bit flag when writing UTF-8 file names or not.
  292.      */
  293.     private boolean useUTF8Flag = true;

  294.     /**
  295.      * Whether to encode non-encodable file names as UTF-8.
  296.      */
  297.     private boolean fallbackToUTF8;

  298.     /**
  299.      * whether to create UnicodePathExtraField-s for each entry.
  300.      */
  301.     private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;

  302.     /**
  303.      * Whether anything inside this archive has used a ZIP64 feature.
  304.      *
  305.      * @since 1.3
  306.      */
  307.     private boolean hasUsedZip64;

  308.     private Zip64Mode zip64Mode = Zip64Mode.AsNeeded;

  309.     private final byte[] copyBuffer = new byte[32768];

  310.     /**
  311.      * Whether we are creating a split zip
  312.      */
  313.     private final boolean isSplitZip;

  314.     /**
  315.      * Holds the number of Central Directories on each disk. This is used when writing Zip64 End Of Central Directory and End Of Central Directory.
  316.      */
  317.     private final Map<Integer, Integer> numberOfCDInDiskData = new HashMap<>();

  318.     /**
  319.      * Creates a new ZIP OutputStream writing to a File. Will use random access if possible.
  320.      *
  321.      * @param file the file to ZIP to
  322.      * @throws IOException on error
  323.      */
  324.     public ZipArchiveOutputStream(final File file) throws IOException {
  325.         this(file.toPath());
  326.     }

  327.     /**
  328.      * Creates a split ZIP Archive.
  329.      *
  330.      * <p>
  331.      * The files making up the archive will use Z01, Z02, ... extensions and the last part of it will be the given {@code
  332.      * file}.
  333.      * </p>
  334.      *
  335.      * <p>
  336.      * 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
  337.      * known before the actual entry data is written.
  338.      * </p>
  339.      *
  340.      * @param file         the file that will become the last part of the split archive
  341.      * @param zipSplitSize maximum size of a single part of the split archive created by this stream. Must be between 64kB and about 4GB.
  342.      *
  343.      * @throws IOException              on error
  344.      * @throws IllegalArgumentException if zipSplitSize is not in the required range
  345.      * @since 1.20
  346.      */
  347.     public ZipArchiveOutputStream(final File file, final long zipSplitSize) throws IOException {
  348.         this(file.toPath(), zipSplitSize);
  349.     }

  350.     /**
  351.      * Creates a new ZIP OutputStream filtering the underlying stream.
  352.      *
  353.      * @param out the outputstream to zip
  354.      */
  355.     public ZipArchiveOutputStream(final OutputStream out) {
  356.         this.out = out;
  357.         this.def = new Deflater(level, true);
  358.         this.streamCompressor = StreamCompressor.create(out, def);
  359.         this.isSplitZip = false;
  360.     }

  361.     /**
  362.      * Creates a split ZIP Archive.
  363.      * <p>
  364.      * The files making up the archive will use Z01, Z02, ... extensions and the last part of it will be the given {@code
  365.      * file}.
  366.      * </p>
  367.      * <p>
  368.      * 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
  369.      * known before the actual entry data is written.
  370.      * </p>
  371.      *
  372.      * @param path         the path to the file that will become the last part of the split archive
  373.      * @param zipSplitSize maximum size of a single part of the split archive created by this stream. Must be between 64kB and about 4GB.
  374.      * @throws IOException              on error
  375.      * @throws IllegalArgumentException if zipSplitSize is not in the required range
  376.      * @since 1.22
  377.      */
  378.     public ZipArchiveOutputStream(final Path path, final long zipSplitSize) throws IOException {
  379.         this.def = new Deflater(level, true);
  380.         this.out = new ZipSplitOutputStream(path, zipSplitSize);
  381.         this.streamCompressor = StreamCompressor.create(this.out, def);
  382.         this.isSplitZip = true;
  383.     }

  384.     /**
  385.      * Creates a new ZIP OutputStream writing to a Path. Will use random access if possible.
  386.      *
  387.      * @param file    the file to ZIP to
  388.      * @param options options specifying how the file is opened.
  389.      * @throws IOException on error
  390.      * @since 1.21
  391.      */
  392.     public ZipArchiveOutputStream(final Path file, final OpenOption... options) throws IOException {
  393.         this.def = new Deflater(level, true);
  394.         this.out = options.length == 0 ? new FileRandomAccessOutputStream(file) : new FileRandomAccessOutputStream(file, options);
  395.         this.streamCompressor = StreamCompressor.create(out, def);
  396.         this.isSplitZip = false;
  397.     }

  398.     /**
  399.      * Creates a new ZIP OutputStream writing to a SeekableByteChannel.
  400.      *
  401.      * <p>
  402.      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to write to an in-memory archive using random access.
  403.      * </p>
  404.      *
  405.      * @param channel the channel to ZIP to
  406.      * @since 1.13
  407.      */
  408.     public ZipArchiveOutputStream(final SeekableByteChannel channel) {
  409.         this.out = new SeekableChannelRandomAccessOutputStream(channel);
  410.         this.def = new Deflater(level, true);
  411.         this.streamCompressor = StreamCompressor.create(out, def);
  412.         this.isSplitZip = false;
  413.     }

  414.     /**
  415.      * Adds an archive entry with a raw input stream.
  416.      * <p>
  417.      * 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
  418.      * stream, and the supplied value is ignored.
  419.      * </p>
  420.      * <p>
  421.      * The entry is put and closed immediately.
  422.      * </p>
  423.      *
  424.      * @param entry     The archive entry to add
  425.      * @param rawStream The raw input stream of a different entry. May be compressed/encrypted.
  426.      * @throws IOException If copying fails
  427.      */
  428.     public void addRawArchiveEntry(final ZipArchiveEntry entry, final InputStream rawStream) throws IOException {
  429.         final ZipArchiveEntry ae = new ZipArchiveEntry(entry);
  430.         if (hasZip64Extra(ae)) {
  431.             // Will be re-added as required. this may make the file generated with this method
  432.             // somewhat smaller than standard mode,
  433.             // since standard mode is unable to remove the ZIP 64 header.
  434.             ae.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
  435.         }
  436.         final boolean is2PhaseSource = ae.getCrc() != ZipArchiveEntry.CRC_UNKNOWN && ae.getSize() != ArchiveEntry.SIZE_UNKNOWN
  437.                 && ae.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN;
  438.         putArchiveEntry(ae, is2PhaseSource);
  439.         copyFromZipInputStream(rawStream);
  440.         closeCopiedEntry(is2PhaseSource);
  441.     }

  442.     /**
  443.      * Adds UnicodeExtra fields for name and file comment if mode is ALWAYS or the data cannot be encoded using the configured encoding.
  444.      */
  445.     private void addUnicodeExtraFields(final ZipArchiveEntry ze, final boolean encodable, final ByteBuffer name) throws IOException {
  446.         if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !encodable) {
  447.             ze.addExtraField(new UnicodePathExtraField(ze.getName(), name.array(), name.arrayOffset(), name.limit() - name.position()));
  448.         }

  449.         final String comm = ze.getComment();
  450.         if (comm != null && !comm.isEmpty()) {

  451.             final boolean commentEncodable = zipEncoding.canEncode(comm);

  452.             if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !commentEncodable) {
  453.                 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
  454.                 ze.addExtraField(new UnicodeCommentExtraField(comm, commentB.array(), commentB.arrayOffset(), commentB.limit() - commentB.position()));
  455.             }
  456.         }
  457.     }

  458.     /**
  459.      * Whether this stream is able to write the given entry.
  460.      * <p>
  461.      * May return false if it is set up to use encryption or a compression method that hasn't been implemented yet.
  462.      * </p>
  463.      *
  464.      * @since 1.1
  465.      */
  466.     @Override
  467.     public boolean canWriteEntryData(final ArchiveEntry ae) {
  468.         if (ae instanceof ZipArchiveEntry) {
  469.             final ZipArchiveEntry zae = (ZipArchiveEntry) ae;
  470.             return zae.getMethod() != ZipMethod.IMPLODING.getCode() && zae.getMethod() != ZipMethod.UNSHRINKING.getCode() && ZipUtil.canHandleEntryData(zae);
  471.         }
  472.         return false;
  473.     }

  474.     /**
  475.      * Verifies the sizes aren't too big in the Zip64Mode.Never case and returns whether the entry would require a Zip64 extra field.
  476.      */
  477.     private boolean checkIfNeedsZip64(final Zip64Mode effectiveMode) throws ZipException {
  478.         final boolean actuallyNeedsZip64 = isZip64Required(entry.entry, effectiveMode);
  479.         if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) {
  480.             throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry));
  481.         }
  482.         return actuallyNeedsZip64;
  483.     }

  484.     /**
  485.      * Closes this output stream and releases any system resources associated with the stream.
  486.      *
  487.      * @throws IOException            if an I/O error occurs.
  488.      * @throws Zip64RequiredException if the archive's size exceeds 4 GByte or there are more than 65535 entries inside the archive and {@link #setUseZip64} is
  489.      *                                {@link Zip64Mode#Never}.
  490.      */
  491.     @Override
  492.     public void close() throws IOException {
  493.         try {
  494.             if (!finished) {
  495.                 finish();
  496.             }
  497.         } finally {
  498.             destroy();
  499.         }
  500.     }

  501.     /**
  502.      * Writes all necessary data for this entry.
  503.      *
  504.      * @throws IOException            on error
  505.      * @throws Zip64RequiredException if the entry's uncompressed or compressed size exceeds 4 GByte and {@link #setUseZip64} is {@link Zip64Mode#Never}.
  506.      */
  507.     @Override
  508.     public void closeArchiveEntry() throws IOException {
  509.         preClose();

  510.         flushDeflater();

  511.         final long bytesWritten = streamCompressor.getTotalBytesWritten() - entry.dataStart;
  512.         final long realCrc = streamCompressor.getCrc32();
  513.         entry.bytesRead = streamCompressor.getBytesRead();
  514.         final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
  515.         final boolean actuallyNeedsZip64 = handleSizesAndCrc(bytesWritten, realCrc, effectiveMode);
  516.         closeEntry(actuallyNeedsZip64, false);
  517.         streamCompressor.reset();
  518.     }

  519.     /**
  520.      * Writes all necessary data for this entry.
  521.      *
  522.      * @param phased This entry is second phase of a 2-phase ZIP creation, size, compressed size and crc are known in ZipArchiveEntry
  523.      * @throws IOException            on error
  524.      * @throws Zip64RequiredException if the entry's uncompressed or compressed size exceeds 4 GByte and {@link #setUseZip64} is {@link Zip64Mode#Never}.
  525.      */
  526.     private void closeCopiedEntry(final boolean phased) throws IOException {
  527.         preClose();
  528.         entry.bytesRead = entry.entry.getSize();
  529.         final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
  530.         final boolean actuallyNeedsZip64 = checkIfNeedsZip64(effectiveMode);
  531.         closeEntry(actuallyNeedsZip64, phased);
  532.     }

  533.     private void closeEntry(final boolean actuallyNeedsZip64, final boolean phased) throws IOException {
  534.         if (!phased && out instanceof RandomAccessOutputStream) {
  535.             rewriteSizesAndCrc(actuallyNeedsZip64);
  536.         }

  537.         if (!phased) {
  538.             writeDataDescriptor(entry.entry);
  539.         }
  540.         entry = null;
  541.     }

  542.     private void copyFromZipInputStream(final InputStream src) throws IOException {
  543.         if (entry == null) {
  544.             throw new IllegalStateException("No current entry");
  545.         }
  546.         ZipUtil.checkRequestedFeatures(entry.entry);
  547.         entry.hasWritten = true;
  548.         int length;
  549.         while ((length = src.read(copyBuffer)) >= 0) {
  550.             streamCompressor.writeCounted(copyBuffer, 0, length);
  551.             count(length);
  552.         }
  553.     }

  554.     /**
  555.      * Creates a new ZIP entry taking some information from the given file and using the provided name.
  556.      * <p>
  557.      * 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
  558.      * will be stripped from the entry name.
  559.      * </p>
  560.      * <p>
  561.      * Must not be used if the stream has already been closed.
  562.      * </p>
  563.      */
  564.     @Override
  565.     public ZipArchiveEntry createArchiveEntry(final File inputFile, final String entryName) throws IOException {
  566.         if (finished) {
  567.             throw new IOException("Stream has already been finished");
  568.         }
  569.         return new ZipArchiveEntry(inputFile, entryName);
  570.     }

  571.     /**
  572.      * Creates a new ZIP entry taking some information from the given file and using the provided name.
  573.      * <p>
  574.      * 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
  575.      * will be stripped from the entry name.
  576.      * </p>
  577.      * <p>
  578.      * Must not be used if the stream has already been closed.
  579.      * </p>
  580.      *
  581.      * @param inputPath path to create the entry from.
  582.      * @param entryName name of the entry.
  583.      * @param options   options indicating how symbolic links are handled.
  584.      * @return a new instance.
  585.      * @throws IOException if an I/O error occurs.
  586.      * @since 1.21
  587.      */
  588.     @Override
  589.     public ZipArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
  590.         if (finished) {
  591.             throw new IOException("Stream has already been finished");
  592.         }
  593.         return new ZipArchiveEntry(inputPath, entryName);
  594.     }

  595.     private byte[] createCentralFileHeader(final ZipArchiveEntry ze) throws IOException {

  596.         final EntryMetaData entryMetaData = metaData.get(ze);
  597.         final boolean needsZip64Extra = hasZip64Extra(ze) || ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC
  598.                 || entryMetaData.offset >= ZipConstants.ZIP64_MAGIC || ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT
  599.                 || zip64Mode == Zip64Mode.Always || zip64Mode == Zip64Mode.AlwaysWithCompatibility;

  600.         if (needsZip64Extra && zip64Mode == Zip64Mode.Never) {
  601.             // must be the offset that is too big, otherwise an
  602.             // exception would have been throw in putArchiveEntry or
  603.             // closeArchiveEntry
  604.             throw new Zip64RequiredException(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE);
  605.         }

  606.         handleZip64Extra(ze, entryMetaData.offset, needsZip64Extra);

  607.         return createCentralFileHeader(ze, getName(ze), entryMetaData, needsZip64Extra);
  608.     }

  609.     /**
  610.      * Writes the central file header entry.
  611.      *
  612.      * @param ze            the entry to write
  613.      * @param name          The encoded name
  614.      * @param entryMetaData meta data for this file
  615.      * @throws IOException on error
  616.      */
  617.     private byte[] createCentralFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final EntryMetaData entryMetaData, final boolean needsZip64Extra)
  618.             throws IOException {
  619.         if (isSplitZip) {
  620.             // calculate the disk number for every central file header,
  621.             // this will be used in writing End Of Central Directory and Zip64 End Of Central Directory
  622.             final int currentSplitSegment = ((ZipSplitOutputStream) this.out).getCurrentSplitSegmentIndex();
  623.             if (numberOfCDInDiskData.get(currentSplitSegment) == null) {
  624.                 numberOfCDInDiskData.put(currentSplitSegment, 1);
  625.             } else {
  626.                 final int originalNumberOfCD = numberOfCDInDiskData.get(currentSplitSegment);
  627.                 numberOfCDInDiskData.put(currentSplitSegment, originalNumberOfCD + 1);
  628.             }
  629.         }

  630.         final byte[] extra = ze.getCentralDirectoryExtra();
  631.         final int extraLength = extra.length;

  632.         // file comment length
  633.         String comm = ze.getComment();
  634.         if (comm == null) {
  635.             comm = "";
  636.         }

  637.         final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
  638.         final int nameLen = name.limit() - name.position();
  639.         final int commentLen = commentB.limit() - commentB.position();
  640.         final int len = CFH_FILENAME_OFFSET + nameLen + extraLength + commentLen;
  641.         final byte[] buf = new byte[len];

  642.         System.arraycopy(CFH_SIG, 0, buf, CFH_SIG_OFFSET, ZipConstants.WORD);

  643.         // version made by
  644.         // CheckStyle:MagicNumber OFF
  645.         ZipShort.putShort(ze.getPlatform() << 8 | (!hasUsedZip64 ? ZipConstants.DATA_DESCRIPTOR_MIN_VERSION : ZipConstants.ZIP64_MIN_VERSION), buf,
  646.                 CFH_VERSION_MADE_BY_OFFSET);

  647.         final int zipMethod = ze.getMethod();
  648.         final boolean encodable = zipEncoding.canEncode(ze.getName());
  649.         ZipShort.putShort(versionNeededToExtract(zipMethod, needsZip64Extra, entryMetaData.usesDataDescriptor), buf, CFH_VERSION_NEEDED_OFFSET);
  650.         getGeneralPurposeBits(!encodable && fallbackToUTF8, entryMetaData.usesDataDescriptor).encode(buf, CFH_GPB_OFFSET);

  651.         // compression method
  652.         ZipShort.putShort(zipMethod, buf, CFH_METHOD_OFFSET);

  653.         // last mod. time and date
  654.         ZipUtil.toDosTime(ze.getTime(), buf, CFH_TIME_OFFSET);

  655.         // CRC
  656.         // compressed length
  657.         // uncompressed length
  658.         ZipLong.putLong(ze.getCrc(), buf, CFH_CRC_OFFSET);
  659.         if (ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always
  660.                 || zip64Mode == Zip64Mode.AlwaysWithCompatibility) {
  661.             ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET);
  662.             ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET);
  663.         } else {
  664.             ZipLong.putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET);
  665.             ZipLong.putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET);
  666.         }

  667.         ZipShort.putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET);

  668.         // extra field length
  669.         ZipShort.putShort(extraLength, buf, CFH_EXTRA_LENGTH_OFFSET);

  670.         ZipShort.putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET);

  671.         // disk number start
  672.         if (isSplitZip) {
  673.             if (ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always) {
  674.                 ZipShort.putShort(ZipConstants.ZIP64_MAGIC_SHORT, buf, CFH_DISK_NUMBER_OFFSET);
  675.             } else {
  676.                 ZipShort.putShort((int) ze.getDiskNumberStart(), buf, CFH_DISK_NUMBER_OFFSET);
  677.             }
  678.         } else {
  679.             System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, ZipConstants.SHORT);
  680.         }

  681.         // internal file attributes
  682.         ZipShort.putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET);

  683.         // external file attributes
  684.         ZipLong.putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET);

  685.         // relative offset of LFH
  686.         if (entryMetaData.offset >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) {
  687.             ZipLong.putLong(ZipConstants.ZIP64_MAGIC, buf, CFH_LFH_OFFSET);
  688.         } else {
  689.             ZipLong.putLong(Math.min(entryMetaData.offset, ZipConstants.ZIP64_MAGIC), buf, CFH_LFH_OFFSET);
  690.         }

  691.         // file name
  692.         System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen);

  693.         final int extraStart = CFH_FILENAME_OFFSET + nameLen;
  694.         System.arraycopy(extra, 0, buf, extraStart, extraLength);

  695.         final int commentStart = extraStart + extraLength;

  696.         // file comment
  697.         System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen);
  698.         return buf;
  699.     }

  700.     private byte[] createLocalFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final boolean encodable, final boolean phased,
  701.             final long archiveOffset) {
  702.         final ZipExtraField oldEx = ze.getExtraField(ResourceAlignmentExtraField.ID);
  703.         if (oldEx != null) {
  704.             ze.removeExtraField(ResourceAlignmentExtraField.ID);
  705.         }
  706.         final ResourceAlignmentExtraField oldAlignmentEx = oldEx instanceof ResourceAlignmentExtraField ? (ResourceAlignmentExtraField) oldEx : null;

  707.         int alignment = ze.getAlignment();
  708.         if (alignment <= 0 && oldAlignmentEx != null) {
  709.             alignment = oldAlignmentEx.getAlignment();
  710.         }

  711.         if (alignment > 1 || oldAlignmentEx != null && !oldAlignmentEx.allowMethodChange()) {
  712.             final int oldLength = LFH_FILENAME_OFFSET + name.limit() - name.position() + ze.getLocalFileDataExtra().length;

  713.             final int padding = (int) (-archiveOffset - oldLength - ZipExtraField.EXTRAFIELD_HEADER_SIZE - ResourceAlignmentExtraField.BASE_SIZE
  714.                     & alignment - 1);
  715.             ze.addExtraField(new ResourceAlignmentExtraField(alignment, oldAlignmentEx != null && oldAlignmentEx.allowMethodChange(), padding));
  716.         }

  717.         final byte[] extra = ze.getLocalFileDataExtra();
  718.         final int nameLen = name.limit() - name.position();
  719.         final int len = LFH_FILENAME_OFFSET + nameLen + extra.length;
  720.         final byte[] buf = new byte[len];

  721.         System.arraycopy(LFH_SIG, 0, buf, LFH_SIG_OFFSET, ZipConstants.WORD);

  722.         // store method in local variable to prevent multiple method calls
  723.         final int zipMethod = ze.getMethod();
  724.         final boolean dataDescriptor = usesDataDescriptor(zipMethod, phased);

  725.         ZipShort.putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze), dataDescriptor), buf, LFH_VERSION_NEEDED_OFFSET);

  726.         final GeneralPurposeBit generalPurposeBit = getGeneralPurposeBits(!encodable && fallbackToUTF8, dataDescriptor);
  727.         generalPurposeBit.encode(buf, LFH_GPB_OFFSET);

  728.         // compression method
  729.         ZipShort.putShort(zipMethod, buf, LFH_METHOD_OFFSET);

  730.         ZipUtil.toDosTime(ze.getTime(), buf, LFH_TIME_OFFSET);

  731.         // CRC
  732.         if (phased || !(zipMethod == DEFLATED || out instanceof RandomAccessOutputStream)) {
  733.             ZipLong.putLong(ze.getCrc(), buf, LFH_CRC_OFFSET);
  734.         } else {
  735.             System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, ZipConstants.WORD);
  736.         }

  737.         // compressed length
  738.         // uncompressed length
  739.         if (hasZip64Extra(entry.entry)) {
  740.             // point to ZIP64 extended information extra field for
  741.             // sizes, may get rewritten once sizes are known if
  742.             // stream is seekable
  743.             ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET);
  744.             ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET);
  745.         } else if (phased) {
  746.             ZipLong.putLong(ze.getCompressedSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
  747.             ZipLong.putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
  748.         } else if (zipMethod == DEFLATED || out instanceof RandomAccessOutputStream) {
  749.             System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, ZipConstants.WORD);
  750.             System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, ZipConstants.WORD);
  751.         } else { // Stored
  752.             ZipLong.putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
  753.             ZipLong.putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
  754.         }
  755.         // file name length
  756.         ZipShort.putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET);

  757.         // extra field length
  758.         ZipShort.putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET);

  759.         // file name
  760.         System.arraycopy(name.array(), name.arrayOffset(), buf, LFH_FILENAME_OFFSET, nameLen);

  761.         // extra fields
  762.         System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length);

  763.         return buf;
  764.     }

  765.     /**
  766.      * Writes next block of compressed data to the output stream.
  767.      *
  768.      * @throws IOException on error
  769.      */
  770.     protected final void deflate() throws IOException {
  771.         streamCompressor.deflate();
  772.     }

  773.     /**
  774.      * Closes the underlying stream/file without finishing the archive, the result will likely be a corrupt archive.
  775.      * <p>
  776.      * This method only exists to support tests that generate corrupt archives so they can clean up any temporary files.
  777.      * </p>
  778.      */
  779.     void destroy() throws IOException {
  780.         if (out != null) {
  781.             out.close();
  782.         }
  783.     }

  784.     /**
  785.      * {@inheritDoc}
  786.      *
  787.      * @throws Zip64RequiredException if the archive's size exceeds 4 GByte or there are more than 65535 entries inside the archive and {@link #setUseZip64} is
  788.      *                                {@link Zip64Mode#Never}.
  789.      */
  790.     @Override
  791.     public void finish() throws IOException {
  792.         if (finished) {
  793.             throw new IOException("This archive has already been finished");
  794.         }

  795.         if (entry != null) {
  796.             throw new IOException("This archive contains unclosed entries.");
  797.         }

  798.         final long cdOverallOffset = streamCompressor.getTotalBytesWritten();
  799.         cdOffset = cdOverallOffset;
  800.         if (isSplitZip) {
  801.             // when creating a split zip, the offset should be
  802.             // the offset to the corresponding segment disk
  803.             final ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream) this.out;
  804.             cdOffset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten();
  805.             cdDiskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex();
  806.         }
  807.         writeCentralDirectoryInChunks();

  808.         cdLength = streamCompressor.getTotalBytesWritten() - cdOverallOffset;

  809.         // calculate the length of end of central directory, as it may be used in writeZip64CentralDirectory
  810.         final ByteBuffer commentData = this.zipEncoding.encode(comment);
  811.         final long commentLength = (long) commentData.limit() - commentData.position();
  812.         eocdLength = ZipConstants.WORD /* length of EOCD_SIG */
  813.                 + ZipConstants.SHORT /* number of this disk */
  814.                 + ZipConstants.SHORT /* disk number of start of central directory */
  815.                 + ZipConstants.SHORT /* total number of entries on this disk */
  816.                 + ZipConstants.SHORT /* total number of entries */
  817.                 + ZipConstants.WORD /* size of central directory */
  818.                 + ZipConstants.WORD /* offset of start of central directory */
  819.                 + ZipConstants.SHORT /* ZIP comment length */
  820.                 + commentLength /* ZIP comment */;

  821.         writeZip64CentralDirectory();
  822.         writeCentralDirectoryEnd();
  823.         metaData.clear();
  824.         entries.clear();
  825.         streamCompressor.close();
  826.         if (isSplitZip) {
  827.             // trigger the ZipSplitOutputStream to write the final split segment
  828.             out.close();
  829.         }
  830.         finished = true;
  831.     }

  832.     /**
  833.      * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
  834.      *
  835.      * @throws IOException if an I/O error occurs.
  836.      */
  837.     @Override
  838.     public void flush() throws IOException {
  839.         if (out != null) {
  840.             out.flush();
  841.         }
  842.     }

  843.     /**
  844.      * Ensures all bytes sent to the deflater are written to the stream.
  845.      */
  846.     private void flushDeflater() throws IOException {
  847.         if (entry.entry.getMethod() == DEFLATED) {
  848.             streamCompressor.flushDeflater();
  849.         }
  850.     }

  851.     /**
  852.      * Returns the total number of bytes written to this stream.
  853.      *
  854.      * @return the number of written bytes
  855.      * @since 1.22
  856.      */
  857.     @Override
  858.     public long getBytesWritten() {
  859.         return streamCompressor.getTotalBytesWritten();
  860.     }

  861.     /**
  862.      * 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.
  863.      *
  864.      * @since 1.3
  865.      */
  866.     private Zip64Mode getEffectiveZip64Mode(final ZipArchiveEntry ze) {
  867.         if (zip64Mode != Zip64Mode.AsNeeded || out instanceof RandomAccessOutputStream ||
  868.                 ze.getMethod() != DEFLATED || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
  869.             return zip64Mode;
  870.         }
  871.         return Zip64Mode.Never;
  872.     }

  873.     /**
  874.      * The encoding to use for file names and the file comment.
  875.      *
  876.      * @return null if using the platform's default character encoding.
  877.      */
  878.     public String getEncoding() {
  879.         return charset != null ? charset.name() : null;
  880.     }

  881.     private ZipEncoding getEntryEncoding(final ZipArchiveEntry ze) {
  882.         final boolean encodable = zipEncoding.canEncode(ze.getName());
  883.         return !encodable && fallbackToUTF8 ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
  884.     }

  885.     private GeneralPurposeBit getGeneralPurposeBits(final boolean utfFallback, final boolean usesDataDescriptor) {
  886.         final GeneralPurposeBit b = new GeneralPurposeBit();
  887.         b.useUTF8ForNames(useUTF8Flag || utfFallback);
  888.         if (usesDataDescriptor) {
  889.             b.useDataDescriptor(true);
  890.         }
  891.         return b;
  892.     }

  893.     private ByteBuffer getName(final ZipArchiveEntry ze) throws IOException {
  894.         return getEntryEncoding(ze).encode(ze.getName());
  895.     }

  896.     /**
  897.      * Gets the existing ZIP64 extended information extra field or create a new one and add it to the entry.
  898.      *
  899.      * @since 1.3
  900.      */
  901.     private Zip64ExtendedInformationExtraField getZip64Extra(final ZipArchiveEntry ze) {
  902.         if (entry != null) {
  903.             entry.causedUseOfZip64 = !hasUsedZip64;
  904.         }
  905.         hasUsedZip64 = true;
  906.         final ZipExtraField extra = ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
  907.         Zip64ExtendedInformationExtraField z64 = extra instanceof Zip64ExtendedInformationExtraField ? (Zip64ExtendedInformationExtraField) extra : null;
  908.         if (z64 == null) {
  909.             /*
  910.              * System.err.println("Adding z64 for " + ze.getName() + ", method: " + ze.getMethod() + " (" + (ze.getMethod() == STORED) + ")" + ", channel: " +
  911.              * (channel != null));
  912.              */
  913.             z64 = new Zip64ExtendedInformationExtraField();
  914.         }

  915.         // even if the field is there already, make sure it is the first one
  916.         ze.addAsFirstExtraField(z64);

  917.         return z64;
  918.     }

  919.     /**
  920.      * 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
  921.      * whether the entry would require a Zip64 extra field.
  922.      */
  923.     private boolean handleSizesAndCrc(final long bytesWritten, final long crc, final Zip64Mode effectiveMode) throws ZipException {
  924.         if (entry.entry.getMethod() == DEFLATED) {
  925.             /*
  926.              * It turns out def.getBytesRead() returns wrong values if the size exceeds 4 GB on Java < Java7 entry.entry.setSize(def.getBytesRead());
  927.              */
  928.             entry.entry.setSize(entry.bytesRead);
  929.             entry.entry.setCompressedSize(bytesWritten);
  930.             entry.entry.setCrc(crc);

  931.         } else if (!(out instanceof RandomAccessOutputStream)) {
  932.             if (entry.entry.getCrc() != crc) {
  933.                 throw new ZipException("Bad CRC checksum for entry " + entry.entry.getName() + ": " + Long.toHexString(entry.entry.getCrc()) + " instead of "
  934.                         + Long.toHexString(crc));
  935.             }

  936.             if (entry.entry.getSize() != bytesWritten) {
  937.                 throw new ZipException("Bad size for entry " + entry.entry.getName() + ": " + entry.entry.getSize() + " instead of " + bytesWritten);
  938.             }
  939.         } else { /* method is STORED and we used SeekableByteChannel */
  940.             entry.entry.setSize(bytesWritten);
  941.             entry.entry.setCompressedSize(bytesWritten);
  942.             entry.entry.setCrc(crc);
  943.         }

  944.         return checkIfNeedsZip64(effectiveMode);
  945.     }

  946.     /**
  947.      * If the entry needs Zip64 extra information inside the central directory then configure its data.
  948.      */
  949.     private void handleZip64Extra(final ZipArchiveEntry ze, final long lfhOffset, final boolean needsZip64Extra) {
  950.         if (needsZip64Extra) {
  951.             final Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze);
  952.             if (ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always
  953.                     || zip64Mode == Zip64Mode.AlwaysWithCompatibility) {
  954.                 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
  955.                 z64.setSize(new ZipEightByteInteger(ze.getSize()));
  956.             } else {
  957.                 // reset value that may have been set for LFH
  958.                 z64.setCompressedSize(null);
  959.                 z64.setSize(null);
  960.             }

  961.             final boolean needsToEncodeLfhOffset = lfhOffset >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always;
  962.             final boolean needsToEncodeDiskNumberStart = ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always;

  963.             if (needsToEncodeLfhOffset || needsToEncodeDiskNumberStart) {
  964.                 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset));
  965.             }
  966.             if (needsToEncodeDiskNumberStart) {
  967.                 z64.setDiskStartNumber(new ZipLong(ze.getDiskNumberStart()));
  968.             }
  969.             ze.setExtra();
  970.         }
  971.     }

  972.     /**
  973.      * Is there a ZIP64 extended information extra field for the entry?
  974.      *
  975.      * @since 1.3
  976.      */
  977.     private boolean hasZip64Extra(final ZipArchiveEntry ze) {
  978.         return ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID) instanceof Zip64ExtendedInformationExtraField;
  979.     }

  980.     /**
  981.      * This method indicates whether this archive is writing to a seekable stream (i.e., to a random access file).
  982.      * <p>
  983.      * For seekable streams, you don't need to calculate the CRC or uncompressed size for {@link #STORED} entries before invoking
  984.      * {@link #putArchiveEntry(ZipArchiveEntry)}.
  985.      * </p>
  986.      *
  987.      * @return true if seekable
  988.      */
  989.     public boolean isSeekable() {
  990.         return out instanceof RandomAccessOutputStream;
  991.     }

  992.     private boolean isTooLargeForZip32(final ZipArchiveEntry zipArchiveEntry) {
  993.         return zipArchiveEntry.getSize() >= ZipConstants.ZIP64_MAGIC || zipArchiveEntry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC;
  994.     }

  995.     private boolean isZip64Required(final ZipArchiveEntry entry1, final Zip64Mode requestedMode) {
  996.         return requestedMode == Zip64Mode.Always || requestedMode == Zip64Mode.AlwaysWithCompatibility || isTooLargeForZip32(entry1);
  997.     }

  998.     private void preClose() throws IOException {
  999.         if (finished) {
  1000.             throw new IOException("Stream has already been finished");
  1001.         }

  1002.         if (entry == null) {
  1003.             throw new IOException("No current entry to close");
  1004.         }

  1005.         if (!entry.hasWritten) {
  1006.             write(ByteUtils.EMPTY_BYTE_ARRAY, 0, 0);
  1007.         }
  1008.     }

  1009.     /**
  1010.      * {@inheritDoc}
  1011.      *
  1012.      * @throws ClassCastException     if entry is not an instance of ZipArchiveEntry
  1013.      * @throws Zip64RequiredException if the entry's uncompressed or compressed size is known to exceed 4 GByte and {@link #setUseZip64} is
  1014.      *                                {@link Zip64Mode#Never}.
  1015.      */
  1016.     @Override
  1017.     public void putArchiveEntry(final ZipArchiveEntry archiveEntry) throws IOException {
  1018.         putArchiveEntry(archiveEntry, false);
  1019.     }

  1020.     /**
  1021.      * 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()}
  1022.      * to complete the process.
  1023.      *
  1024.      * @param archiveEntry The archiveEntry
  1025.      * @param phased       If true size, compressedSize and crc required to be known up-front in the archiveEntry
  1026.      * @throws ClassCastException     if entry is not an instance of ZipArchiveEntry
  1027.      * @throws Zip64RequiredException if the entry's uncompressed or compressed size is known to exceed 4 GByte and {@link #setUseZip64} is
  1028.      *                                {@link Zip64Mode#Never}.
  1029.      */
  1030.     private void putArchiveEntry(final ZipArchiveEntry archiveEntry, final boolean phased) throws IOException {
  1031.         if (finished) {
  1032.             throw new IOException("Stream has already been finished");
  1033.         }

  1034.         if (entry != null) {
  1035.             closeArchiveEntry();
  1036.         }

  1037.         entry = new CurrentEntry(archiveEntry);
  1038.         entries.add(entry.entry);

  1039.         setDefaults(entry.entry);

  1040.         final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
  1041.         validateSizeInformation(effectiveMode);

  1042.         if (shouldAddZip64Extra(entry.entry, effectiveMode)) {

  1043.             final Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry);

  1044.             final ZipEightByteInteger size;
  1045.             final ZipEightByteInteger compressedSize;
  1046.             if (phased) {
  1047.                 // sizes are already known
  1048.                 size = new ZipEightByteInteger(entry.entry.getSize());
  1049.                 compressedSize = new ZipEightByteInteger(entry.entry.getCompressedSize());
  1050.             } else if (entry.entry.getMethod() == STORED && entry.entry.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
  1051.                 // actually, we already know the sizes
  1052.                 compressedSize = size = new ZipEightByteInteger(entry.entry.getSize());
  1053.             } else {
  1054.                 // just a placeholder, real data will be in data
  1055.                 // descriptor or inserted later via SeekableByteChannel
  1056.                 compressedSize = size = ZipEightByteInteger.ZERO;
  1057.             }
  1058.             z64.setSize(size);
  1059.             z64.setCompressedSize(compressedSize);
  1060.             entry.entry.setExtra();
  1061.         }

  1062.         if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
  1063.             def.setLevel(level);
  1064.             hasCompressionLevelChanged = false;
  1065.         }
  1066.         writeLocalFileHeader(archiveEntry, phased);
  1067.     }

  1068.     /**
  1069.      * When using random access output, write the local file header and potentially the ZIP64 extra containing the correct CRC and compressed/uncompressed
  1070.      * sizes.
  1071.      */
  1072.     private void rewriteSizesAndCrc(final boolean actuallyNeedsZip64) throws IOException {
  1073.         final RandomAccessOutputStream randomStream = (RandomAccessOutputStream) out;
  1074.         long dataStart = entry.localDataStart;
  1075.         if (randomStream instanceof ZipSplitOutputStream) {
  1076.             dataStart = ((ZipSplitOutputStream) randomStream).calculateDiskPosition(entry.entry.getDiskNumberStart(), dataStart);
  1077.         }

  1078.         long position = dataStart;
  1079.         randomStream.writeFully(ZipLong.getBytes(entry.entry.getCrc()), position); position += ZipConstants.WORD;
  1080.         if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) {
  1081.             randomStream.writeFully(ZipLong.getBytes(entry.entry.getCompressedSize()), position); position += ZipConstants.WORD;
  1082.             randomStream.writeFully(ZipLong.getBytes(entry.entry.getSize()), position); position += ZipConstants.WORD;
  1083.         } else {
  1084.             randomStream.writeFully(ZipLong.ZIP64_MAGIC.getBytes(), position); position += ZipConstants.WORD;
  1085.             randomStream.writeFully(ZipLong.ZIP64_MAGIC.getBytes(), position); position += ZipConstants.WORD;
  1086.         }

  1087.         if (hasZip64Extra(entry.entry)) {
  1088.             final ByteBuffer name = getName(entry.entry);
  1089.             final int nameLen = name.limit() - name.position();
  1090.             // seek to ZIP64 extra, skip header and size information
  1091.             position = dataStart + 3 * ZipConstants.WORD + 2 * ZipConstants.SHORT + nameLen + 2 * ZipConstants.SHORT;
  1092.             // inside the ZIP64 extra uncompressed size comes
  1093.             // first, unlike the LFH, CD or data descriptor
  1094.             randomStream.writeFully(ZipEightByteInteger.getBytes(entry.entry.getSize()), position); position += ZipConstants.DWORD;
  1095.             randomStream.writeFully(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize()), position); position += ZipConstants.DWORD;

  1096.             if (!actuallyNeedsZip64) {
  1097.                 // do some cleanup:
  1098.                 // * rewrite version needed to extract
  1099.                 position = dataStart - 5 * ZipConstants.SHORT;
  1100.                 randomStream.writeFully(ZipShort.getBytes(versionNeededToExtract(entry.entry.getMethod(), false, false)), position);
  1101.                 position += ZipConstants.SHORT;

  1102.                 // * remove ZIP64 extra, so it doesn't get written
  1103.                 // to the central directory
  1104.                 entry.entry.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
  1105.                 entry.entry.setExtra();

  1106.                 // * reset hasUsedZip64 if it has been set because
  1107.                 // of this entry
  1108.                 if (entry.causedUseOfZip64) {
  1109.                     hasUsedZip64 = false;
  1110.                 }
  1111.             }
  1112.         }
  1113.     }

  1114.     /**
  1115.      * Sets the file comment.
  1116.      *
  1117.      * @param comment the comment
  1118.      */
  1119.     public void setComment(final String comment) {
  1120.         this.comment = comment;
  1121.     }

  1122.     /**
  1123.      * Whether to create Unicode Extra Fields.
  1124.      * <p>
  1125.      * Defaults to NEVER.
  1126.      * </p>
  1127.      *
  1128.      * @param b whether to create Unicode Extra Fields.
  1129.      */
  1130.     public void setCreateUnicodeExtraFields(final UnicodeExtraFieldPolicy b) {
  1131.         createUnicodeExtraFields = b;
  1132.     }

  1133.     /**
  1134.      * Provides default values for compression method and last modification time.
  1135.      */
  1136.     private void setDefaults(final ZipArchiveEntry entry) {
  1137.         if (entry.getMethod() == -1) { // not specified
  1138.             entry.setMethod(method);
  1139.         }

  1140.         if (entry.getTime() == -1) { // not specified
  1141.             entry.setTime(System.currentTimeMillis());
  1142.         }
  1143.     }

  1144.     private void setEncoding(final Charset encoding) {
  1145.         this.charset = encoding;
  1146.         this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
  1147.         if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) {
  1148.             useUTF8Flag = false;
  1149.         }
  1150.     }

  1151.     /**
  1152.      * The encoding to use for file names and the file comment.
  1153.      * <p>
  1154.      * 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>.
  1155.      * Defaults to UTF-8.
  1156.      * </p>
  1157.      *
  1158.      * @param encoding the encoding to use for file names, use null for the platform's default encoding
  1159.      */
  1160.     public void setEncoding(final String encoding) {
  1161.         setEncoding(Charsets.toCharset(encoding));
  1162.     }

  1163.     /**
  1164.      * Whether to fall back to UTF and the language encoding flag if the file name cannot be encoded using the specified encoding.
  1165.      * <p>
  1166.      * Defaults to false.
  1167.      * </p>
  1168.      *
  1169.      * @param b whether to fall back to UTF and the language encoding flag if the file name cannot be encoded using the specified encoding.
  1170.      */
  1171.     public void setFallbackToUTF8(final boolean b) {
  1172.         fallbackToUTF8 = b;
  1173.     }

  1174.     /**
  1175.      * Sets the compression level for subsequent entries.
  1176.      * <p>
  1177.      * Default is Deflater.DEFAULT_COMPRESSION.
  1178.      * </p>
  1179.      *
  1180.      * @param level the compression level.
  1181.      * @throws IllegalArgumentException if an invalid compression level is specified.
  1182.      */
  1183.     public void setLevel(final int level) {
  1184.         if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) {
  1185.             throw new IllegalArgumentException("Invalid compression level: " + level);
  1186.         }
  1187.         if (this.level == level) {
  1188.             return;
  1189.         }
  1190.         hasCompressionLevelChanged = true;
  1191.         this.level = level;
  1192.     }

  1193.     /**
  1194.      * Sets the default compression method for subsequent entries.
  1195.      * <p>
  1196.      * Default is DEFLATED.
  1197.      * </p>
  1198.      *
  1199.      * @param method an {@code int} from java.util.zip.ZipEntry
  1200.      */
  1201.     public void setMethod(final int method) {
  1202.         this.method = method;
  1203.     }

  1204.     /**
  1205.      * Whether to set the language encoding flag if the file name encoding is UTF-8.
  1206.      * <p>
  1207.      * Defaults to true.
  1208.      * </p>
  1209.      *
  1210.      * @param b whether to set the language encoding flag if the file name encoding is UTF-8
  1211.      */
  1212.     public void setUseLanguageEncodingFlag(final boolean b) {
  1213.         useUTF8Flag = b && ZipEncodingHelper.isUTF8(charset);
  1214.     }

  1215.     /**
  1216.      * Whether Zip64 extensions will be used.
  1217.      * <p>
  1218.      * When setting the mode to {@link Zip64Mode#Never Never}, {@link #putArchiveEntry}, {@link #closeArchiveEntry}, {@link #finish} or {@link #close} may throw
  1219.      * 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
  1220.      * archive. Any archive created in this mode will be readable by implementations that don't support Zip64.
  1221.      * </p>
  1222.      * <p>
  1223.      * 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
  1224.      * unreadable by implementations that don't support Zip64 even if all its contents would be.
  1225.      * </p>
  1226.      * <p>
  1227.      * When setting the mode to {@link Zip64Mode#AsNeeded AsNeeded}, Zip64 extensions will transparently be used for those entries that require them. This mode
  1228.      * 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
  1229.      * seekable output (i.e. you have used the {@link #ZipArchiveOutputStream(java.io.File) File-arg constructor}) - this mode is not valid when the output
  1230.      * stream is not seekable and the uncompressed size is unknown when {@link #putArchiveEntry} is called.
  1231.      * </p>
  1232.      * <p>
  1233.      * If no entry inside the resulting archive requires Zip64 extensions then {@link Zip64Mode#Never Never} will create the smallest archive.
  1234.      * {@link Zip64Mode#AsNeeded AsNeeded} will create a slightly bigger archive if the uncompressed size of any entry has initially been unknown and create an
  1235.      * archive identical to {@link Zip64Mode#Never Never} otherwise. {@link Zip64Mode#Always Always} will create an archive that is at least 24 bytes per entry
  1236.      * bigger than the one {@link Zip64Mode#Never Never} would create.
  1237.      * </p>
  1238.      * <p>
  1239.      * Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless {@link #putArchiveEntry} is called with an entry of unknown size and data is written to a
  1240.      * non-seekable stream - in this case the default is {@link Zip64Mode#Never Never}.
  1241.      * </p>
  1242.      *
  1243.      * @since 1.3
  1244.      * @param mode Whether Zip64 extensions will be used.
  1245.      */
  1246.     public void setUseZip64(final Zip64Mode mode) {
  1247.         zip64Mode = mode;
  1248.     }

  1249.     /**
  1250.      * Whether to add a Zip64 extended information extra field to the local file header.
  1251.      * <p>
  1252.      * Returns true if
  1253.      * </p>
  1254.      * <ul>
  1255.      * <li>mode is Always</li>
  1256.      * <li>or we already know it is going to be needed</li>
  1257.      * <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>
  1258.      * </ul>
  1259.      */
  1260.     private boolean shouldAddZip64Extra(final ZipArchiveEntry entry, final Zip64Mode mode) {
  1261.         return mode == Zip64Mode.Always || mode == Zip64Mode.AlwaysWithCompatibility || entry.getSize() >= ZipConstants.ZIP64_MAGIC
  1262.                 || entry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC
  1263.                 || entry.getSize() == ArchiveEntry.SIZE_UNKNOWN && out instanceof RandomAccessOutputStream && mode != Zip64Mode.Never;
  1264.     }

  1265.     /**
  1266.      * 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
  1267.      * 0xFFFFFFFF) and the ZIP64 format record SHOULD be created.
  1268.      *
  1269.      * @return true if zip64 End Of Central Directory is needed
  1270.      */
  1271.     private boolean shouldUseZip64EOCD() {
  1272.         int numberOfThisDisk = 0;
  1273.         if (isSplitZip) {
  1274.             numberOfThisDisk = ((ZipSplitOutputStream) this.out).getCurrentSplitSegmentIndex();
  1275.         }
  1276.         final int numOfEntriesOnThisDisk = numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0);
  1277.         return numberOfThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT /* number of this disk */
  1278.                 || cdDiskNumberStart >= ZipConstants.ZIP64_MAGIC_SHORT /* number of the disk with the start of the central directory */
  1279.                 || numOfEntriesOnThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT /* total number of entries in the central directory on this disk */
  1280.                 || entries.size() >= ZipConstants.ZIP64_MAGIC_SHORT /* total number of entries in the central directory */
  1281.                 || cdLength >= ZipConstants.ZIP64_MAGIC /* size of the central directory */
  1282.                 || cdOffset >= ZipConstants.ZIP64_MAGIC; /*
  1283.                                                           * offset of start of central directory with respect to the starting disk number
  1284.                                                           */
  1285.     }

  1286.     private boolean usesDataDescriptor(final int zipMethod, final boolean phased) {
  1287.         return !phased && zipMethod == DEFLATED && !(out instanceof RandomAccessOutputStream);
  1288.     }

  1289.     /**
  1290.      * If the Zip64 mode is set to never, then all the data in End Of Central Directory should not exceed their limits.
  1291.      *
  1292.      * @throws Zip64RequiredException if Zip64 is actually needed
  1293.      */
  1294.     private void validateIfZip64IsNeededInEOCD() throws Zip64RequiredException {
  1295.         // exception will only be thrown if the Zip64 mode is never while Zip64 is actually needed
  1296.         if (zip64Mode != Zip64Mode.Never) {
  1297.             return;
  1298.         }

  1299.         int numberOfThisDisk = 0;
  1300.         if (isSplitZip) {
  1301.             numberOfThisDisk = ((ZipSplitOutputStream) this.out).getCurrentSplitSegmentIndex();
  1302.         }
  1303.         if (numberOfThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT) {
  1304.             throw new Zip64RequiredException(Zip64RequiredException.DISK_NUMBER_TOO_BIG_MESSAGE);
  1305.         }

  1306.         if (cdDiskNumberStart >= ZipConstants.ZIP64_MAGIC_SHORT) {
  1307.             throw new Zip64RequiredException(Zip64RequiredException.CENTRAL_DIRECTORY_DISK_NUMBER_TOO_BIG_MESSAGE);
  1308.         }

  1309.         final int numOfEntriesOnThisDisk = numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0);
  1310.         if (numOfEntriesOnThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT) {
  1311.             throw new Zip64RequiredException(Zip64RequiredException.TOO_MANY_ENTRIES_ON_DISK_MESSAGE);
  1312.         }

  1313.         // number of entries
  1314.         if (entries.size() >= ZipConstants.ZIP64_MAGIC_SHORT) {
  1315.             throw new Zip64RequiredException(Zip64RequiredException.TOO_MANY_ENTRIES_MESSAGE);
  1316.         }

  1317.         if (cdLength >= ZipConstants.ZIP64_MAGIC) {
  1318.             throw new Zip64RequiredException(Zip64RequiredException.CENTRAL_DIRECTORY_SIZE_TOO_BIG_MESSAGE);
  1319.         }

  1320.         if (cdOffset >= ZipConstants.ZIP64_MAGIC) {
  1321.             throw new Zip64RequiredException(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE);
  1322.         }
  1323.     }

  1324.     /**
  1325.      * 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
  1326.      * Zip64 extra but the mode has been set to Never.
  1327.      */
  1328.     private void validateSizeInformation(final Zip64Mode effectiveMode) throws ZipException {
  1329.         // Size/CRC not required if SeekableByteChannel is used
  1330.         if (entry.entry.getMethod() == STORED && !(out instanceof RandomAccessOutputStream)) {
  1331.             if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) {
  1332.                 throw new ZipException("Uncompressed size is required for" + " STORED method when not writing to a" + " file");
  1333.             }
  1334.             if (entry.entry.getCrc() == ZipArchiveEntry.CRC_UNKNOWN) {
  1335.                 throw new ZipException("CRC checksum is required for STORED" + " method when not writing to a file");
  1336.             }
  1337.             entry.entry.setCompressedSize(entry.entry.getSize());
  1338.         }

  1339.         if ((entry.entry.getSize() >= ZipConstants.ZIP64_MAGIC || entry.entry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC)
  1340.                 && effectiveMode == Zip64Mode.Never) {
  1341.             throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry));
  1342.         }
  1343.     }

  1344.     private int versionNeededToExtract(final int zipMethod, final boolean zip64, final boolean usedDataDescriptor) {
  1345.         if (zip64) {
  1346.             return ZipConstants.ZIP64_MIN_VERSION;
  1347.         }
  1348.         if (usedDataDescriptor) {
  1349.             return ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
  1350.         }
  1351.         return versionNeededToExtractMethod(zipMethod);
  1352.     }

  1353.     private int versionNeededToExtractMethod(final int zipMethod) {
  1354.         return zipMethod == DEFLATED ? ZipConstants.DEFLATE_MIN_VERSION : ZipConstants.INITIAL_VERSION;
  1355.     }

  1356.     /**
  1357.      * Writes bytes to ZIP entry.
  1358.      *
  1359.      * @param b      the byte array to write
  1360.      * @param offset the start position to write from
  1361.      * @param length the number of bytes to write
  1362.      * @throws IOException on error
  1363.      */
  1364.     @Override
  1365.     public void write(final byte[] b, final int offset, final int length) throws IOException {
  1366.         if (entry == null) {
  1367.             throw new IllegalStateException("No current entry");
  1368.         }
  1369.         ZipUtil.checkRequestedFeatures(entry.entry);
  1370.         final long writtenThisTime = streamCompressor.write(b, offset, length, entry.entry.getMethod());
  1371.         count(writtenThisTime);
  1372.     }

  1373.     /**
  1374.      * Writes the &quot;End of central dir record&quot;.
  1375.      *
  1376.      * @throws IOException            on error
  1377.      * @throws Zip64RequiredException if the archive's size exceeds 4 GByte or there are more than 65535 entries inside the archive and
  1378.      *                                {@link #setUseZip64(Zip64Mode)} is {@link Zip64Mode#Never}.
  1379.      */
  1380.     protected void writeCentralDirectoryEnd() throws IOException {
  1381.         if (!hasUsedZip64 && isSplitZip) {
  1382.             ((ZipSplitOutputStream) this.out).prepareToWriteUnsplittableContent(eocdLength);
  1383.         }

  1384.         validateIfZip64IsNeededInEOCD();

  1385.         writeCounted(EOCD_SIG);

  1386.         // number of this disk
  1387.         int numberOfThisDisk = 0;
  1388.         if (isSplitZip) {
  1389.             numberOfThisDisk = ((ZipSplitOutputStream) this.out).getCurrentSplitSegmentIndex();
  1390.         }
  1391.         writeCounted(ZipShort.getBytes(numberOfThisDisk));

  1392.         // disk number of the start of central directory
  1393.         writeCounted(ZipShort.getBytes((int) cdDiskNumberStart));

  1394.         // number of entries
  1395.         final int numberOfEntries = entries.size();

  1396.         // total number of entries in the central directory on this disk
  1397.         final int numOfEntriesOnThisDisk = isSplitZip ? numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0) : numberOfEntries;
  1398.         final byte[] numOfEntriesOnThisDiskData = ZipShort.getBytes(Math.min(numOfEntriesOnThisDisk, ZipConstants.ZIP64_MAGIC_SHORT));
  1399.         writeCounted(numOfEntriesOnThisDiskData);

  1400.         // number of entries
  1401.         final byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, ZipConstants.ZIP64_MAGIC_SHORT));
  1402.         writeCounted(num);

  1403.         // length and location of CD
  1404.         writeCounted(ZipLong.getBytes(Math.min(cdLength, ZipConstants.ZIP64_MAGIC)));
  1405.         writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZipConstants.ZIP64_MAGIC)));

  1406.         // ZIP file comment
  1407.         final ByteBuffer data = this.zipEncoding.encode(comment);
  1408.         final int dataLen = data.limit() - data.position();
  1409.         writeCounted(ZipShort.getBytes(dataLen));
  1410.         streamCompressor.writeCounted(data.array(), data.arrayOffset(), dataLen);
  1411.     }

  1412.     private void writeCentralDirectoryInChunks() throws IOException {
  1413.         final int NUM_PER_WRITE = 1000;
  1414.         final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(70 * NUM_PER_WRITE);
  1415.         int count = 0;
  1416.         for (final ZipArchiveEntry ze : entries) {
  1417.             byteArrayOutputStream.write(createCentralFileHeader(ze));
  1418.             if (++count > NUM_PER_WRITE) {
  1419.                 writeCounted(byteArrayOutputStream.toByteArray());
  1420.                 byteArrayOutputStream.reset();
  1421.                 count = 0;
  1422.             }
  1423.         }
  1424.         writeCounted(byteArrayOutputStream.toByteArray());
  1425.     }

  1426.     /**
  1427.      * Writes the central file header entry.
  1428.      *
  1429.      * @param ze the entry to write
  1430.      * @throws IOException            on error
  1431.      * @throws Zip64RequiredException if the archive's size exceeds 4 GByte and {@link #setUseZip64(Zip64Mode)} is {@link Zip64Mode#Never}.
  1432.      */
  1433.     protected void writeCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
  1434.         final byte[] centralFileHeader = createCentralFileHeader(ze);
  1435.         writeCounted(centralFileHeader);
  1436.     }

  1437.     /**
  1438.      * Write bytes to output or random access file.
  1439.      *
  1440.      * @param data the byte array to write
  1441.      * @throws IOException on error
  1442.      */
  1443.     private void writeCounted(final byte[] data) throws IOException {
  1444.         streamCompressor.writeCounted(data);
  1445.     }

  1446.     /**
  1447.      * Writes the data descriptor entry.
  1448.      *
  1449.      * @param ze the entry to write
  1450.      * @throws IOException on error
  1451.      */
  1452.     protected void writeDataDescriptor(final ZipArchiveEntry ze) throws IOException {
  1453.         if (!usesDataDescriptor(ze.getMethod(), false)) {
  1454.             return;
  1455.         }
  1456.         writeCounted(DD_SIG);
  1457.         writeCounted(ZipLong.getBytes(ze.getCrc()));
  1458.         if (!hasZip64Extra(ze)) {
  1459.             writeCounted(ZipLong.getBytes(ze.getCompressedSize()));
  1460.             writeCounted(ZipLong.getBytes(ze.getSize()));
  1461.         } else {
  1462.             writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize()));
  1463.             writeCounted(ZipEightByteInteger.getBytes(ze.getSize()));
  1464.         }
  1465.     }

  1466.     /**
  1467.      * Writes the local file header entry
  1468.      *
  1469.      * @param ze the entry to write
  1470.      * @throws IOException on error
  1471.      */
  1472.     protected void writeLocalFileHeader(final ZipArchiveEntry ze) throws IOException {
  1473.         writeLocalFileHeader(ze, false);
  1474.     }

  1475.     private void writeLocalFileHeader(final ZipArchiveEntry ze, final boolean phased) throws IOException {
  1476.         final boolean encodable = zipEncoding.canEncode(ze.getName());
  1477.         final ByteBuffer name = getName(ze);

  1478.         if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
  1479.             addUnicodeExtraFields(ze, encodable, name);
  1480.         }

  1481.         long localHeaderStart = streamCompressor.getTotalBytesWritten();
  1482.         if (isSplitZip) {
  1483.             // when creating a split zip, the offset should be
  1484.             // the offset to the corresponding segment disk
  1485.             final ZipSplitOutputStream splitOutputStream = (ZipSplitOutputStream) this.out;
  1486.             ze.setDiskNumberStart(splitOutputStream.getCurrentSplitSegmentIndex());
  1487.             localHeaderStart = splitOutputStream.getCurrentSplitSegmentBytesWritten();
  1488.         }

  1489.         final byte[] localHeader = createLocalFileHeader(ze, name, encodable, phased, localHeaderStart);
  1490.         metaData.put(ze, new EntryMetaData(localHeaderStart, usesDataDescriptor(ze.getMethod(), phased)));
  1491.         entry.localDataStart = localHeaderStart + LFH_CRC_OFFSET; // At crc offset
  1492.         writeCounted(localHeader);
  1493.         entry.dataStart = streamCompressor.getTotalBytesWritten();
  1494.     }

  1495.     /**
  1496.      * Write bytes to output or random access file.
  1497.      *
  1498.      * @param data the byte array to write
  1499.      * @throws IOException on error
  1500.      */
  1501.     protected final void writeOut(final byte[] data) throws IOException {
  1502.         streamCompressor.writeOut(data, 0, data.length);
  1503.     }

  1504.     /**
  1505.      * Write bytes to output or random access file.
  1506.      *
  1507.      * @param data   the byte array to write
  1508.      * @param offset the start position to write from
  1509.      * @param length the number of bytes to write
  1510.      * @throws IOException on error
  1511.      */
  1512.     protected final void writeOut(final byte[] data, final int offset, final int length) throws IOException {
  1513.         streamCompressor.writeOut(data, offset, length);
  1514.     }

  1515.     /**
  1516.      * Write preamble data. For most of the time, this is used to make self-extracting zips.
  1517.      *
  1518.      * @param preamble data to write
  1519.      * @throws IOException if an entry already exists
  1520.      * @since 1.21
  1521.      */
  1522.     public void writePreamble(final byte[] preamble) throws IOException {
  1523.         writePreamble(preamble, 0, preamble.length);
  1524.     }

  1525.     /**
  1526.      * Write preamble data. For most of the time, this is used to make self-extracting zips.
  1527.      *
  1528.      * @param preamble data to write
  1529.      * @param offset   the start offset in the data
  1530.      * @param length   the number of bytes to write
  1531.      * @throws IOException if an entry already exists
  1532.      * @since 1.21
  1533.      */
  1534.     public void writePreamble(final byte[] preamble, final int offset, final int length) throws IOException {
  1535.         if (entry != null) {
  1536.             throw new IllegalStateException("Preamble must be written before creating an entry");
  1537.         }
  1538.         this.streamCompressor.writeCounted(preamble, offset, length);
  1539.     }

  1540.     /**
  1541.      * Writes the &quot;ZIP64 End of central dir record&quot; and &quot;ZIP64 End of central dir locator&quot;.
  1542.      *
  1543.      * @throws IOException on error
  1544.      * @since 1.3
  1545.      */
  1546.     protected void writeZip64CentralDirectory() throws IOException {
  1547.         if (zip64Mode == Zip64Mode.Never) {
  1548.             return;
  1549.         }

  1550.         if (!hasUsedZip64 && shouldUseZip64EOCD()) {
  1551.             // actually "will use"
  1552.             hasUsedZip64 = true;
  1553.         }

  1554.         if (!hasUsedZip64) {
  1555.             return;
  1556.         }

  1557.         long offset = streamCompressor.getTotalBytesWritten();
  1558.         long diskNumberStart = 0L;
  1559.         if (isSplitZip) {
  1560.             // when creating a split zip, the offset of should be
  1561.             // the offset to the corresponding segment disk
  1562.             final ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream) this.out;
  1563.             offset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten();
  1564.             diskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex();
  1565.         }

  1566.         writeOut(ZIP64_EOCD_SIG);
  1567.         // size of zip64 end of central directory, we don't have any variable length
  1568.         // as we don't support the extensible data sector, yet
  1569.         writeOut(ZipEightByteInteger.getBytes(ZipConstants.SHORT /* version made by */
  1570.                 + ZipConstants.SHORT /* version needed to extract */
  1571.                 + ZipConstants.WORD /* disk number */
  1572.                 + ZipConstants.WORD /* disk with central directory */
  1573.                 + ZipConstants.DWORD /* number of entries in CD on this disk */
  1574.                 + ZipConstants.DWORD /* total number of entries */
  1575.                 + ZipConstants.DWORD /* size of CD */
  1576.                 + (long) ZipConstants.DWORD /* offset of CD */
  1577.         ));

  1578.         // version made by and version needed to extract
  1579.         writeOut(ZipShort.getBytes(ZipConstants.ZIP64_MIN_VERSION));
  1580.         writeOut(ZipShort.getBytes(ZipConstants.ZIP64_MIN_VERSION));

  1581.         // number of this disk
  1582.         int numberOfThisDisk = 0;
  1583.         if (isSplitZip) {
  1584.             numberOfThisDisk = ((ZipSplitOutputStream) this.out).getCurrentSplitSegmentIndex();
  1585.         }
  1586.         writeOut(ZipLong.getBytes(numberOfThisDisk));

  1587.         // disk number of the start of central directory
  1588.         writeOut(ZipLong.getBytes(cdDiskNumberStart));

  1589.         // total number of entries in the central directory on this disk
  1590.         final int numOfEntriesOnThisDisk = isSplitZip ? numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0) : entries.size();
  1591.         final byte[] numOfEntriesOnThisDiskData = ZipEightByteInteger.getBytes(numOfEntriesOnThisDisk);
  1592.         writeOut(numOfEntriesOnThisDiskData);

  1593.         // number of entries
  1594.         final byte[] num = ZipEightByteInteger.getBytes(entries.size());
  1595.         writeOut(num);

  1596.         // length and location of CD
  1597.         writeOut(ZipEightByteInteger.getBytes(cdLength));
  1598.         writeOut(ZipEightByteInteger.getBytes(cdOffset));

  1599.         // no "zip64 extensible data sector" for now

  1600.         if (isSplitZip) {
  1601.             // based on the ZIP specification, the End Of Central Directory record and
  1602.             // the Zip64 End Of Central Directory locator record must be on the same segment
  1603.             final int zip64EOCDLOCLength = ZipConstants.WORD /* length of ZIP64_EOCD_LOC_SIG */
  1604.                     + ZipConstants.WORD /* disk number of ZIP64_EOCD_SIG */
  1605.                     + ZipConstants.DWORD /* offset of ZIP64_EOCD_SIG */
  1606.                     + ZipConstants.WORD /* total number of disks */;

  1607.             final long unsplittableContentSize = zip64EOCDLOCLength + eocdLength;
  1608.             ((ZipSplitOutputStream) this.out).prepareToWriteUnsplittableContent(unsplittableContentSize);
  1609.         }

  1610.         // and now the "ZIP64 end of central directory locator"
  1611.         writeOut(ZIP64_EOCD_LOC_SIG);

  1612.         // disk number holding the ZIP64 EOCD record
  1613.         writeOut(ZipLong.getBytes(diskNumberStart));
  1614.         // relative offset of ZIP64 EOCD record
  1615.         writeOut(ZipEightByteInteger.getBytes(offset));
  1616.         // total number of disks
  1617.         if (isSplitZip) {
  1618.             // the Zip64 End Of Central Directory Locator and the End Of Central Directory must be
  1619.             // in the same split disk, it means they must be located in the last disk
  1620.             final int totalNumberOfDisks = ((ZipSplitOutputStream) this.out).getCurrentSplitSegmentIndex() + 1;
  1621.             writeOut(ZipLong.getBytes(totalNumberOfDisks));
  1622.         } else {
  1623.             writeOut(ONE);
  1624.         }
  1625.     }
  1626. }