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