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