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