    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 byte[] localHeader = createLocalFileHeader(ze, name, encodable, phased);
    1030        final long localHeaderStart = streamCompressor.getTotalBytesWritten();
    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)  {
    1040        final byte[] extra = ze.getLocalFileDataExtra();
    1041        final int nameLen = name.limit() - name.position();
    1042        final int len= LFH_FILENAME_OFFSET + nameLen + extra.length;
    1043        final byte[] buf = new byte[len];
    1044
    1045        System.arraycopy(LFH_SIG,  0, buf, LFH_SIG_OFFSET, WORD);
    1046
    1047        //store method in local variable to prevent multiple method calls
    1048        final int zipMethod = ze.getMethod();
    1049
    1050        if (phased &&  !isZip64Required(entry.entry, zip64Mode)){
    1051            putShort(INITIAL_VERSION, buf, LFH_VERSION_NEEDED_OFFSET);
    1052        } else {
    1053            putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze)), buf, LFH_VERSION_NEEDED_OFFSET);
    1054        }
    1055
    1056        final GeneralPurposeBit generalPurposeBit = getGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8);
    1057        generalPurposeBit.encode(buf, LFH_GPB_OFFSET);
    1058
    1059        // compression method
    1060        putShort(zipMethod, buf, LFH_METHOD_OFFSET);
    1061
    1062        ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, LFH_TIME_OFFSET);
    1063
    1064        // CRC
    1065        if (phased){
    1066            putLong(ze.getCrc(), buf, LFH_CRC_OFFSET);
    1067        } else if (zipMethod == DEFLATED || channel != null) {
    1068            System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, WORD);
    1069        } else {
    1070            putLong(ze.getCrc(), buf, LFH_CRC_OFFSET);
    1071        }
    1072
    1073        // compressed length
    1074        // uncompressed length
    1075        if (hasZip64Extra(entry.entry)){
    1076            // point to ZIP64 extended information extra field for
    1077            // sizes, may get rewritten once sizes are known if
    1078            // stream is seekable
    1079            ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET);
    1080            ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET);
    1081        } else if (phased) {
    1082            putLong(ze.getCompressedSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
    1083            putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
    1084        } else if (zipMethod == DEFLATED || channel != null) {
    1085            System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, WORD);
    1086            System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, WORD);
    1087        } else { // Stored
    1088            putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET);
    1089            putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET);
    1090        }
    1091        // file name length
    1092        putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET);
    1093
    1094        // extra field length
    1095        putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET);
    1096
    1097        // file name
    1098        System.arraycopy( name.array(), name.arrayOffset(), buf, LFH_FILENAME_OFFSET, nameLen);
    1099
    1100        System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length);
    1101        return buf;
    1102    }
    1103
    1104
    1105    /**
    1106     * Adds UnicodeExtra fields for name and file comment if mode is
    1107     * ALWAYS or the data cannot be encoded using the configured
    1108     * encoding.
    1109     */
    1110    private void addUnicodeExtraFields(final ZipArchiveEntry ze, final boolean encodable,
    1111                                       final ByteBuffer name)
    1112        throws IOException {
    1113        if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
    1114            || !encodable) {
    1115            ze.addExtraField(new UnicodePathExtraField(ze.getName(),
    1116                                                       name.array(),
    1117                                                       name.arrayOffset(),
    1118                                                       name.limit()
    1119                                                       - name.position()));
    1120        }
    1121
    1122        final String comm = ze.getComment();
    1123        if (comm != null && !"".equals(comm)) {
    1124
    1125            final boolean commentEncodable = zipEncoding.canEncode(comm);
    1126
    1127            if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
    1128                || !commentEncodable) {
    1129                final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
    1130                ze.addExtraField(new UnicodeCommentExtraField(comm,
    1131                                                              commentB.array(),
    1132                                                              commentB.arrayOffset(),
    1133                                                              commentB.limit()
    1134                                                              - commentB.position())
    1135                                 );
    1136            }
    1137        }
    1138    }
    1139
    1140    /**
    1141     * Writes the data descriptor entry.
    1142     * @param ze the entry to write
    1143     * @throws IOException on error
    1144     */
    1145    protected void writeDataDescriptor(final ZipArchiveEntry ze) throws IOException {
    1146        if (ze.getMethod() != DEFLATED || channel != null) {
    1147            return;
    1148        }
    1149        writeCounted(DD_SIG);
    1150        writeCounted(ZipLong.getBytes(ze.getCrc()));
    1151        if (!hasZip64Extra(ze)) {
    1152            writeCounted(ZipLong.getBytes(ze.getCompressedSize()));
    1153            writeCounted(ZipLong.getBytes(ze.getSize()));
    1154        } else {
    1155            writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize()));
    1156            writeCounted(ZipEightByteInteger.getBytes(ze.getSize()));
    1157        }
    1158    }
    1159
    1160    /**
    1161     * Writes the central file header entry.
    1162     * @param ze the entry to write
    1163     * @throws IOException on error
    1164     * @throws Zip64RequiredException if the archive's size exceeds 4
    1165     * GByte and {@link Zip64Mode #setUseZip64} is {@link
    1166     * Zip64Mode#Never}.
    1167     */
    1168    protected void writeCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
    1169        final byte[] centralFileHeader = createCentralFileHeader(ze);
    1170        writeCounted(centralFileHeader);
    1171    }
    1172
    1173    private byte[] createCentralFileHeader(final ZipArchiveEntry ze) throws IOException {
    1174
    1175        final long lfhOffset = offsets.get(ze);
    1176        final boolean needsZip64Extra = hasZip64Extra(ze)
    1177                || ze.getCompressedSize() >= ZIP64_MAGIC
    1178                || ze.getSize() >= ZIP64_MAGIC
    1179                || lfhOffset >= ZIP64_MAGIC
    1180                || zip64Mode == Zip64Mode.Always;
    1181
    1182        if (needsZip64Extra && zip64Mode == Zip64Mode.Never) {
    1183            // must be the offset that is too big, otherwise an
    1184            // exception would have been throw in putArchiveEntry or
    1185            // closeArchiveEntry
    1186            throw new Zip64RequiredException(Zip64RequiredException
    1187                    .ARCHIVE_TOO_BIG_MESSAGE);
    1188        }
    1189
    1190
    1191        handleZip64Extra(ze, lfhOffset, needsZip64Extra);
    1192
    1193        return createCentralFileHeader(ze, getName(ze), lfhOffset, needsZip64Extra);
    1194    }
    1195
    1196    /**
    1197     * Writes the central file header entry.
    1198     * @param ze the entry to write
    1199     * @param name The encoded name
    1200     * @param lfhOffset Local file header offset for this file
    1201     * @throws IOException on error
    1202     */
    1203    private byte[] createCentralFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final long lfhOffset,
    1204                                           final boolean needsZip64Extra) throws IOException {
    1205        final byte[] extra = ze.getCentralDirectoryExtra();
    1206
    1207        // file comment length
    1208        String comm = ze.getComment();
    1209        if (comm == null) {
    1210            comm = "";
    1211        }
    1212
    1213        final ByteBuffer commentB = getEntryEncoding(ze).encode(comm);
    1214        final int nameLen = name.limit() - name.position();
    1215        final int commentLen = commentB.limit() - commentB.position();
    1216        final int len= CFH_FILENAME_OFFSET + nameLen + extra.length + commentLen;
    1217        final byte[] buf = new byte[len];
    1218
    1219        System.arraycopy(CFH_SIG,  0, buf, CFH_SIG_OFFSET, WORD);
    1220
    1221        // version made by
    1222        // CheckStyle:MagicNumber OFF
    1223        putShort((ze.getPlatform() << 8) | (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION : ZIP64_MIN_VERSION),
    1224                buf, CFH_VERSION_MADE_BY_OFFSET);
    1225
    1226        final int zipMethod = ze.getMethod();
    1227        final boolean encodable = zipEncoding.canEncode(ze.getName());
    1228        putShort(versionNeededToExtract(zipMethod, needsZip64Extra), buf, CFH_VERSION_NEEDED_OFFSET);
    1229        getGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8).encode(buf, CFH_GPB_OFFSET);
    1230
    1231        // compression method
    1232        putShort(zipMethod, buf, CFH_METHOD_OFFSET);
    1233
    1234
    1235        // last mod. time and date
    1236        ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, CFH_TIME_OFFSET);
    1237
    1238        // CRC
    1239        // compressed length
    1240        // uncompressed length
    1241        putLong(ze.getCrc(), buf, CFH_CRC_OFFSET);
    1242        if (ze.getCompressedSize() >= ZIP64_MAGIC
    1243                || ze.getSize() >= ZIP64_MAGIC
    1244                || zip64Mode == Zip64Mode.Always) {
    1245            ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET);
    1246            ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET);
    1247        } else {
    1248            putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET);
    1249            putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET);
    1250        }
    1251
    1252        putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET);
    1253
    1254        // extra field length
    1255        putShort(extra.length, buf, CFH_EXTRA_LENGTH_OFFSET);
    1256
    1257        putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET);
    1258
    1259        // disk number start
    1260        System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, SHORT);
    1261
    1262        // internal file attributes
    1263        putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET);
    1264
    1265        // external file attributes
    1266        putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET);
    1267
    1268        // relative offset of LFH
    1269        if (lfhOffset >= ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) {
    1270            putLong(ZIP64_MAGIC, buf, CFH_LFH_OFFSET);
    1271        } else {
    1272            putLong(Math.min(lfhOffset, ZIP64_MAGIC), buf, CFH_LFH_OFFSET);
    1273        }
    1274
    1275        // file name
    1276        System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen);
    1277
    1278        final int extraStart = CFH_FILENAME_OFFSET + nameLen;
    1279        System.arraycopy(extra, 0, buf, extraStart, extra.length);
    1280
    1281        final int commentStart = extraStart + extra.length;
    1282
    1283        // file comment
    1284        System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen);
    1285        return buf;
    1286    }
    1287
    1288    /**
    1289     * If the entry needs Zip64 extra information inside the central
    1290     * directory then configure its data.
    1291     */
    1292    private void handleZip64Extra(final ZipArchiveEntry ze, final long lfhOffset,
    1293                                  final boolean needsZip64Extra) {
    1294        if (needsZip64Extra) {
    1295            final Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze);
    1296            if (ze.getCompressedSize() >= ZIP64_MAGIC
    1297                || ze.getSize() >= ZIP64_MAGIC
    1298                || zip64Mode == Zip64Mode.Always) {
    1299                z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize()));
    1300                z64.setSize(new ZipEightByteInteger(ze.getSize()));
    1301            } else {
    1302                // reset value that may have been set for LFH
    1303                z64.setCompressedSize(null);
    1304                z64.setSize(null);
    1305            }
    1306            if (lfhOffset >= ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) {
    1307                z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset));
    1308            }
    1309            ze.setExtra();
    1310        }
    1311    }
    1312
    1313    /**
    1314     * Writes the &quot;End of central dir record&quot;.
    1315     * @throws IOException on error
    1316     * @throws Zip64RequiredException if the archive's size exceeds 4
    1317     * GByte or there are more than 65535 entries inside the archive
    1318     * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}.
    1319     */
    1320    protected void writeCentralDirectoryEnd() throws IOException {
    1321        writeCounted(EOCD_SIG);
    1322
    1323        // disk numbers
    1324        writeCounted(ZERO);
    1325        writeCounted(ZERO);
    1326
    1327        // number of entries
    1328        final int numberOfEntries = entries.size();
    1329        if (numberOfEntries > ZIP64_MAGIC_SHORT
    1330            && zip64Mode == Zip64Mode.Never) {
    1331            throw new Zip64RequiredException(Zip64RequiredException
    1332                                             .TOO_MANY_ENTRIES_MESSAGE);
    1333        }
    1334        if (cdOffset > ZIP64_MAGIC && zip64Mode == Zip64Mode.Never) {
    1335            throw new Zip64RequiredException(Zip64RequiredException
    1336                                             .ARCHIVE_TOO_BIG_MESSAGE);
    1337        }
    1338
    1339        final byte[] num = ZipShort.getBytes(Math.min(numberOfEntries,
    1340                                                ZIP64_MAGIC_SHORT));
    1341        writeCounted(num);
    1342        writeCounted(num);
    1343
    1344        // length and location of CD
    1345        writeCounted(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC)));
    1346        writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC)));
    1347
    1348        // ZIP file comment
    1349        final ByteBuffer data = this.zipEncoding.encode(comment);
    1350        final int dataLen = data.limit() - data.position();
    1351        writeCounted(ZipShort.getBytes(dataLen));
    1352        streamCompressor.writeCounted(data.array(), data.arrayOffset(), dataLen);
    1353    }
    1354
    1355    /**
    1356     * Writes the &quot;ZIP64 End of central dir record&quot; and
    1357     * &quot;ZIP64 End of central dir locator&quot;.
    1358     * @throws IOException on error
    1359     * @since 1.3
    1360     */
    1361    protected void writeZip64CentralDirectory() throws IOException {
    1362        if (zip64Mode == Zip64Mode.Never) {
    1363            return;
    1364        }
    1365
    1366        if (!hasUsedZip64
    1367            && (cdOffset >= ZIP64_MAGIC || cdLength >= ZIP64_MAGIC
    1368                || entries.size() >= ZIP64_MAGIC_SHORT)) {
    1369            // actually "will use"
    1370            hasUsedZip64 = true;
    1371        }
    1372
    1373        if (!hasUsedZip64) {
    1374            return;
    1375        }
    1376
    1377        final long offset = streamCompressor.getTotalBytesWritten();
    1378
    1379        writeOut(ZIP64_EOCD_SIG);
    1380        // size, we don't have any variable length as we don't support
    1381        // the extensible data sector, yet
    1382        writeOut(ZipEightByteInteger
    1383                 .getBytes(SHORT   /* version made by */
    1384                           + SHORT /* version needed to extract */
    1385                           + WORD  /* disk number */
    1386                           + WORD  /* disk with central directory */
    1387                           + DWORD /* number of entries in CD on this disk */
    1388                           + DWORD /* total number of entries */
    1389                           + DWORD /* size of CD */
    1390                           + (long) DWORD /* offset of CD */
    1391                           ));
    1392
    1393        // version made by and version needed to extract
    1394        writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION));
    1395        writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION));
    1396
    1397        // disk numbers - four bytes this time
    1398        writeOut(LZERO);
    1399        writeOut(LZERO);
    1400
    1401        // number of entries
    1402        final byte[] num = ZipEightByteInteger.getBytes(entries.size());
    1403        writeOut(num);
    1404        writeOut(num);
    1405
    1406        // length and location of CD
    1407        writeOut(ZipEightByteInteger.getBytes(cdLength));
    1408        writeOut(ZipEightByteInteger.getBytes(cdOffset));
    1409
    1410        // no "zip64 extensible data sector" for now
    1411
    1412        // and now the "ZIP64 end of central directory locator"
    1413        writeOut(ZIP64_EOCD_LOC_SIG);
    1414
    1415        // disk number holding the ZIP64 EOCD record
    1416        writeOut(LZERO);
    1417        // relative offset of ZIP64 EOCD record
    1418        writeOut(ZipEightByteInteger.getBytes(offset));
    1419        // total number of disks
    1420        writeOut(ONE);
    1421    }
    1422
    1423    /**
    1424     * Write bytes to output or random access file.
    1425     * @param data the byte array to write
    1426     * @throws IOException on error
    1427     */
    1428    protected final void writeOut(final byte[] data) throws IOException {
    1429        streamCompressor.writeOut(data, 0, data.length);
    1430    }
    1431
    1432
    1433    /**
    1434     * Write bytes to output or random access file.
    1435     * @param data the byte array to write
    1436     * @param offset the start position to write from
    1437     * @param length the number of bytes to write
    1438     * @throws IOException on error
    1439     */
    1440    protected final void writeOut(final byte[] data, final int offset, final int length)
    1441            throws IOException {
    1442        streamCompressor.writeOut(data, offset, length);
    1443    }
    1444
    1445
    1446    private GeneralPurposeBit getGeneralPurposeBits(final int zipMethod, final boolean utfFallback) {
    1447        final GeneralPurposeBit b = new GeneralPurposeBit();
    1448        b.useUTF8ForNames(useUTF8Flag || utfFallback);
    1449        if (isDeflatedToOutputStream(zipMethod)) {
    1450            b.useDataDescriptor(true);
    1451        }
    1452        return b;
    1453    }
    1454
    1455    private int versionNeededToExtract(final int zipMethod, final boolean zip64) {
    1456        if (zip64) {
    1457            return ZIP64_MIN_VERSION;
    1458        }
    1459        // requires version 2 as we are going to store length info
    1460        // in the data descriptor
    1461        return (isDeflatedToOutputStream(zipMethod)) ?
    1462                DATA_DESCRIPTOR_MIN_VERSION :
    1463                INITIAL_VERSION;
    1464    }
    1465
    1466    private boolean isDeflatedToOutputStream(final int zipMethod) {
    1467        return zipMethod == DEFLATED && channel == null;
    1468    }
    1469
    1470
    1471    /**
    1472     * Creates a new zip entry taking some information from the given
    1473     * file and using the provided name.
    1474     *
    1475     * <p>The name will be adjusted to end with a forward slash "/" if
    1476     * the file is a directory.  If the file is not a directory a
    1477     * potential trailing forward slash will be stripped from the
    1478     * entry name.</p>
    1479     *
    1480     * <p>Must not be used if the stream has already been closed.</p>
    1481     */
    1482    @Override
    1483    public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName)
    1484        throws IOException {
    1485        if (finished) {
    1486            throw new IOException("Stream has already been finished");
    1487        }
    1488        return new ZipArchiveEntry(inputFile, entryName);
    1489    }
    1490
    1491    /**
    1492     * Get the existing ZIP64 extended information extra field or
    1493     * create a new one and add it to the entry.
    1494     *
    1495     * @since 1.3
    1496     */
    1497    private Zip64ExtendedInformationExtraField
    1498        getZip64Extra(final ZipArchiveEntry ze) {
    1499        if (entry != null) {
    1500            entry.causedUseOfZip64 = !hasUsedZip64;
    1501        }
    1502        hasUsedZip64 = true;
    1503        Zip64ExtendedInformationExtraField z64 =
    1504            (Zip64ExtendedInformationExtraField)
    1505            ze.getExtraField(Zip64ExtendedInformationExtraField
    1506                             .HEADER_ID);
    1507        if (z64 == null) {
    1508            /*
    1509              System.err.println("Adding z64 for " + ze.getName()
    1510              + ", method: " + ze.getMethod()
    1511              + " (" + (ze.getMethod() == STORED) + ")"
    1512              + ", channel: " + (channel != null));
    1513            */
    1514            z64 = new Zip64ExtendedInformationExtraField();
    1515        }
    1516
    1517        // even if the field is there already, make sure it is the first one
    1518        ze.addAsFirstExtraField(z64);
    1519
    1520        return z64;
    1521    }
    1522
    1523    /**
    1524     * Is there a ZIP64 extended information extra field for the
    1525     * entry?
    1526     *
    1527     * @since 1.3
    1528     */
    1529    private boolean hasZip64Extra(final ZipArchiveEntry ze) {
    1530        return ze.getExtraField(Zip64ExtendedInformationExtraField
    1531                                .HEADER_ID)
    1532            != null;
    1533    }
    1534
    1535    /**
    1536     * If the mode is AsNeeded and the entry is a compressed entry of
    1537     * unknown size that gets written to a non-seekable stream the
    1538     * change the default to Never.
    1539     *
    1540     * @since 1.3
    1541     */
    1542    private Zip64Mode getEffectiveZip64Mode(final ZipArchiveEntry ze) {
    1543        if (zip64Mode != Zip64Mode.AsNeeded
    1544            || channel != null
    1545            || ze.getMethod() != DEFLATED
    1546            || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) {
    1547            return zip64Mode;
    1548        }
    1549        return Zip64Mode.Never;
    1550    }
    1551
    1552    private ZipEncoding getEntryEncoding(final ZipArchiveEntry ze) {
    1553        final boolean encodable = zipEncoding.canEncode(ze.getName());
    1554        return !encodable && fallbackToUTF8
    1555            ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding;
    1556    }
    1557
    1558    private ByteBuffer getName(final ZipArchiveEntry ze) throws IOException {
    1559        return getEntryEncoding(ze).encode(ze.getName());
    1560    }
    1561
    1562    /**
    1563     * Closes the underlying stream/file without finishing the
    1564     * archive, the result will likely be a corrupt archive.
    1565     *
    1566     * <p>This method only exists to support tests that generate
    1567     * corrupt archives so they can clean up any temporary files.</p>
    1568     */
    1569    void destroy() throws IOException {
    1570        if (channel != null) {
    1571            channel.close();
    1572        }
    1573        if (out != null) {
    1574            out.close();
    1575        }
    1576    }
    1577
    1578    /**
    1579     * enum that represents the possible policies for creating Unicode
    1580     * extra fields.
    1581     */
    1582    public static final class UnicodeExtraFieldPolicy {
    1583        /**
    1584         * Always create Unicode extra fields.
    1585         */
    1586        public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
    1587        /**
    1588         * Never create Unicode extra fields.
    1589         */
    1590        public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
    1591        /**
    1592         * Create Unicode extra fields for filenames that cannot be
    1593         * encoded using the specified encoding.
    1594         */
    1595        public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
    1596            new UnicodeExtraFieldPolicy("not encodeable");
    1597
    1598        private final String name;
    1599        private UnicodeExtraFieldPolicy(final String n) {
    1600            name = n;
    1601        }
    1602        @Override
    1603        public String toString() {
    1604            return name;
    1605        }
    1606    }
    1607
    1608    /**
    1609     * Structure collecting information for the entry that is
    1610     * currently being written.
    1611     */
    1612    private static final class CurrentEntry {
    1613        private CurrentEntry(final ZipArchiveEntry entry) {
    1614            this.entry = entry;
    1615        }
    1616        /**
    1617         * Current ZIP entry.
    1618         */
    1619        private final ZipArchiveEntry entry;
    1620        /**
    1621         * Offset for CRC entry in the local file header data for the
    1622         * current entry starts here.
    1623         */
    1624        private long localDataStart = 0;
    1625        /**
    1626         * Data for local header data
    1627         */
    1628        private long dataStart = 0;
    1629        /**
    1630         * Number of bytes read for the current entry (can't rely on
    1631         * Deflater#getBytesRead) when using DEFLATED.
    1632         */
    1633        private long bytesRead = 0;
    1634        /**
    1635         * Whether current entry was the first one using ZIP64 features.
    1636         */
    1637        private boolean causedUseOfZip64 = false;
    1638        /**
    1639         * Whether write() has been called at all.
    1640         *
    1641         * <p>In order to create a valid archive {@link
    1642         * #closeArchiveEntry closeArchiveEntry} will write an empty
    1643         * array to get the CRC right if nothing has been written to
    1644         * the stream at all.</p>
    1645         */
    1646        private boolean hasWritten;
    1647    }
    1648
    1649}