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