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