    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     */
    018    package org.apache.commons.compress.archivers.zip;
    019
    020    import java.io.File;
    021    import java.io.FileOutputStream;
    022    import java.io.IOException;
    023    import java.io.OutputStream;
    024    import java.io.RandomAccessFile;
    025    import java.nio.ByteBuffer;
    026    import java.util.HashMap;
    027    import java.util.Iterator;
    028    import java.util.LinkedList;
    029    import java.util.List;
    030    import java.util.Map;
    031    import java.util.zip.CRC32;
    032    import java.util.zip.Deflater;
    033    import java.util.zip.ZipException;
    034
    035    import org.apache.commons.compress.archivers.ArchiveEntry;
    036    import org.apache.commons.compress.archivers.ArchiveOutputStream;
    037
    038    /**
    039     * Reimplementation of {@link java.util.zip.ZipOutputStream
    040     * java.util.zip.ZipOutputStream} that does handle the extended
    041     * functionality of this package, especially internal/external file
    042     * attributes and extra fields with different layouts for local file
    043     * data and central directory entries.
    044     *
    045     * <p>This class will try to use {@link java.io.RandomAccessFile
    046     * RandomAccessFile} when you know that the output is going to go to a
    047     * file.</p>
    048     *
    049     * <p>If RandomAccessFile cannot be used, this implementation will use
    050     * a Data Descriptor to store size and CRC information for {@link
    051     * #DEFLATED DEFLATED} entries, this means, you don't need to
    052     * calculate them yourself.  Unfortunately this is not possible for
    053     * the {@link #STORED STORED} method, here setting the CRC and
    054     * uncompressed size information is required before {@link
    055     * #putArchiveEntry(ArchiveEntry)} can be called.</p>
    056     * @NotThreadSafe
    057     */
    058    public class ZipArchiveOutputStream extends ArchiveOutputStream {
    059
    060        static final int BYTE_MASK = 0xFF;
    061        private static final int SHORT = 2;
    062        private static final int WORD = 4;
    063        static final int BUFFER_SIZE = 512;
    064
    065        /** indicates if this archive is finished. protected for use in Jar implementation */
    066        protected boolean finished = false;
    067
    068        /*
    069         * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs
    070         * when it gets handed a really big buffer.  See
    071         * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396
    072         *
    073         * Using a buffer size of 8 kB proved to be a good compromise
    074         */
    075        private static final int DEFLATER_BLOCK_SIZE = 8192;
    076
    077        /**
    078         * Compression method for deflated entries.
    079         */
    080        public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED;
    081
    082        /**
    083         * Default compression level for deflated entries.
    084         */
    085        public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;
    086
    087        /**
    088         * Compression method for stored entries.
    089         */
    090        public static final int STORED = java.util.zip.ZipEntry.STORED;
    091
    092        /**
    093         * default encoding for file names and comment.
    094         */
    095        static final String DEFAULT_ENCODING = ZipEncodingHelper.UTF8;
    096
    097        /**
    098         * General purpose flag, which indicates that filenames are
    099         * written in utf-8.
    100         * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead
    101         */
    102        public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG;
    103
    104        /**
    105         * Current entry.
    106         */
    107        private ZipArchiveEntry entry;
    108
    109        /**
    110         * The file comment.
    111         */
    112        private String comment = "";
    113
    114        /**
    115         * Compression level for next entry.
    116         */
    117        private int level = DEFAULT_COMPRESSION;
    118
    119        /**
    120         * Has the compression level changed when compared to the last
    121         * entry?
    122         */
    123        private boolean hasCompressionLevelChanged = false;
    124
    125        /**
    126         * Default compression method for next entry.
    127         */
    128        private int method = java.util.zip.ZipEntry.DEFLATED;
    129
    130        /**
    131         * List of ZipArchiveEntries written so far.
    132         */
    133        private final List entries = new LinkedList();
    134
    135        /**
    136         * CRC instance to avoid parsing DEFLATED data twice.
    137         */
    138        private final CRC32 crc = new CRC32();
    139
    140        /**
    141         * Count the bytes written to out.
    142         */
    143        private long written = 0;
    144
    145        /**
    146         * Data for local header data
    147         */
    148        private long dataStart = 0;
    149
    150        /**
    151         * Offset for CRC entry in the local file header data for the
    152         * current entry starts here.
    153         */
    154        private long localDataStart = 0;
    155
    156        /**
    157         * Start of central directory.
    158         */
    159        private long cdOffset = 0;
    160
    161        /**
    162         * Length of central directory.
    163         */
    164        private long cdLength = 0;
    165
    166        /**
    167         * Helper, a 0 as ZipShort.
    168         */
    169        private static final byte[] ZERO = {0, 0};
    170
    171        /**
    172         * Helper, a 0 as ZipLong.
    173         */
    174        private static final byte[] LZERO = {0, 0, 0, 0};
    175
    176        /**
    177         * Holds the offsets of the LFH starts for each entry.
    178         */
    179        private final Map offsets = new HashMap();
    180
    181        /**
    182         * The encoding to use for filenames and the file comment.
    183         *
    184         * <p>For a list of possible values see <a
    185         * 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>.
    186         * Defaults to UTF-8.</p>
    187         */
    188        private String encoding = DEFAULT_ENCODING;
    189
    190        /**
    191         * The zip encoding to use for filenames and the file comment.
    192         *
    193         * This field is of internal use and will be set in {@link
    194         * #setEncoding(String)}.
    195         */
    196        private ZipEncoding zipEncoding =
    197            ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING);
    198
    199        /**
    200         * This Deflater object is used for output.
    201         *
    202         */
    203        protected final Deflater def = new Deflater(level, true);
    204
    205        /**
    206         * This buffer servers as a Deflater.
    207         *
    208         */
    209        private final byte[] buf = new byte[BUFFER_SIZE];
    210
    211        /**
    212         * Optional random access output.
    213         */
    214        private final RandomAccessFile raf;
    215
    216        private final OutputStream out;
    217
    218        /**
    219         * whether to use the general purpose bit flag when writing UTF-8
    220         * filenames or not.
    221         */
    222        private boolean useUTF8Flag = true;
    223
    224        /**
    225         * Whether to encode non-encodable file names as UTF-8.
    226         */
    227        private boolean fallbackToUTF8 = false;
    228
    229        /**
    230         * whether to create UnicodePathExtraField-s for each entry.
    231         */
    232        private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
    233
    234        /**
    235         * Creates a new ZIP OutputStream filtering the underlying stream.
    236         * @param out the outputstream to zip
    237         */
    238        public ZipArchiveOutputStream(OutputStream out) {
    239            this.out = out;
    240            this.raf = null;
    241        }
    242
    243        /**
    244         * Creates a new ZIP OutputStream writing to a File.  Will use
    245         * random access if possible.
    246         * @param file the file to zip to
    247         * @throws IOException on error
    248         */
    249        public ZipArchiveOutputStream(File file) throws IOException {
    250            OutputStream o = null;
    251            RandomAccessFile _raf = null;
    252            try {
    253                _raf = new RandomAccessFile(file, "rw");
    254                _raf.setLength(0);
    255            } catch (IOException e) {
    256                if (_raf != null) {
    257                    try {
    258                        _raf.close();
    259                    } catch (IOException inner) {
    260                        // ignore
    261                    }
    262                    _raf = null;
    263                }
    264                o = new FileOutputStream(file);
    265            }
    266            out = o;
    267            raf = _raf;
    268        }
    269
    270        /**
    271         * This method indicates whether this archive is writing to a
    272         * seekable stream (i.e., to a random access file).
    273         *
    274         * <p>For seekable streams, you don't need to calculate the CRC or
    275         * uncompressed size for {@link #STORED} entries before
    276         * invoking {@link #putArchiveEntry(ArchiveEntry)}.
    277         * @return true if seekable
    278         */
    279        public boolean isSeekable() {
    280            return raf != null;
    281        }
    282
    283        /**
    284         * The encoding to use for filenames and the file comment.
    285         *
    286         * <p>For a list of possible values see <a
    287         * 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>.
    288         * Defaults to UTF-8.</p>
    289         * @param encoding the encoding to use for file names, use null
    290         * for the platform's default encoding
    291         */
    292        public void setEncoding(final String encoding) {
    293            this.encoding = encoding;
    294            this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
    295            useUTF8Flag &= ZipEncodingHelper.isUTF8(encoding);
    296        }
    297
    298        /**
    299         * The encoding to use for filenames and the file comment.
    300         *
    301         * @return null if using the platform's default character encoding.
    302         */
    303        public String getEncoding() {
    304            return encoding;
    305        }
    306
    307        /**
    308         * Whether to set the language encoding flag if the file name
    309         * encoding is UTF-8.
    310         *
    311         * <p>Defaults to true.</p>
    312         */
    313        public void setUseLanguageEncodingFlag(boolean b) {
    314            useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding);
    315        }
    316
    317        /**
    318         * Whether to create Unicode Extra Fields.
    319         *
    320         * <p>Defaults to NEVER.</p>
    321         */
    322        public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {
    323            createUnicodeExtraFields = b;
    324        }
    325
    326        /**
    327         * Whether to fall back to UTF and the language encoding flag if
    328         * the file name cannot be encoded using the specified encoding.
    329         *
    330         * <p>Defaults to false.</p>
    331         */
    332        public void setFallbackToUTF8(boolean b) {
    333            fallbackToUTF8 = b;
    334        }
    335
    336        /** {@inheritDoc} */
    337        public void finish() throws IOException {
    338            if (finished) {
    339                throw new IOException("This archive has already been finished");
    340            }
    341
    342            if (entry != null) {
    343                throw new IOException("This archives contains unclosed entries.");
    344            }
    345
    346            cdOffset = written;
    347            for (Iterator i = entries.iterator(); i.hasNext(); ) {
    348                writeCentralFileHeader((ZipArchiveEntry) i.next());
    349            }
    350            cdLength = written - cdOffset;
    351            writeCentralDirectoryEnd();
    352            offsets.clear();
    353            entries.clear();
    354            finished = true;
    355        }
    356
    357        /**
    358         * Writes all necessary data for this entry.
    359         * @throws IOException on error
    360         */
    361        public void closeArchiveEntry() throws IOException {
    362            if (finished) {
    363                throw new IOException("Stream has already been finished");
    364            }
    365
    366            if (entry == null) {
    367                throw new IOException("No current entry to close");
    368            }
    369
    370            long realCrc = crc.getValue();
    371            crc.reset();
    372
    373            if (entry.getMethod() == DEFLATED) {
    374                def.finish();
    375                while (!def.finished()) {
    376                    deflate();
    377                }
    378
    379                entry.setSize(ZipUtil.adjustToLong(def.getTotalIn()));
    380                entry.setCompressedSize(ZipUtil.adjustToLong(def.getTotalOut()));
    381                entry.setCrc(realCrc);
    382
    383                def.reset();
    384
    385                written += entry.getCompressedSize();
    386            } else if (raf == null) {
    387                if (entry.getCrc() != realCrc) {
    388                    throw new ZipException("bad CRC checksum for entry "
    389                                           + entry.getName() + ": "
    390                                           + Long.toHexString(entry.getCrc())
    391                                           + " instead of "
    392                                           + Long.toHexString(realCrc));
    393                }
    394
    395                if (entry.getSize() != written - dataStart) {
    396                    throw new ZipException("bad size for entry "
    397                                           + entry.getName() + ": "
    398                                           + entry.getSize()
    399                                           + " instead of "
    400                                           + (written - dataStart));
    401                }
    402            } else { /* method is STORED and we used RandomAccessFile */
    403                long size = written - dataStart;
    404
    405                entry.setSize(size);
    406                entry.setCompressedSize(size);
    407                entry.setCrc(realCrc);
    408            }
    409
    410            // If random access output, write the local file header containing
    411            // the correct CRC and compressed/uncompressed sizes
    412            if (raf != null) {
    413                long save = raf.getFilePointer();
    414
    415                raf.seek(localDataStart);
    416                writeOut(ZipLong.getBytes(entry.getCrc()));
    417                writeOut(ZipLong.getBytes(entry.getCompressedSize()));
    418                writeOut(ZipLong.getBytes(entry.getSize()));
    419                raf.seek(save);
    420            }
    421
    422            writeDataDescriptor(entry);
    423            entry = null;
    424        }
    425
    426        /**
    427         * {@inheritDoc}
    428         * @throws ClassCastException if entry is not an instance of ZipArchiveEntry
    429         */
    430        public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException {
    431            if (finished) {
    432                throw new IOException("Stream has already been finished");
    433            }
    434
    435            if (entry != null) {
    436                closeArchiveEntry();
    437            }
    438
    439            entry = ((ZipArchiveEntry) archiveEntry);
    440            entries.add(entry);
    441
    442            if (entry.getMethod() == -1) { // not specified
    443                entry.setMethod(method);
    444            }
    445
    446            if (entry.getTime() == -1) { // not specified
    447                entry.setTime(System.currentTimeMillis());
    448            }
    449
    450            // Size/CRC not required if RandomAccessFile is used
    451            if (entry.getMethod() == STORED && raf == null) {
    452                if (entry.getSize() == -1) {
    453                    throw new ZipException("uncompressed size is required for"
    454                                           + " STORED method when not writing to a"
    455                                           + " file");
    456                }
    457                if (entry.getCrc() == -1) {
    458                    throw new ZipException("crc checksum is required for STORED"
    459                                           + " method when not writing to a file");
    460                }
    461                entry.setCompressedSize(entry.getSize());
    462            }
    463
    464            if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
    465                def.setLevel(level);
    466                hasCompressionLevelChanged = false;
    467            }
    468            writeLocalFileHeader(entry);
    469        }
    470
    471        /**
    472         * Set the file comment.
    473         * @param comment the comment
    474         */
    475        public void setComment(String comment) {
    476            this.comment = comment;
    477        }
    478
    479        /**
    480         * Sets the compression level for subsequent entries.
    481         *
    482         * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
    483         * @param level the compression level.
    484         * @throws IllegalArgumentException if an invalid compression
    485         * level is specified.
    486         */
    487        public void setLevel(int level) {
    488            if (level < Deflater.DEFAULT_COMPRESSION
    489                || level > Deflater.BEST_COMPRESSION) {
    490                throw new IllegalArgumentException("Invalid compression level: "
    491                                                   + level);
    492            }
    493            hasCompressionLevelChanged = (this.level != level);
    494            this.level = level;
    495        }
    496
    497        /**
    498         * Sets the default compression method for subsequent entries.
    499         *
    500         * <p>Default is DEFLATED.</p>
    501         * @param method an <code>int</code> from java.util.zip.ZipEntry
    502         */
    503        public void setMethod(int method) {
    504            this.method = method;
    505        }
    506
    507        /**
    508         * Whether this stream is able to write the given entry.
    509         *
    510         * <p>May return false if it is set up to use encryption or a
    511         * compression method that hasn't been implemented yet.</p>
    512         * @since Apache Commons Compress 1.1
    513         */
    514        public boolean canWriteEntryData(ArchiveEntry ae) {
    515            if (ae instanceof ZipArchiveEntry) {
    516                return ZipUtil.canHandleEntryData((ZipArchiveEntry) ae);
    517            }
    518            return false;
    519        }
    520
    521        /**
    522         * Writes bytes to ZIP entry.
    523         * @param b the byte array to write
    524         * @param offset the start position to write from
    525         * @param length the number of bytes to write
    526         * @throws IOException on error
    527         */
    528        public void write(byte[] b, int offset, int length) throws IOException {
    529            ZipUtil.checkRequestedFeatures(entry);
    530            if (entry.getMethod() == DEFLATED) {
    531                if (length > 0) {
    532                    if (!def.finished()) {
    533                        if (length <= DEFLATER_BLOCK_SIZE) {
    534                            def.setInput(b, offset, length);
    535                            deflateUntilInputIsNeeded();
    536                        } else {
    537                            final int fullblocks = length / DEFLATER_BLOCK_SIZE;
    538                            for (int i = 0; i < fullblocks; i++) {
    539                                def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
    540                                             DEFLATER_BLOCK_SIZE);
    541                                deflateUntilInputIsNeeded();
    542                            }
    543                            final int done = fullblocks * DEFLATER_BLOCK_SIZE;
    544                            if (done < length) {
    545                                def.setInput(b, offset + done, length - done);
    546                                deflateUntilInputIsNeeded();
    547                            }
    548                        }
    549                    }
    550                }
    551            } else {
    552                writeOut(b, offset, length);
    553                written += length;
    554            }
    555            crc.update(b, offset, length);
    556            count(length);
    557        }
    558
    559        /**
    560         * Closes this output stream and releases any system resources
    561         * associated with the stream.
    562         *
    563         * @exception  IOException  if an I/O error occurs.
    564         */
    565        public void close() throws IOException {
    566            if (!finished) {
    567                finish();
    568            }
    569
    570            if (raf != null) {
    571                raf.close();
    572            }
    573            if (out != null) {
    574                out.close();
    575            }
    576        }
    577
    578        /**
    579         * Flushes this output stream and forces any buffered output bytes
    580         * to be written out to the stream.
    581         *
    582         * @exception  IOException  if an I/O error occurs.
    583         */
    584        public void flush() throws IOException {
    585            if (out != null) {
    586                out.flush();
    587            }
    588        }
    589
    590        /*
    591         * Various ZIP constants
    592         */
    593        /**
    594         * local file header signature
    595         */
    596        static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
    597        /**
    598         * data descriptor signature
    599         */
    600        static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes();
    601        /**
    602         * central file header signature
    603         */
    604        static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
    605        /**
    606         * end of central dir signature
    607         */
    608        static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
    609
    610        /**
    611         * Writes next block of compressed data to the output stream.
    612         * @throws IOException on error
    613         */
    614        protected final void deflate() throws IOException {
    615            int len = def.deflate(buf, 0, buf.length);
    616            if (len > 0) {
    617                writeOut(buf, 0, len);
    618            }
    619        }
    620
    621        /**
    622         * Writes the local file header entry
    623         * @param ze the entry to write
    624         * @throws IOException on error
    625         */
    626        protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException {
    627
    628            boolean encodable = zipEncoding.canEncode(ze.getName());
    629
    630            final ZipEncoding entryEncoding;
    631
    632            if (!encodable && fallbackToUTF8) {
    633                entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
    634            } else {
    635                entryEncoding = zipEncoding;
    636            }
    637
    638            ByteBuffer name = entryEncoding.encode(ze.getName());
    639
    640            if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
    641
    642                if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
    643                    || !encodable) {
    644                    ze.addExtraField(new UnicodePathExtraField(ze.getName(),
    645                                                               name.array(),
    646                                                               name.arrayOffset(),
    647                                                               name.limit()));
    648                }
    649
    650                String comm = ze.getComment();
    651                if (comm != null && !"".equals(comm)) {
    652
    653                    boolean commentEncodable = this.zipEncoding.canEncode(comm);
    654
    655                    if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
    656                        || !commentEncodable) {
    657                        ByteBuffer commentB = entryEncoding.encode(comm);
    658                        ze.addExtraField(new UnicodeCommentExtraField(comm,
    659                                                                      commentB.array(),
    660                                                                      commentB.arrayOffset(),
    661                                                                      commentB.limit())
    662                                         );
    663                    }
    664                }
    665            }
    666
    667            offsets.put(ze, ZipLong.getBytes(written));
    668
    669            writeOut(LFH_SIG);
    670            written += WORD;
    671
    672            //store method in local variable to prevent multiple method calls
    673            final int zipMethod = ze.getMethod();
    674
    675            writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
    676                                                             !encodable
    677                                                             && fallbackToUTF8);
    678            written += WORD;
    679
    680            // compression method
    681            writeOut(ZipShort.getBytes(zipMethod));
    682            written += SHORT;
    683
    684            // last mod. time and date
    685            writeOut(ZipUtil.toDosTime(ze.getTime()));
    686            written += WORD;
    687
    688            // CRC
    689            // compressed length
    690            // uncompressed length
    691            localDataStart = written;
    692            if (zipMethod == DEFLATED || raf != null) {
    693                writeOut(LZERO);
    694                writeOut(LZERO);
    695                writeOut(LZERO);
    696            } else {
    697                writeOut(ZipLong.getBytes(ze.getCrc()));
    698                writeOut(ZipLong.getBytes(ze.getSize()));
    699                writeOut(ZipLong.getBytes(ze.getSize()));
    700            }
    701            // CheckStyle:MagicNumber OFF
    702            written += 12;
    703            // CheckStyle:MagicNumber ON
    704
    705            // file name length
    706            writeOut(ZipShort.getBytes(name.limit()));
    707            written += SHORT;
    708
    709            // extra field length
    710            byte[] extra = ze.getLocalFileDataExtra();
    711            writeOut(ZipShort.getBytes(extra.length));
    712            written += SHORT;
    713
    714            // file name
    715            writeOut(name.array(), name.arrayOffset(), name.limit());
    716            written += name.limit();
    717
    718            // extra field
    719            writeOut(extra);
    720            written += extra.length;
    721
    722            dataStart = written;
    723        }
    724
    725        /**
    726         * Writes the data descriptor entry.
    727         * @param ze the entry to write
    728         * @throws IOException on error
    729         */
    730        protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException {
    731            if (ze.getMethod() != DEFLATED || raf != null) {
    732                return;
    733            }
    734            writeOut(DD_SIG);
    735            writeOut(ZipLong.getBytes(entry.getCrc()));
    736            writeOut(ZipLong.getBytes(entry.getCompressedSize()));
    737            writeOut(ZipLong.getBytes(entry.getSize()));
    738            // CheckStyle:MagicNumber OFF
    739            written += 16;
    740            // CheckStyle:MagicNumber ON
    741        }
    742
    743        /**
    744         * Writes the central file header entry.
    745         * @param ze the entry to write
    746         * @throws IOException on error
    747         */
    748        protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException {
    749            writeOut(CFH_SIG);
    750            written += WORD;
    751
    752            // version made by
    753            // CheckStyle:MagicNumber OFF
    754            writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
    755            written += SHORT;
    756
    757            final int zipMethod = ze.getMethod();
    758            final boolean encodable = zipEncoding.canEncode(ze.getName());
    759            writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
    760                                                             !encodable
    761                                                             && fallbackToUTF8);
    762            written += WORD;
    763
    764            // compression method
    765            writeOut(ZipShort.getBytes(zipMethod));
    766            written += SHORT;
    767
    768            // last mod. time and date
    769            writeOut(ZipUtil.toDosTime(ze.getTime()));
    770            written += WORD;
    771
    772            // CRC
    773            // compressed length
    774            // uncompressed length
    775            writeOut(ZipLong.getBytes(ze.getCrc()));
    776            writeOut(ZipLong.getBytes(ze.getCompressedSize()));
    777            writeOut(ZipLong.getBytes(ze.getSize()));
    778            // CheckStyle:MagicNumber OFF
    779            written += 12;
    780            // CheckStyle:MagicNumber ON
    781
    782            // file name length
    783            final ZipEncoding entryEncoding;
    784
    785            if (!encodable && fallbackToUTF8) {
    786                entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
    787            } else {
    788                entryEncoding = zipEncoding;
    789            }
    790
    791            ByteBuffer name = entryEncoding.encode(ze.getName());
    792
    793            writeOut(ZipShort.getBytes(name.limit()));
    794            written += SHORT;
    795
    796            // extra field length
    797            byte[] extra = ze.getCentralDirectoryExtra();
    798            writeOut(ZipShort.getBytes(extra.length));
    799            written += SHORT;
    800
    801            // file comment length
    802            String comm = ze.getComment();
    803            if (comm == null) {
    804                comm = "";
    805            }
    806
    807            ByteBuffer commentB = entryEncoding.encode(comm);
    808
    809            writeOut(ZipShort.getBytes(commentB.limit()));
    810            written += SHORT;
    811
    812            // disk number start
    813            writeOut(ZERO);
    814            written += SHORT;
    815
    816            // internal file attributes
    817            writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
    818            written += SHORT;
    819
    820            // external file attributes
    821            writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
    822            written += WORD;
    823
    824            // relative offset of LFH
    825            writeOut((byte[]) offsets.get(ze));
    826            written += WORD;
    827
    828            // file name
    829            writeOut(name.array(), name.arrayOffset(), name.limit());
    830            written += name.limit();
    831
    832            // extra field
    833            writeOut(extra);
    834            written += extra.length;
    835
    836            // file comment
    837            writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit());
    838            written += commentB.limit();
    839        }
    840
    841        /**
    842         * Writes the &quot;End of central dir record&quot;.
    843         * @throws IOException on error
    844         */
    845        protected void writeCentralDirectoryEnd() throws IOException {
    846            writeOut(EOCD_SIG);
    847
    848            // disk numbers
    849            writeOut(ZERO);
    850            writeOut(ZERO);
    851
    852            // number of entries
    853            byte[] num = ZipShort.getBytes(entries.size());
    854            writeOut(num);
    855            writeOut(num);
    856
    857            // length and location of CD
    858            writeOut(ZipLong.getBytes(cdLength));
    859            writeOut(ZipLong.getBytes(cdOffset));
    860
    861            // ZIP file comment
    862            ByteBuffer data = this.zipEncoding.encode(comment);
    863            writeOut(ZipShort.getBytes(data.limit()));
    864            writeOut(data.array(), data.arrayOffset(), data.limit());
    865        }
    866
    867        /**
    868         * Write bytes to output or random access file.
    869         * @param data the byte array to write
    870         * @throws IOException on error
    871         */
    872        protected final void writeOut(byte[] data) throws IOException {
    873            writeOut(data, 0, data.length);
    874        }
    875
    876        /**
    877         * Write bytes to output or random access file.
    878         * @param data the byte array to write
    879         * @param offset the start position to write from
    880         * @param length the number of bytes to write
    881         * @throws IOException on error
    882         */
    883        protected final void writeOut(byte[] data, int offset, int length)
    884            throws IOException {
    885            if (raf != null) {
    886                raf.write(data, offset, length);
    887            } else {
    888                out.write(data, offset, length);
    889            }
    890        }
    891
    892        private void deflateUntilInputIsNeeded() throws IOException {
    893            while (!def.needsInput()) {
    894                deflate();
    895            }
    896        }
    897
    898        private void writeVersionNeededToExtractAndGeneralPurposeBits(final int
    899                                                                      zipMethod,
    900                                                                      final boolean
    901                                                                      utfFallback)
    902            throws IOException {
    903
    904            // CheckStyle:MagicNumber OFF
    905            int versionNeededToExtract = 10;
    906            GeneralPurposeBit b = new GeneralPurposeBit();
    907            b.useUTF8ForNames(useUTF8Flag || utfFallback);
    908            if (zipMethod == DEFLATED && raf == null) {
    909                // requires version 2 as we are going to store length info
    910                // in the data descriptor
    911                versionNeededToExtract =  20;
    912                b.useDataDescriptor(true);
    913            }
    914            // CheckStyle:MagicNumber ON
    915
    916            // version needed to extract
    917            writeOut(ZipShort.getBytes(versionNeededToExtract));
    918            // general purpose bit flag
    919            writeOut(b.encode());
    920        }
    921
    922        /**
    923         * enum that represents the possible policies for creating Unicode
    924         * extra fields.
    925         */
    926        public static final class UnicodeExtraFieldPolicy {
    927            /**
    928             * Always create Unicode extra fields.
    929             */
    930            public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
    931            /**
    932             * Never create Unicode extra fields.
    933             */
    934            public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
    935            /**
    936             * Create Unicode extra fields for filenames that cannot be
    937             * encoded using the specified encoding.
    938             */
    939            public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
    940                new UnicodeExtraFieldPolicy("not encodeable");
    941
    942            private final String name;
    943            private UnicodeExtraFieldPolicy(String n) {
    944                name = n;
    945            }
    946            public String toString() {
    947                return name;
    948            }
    949        }
    950
    951        /**
    952         * Creates a new zip entry taking some information from the given
    953         * file and using the provided name.
    954         *
    955         * <p>The name will be adjusted to end with a forward slash "/" if
    956         * the file is a directory.  If the file is not a directory a
    957         * potential trailing forward slash will be stripped from the
    958         * entry name.</p>
    959         *
    960         * <p>Must not be used if the stream has already been closed.</p>
    961         */
    962        public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
    963                throws IOException {
    964            if (finished) {
    965                throw new IOException("Stream has already been finished");
    966            }
    967            return new ZipArchiveEntry(inputFile, entryName);
    968        }
    969    }