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