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
19 import java.io.ByteArrayOutputStream;
20 import java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.nio.ByteBuffer;
25 import java.nio.channels.SeekableByteChannel;
26 import java.nio.charset.Charset;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.LinkOption;
29 import java.nio.file.OpenOption;
30 import java.nio.file.Path;
31 import java.util.HashMap;
32 import java.util.LinkedList;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.zip.Deflater;
36 import java.util.zip.ZipException;
37
38 import org.apache.commons.compress.archivers.ArchiveEntry;
39 import org.apache.commons.compress.archivers.ArchiveOutputStream;
40 import org.apache.commons.compress.utils.ByteUtils;
41 import org.apache.commons.io.Charsets;
42
43 /**
44  * Reimplementation of {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} to handle the extended functionality of this package, especially
45  * internal/external file attributes and extra fields with different layouts for local file data and central directory entries.
46  * <p>
47  * 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
48  * created.
49  * </p>
50  * <p>
51  * If SeekableByteChannel cannot be used, this implementation will use a Data Descriptor to store size and CRC information for {@link #DEFLATED DEFLATED}
52  * 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
53  * uncompressed size information is required before {@link #putArchiveEntry(ZipArchiveEntry)} can be called.
54  * </p>
55  * <p>
56  * 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
57  * 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
58  * write a ZipArchiveEntry of unknown size, then Zip64 extensions will be disabled by default.
59  * </p>
60  *
61  * @NotThreadSafe
62  */

63 public class ZipArchiveOutputStream extends ArchiveOutputStream<ZipArchiveEntry> {
64
65     /**
66      * Structure collecting information for the entry that is currently being written.
67      */

68     private static final class CurrentEntry {
69
70         /**
71          * Current ZIP entry.
72          */

73         private final ZipArchiveEntry entry;
74
75         /**
76          * Offset for CRC entry in the local file header data for the current entry starts here.
77          */

78         private long localDataStart;
79
80         /**
81          * Data for local header data
82          */

83         private long dataStart;
84
85         /**
86          * Number of bytes read for the current entry (can't rely on Deflater#getBytesRead) when using DEFLATED.
87          */

88         private long bytesRead;
89
90         /**
91          * Whether current entry was the first one using ZIP64 features.
92          */

93         private boolean causedUseOfZip64;
94
95         /**
96          * Whether write() has been called at all.
97          *
98          * <p>
99          * In order to create a valid archive {@link #closeArchiveEntry closeArchiveEntry} will write an empty array to get the CRC right if nothing has been
100          * written to the stream at all.
101          * </p>
102          */

103         private boolean hasWritten;
104
105         private CurrentEntry(final ZipArchiveEntry entry) {
106             this.entry = entry;
107         }
108     }
109
110     private static final class EntryMetaData {
111         private final long offset;
112         private final boolean usesDataDescriptor;
113
114         private EntryMetaData(final long offset, final boolean usesDataDescriptor) {
115             this.offset = offset;
116             this.usesDataDescriptor = usesDataDescriptor;
117         }
118     }
119
120     /**
121      * enum that represents the possible policies for creating Unicode extra fields.
122      */

123     public static final class UnicodeExtraFieldPolicy {
124
125         /**
126          * Always create Unicode extra fields.
127          */

128         public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
129
130         /**
131          * Never create Unicode extra fields.
132          */

133         public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
134
135         /**
136          * Creates Unicode extra fields for file names that cannot be encoded using the specified encoding.
137          */

138         public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = new UnicodeExtraFieldPolicy("not encodeable");
139
140         private final String name;
141
142         private UnicodeExtraFieldPolicy(final String n) {
143             name = n;
144         }
145
146         @Override
147         public String toString() {
148             return name;
149         }
150     }
151
152     static final int BUFFER_SIZE = 512;
153     private static final int LFH_SIG_OFFSET = 0;
154     private static final int LFH_VERSION_NEEDED_OFFSET = 4;
155     private static final int LFH_GPB_OFFSET = 6;
156     private static final int LFH_METHOD_OFFSET = 8;
157     private static final int LFH_TIME_OFFSET = 10;
158     private static final int LFH_CRC_OFFSET = 14;
159     private static final int LFH_COMPRESSED_SIZE_OFFSET = 18;
160     private static final int LFH_ORIGINAL_SIZE_OFFSET = 22;
161     private static final int LFH_FILENAME_LENGTH_OFFSET = 26;
162     private static final int LFH_EXTRA_LENGTH_OFFSET = 28;
163     private static final int LFH_FILENAME_OFFSET = 30;
164     private static final int CFH_SIG_OFFSET = 0;
165     private static final int CFH_VERSION_MADE_BY_OFFSET = 4;
166     private static final int CFH_VERSION_NEEDED_OFFSET = 6;
167     private static final int CFH_GPB_OFFSET = 8;
168     private static final int CFH_METHOD_OFFSET = 10;
169     private static final int CFH_TIME_OFFSET = 12;
170     private static final int CFH_CRC_OFFSET = 16;
171     private static final int CFH_COMPRESSED_SIZE_OFFSET = 20;
172     private static final int CFH_ORIGINAL_SIZE_OFFSET = 24;
173     private static final int CFH_FILENAME_LENGTH_OFFSET = 28;
174     private static final int CFH_EXTRA_LENGTH_OFFSET = 30;
175     private static final int CFH_COMMENT_LENGTH_OFFSET = 32;
176     private static final int CFH_DISK_NUMBER_OFFSET = 34;
177     private static final int CFH_INTERNAL_ATTRIBUTES_OFFSET = 36;
178
179     private static final int CFH_EXTERNAL_ATTRIBUTES_OFFSET = 38;
180
181     private static final int CFH_LFH_OFFSET = 42;
182
183     private static final int CFH_FILENAME_OFFSET = 46;
184
185     /**
186      * Compression method for deflated entries.
187      */

188     public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
189
190     /**
191      * Default compression level for deflated entries.
192      */

193     public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
194
195     /**
196      * Compression method for stored entries.
197      */

198     public static final int STORED = java.util.zip.ZipEntry.STORED;
199
200     /**
201      * Default encoding for file names and comment.
202      */

203     static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
204
205     /**
206      * General purpose flag, which indicates that file names are written in UTF-8.
207      *
208      * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead
209      */

210     @Deprecated
211     public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG;
212
213     /**
214      * Helper, a 0 as ZipShort.
215      */

216     private static final byte[] ZERO = { 0, 0 };
217
218     /**
219      * Helper, a 0 as ZipLong.
220      */

221     private static final byte[] LZERO = { 0, 0, 0, 0 };
222
223     private static final byte[] ONE = ZipLong.getBytes(1L);
224
225     /*
226      * Various ZIP constants shared between this class, ZipArchiveInputStream and ZipFile
227      */

228     /**
229      * local file header signature
230      */

231     static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); // NOSONAR
232
233     /**
234      * data descriptor signature
235      */

236     static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes(); // NOSONAR
237
238     /**
239      * central file header signature
240      */

241     static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); // NOSONAR
242
243     /**
244      * end of central dir signature
245      */

246     static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); // NOSONAR
247
248     /**
249      * ZIP64 end of central dir signature
250      */

251     static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L); // NOSONAR
252
253     /**
254      * ZIP64 end of central dir locator signature
255      */

256     static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L); // NOSONAR
257
258     /** Indicates if this archive is finished. protected for use in Jar implementation */
259     protected boolean finished;
260
261     /**
262      * Current entry.
263      */

264     private CurrentEntry entry;
265
266     /**
267      * The file comment.
268      */

269     private String comment = "";
270
271     /**
272      * Compression level for next entry.
273      */

274     private int level = DEFAULT_COMPRESSION;
275
276     /**
277      * Has the compression level changed when compared to the last entry?
278      */

279     private boolean hasCompressionLevelChanged;
280
281     /**
282      * Default compression method for next entry.
283      */

284     private int method = java.util.zip.ZipEntry.DEFLATED;
285
286     /**
287      * List of ZipArchiveEntries written so far.
288      */

289     private final List<ZipArchiveEntry> entries = new LinkedList<>();
290
291     private final StreamCompressor streamCompressor;
292
293     /**
294      * Start of central directory.
295      */

296     private long cdOffset;
297
298     /**
299      * Length of central directory.
300      */

301     private long cdLength;
302
303     /**
304      * Disk number start of central directory.
305      */

306     private long cdDiskNumberStart;
307
308     /**
309      * Length of end of central directory
310      */

311     private long eocdLength;
312
313     /**
314      * Holds some book-keeping data for each entry.
315      */

316     private final Map<ZipArchiveEntry, EntryMetaData> metaData = new HashMap<>();
317
318     /**
319      * The encoding to use for file names and the file comment.
320      *
321      * <p>
322      * 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>.
323      * Defaults to UTF-8.
324      * </p>
325      */

326     private Charset charset = DEFAULT_CHARSET;
327
328     /**
329      * The ZIP encoding to use for file names and the file comment.
330      *
331      * This field is of internal use and will be set in {@link #setEncoding(String)}.
332      */

333     private ZipEncoding zipEncoding = ZipEncodingHelper.getZipEncoding(DEFAULT_CHARSET);
334
335     /**
336      * This Deflater object is used for output.
337      */

338     protected final Deflater def;
339
340     private final OutputStream outputStream;
341
342     /**
343      * whether to use the general purpose bit flag when writing UTF-8 file names or not.
344      */

345     private boolean useUTF8Flag = true;
346
347     /**
348      * Whether to encode non-encodable file names as UTF-8.
349      */

350     private boolean fallbackToUTF8;
351
352     /**
353      * whether to create UnicodePathExtraField-s for each entry.
354      */

355     private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
356
357     /**
358      * Whether anything inside this archive has used a ZIP64 feature.
359      *
360      * @since 1.3
361      */

362     private boolean hasUsedZip64;
363
364     private Zip64Mode zip64Mode = Zip64Mode.AsNeeded;
365
366     private final byte[] copyBuffer = new byte[32768];
367
368     /**
369      * Whether we are creating a split zip
370      */

371     private final boolean isSplitZip;
372
373     /**
374      * Holds the number of Central Directories on each disk, this is used when writing Zip64 End Of Central Directory and End Of Central Directory
375      */

376     private final Map<Integer, Integer> numberOfCDInDiskData = new HashMap<>();
377
378     /**
379      * Creates a new ZIP OutputStream writing to a File. Will use random access if possible.
380      *
381      * @param file the file to ZIP to
382      * @throws IOException on error
383      */

384     public ZipArchiveOutputStream(final File file) throws IOException {
385         this(file.toPath());
386     }
387
388     /**
389      * Creates a split ZIP Archive.
390      *
391      * <p>
392      * The files making up the archive will use Z01, Z02, ... extensions and the last part of it will be the given {@code
393      * file}.
394      * </p>
395      *
396      * <p>
397      * 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
398      * known before the actual entry data is written.
399      * </p>
400      *
401      * @param file         the file that will become the last part of the split archive
402      * @param zipSplitSize maximum size of a single part of the split archive created by this stream. Must be between 64kB and about 4GB.
403      *
404      * @throws IOException              on error
405      * @throws IllegalArgumentException if zipSplitSize is not in the required range
406      * @since 1.20
407      */

408     public ZipArchiveOutputStream(final File file, final long zipSplitSize) throws IOException {
409         this(file.toPath(), zipSplitSize);
410     }
411
412     /**
413      * Creates a new ZIP OutputStream filtering the underlying stream.
414      *
415      * @param out the outputstream to zip
416      */

417     public ZipArchiveOutputStream(final OutputStream out) {
418         this.outputStream = out;
419         this.def = new Deflater(level, true);
420         this.streamCompressor = StreamCompressor.create(out, def);
421         this.isSplitZip = false;
422     }
423
424     /**
425      * Creates a split ZIP Archive.
426      * <p>
427      * The files making up the archive will use Z01, Z02, ... extensions and the last part of it will be the given {@code
428      * file}.
429      * </p>
430      * <p>
431      * 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
432      * known before the actual entry data is written.
433      * </p>
434      *
435      * @param path         the path to the file that will become the last part of the split archive
436      * @param zipSplitSize maximum size of a single part of the split archive created by this stream. Must be between 64kB and about 4GB.
437      * @throws IOException              on error
438      * @throws IllegalArgumentException if zipSplitSize is not in the required range
439      * @since 1.22
440      */

441     public ZipArchiveOutputStream(final Path path, final long zipSplitSize) throws IOException {
442         this.def = new Deflater(level, true);
443         this.outputStream = new ZipSplitOutputStream(path, zipSplitSize);
444         this.streamCompressor = StreamCompressor.create(this.outputStream, def);
445         this.isSplitZip = true;
446     }
447
448     /**
449      * Creates a new ZIP OutputStream writing to a Path. Will use random access if possible.
450      *
451      * @param file    the file to ZIP to
452      * @param options options specifying how the file is opened.
453      * @throws IOException on error
454      * @since 1.21
455      */

456     public ZipArchiveOutputStream(final Path file, final OpenOption... options) throws IOException {
457         this.def = new Deflater(level, true);
458         this.outputStream = options.length == 0 ? new FileRandomAccessOutputStream(file) : new FileRandomAccessOutputStream(file, options);
459         this.streamCompressor = StreamCompressor.create(outputStream, def);
460         this.isSplitZip = false;
461     }
462
463     /**
464      * Creates a new ZIP OutputStream writing to a SeekableByteChannel.
465      *
466      * <p>
467      * {@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to write to an in-memory archive using random access.
468      * </p>
469      *
470      * @param channel the channel to ZIP to
471      * @since 1.13
472      */

473     public ZipArchiveOutputStream(final SeekableByteChannel channel) {
474         this.outputStream = new SeekableChannelRandomAccessOutputStream(channel);
475         this.def = new Deflater(level, true);
476         this.streamCompressor = StreamCompressor.create(outputStream, def);
477         this.isSplitZip = false;
478     }
479
480     /**
481      * Adds an archive entry with a raw input stream.
482      * <p>
483      * 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
484      * stream, and the supplied value is ignored.
485      * </p>
486      * <p>
487      * The entry is put and closed immediately.
488      * </p>
489      *
490      * @param entry     The archive entry to add
491      * @param rawStream The raw input stream of a different entry. May be compressed/encrypted.
492      * @throws IOException If copying fails
493      */

494     public void addRawArchiveEntry(final ZipArchiveEntry entry, final InputStream rawStream) throws IOException {
495         final ZipArchiveEntry ae = new ZipArchiveEntry(entry);
496         if (hasZip64Extra(ae)) {
497             // Will be re-added as required. this may make the file generated with this method
498             // somewhat smaller than standard mode,
499             // since standard mode is unable to remove the ZIP 64 header.
500             ae.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
501         }
502         final boolean is2PhaseSource = ae.getCrc() != ZipArchiveEntry.CRC_UNKNOWN && ae.getSize() != ArchiveEntry.SIZE_UNKNOWN
503                 && ae.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN;
504         putArchiveEntry(ae, is2PhaseSource);
505         copyFromZipInputStream(rawStream);
506         closeCopiedEntry(is2PhaseSource);
507     }
508
509     /**
510      * Adds UnicodeExtra fields for name and file comment if mode is ALWAYS or the data cannot be encoded using the configured encoding.
511      */

512     private void addUnicodeExtraFields(final ZipArchiveEntry ze, final boolean encodable, final ByteBuffer name) throws IOException {
513         if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !encodable) {
514             ze.addExtraField(new UnicodePathExtraField(ze.getName(), name.array(), name.arrayOffset(), name.limit() - name.position()));
515         }
516
517         final String comm = ze.getComment();
518         if (comm != null && !comm.isEmpty()) {
519
520             final boolean commentEncodable = zipEncoding.canEncode(comm);
521
522             if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS || !commentEncodable) {
523                 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
524                 ze.addExtraField(new UnicodeCommentExtraField(comm, commentB.array(), commentB.arrayOffset(), commentB.limit() - commentB.position()));
525             }
526         }
527     }
528
529     /**
530      * Whether this stream is able to write the given entry.
531      * <p>
532      * May return false if it is set up to use encryption or a compression method that hasn't been implemented yet.
533      * </p>
534      *
535      * @since 1.1
536      */

537     @Override
538     public boolean canWriteEntryData(final ArchiveEntry ae) {
539         if (ae instanceof ZipArchiveEntry) {
540             final ZipArchiveEntry zae = (ZipArchiveEntry) ae;
541             return zae.getMethod() != ZipMethod.IMPLODING.getCode() && zae.getMethod() != ZipMethod.UNSHRINKING.getCode() && ZipUtil.canHandleEntryData(zae);
542         }
543         return false;
544     }
545
546     /**
547      * Verifies the sizes aren't too big in the Zip64Mode.Never case and returns whether the entry would require a Zip64 extra field.
548      */

549     private boolean checkIfNeedsZip64(final Zip64Mode effectiveMode) throws ZipException {
550         final boolean actuallyNeedsZip64 = isZip64Required(entry.entry, effectiveMode);
551         if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) {
552             throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry));
553         }
554         return actuallyNeedsZip64;
555     }
556
557     /**
558      * Closes this output stream and releases any system resources associated with the stream.
559      *
560      * @throws IOException            if an I/O error occurs.
561      * @throws Zip64RequiredException if the archive's size exceeds 4 GByte or there are more than 65535 entries inside the archive and {@link #setUseZip64} is
562      *                                {@link Zip64Mode#Never}.
563      */

564     @Override
565     public void close() throws IOException {
566         try {
567             if (!finished) {
568                 finish();
569             }
570         } finally {
571             destroy();
572         }
573     }
574
575     /**
576      * Writes all necessary data for this entry.
577      *
578      * @throws IOException            on error
579      * @throws Zip64RequiredException if the entry's uncompressed or compressed size exceeds 4 GByte and {@link #setUseZip64} is {@link Zip64Mode#Never}.
580      */

581     @Override
582     public void closeArchiveEntry() throws IOException {
583         preClose();
584
585         flushDeflater();
586
587         final long bytesWritten = streamCompressor.getTotalBytesWritten() - entry.dataStart;
588         final long realCrc = streamCompressor.getCrc32();
589         entry.bytesRead = streamCompressor.getBytesRead();
590         final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
591         final boolean actuallyNeedsZip64 = handleSizesAndCrc(bytesWritten, realCrc, effectiveMode);
592         closeEntry(actuallyNeedsZip64, false);
593         streamCompressor.reset();
594     }
595
596     /**
597      * Writes all necessary data for this entry.
598      *
599      * @param phased This entry is second phase of a 2-phase ZIP creation, size, compressed size and crc are known in ZipArchiveEntry
600      * @throws IOException            on error
601      * @throws Zip64RequiredException if the entry's uncompressed or compressed size exceeds 4 GByte and {@link #setUseZip64} is {@link Zip64Mode#Never}.
602      */

603     private void closeCopiedEntry(final boolean phased) throws IOException {
604         preClose();
605         entry.bytesRead = entry.entry.getSize();
606         final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
607         final boolean actuallyNeedsZip64 = checkIfNeedsZip64(effectiveMode);
608         closeEntry(actuallyNeedsZip64, phased);
609     }
610
611     private void closeEntry(final boolean actuallyNeedsZip64, final boolean phased) throws IOException {
612         if (!phased && outputStream instanceof RandomAccessOutputStream) {
613             rewriteSizesAndCrc(actuallyNeedsZip64);
614         }
615
616         if (!phased) {
617             writeDataDescriptor(entry.entry);
618         }
619         entry = null;
620     }
621
622     private void copyFromZipInputStream(final InputStream src) throws IOException {
623         if (entry == null) {
624             throw new IllegalStateException("No current entry");
625         }
626         ZipUtil.checkRequestedFeatures(entry.entry);
627         entry.hasWritten = true;
628         int length;
629         while ((length = src.read(copyBuffer)) >= 0) {
630             streamCompressor.writeCounted(copyBuffer, 0, length);
631             count(length);
632         }
633     }
634
635     /**
636      * Creates a new ZIP entry taking some information from the given file and using the provided name.
637      * <p>
638      * 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
639      * will be stripped from the entry name.
640      * </p>
641      * <p>
642      * Must not be used if the stream has already been closed.
643      * </p>
644      */

645     @Override
646     public ZipArchiveEntry createArchiveEntry(final File inputFile, final String entryName) throws IOException {
647         if (finished) {
648             throw new IOException("Stream has already been finished");
649         }
650         return new ZipArchiveEntry(inputFile, entryName);
651     }
652
653     /**
654      * Creates a new ZIP entry taking some information from the given file and using the provided name.
655      * <p>
656      * 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
657      * will be stripped from the entry name.
658      * </p>
659      * <p>
660      * Must not be used if the stream has already been closed.
661      * </p>
662      *
663      * @param inputPath path to create the entry from.
664      * @param entryName name of the entry.
665      * @param options   options indicating how symbolic links are handled.
666      * @return a new instance.
667      * @throws IOException if an I/O error occurs.
668      * @since 1.21
669      */

670     @Override
671     public ZipArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException {
672         if (finished) {
673             throw new IOException("Stream has already been finished");
674         }
675         return new ZipArchiveEntry(inputPath, entryName);
676     }
677
678     private byte[] createCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
679
680         final EntryMetaData entryMetaData = metaData.get(ze);
681         final boolean needsZip64Extra = hasZip64Extra(ze) || ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC
682                 || entryMetaData.offset >= ZipConstants.ZIP64_MAGIC || ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT
683                 || zip64Mode == Zip64Mode.Always || zip64Mode == Zip64Mode.AlwaysWithCompatibility;
684
685         if (needsZip64Extra && zip64Mode == Zip64Mode.Never) {
686             // must be the offset that is too big, otherwise an
687             // exception would have been throw in putArchiveEntry or
688             // closeArchiveEntry
689             throw new Zip64RequiredException(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE);
690         }
691
692         handleZip64Extra(ze, entryMetaData.offset, needsZip64Extra);
693
694         return createCentralFileHeader(ze, getName(ze), entryMetaData, needsZip64Extra);
695     }
696
697     /**
698      * Writes the central file header entry.
699      *
700      * @param ze            the entry to write
701      * @param name          The encoded name
702      * @param entryMetaData meta data for this file
703      * @throws IOException on error
704      */

705     private byte[] createCentralFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final EntryMetaData entryMetaData, final boolean needsZip64Extra)
706             throws IOException {
707         if (isSplitZip) {
708             // calculate the disk number for every central file header,
709             // this will be used in writing End Of Central Directory and Zip64 End Of Central Directory
710             final int currentSplitSegment = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
711             if (numberOfCDInDiskData.get(currentSplitSegment) == null) {
712                 numberOfCDInDiskData.put(currentSplitSegment, 1);
713             } else {
714                 final int originalNumberOfCD = numberOfCDInDiskData.get(currentSplitSegment);
715                 numberOfCDInDiskData.put(currentSplitSegment, originalNumberOfCD + 1);
716             }
717         }
718
719         final byte[] extra = ze.getCentralDirectoryExtra();
720         final int extraLength = extra.length;
721
722         // file comment length
723         String comm = ze.getComment();
724         if (comm == null) {
725             comm = "";
726         }
727
728         final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
729         final int nameLen = name.limit() - name.position();
730         final int commentLen = commentB.limit() - commentB.position();
731         final int len = CFH_FILENAME_OFFSET + nameLen + extraLength + commentLen;
732         final byte[] buf = new byte[len];
733
734         System.arraycopy(CFH_SIG, 0, buf, CFH_SIG_OFFSET, ZipConstants.WORD);
735
736         // version made by
737         // CheckStyle:MagicNumber OFF
738         ZipShort.putShort(ze.getPlatform() << 8 | (!hasUsedZip64 ? ZipConstants.DATA_DESCRIPTOR_MIN_VERSION : ZipConstants.ZIP64_MIN_VERSION), buf,
739                 CFH_VERSION_MADE_BY_OFFSET);
740
741         final int zipMethod = ze.getMethod();
742         final boolean encodable = zipEncoding.canEncode(ze.getName());
743         ZipShort.putShort(versionNeededToExtract(zipMethod, needsZip64Extra, entryMetaData.usesDataDescriptor), buf, CFH_VERSION_NEEDED_OFFSET);
744         getGeneralPurposeBits(!encodable && fallbackToUTF8, entryMetaData.usesDataDescriptor).encode(buf, CFH_GPB_OFFSET);
745
746         // compression method
747         ZipShort.putShort(zipMethod, buf, CFH_METHOD_OFFSET);
748
749         // last mod. time and date
750         ZipUtil.toDosTime(ze.getTime(), buf, CFH_TIME_OFFSET);
751
752         // CRC
753         // compressed length
754         // uncompressed length
755         ZipLong.putLong(ze.getCrc(), buf, CFH_CRC_OFFSET);
756         if (ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always
757                 || zip64Mode == Zip64Mode.AlwaysWithCompatibility) {
758             ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET);
759             ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET);
760         } else {
761             ZipLong.putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET);
762             ZipLong.putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET);
763         }
764
765         ZipShort.putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET);
766
767         // extra field length
768         ZipShort.putShort(extraLength, buf, CFH_EXTRA_LENGTH_OFFSET);
769
770         ZipShort.putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET);
771
772         // disk number start
773         if (isSplitZip) {
774             if (ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always) {
775                 ZipShort.putShort(ZipConstants.ZIP64_MAGIC_SHORT, buf, CFH_DISK_NUMBER_OFFSET);
776             } else {
777                 ZipShort.putShort((int) ze.getDiskNumberStart(), buf, CFH_DISK_NUMBER_OFFSET);
778             }
779         } else {
780             System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, ZipConstants.SHORT);
781         }
782
783         // internal file attributes
784         ZipShort.putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET);
785
786         // external file attributes
787         ZipLong.putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET);
788
789         // relative offset of LFH
790         if (entryMetaData.offset >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) {
791             ZipLong.putLong(ZipConstants.ZIP64_MAGIC, buf, CFH_LFH_OFFSET);
792         } else {
793             ZipLong.putLong(Math.min(entryMetaData.offset, ZipConstants.ZIP64_MAGIC), buf, CFH_LFH_OFFSET);
794         }
795
796         // file name
797         System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen);
798
799         final int extraStart = CFH_FILENAME_OFFSET + nameLen;
800         System.arraycopy(extra, 0, buf, extraStart, extraLength);
801
802         final int commentStart = extraStart + extraLength;
803
804         // file comment
805         System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen);
806         return buf;
807     }
808
809     private byte[] createLocalFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final boolean encodable, final boolean phased,
810             final long archiveOffset) {
811         final ZipExtraField oldEx = ze.getExtraField(ResourceAlignmentExtraField.ID);
812         if (oldEx != null) {
813             ze.removeExtraField(ResourceAlignmentExtraField.ID);
814         }
815         final ResourceAlignmentExtraField oldAlignmentEx = oldEx instanceof ResourceAlignmentExtraField ? (ResourceAlignmentExtraField) oldEx : null;
816
817         int alignment = ze.getAlignment();
818         if (alignment <= 0 && oldAlignmentEx != null) {
819             alignment = oldAlignmentEx.getAlignment();
820         }
821
822         if (alignment > 1 || oldAlignmentEx != null && !oldAlignmentEx.allowMethodChange()) {
823             final int oldLength = LFH_FILENAME_OFFSET + name.limit() - name.position() + ze.getLocalFileDataExtra().length;
824
825             final int padding = (int) (-archiveOffset - oldLength - ZipExtraField.EXTRAFIELD_HEADER_SIZE - ResourceAlignmentExtraField.BASE_SIZE
826                     & alignment - 1);
827             ze.addExtraField(new ResourceAlignmentExtraField(alignment, oldAlignmentEx != null && oldAlignmentEx.allowMethodChange(), padding));
828         }
829
830         final byte[] extra = ze.getLocalFileDataExtra();
831         final int nameLen = name.limit() - name.position();
832         final int len = LFH_FILENAME_OFFSET + nameLen + extra.length;
833         final byte[] buf = new byte[len];
834
835         System.arraycopy(LFH_SIG, 0, buf, LFH_SIG_OFFSET, ZipConstants.WORD);
836
837         // store method in local variable to prevent multiple method calls
838         final int zipMethod = ze.getMethod();
839         final boolean dataDescriptor = usesDataDescriptor(zipMethod, phased);
840
841         ZipShort.putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze), dataDescriptor), buf, LFH_VERSION_NEEDED_OFFSET);
842
843         final GeneralPurposeBit generalPurposeBit = getGeneralPurposeBits(!encodable && fallbackToUTF8, dataDescriptor);
844         generalPurposeBit.encode(buf, LFH_GPB_OFFSET);
845
846         // compression method
847         ZipShort.putShort(zipMethod, buf, LFH_METHOD_OFFSET);
848
849         ZipUtil.toDosTime(ze.getTime(), buf, LFH_TIME_OFFSET);
850
851         // CRC
852         if (phased || !(zipMethod == DEFLATED || outputStream instanceof RandomAccessOutputStream)) {
853             ZipLong.putLong(ze.getCrc(), buf, LFH_CRC_OFFSET);
854         } else {
855             System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, ZipConstants.WORD);
856         }
857
858         // compressed length
859         // uncompressed length
860         if (hasZip64Extra(entry.entry)) {
861             // point to ZIP64 extended information extra field for
862             // sizes, may get rewritten once sizes are known if
863             // stream is seekable
864             ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET);
865             ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET);
866         } else if (phased) {
867             ZipLong.putLong(ze.getCompressedSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
868             ZipLong.putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
869         } else if (zipMethod == DEFLATED || outputStream instanceof RandomAccessOutputStream) {
870             System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, ZipConstants.WORD);
871             System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, ZipConstants.WORD);
872         } else { // Stored
873             ZipLong.putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
874             ZipLong.putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
875         }
876         // file name length
877         ZipShort.putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET);
878
879         // extra field length
880         ZipShort.putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET);
881
882         // file name
883         System.arraycopy(name.array(), name.arrayOffset(), buf, LFH_FILENAME_OFFSET, nameLen);
884
885         // extra fields
886         System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length);
887
888         return buf;
889     }
890
891     /**
892      * Writes next block of compressed data to the output stream.
893      *
894      * @throws IOException on error
895      */

896     protected final void deflate() throws IOException {
897         streamCompressor.deflate();
898     }
899
900     /**
901      * Closes the underlying stream/file without finishing the archive, the result will likely be a corrupt archive.
902      * <p>
903      * This method only exists to support tests that generate corrupt archives so they can clean up any temporary files.
904      * </p>
905      */

906     void destroy() throws IOException {
907         if (outputStream != null) {
908             outputStream.close();
909         }
910     }
911
912     /**
913      * {@inheritDoc}
914      *
915      * @throws Zip64RequiredException if the archive's size exceeds 4 GByte or there are more than 65535 entries inside the archive and {@link #setUseZip64} is
916      *                                {@link Zip64Mode#Never}.
917      */

918     @Override
919     public void finish() throws IOException {
920         if (finished) {
921             throw new IOException("This archive has already been finished");
922         }
923
924         if (entry != null) {
925             throw new IOException("This archive contains unclosed entries.");
926         }
927
928         final long cdOverallOffset = streamCompressor.getTotalBytesWritten();
929         cdOffset = cdOverallOffset;
930         if (isSplitZip) {
931             // when creating a split zip, the offset should be
932             // the offset to the corresponding segment disk
933             final ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream) this.outputStream;
934             cdOffset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten();
935             cdDiskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex();
936         }
937         writeCentralDirectoryInChunks();
938
939         cdLength = streamCompressor.getTotalBytesWritten() - cdOverallOffset;
940
941         // calculate the length of end of central directory, as it may be used in writeZip64CentralDirectory
942         final ByteBuffer commentData = this.zipEncoding.encode(comment);
943         final long commentLength = (long) commentData.limit() - commentData.position();
944         eocdLength = ZipConstants.WORD /* length of EOCD_SIG */
945                 + ZipConstants.SHORT /* number of this disk */
946                 + ZipConstants.SHORT /* disk number of start of central directory */
947                 + ZipConstants.SHORT /* total number of entries on this disk */
948                 + ZipConstants.SHORT /* total number of entries */
949                 + ZipConstants.WORD /* size of central directory */
950                 + ZipConstants.WORD /* offset of start of central directory */
951                 + ZipConstants.SHORT /* ZIP comment length */
952                 + commentLength /* ZIP comment */;
953
954         writeZip64CentralDirectory();
955         writeCentralDirectoryEnd();
956         metaData.clear();
957         entries.clear();
958         streamCompressor.close();
959         if (isSplitZip) {
960             // trigger the ZipSplitOutputStream to write the final split segment
961             outputStream.close();
962         }
963         finished = true;
964     }
965
966     /**
967      * Flushes this output stream and forces any buffered output bytes to be written out to the stream.
968      *
969      * @throws IOException if an I/O error occurs.
970      */

971     @Override
972     public void flush() throws IOException {
973         if (outputStream != null) {
974             outputStream.flush();
975         }
976     }
977
978     /**
979      * Ensures all bytes sent to the deflater are written to the stream.
980      */

981     private void flushDeflater() throws IOException {
982         if (entry.entry.getMethod() == DEFLATED) {
983             streamCompressor.flushDeflater();
984         }
985     }
986
987     /**
988      * Returns the total number of bytes written to this stream.
989      *
990      * @return the number of written bytes
991      * @since 1.22
992      */

993     @Override
994     public long getBytesWritten() {
995         return streamCompressor.getTotalBytesWritten();
996     }
997
998     /**
999      * 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.
1000      *
1001      * @since 1.3
1002      */

1003     private Zip64Mode getEffectiveZip64Mode(final ZipArchiveEntry ze) {
1004         if (zip64Mode != Zip64Mode.AsNeeded || outputStream instanceof RandomAccessOutputStream ||
1005                 ze.getMethod() != DEFLATED || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
1006             return zip64Mode;
1007         }
1008         return Zip64Mode.Never;
1009     }
1010
1011     /**
1012      * The encoding to use for file names and the file comment.
1013      *
1014      * @return null if using the platform's default character encoding.
1015      */

1016     public String getEncoding() {
1017         return charset != null ? charset.name() : null;
1018     }
1019
1020     private ZipEncoding getEntryEncoding(final ZipArchiveEntry ze) {
1021         final boolean encodable = zipEncoding.canEncode(ze.getName());
1022         return !encodable && fallbackToUTF8 ? ZipEncodingHelper.ZIP_ENCODING_UTF_8 : zipEncoding;
1023     }
1024
1025     private GeneralPurposeBit getGeneralPurposeBits(final boolean utfFallback, final boolean usesDataDescriptor) {
1026         final GeneralPurposeBit b = new GeneralPurposeBit();
1027         b.useUTF8ForNames(useUTF8Flag || utfFallback);
1028         if (usesDataDescriptor) {
1029             b.useDataDescriptor(true);
1030         }
1031         return b;
1032     }
1033
1034     private ByteBuffer getName(final ZipArchiveEntry ze) throws IOException {
1035         return getEntryEncoding(ze).encode(ze.getName());
1036     }
1037
1038     /**
1039      * Gets the existing ZIP64 extended information extra field or create a new one and add it to the entry.
1040      *
1041      * @since 1.3
1042      */

1043     private Zip64ExtendedInformationExtraField getZip64Extra(final ZipArchiveEntry ze) {
1044         if (entry != null) {
1045             entry.causedUseOfZip64 = !hasUsedZip64;
1046         }
1047         hasUsedZip64 = true;
1048         final ZipExtraField extra = ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
1049         Zip64ExtendedInformationExtraField z64 = extra instanceof Zip64ExtendedInformationExtraField ? (Zip64ExtendedInformationExtraField) extra : null;
1050         if (z64 == null) {
1051             /*
1052              * System.err.println("Adding z64 for " + ze.getName() + ", method: " + ze.getMethod() + " (" + (ze.getMethod() == STORED) + ")" + ", channel: " +
1053              * (channel != null));
1054              */

1055             z64 = new Zip64ExtendedInformationExtraField();
1056         }
1057
1058         // even if the field is there already, make sure it is the first one
1059         ze.addAsFirstExtraField(z64);
1060
1061         return z64;
1062     }
1063
1064     /**
1065      * 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
1066      * whether the entry would require a Zip64 extra field.
1067      */

1068     private boolean handleSizesAndCrc(final long bytesWritten, final long crc, final Zip64Mode effectiveMode) throws ZipException {
1069         if (entry.entry.getMethod() == DEFLATED) {
1070             /*
1071              * It turns out def.getBytesRead() returns wrong values if the size exceeds 4 GB on Java < Java7 entry.entry.setSize(def.getBytesRead());
1072              */

1073             entry.entry.setSize(entry.bytesRead);
1074             entry.entry.setCompressedSize(bytesWritten);
1075             entry.entry.setCrc(crc);
1076
1077         } else if (!(outputStream instanceof RandomAccessOutputStream)) {
1078             if (entry.entry.getCrc() != crc) {
1079                 throw new ZipException("Bad CRC checksum for entry " + entry.entry.getName() + ": " + Long.toHexString(entry.entry.getCrc()) + " instead of "
1080                         + Long.toHexString(crc));
1081             }
1082
1083             if (entry.entry.getSize() != bytesWritten) {
1084                 throw new ZipException("Bad size for entry " + entry.entry.getName() + ": " + entry.entry.getSize() + " instead of " + bytesWritten);
1085             }
1086         } else { /* method is STORED and we used SeekableByteChannel */
1087             entry.entry.setSize(bytesWritten);
1088             entry.entry.setCompressedSize(bytesWritten);
1089             entry.entry.setCrc(crc);
1090         }
1091
1092         return checkIfNeedsZip64(effectiveMode);
1093     }
1094
1095     /**
1096      * If the entry needs Zip64 extra information inside the central directory then configure its data.
1097      */

1098     private void handleZip64Extra(final ZipArchiveEntry ze, final long lfhOffset, final boolean needsZip64Extra) {
1099         if (needsZip64Extra) {
1100             final Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze);
1101             if (ze.getCompressedSize() >= ZipConstants.ZIP64_MAGIC || ze.getSize() >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always
1102                     || zip64Mode == Zip64Mode.AlwaysWithCompatibility) {
1103                 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
1104                 z64.setSize(new ZipEightByteInteger(ze.getSize()));
1105             } else {
1106                 // reset value that may have been set for LFH
1107                 z64.setCompressedSize(null);
1108                 z64.setSize(null);
1109             }
1110
1111             final boolean needsToEncodeLfhOffset = lfhOffset >= ZipConstants.ZIP64_MAGIC || zip64Mode == Zip64Mode.Always;
1112             final boolean needsToEncodeDiskNumberStart = ze.getDiskNumberStart() >= ZipConstants.ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always;
1113
1114             if (needsToEncodeLfhOffset || needsToEncodeDiskNumberStart) {
1115                 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset));
1116             }
1117             if (needsToEncodeDiskNumberStart) {
1118                 z64.setDiskStartNumber(new ZipLong(ze.getDiskNumberStart()));
1119             }
1120             ze.setExtra();
1121         }
1122     }
1123
1124     /**
1125      * Is there a ZIP64 extended information extra field for the entry?
1126      *
1127      * @since 1.3
1128      */

1129     private boolean hasZip64Extra(final ZipArchiveEntry ze) {
1130         return ze.getExtraField(Zip64ExtendedInformationExtraField.HEADER_ID) instanceof Zip64ExtendedInformationExtraField;
1131     }
1132
1133     /**
1134      * This method indicates whether this archive is writing to a seekable stream (i.e., to a random access file).
1135      * <p>
1136      * For seekable streams, you don't need to calculate the CRC or uncompressed size for {@link #STORED} entries before invoking
1137      * {@link #putArchiveEntry(ZipArchiveEntry)}.
1138      * </p>
1139      *
1140      * @return true if seekable
1141      */

1142     public boolean isSeekable() {
1143         return outputStream instanceof RandomAccessOutputStream;
1144     }
1145
1146     private boolean isTooLargeForZip32(final ZipArchiveEntry zipArchiveEntry) {
1147         return zipArchiveEntry.getSize() >= ZipConstants.ZIP64_MAGIC || zipArchiveEntry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC;
1148     }
1149
1150     private boolean isZip64Required(final ZipArchiveEntry entry1, final Zip64Mode requestedMode) {
1151         return requestedMode == Zip64Mode.Always || requestedMode == Zip64Mode.AlwaysWithCompatibility || isTooLargeForZip32(entry1);
1152     }
1153
1154     private void preClose() throws IOException {
1155         if (finished) {
1156             throw new IOException("Stream has already been finished");
1157         }
1158
1159         if (entry == null) {
1160             throw new IOException("No current entry to close");
1161         }
1162
1163         if (!entry.hasWritten) {
1164             write(ByteUtils.EMPTY_BYTE_ARRAY, 0, 0);
1165         }
1166     }
1167
1168     /**
1169      * {@inheritDoc}
1170      *
1171      * @throws ClassCastException     if entry is not an instance of ZipArchiveEntry
1172      * @throws Zip64RequiredException if the entry's uncompressed or compressed size is known to exceed 4 GByte and {@link #setUseZip64} is
1173      *                                {@link Zip64Mode#Never}.
1174      */

1175     @Override
1176     public void putArchiveEntry(final ZipArchiveEntry archiveEntry) throws IOException {
1177         putArchiveEntry(archiveEntry, false);
1178     }
1179
1180     /**
1181      * 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()}
1182      * to complete the process.
1183      *
1184      * @param archiveEntry The archiveEntry
1185      * @param phased       If true size, compressedSize and crc required to be known up-front in the archiveEntry
1186      * @throws ClassCastException     if entry is not an instance of ZipArchiveEntry
1187      * @throws Zip64RequiredException if the entry's uncompressed or compressed size is known to exceed 4 GByte and {@link #setUseZip64} is
1188      *                                {@link Zip64Mode#Never}.
1189      */

1190     private void putArchiveEntry(final ZipArchiveEntry archiveEntry, final boolean phased) throws IOException {
1191         if (finished) {
1192             throw new IOException("Stream has already been finished");
1193         }
1194
1195         if (entry != null) {
1196             closeArchiveEntry();
1197         }
1198
1199         entry = new CurrentEntry(archiveEntry);
1200         entries.add(entry.entry);
1201
1202         setDefaults(entry.entry);
1203
1204         final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry);
1205         validateSizeInformation(effectiveMode);
1206
1207         if (shouldAddZip64Extra(entry.entry, effectiveMode)) {
1208
1209             final Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry);
1210
1211             final ZipEightByteInteger size;
1212             final ZipEightByteInteger compressedSize;
1213             if (phased) {
1214                 // sizes are already known
1215                 size = new ZipEightByteInteger(entry.entry.getSize());
1216                 compressedSize = new ZipEightByteInteger(entry.entry.getCompressedSize());
1217             } else if (entry.entry.getMethod() == STORED && entry.entry.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
1218                 // actually, we already know the sizes
1219                 compressedSize = size = new ZipEightByteInteger(entry.entry.getSize());
1220             } else {
1221                 // just a placeholder, real data will be in data
1222                 // descriptor or inserted later via SeekableByteChannel
1223                 compressedSize = size = ZipEightByteInteger.ZERO;
1224             }
1225             z64.setSize(size);
1226             z64.setCompressedSize(compressedSize);
1227             entry.entry.setExtra();
1228         }
1229
1230         if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
1231             def.setLevel(level);
1232             hasCompressionLevelChanged = false;
1233         }
1234         writeLocalFileHeader(archiveEntry, phased);
1235     }
1236
1237     /**
1238      * When using random access output, write the local file header and potentially the ZIP64 extra containing the correct CRC and compressed/uncompressed
1239      * sizes.
1240      */

1241     private void rewriteSizesAndCrc(final boolean actuallyNeedsZip64) throws IOException {
1242         final RandomAccessOutputStream randomStream = (RandomAccessOutputStream) outputStream;
1243         long dataStart = entry.localDataStart;
1244         if (randomStream instanceof ZipSplitOutputStream) {
1245             dataStart = ((ZipSplitOutputStream) randomStream).calculateDiskPosition(entry.entry.getDiskNumberStart(), dataStart);
1246         }
1247
1248         long position = dataStart;
1249         randomStream.writeFully(ZipLong.getBytes(entry.entry.getCrc()), position); position += ZipConstants.WORD;
1250         if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) {
1251             randomStream.writeFully(ZipLong.getBytes(entry.entry.getCompressedSize()), position); position += ZipConstants.WORD;
1252             randomStream.writeFully(ZipLong.getBytes(entry.entry.getSize()), position); position += ZipConstants.WORD;
1253         } else {
1254             randomStream.writeFully(ZipLong.ZIP64_MAGIC.getBytes(), position); position += ZipConstants.WORD;
1255             randomStream.writeFully(ZipLong.ZIP64_MAGIC.getBytes(), position); position += ZipConstants.WORD;
1256         }
1257
1258         if (hasZip64Extra(entry.entry)) {
1259             final ByteBuffer name = getName(entry.entry);
1260             final int nameLen = name.limit() - name.position();
1261             // seek to ZIP64 extra, skip header and size information
1262             position = dataStart + 3 * ZipConstants.WORD + 2 * ZipConstants.SHORT + nameLen + 2 * ZipConstants.SHORT;
1263             // inside the ZIP64 extra uncompressed size comes
1264             // first, unlike the LFH, CD or data descriptor
1265             randomStream.writeFully(ZipEightByteInteger.getBytes(entry.entry.getSize()), position); position += ZipConstants.DWORD;
1266             randomStream.writeFully(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize()), position); position += ZipConstants.DWORD;
1267
1268             if (!actuallyNeedsZip64) {
1269                 // do some cleanup:
1270                 // * rewrite version needed to extract
1271                 position = dataStart - 5 * ZipConstants.SHORT;
1272                 randomStream.writeFully(ZipShort.getBytes(versionNeededToExtract(entry.entry.getMethod(), falsefalse)), position);
1273                 position += ZipConstants.SHORT;
1274
1275                 // * remove ZIP64 extra, so it doesn't get written
1276                 // to the central directory
1277                 entry.entry.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID);
1278                 entry.entry.setExtra();
1279
1280                 // * reset hasUsedZip64 if it has been set because
1281                 // of this entry
1282                 if (entry.causedUseOfZip64) {
1283                     hasUsedZip64 = false;
1284                 }
1285             }
1286         }
1287     }
1288
1289     /**
1290      * Sets the file comment.
1291      *
1292      * @param comment the comment
1293      */

1294     public void setComment(final String comment) {
1295         this.comment = comment;
1296     }
1297
1298     /**
1299      * Whether to create Unicode Extra Fields.
1300      * <p>
1301      * Defaults to NEVER.
1302      * </p>
1303      *
1304      * @param b whether to create Unicode Extra Fields.
1305      */

1306     public void setCreateUnicodeExtraFields(final UnicodeExtraFieldPolicy b) {
1307         createUnicodeExtraFields = b;
1308     }
1309
1310     /**
1311      * Provides default values for compression method and last modification time.
1312      */

1313     private void setDefaults(final ZipArchiveEntry entry) {
1314         if (entry.getMethod() == -1) { // not specified
1315             entry.setMethod(method);
1316         }
1317
1318         if (entry.getTime() == -1) { // not specified
1319             entry.setTime(System.currentTimeMillis());
1320         }
1321     }
1322
1323     private void setEncoding(final Charset encoding) {
1324         this.charset = encoding;
1325         this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
1326         if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) {
1327             useUTF8Flag = false;
1328         }
1329     }
1330
1331     /**
1332      * The encoding to use for file names and the file comment.
1333      * <p>
1334      * 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>.
1335      * Defaults to UTF-8.
1336      * </p>
1337      *
1338      * @param encoding the encoding to use for file names, use null for the platform's default encoding
1339      */

1340     public void setEncoding(final String encoding) {
1341         setEncoding(Charsets.toCharset(encoding));
1342     }
1343
1344     /**
1345      * Whether to fall back to UTF and the language encoding flag if the file name cannot be encoded using the specified encoding.
1346      * <p>
1347      * Defaults to false.
1348      * </p>
1349      *
1350      * @param b whether to fall back to UTF and the language encoding flag if the file name cannot be encoded using the specified encoding.
1351      */

1352     public void setFallbackToUTF8(final boolean b) {
1353         fallbackToUTF8 = b;
1354     }
1355
1356     /**
1357      * Sets the compression level for subsequent entries.
1358      * <p>
1359      * Default is Deflater.DEFAULT_COMPRESSION.
1360      * </p>
1361      *
1362      * @param level the compression level.
1363      * @throws IllegalArgumentException if an invalid compression level is specified.
1364      */

1365     public void setLevel(final int level) {
1366         if (level < Deflater.DEFAULT_COMPRESSION || level > Deflater.BEST_COMPRESSION) {
1367             throw new IllegalArgumentException("Invalid compression level: " + level);
1368         }
1369         if (this.level == level) {
1370             return;
1371         }
1372         hasCompressionLevelChanged = true;
1373         this.level = level;
1374     }
1375
1376     /**
1377      * Sets the default compression method for subsequent entries.
1378      * <p>
1379      * Default is DEFLATED.
1380      * </p>
1381      *
1382      * @param method an {@code int} from java.util.zip.ZipEntry
1383      */

1384     public void setMethod(final int method) {
1385         this.method = method;
1386     }
1387
1388     /**
1389      * Whether to set the language encoding flag if the file name encoding is UTF-8.
1390      * <p>
1391      * Defaults to true.
1392      * </p>
1393      *
1394      * @param b whether to set the language encoding flag if the file name encoding is UTF-8
1395      */

1396     public void setUseLanguageEncodingFlag(final boolean b) {
1397         useUTF8Flag = b && ZipEncodingHelper.isUTF8(charset);
1398     }
1399
1400     /**
1401      * Whether Zip64 extensions will be used.
1402      * <p>
1403      * When setting the mode to {@link Zip64Mode#Never Never}, {@link #putArchiveEntry}, {@link #closeArchiveEntry}, {@link #finish} or {@link #close} may throw
1404      * 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
1405      * archive. Any archive created in this mode will be readable by implementations that don't support Zip64.
1406      * </p>
1407      * <p>
1408      * 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
1409      * unreadable by implementations that don't support Zip64 even if all its contents would be.
1410      * </p>
1411      * <p>
1412      * When setting the mode to {@link Zip64Mode#AsNeeded AsNeeded}, Zip64 extensions will transparently be used for those entries that require them. This mode
1413      * 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
1414      * seekable output (i.e. you have used the {@link #ZipArchiveOutputStream(java.io.File) File-arg constructor}) - this mode is not valid when the output
1415      * stream is not seekable and the uncompressed size is unknown when {@link #putArchiveEntry} is called.
1416      * </p>
1417      * <p>
1418      * If no entry inside the resulting archive requires Zip64 extensions then {@link Zip64Mode#Never Never} will create the smallest archive.
1419      * {@link Zip64Mode#AsNeeded AsNeeded} will create a slightly bigger archive if the uncompressed size of any entry has initially been unknown and create an
1420      * archive identical to {@link Zip64Mode#Never Never} otherwise. {@link Zip64Mode#Always Always} will create an archive that is at least 24 bytes per entry
1421      * bigger than the one {@link Zip64Mode#Never Never} would create.
1422      * </p>
1423      * <p>
1424      * Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless {@link #putArchiveEntry} is called with an entry of unknown size and data is written to a
1425      * non-seekable stream - in this case the default is {@link Zip64Mode#Never Never}.
1426      * </p>
1427      *
1428      * @since 1.3
1429      * @param mode Whether Zip64 extensions will be used.
1430      */

1431     public void setUseZip64(final Zip64Mode mode) {
1432         zip64Mode = mode;
1433     }
1434
1435     /**
1436      * Whether to add a Zip64 extended information extra field to the local file header.
1437      * <p>
1438      * Returns true if
1439      * </p>
1440      * <ul>
1441      * <li>mode is Always</li>
1442      * <li>or we already know it is going to be needed</li>
1443      * <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>
1444      * </ul>
1445      */

1446     private boolean shouldAddZip64Extra(final ZipArchiveEntry entry, final Zip64Mode mode) {
1447         return mode == Zip64Mode.Always || mode == Zip64Mode.AlwaysWithCompatibility || entry.getSize() >= ZipConstants.ZIP64_MAGIC
1448                 || entry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC
1449                 || entry.getSize() == ArchiveEntry.SIZE_UNKNOWN && outputStream instanceof RandomAccessOutputStream && mode != Zip64Mode.Never;
1450     }
1451
1452     /**
1453      * 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
1454      * 0xFFFFFFFF) and the ZIP64 format record SHOULD be created.
1455      *
1456      * @return true if zip64 End Of Central Directory is needed
1457      */

1458     private boolean shouldUseZip64EOCD() {
1459         int numberOfThisDisk = 0;
1460         if (isSplitZip) {
1461             numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1462         }
1463         final int numOfEntriesOnThisDisk = numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0);
1464         return numberOfThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT /* number of this disk */
1465                 || cdDiskNumberStart >= ZipConstants.ZIP64_MAGIC_SHORT /* number of the disk with the start of the central directory */
1466                 || numOfEntriesOnThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT /* total number of entries in the central directory on this disk */
1467                 || entries.size() >= ZipConstants.ZIP64_MAGIC_SHORT /* total number of entries in the central directory */
1468                 || cdLength >= ZipConstants.ZIP64_MAGIC /* size of the central directory */
1469                 || cdOffset >= ZipConstants.ZIP64_MAGIC; /*
1470                                                           * offset of start of central directory with respect to the starting disk number
1471                                                           */

1472     }
1473
1474     private boolean usesDataDescriptor(final int zipMethod, final boolean phased) {
1475         return !phased && zipMethod == DEFLATED && !(outputStream instanceof RandomAccessOutputStream);
1476     }
1477
1478     /**
1479      * If the Zip64 mode is set to never, then all the data in End Of Central Directory should not exceed their limits.
1480      *
1481      * @throws Zip64RequiredException if Zip64 is actually needed
1482      */

1483     private void validateIfZip64IsNeededInEOCD() throws Zip64RequiredException {
1484         // exception will only be thrown if the Zip64 mode is never while Zip64 is actually needed
1485         if (zip64Mode != Zip64Mode.Never) {
1486             return;
1487         }
1488
1489         int numberOfThisDisk = 0;
1490         if (isSplitZip) {
1491             numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1492         }
1493         if (numberOfThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT) {
1494             throw new Zip64RequiredException(Zip64RequiredException.DISK_NUMBER_TOO_BIG_MESSAGE);
1495         }
1496
1497         if (cdDiskNumberStart >= ZipConstants.ZIP64_MAGIC_SHORT) {
1498             throw new Zip64RequiredException(Zip64RequiredException.CENTRAL_DIRECTORY_DISK_NUMBER_TOO_BIG_MESSAGE);
1499         }
1500
1501         final int numOfEntriesOnThisDisk = numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0);
1502         if (numOfEntriesOnThisDisk >= ZipConstants.ZIP64_MAGIC_SHORT) {
1503             throw new Zip64RequiredException(Zip64RequiredException.TOO_MANY_ENTRIES_ON_DISK_MESSAGE);
1504         }
1505
1506         // number of entries
1507         if (entries.size() >= ZipConstants.ZIP64_MAGIC_SHORT) {
1508             throw new Zip64RequiredException(Zip64RequiredException.TOO_MANY_ENTRIES_MESSAGE);
1509         }
1510
1511         if (cdLength >= ZipConstants.ZIP64_MAGIC) {
1512             throw new Zip64RequiredException(Zip64RequiredException.CENTRAL_DIRECTORY_SIZE_TOO_BIG_MESSAGE);
1513         }
1514
1515         if (cdOffset >= ZipConstants.ZIP64_MAGIC) {
1516             throw new Zip64RequiredException(Zip64RequiredException.ARCHIVE_TOO_BIG_MESSAGE);
1517         }
1518     }
1519
1520     /**
1521      * 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
1522      * Zip64 extra but the mode has been set to Never.
1523      */

1524     private void validateSizeInformation(final Zip64Mode effectiveMode) throws ZipException {
1525         // Size/CRC not required if SeekableByteChannel is used
1526         if (entry.entry.getMethod() == STORED && !(outputStream instanceof RandomAccessOutputStream)) {
1527             if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) {
1528                 throw new ZipException("Uncompressed size is required for" + " STORED method when not writing to a" + " file");
1529             }
1530             if (entry.entry.getCrc() == ZipArchiveEntry.CRC_UNKNOWN) {
1531                 throw new ZipException("CRC checksum is required for STORED" + " method when not writing to a file");
1532             }
1533             entry.entry.setCompressedSize(entry.entry.getSize());
1534         }
1535
1536         if ((entry.entry.getSize() >= ZipConstants.ZIP64_MAGIC || entry.entry.getCompressedSize() >= ZipConstants.ZIP64_MAGIC)
1537                 && effectiveMode == Zip64Mode.Never) {
1538             throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry));
1539         }
1540     }
1541
1542     private int versionNeededToExtract(final int zipMethod, final boolean zip64, final boolean usedDataDescriptor) {
1543         if (zip64) {
1544             return ZipConstants.ZIP64_MIN_VERSION;
1545         }
1546         if (usedDataDescriptor) {
1547             return ZipConstants.DATA_DESCRIPTOR_MIN_VERSION;
1548         }
1549         return versionNeededToExtractMethod(zipMethod);
1550     }
1551
1552     private int versionNeededToExtractMethod(final int zipMethod) {
1553         return zipMethod == DEFLATED ? ZipConstants.DEFLATE_MIN_VERSION : ZipConstants.INITIAL_VERSION;
1554     }
1555
1556     /**
1557      * Writes bytes to ZIP entry.
1558      *
1559      * @param b      the byte array to write
1560      * @param offset the start position to write from
1561      * @param length the number of bytes to write
1562      * @throws IOException on error
1563      */

1564     @Override
1565     public void write(final byte[] b, final int offset, final int length) throws IOException {
1566         if (entry == null) {
1567             throw new IllegalStateException("No current entry");
1568         }
1569         ZipUtil.checkRequestedFeatures(entry.entry);
1570         final long writtenThisTime = streamCompressor.write(b, offset, length, entry.entry.getMethod());
1571         count(writtenThisTime);
1572     }
1573
1574     /**
1575      * Writes the &quot;End of central dir record&quot;.
1576      *
1577      * @throws IOException            on error
1578      * @throws Zip64RequiredException if the archive's size exceeds 4 GByte or there are more than 65535 entries inside the archive and
1579      *                                {@link #setUseZip64(Zip64Mode)} is {@link Zip64Mode#Never}.
1580      */

1581     protected void writeCentralDirectoryEnd() throws IOException {
1582         if (!hasUsedZip64 && isSplitZip) {
1583             ((ZipSplitOutputStream) this.outputStream).prepareToWriteUnsplittableContent(eocdLength);
1584         }
1585
1586         validateIfZip64IsNeededInEOCD();
1587
1588         writeCounted(EOCD_SIG);
1589
1590         // number of this disk
1591         int numberOfThisDisk = 0;
1592         if (isSplitZip) {
1593             numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1594         }
1595         writeCounted(ZipShort.getBytes(numberOfThisDisk));
1596
1597         // disk number of the start of central directory
1598         writeCounted(ZipShort.getBytes((int) cdDiskNumberStart));
1599
1600         // number of entries
1601         final int numberOfEntries = entries.size();
1602
1603         // total number of entries in the central directory on this disk
1604         final int numOfEntriesOnThisDisk = isSplitZip ? numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0) : numberOfEntries;
1605         final byte[] numOfEntriesOnThisDiskData = ZipShort.getBytes(Math.min(numOfEntriesOnThisDisk, ZipConstants.ZIP64_MAGIC_SHORT));
1606         writeCounted(numOfEntriesOnThisDiskData);
1607
1608         // number of entries
1609         final byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, ZipConstants.ZIP64_MAGIC_SHORT));
1610         writeCounted(num);
1611
1612         // length and location of CD
1613         writeCounted(ZipLong.getBytes(Math.min(cdLength, ZipConstants.ZIP64_MAGIC)));
1614         writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZipConstants.ZIP64_MAGIC)));
1615
1616         // ZIP file comment
1617         final ByteBuffer data = this.zipEncoding.encode(comment);
1618         final int dataLen = data.limit() - data.position();
1619         writeCounted(ZipShort.getBytes(dataLen));
1620         streamCompressor.writeCounted(data.array(), data.arrayOffset(), dataLen);
1621     }
1622
1623     private void writeCentralDirectoryInChunks() throws IOException {
1624         final int NUM_PER_WRITE = 1000;
1625         final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(70 * NUM_PER_WRITE);
1626         int count = 0;
1627         for (final ZipArchiveEntry ze : entries) {
1628             byteArrayOutputStream.write(createCentralFileHeader(ze));
1629             if (++count > NUM_PER_WRITE) {
1630                 writeCounted(byteArrayOutputStream.toByteArray());
1631                 byteArrayOutputStream.reset();
1632                 count = 0;
1633             }
1634         }
1635         writeCounted(byteArrayOutputStream.toByteArray());
1636     }
1637
1638     /**
1639      * Writes the central file header entry.
1640      *
1641      * @param ze the entry to write
1642      * @throws IOException            on error
1643      * @throws Zip64RequiredException if the archive's size exceeds 4 GByte and {@link #setUseZip64(Zip64Mode)} is {@link Zip64Mode#Never}.
1644      */

1645     protected void writeCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
1646         final byte[] centralFileHeader = createCentralFileHeader(ze);
1647         writeCounted(centralFileHeader);
1648     }
1649
1650     /**
1651      * Write bytes to output or random access file.
1652      *
1653      * @param data the byte array to write
1654      * @throws IOException on error
1655      */

1656     private void writeCounted(final byte[] data) throws IOException {
1657         streamCompressor.writeCounted(data);
1658     }
1659
1660     /**
1661      * Writes the data descriptor entry.
1662      *
1663      * @param ze the entry to write
1664      * @throws IOException on error
1665      */

1666     protected void writeDataDescriptor(final ZipArchiveEntry ze) throws IOException {
1667         if (!usesDataDescriptor(ze.getMethod(), false)) {
1668             return;
1669         }
1670         writeCounted(DD_SIG);
1671         writeCounted(ZipLong.getBytes(ze.getCrc()));
1672         if (!hasZip64Extra(ze)) {
1673             writeCounted(ZipLong.getBytes(ze.getCompressedSize()));
1674             writeCounted(ZipLong.getBytes(ze.getSize()));
1675         } else {
1676             writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize()));
1677             writeCounted(ZipEightByteInteger.getBytes(ze.getSize()));
1678         }
1679     }
1680
1681     /**
1682      * Writes the local file header entry
1683      *
1684      * @param ze the entry to write
1685      * @throws IOException on error
1686      */

1687     protected void writeLocalFileHeader(final ZipArchiveEntry ze) throws IOException {
1688         writeLocalFileHeader(ze, false);
1689     }
1690
1691     private void writeLocalFileHeader(final ZipArchiveEntry ze, final boolean phased) throws IOException {
1692         final boolean encodable = zipEncoding.canEncode(ze.getName());
1693         final ByteBuffer name = getName(ze);
1694
1695         if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
1696             addUnicodeExtraFields(ze, encodable, name);
1697         }
1698
1699         long localHeaderStart = streamCompressor.getTotalBytesWritten();
1700         if (isSplitZip) {
1701             // when creating a split zip, the offset should be
1702             // the offset to the corresponding segment disk
1703             final ZipSplitOutputStream splitOutputStream = (ZipSplitOutputStream) this.outputStream;
1704             ze.setDiskNumberStart(splitOutputStream.getCurrentSplitSegmentIndex());
1705             localHeaderStart = splitOutputStream.getCurrentSplitSegmentBytesWritten();
1706         }
1707
1708         final byte[] localHeader = createLocalFileHeader(ze, name, encodable, phased, localHeaderStart);
1709         metaData.put(ze, new EntryMetaData(localHeaderStart, usesDataDescriptor(ze.getMethod(), phased)));
1710         entry.localDataStart = localHeaderStart + LFH_CRC_OFFSET; // At crc offset
1711         writeCounted(localHeader);
1712         entry.dataStart = streamCompressor.getTotalBytesWritten();
1713     }
1714
1715     /**
1716      * Write bytes to output or random access file.
1717      *
1718      * @param data the byte array to write
1719      * @throws IOException on error
1720      */

1721     protected final void writeOut(final byte[] data) throws IOException {
1722         streamCompressor.writeOut(data, 0, data.length);
1723     }
1724
1725     /**
1726      * Write bytes to output or random access file.
1727      *
1728      * @param data   the byte array to write
1729      * @param offset the start position to write from
1730      * @param length the number of bytes to write
1731      * @throws IOException on error
1732      */

1733     protected final void writeOut(final byte[] data, final int offset, final int length) throws IOException {
1734         streamCompressor.writeOut(data, offset, length);
1735     }
1736
1737     /**
1738      * Write preamble data. For most of the time, this is used to make self-extracting zips.
1739      *
1740      * @param preamble data to write
1741      * @throws IOException if an entry already exists
1742      * @since 1.21
1743      */

1744     public void writePreamble(final byte[] preamble) throws IOException {
1745         writePreamble(preamble, 0, preamble.length);
1746     }
1747
1748     /**
1749      * Write preamble data. For most of the time, this is used to make self-extracting zips.
1750      *
1751      * @param preamble data to write
1752      * @param offset   the start offset in the data
1753      * @param length   the number of bytes to write
1754      * @throws IOException if an entry already exists
1755      * @since 1.21
1756      */

1757     public void writePreamble(final byte[] preamble, final int offset, final int length) throws IOException {
1758         if (entry != null) {
1759             throw new IllegalStateException("Preamble must be written before creating an entry");
1760         }
1761         this.streamCompressor.writeCounted(preamble, offset, length);
1762     }
1763
1764     /**
1765      * Writes the &quot;ZIP64 End of central dir record&quot; and &quot;ZIP64 End of central dir locator&quot;.
1766      *
1767      * @throws IOException on error
1768      * @since 1.3
1769      */

1770     protected void writeZip64CentralDirectory() throws IOException {
1771         if (zip64Mode == Zip64Mode.Never) {
1772             return;
1773         }
1774
1775         if (!hasUsedZip64 && shouldUseZip64EOCD()) {
1776             // actually "will use"
1777             hasUsedZip64 = true;
1778         }
1779
1780         if (!hasUsedZip64) {
1781             return;
1782         }
1783
1784         long offset = streamCompressor.getTotalBytesWritten();
1785         long diskNumberStart = 0L;
1786         if (isSplitZip) {
1787             // when creating a split zip, the offset of should be
1788             // the offset to the corresponding segment disk
1789             final ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream) this.outputStream;
1790             offset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten();
1791             diskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex();
1792         }
1793
1794         writeOut(ZIP64_EOCD_SIG);
1795         // size of zip64 end of central directory, we don't have any variable length
1796         // as we don't support the extensible data sector, yet
1797         writeOut(ZipEightByteInteger.getBytes(ZipConstants.SHORT /* version made by */
1798                 + ZipConstants.SHORT /* version needed to extract */
1799                 + ZipConstants.WORD /* disk number */
1800                 + ZipConstants.WORD /* disk with central directory */
1801                 + ZipConstants.DWORD /* number of entries in CD on this disk */
1802                 + ZipConstants.DWORD /* total number of entries */
1803                 + ZipConstants.DWORD /* size of CD */
1804                 + (long) ZipConstants.DWORD /* offset of CD */
1805         ));
1806
1807         // version made by and version needed to extract
1808         writeOut(ZipShort.getBytes(ZipConstants.ZIP64_MIN_VERSION));
1809         writeOut(ZipShort.getBytes(ZipConstants.ZIP64_MIN_VERSION));
1810
1811         // number of this disk
1812         int numberOfThisDisk = 0;
1813         if (isSplitZip) {
1814             numberOfThisDisk = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex();
1815         }
1816         writeOut(ZipLong.getBytes(numberOfThisDisk));
1817
1818         // disk number of the start of central directory
1819         writeOut(ZipLong.getBytes(cdDiskNumberStart));
1820
1821         // total number of entries in the central directory on this disk
1822         final int numOfEntriesOnThisDisk = isSplitZip ? numberOfCDInDiskData.getOrDefault(numberOfThisDisk, 0) : entries.size();
1823         final byte[] numOfEntriesOnThisDiskData = ZipEightByteInteger.getBytes(numOfEntriesOnThisDisk);
1824         writeOut(numOfEntriesOnThisDiskData);
1825
1826         // number of entries
1827         final byte[] num = ZipEightByteInteger.getBytes(entries.size());
1828         writeOut(num);
1829
1830         // length and location of CD
1831         writeOut(ZipEightByteInteger.getBytes(cdLength));
1832         writeOut(ZipEightByteInteger.getBytes(cdOffset));
1833
1834         // no "zip64 extensible data sector" for now
1835
1836         if (isSplitZip) {
1837             // based on the ZIP specification, the End Of Central Directory record and
1838             // the Zip64 End Of Central Directory locator record must be on the same segment
1839             final int zip64EOCDLOCLength = ZipConstants.WORD /* length of ZIP64_EOCD_LOC_SIG */
1840                     + ZipConstants.WORD /* disk number of ZIP64_EOCD_SIG */
1841                     + ZipConstants.DWORD /* offset of ZIP64_EOCD_SIG */
1842                     + ZipConstants.WORD /* total number of disks */;
1843
1844             final long unsplittableContentSize = zip64EOCDLOCLength + eocdLength;
1845             ((ZipSplitOutputStream) this.outputStream).prepareToWriteUnsplittableContent(unsplittableContentSize);
1846         }
1847
1848         // and now the "ZIP64 end of central directory locator"
1849         writeOut(ZIP64_EOCD_LOC_SIG);
1850
1851         // disk number holding the ZIP64 EOCD record
1852         writeOut(ZipLong.getBytes(diskNumberStart));
1853         // relative offset of ZIP64 EOCD record
1854         writeOut(ZipEightByteInteger.getBytes(offset));
1855         // total number of disks
1856         if (isSplitZip) {
1857             // the Zip64 End Of Central Directory Locator and the End Of Central Directory must be
1858             // in the same split disk, it means they must be located in the last disk
1859             final int totalNumberOfDisks = ((ZipSplitOutputStream) this.outputStream).getCurrentSplitSegmentIndex() + 1;
1860             writeOut(ZipLong.getBytes(totalNumberOfDisks));
1861         } else {
1862             writeOut(ONE);
1863         }
1864     }
1865 }
1866