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