    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 && !def.finished()) {
    532                    if (length <= DEFLATER_BLOCK_SIZE) {
    533                        def.setInput(b, offset, length);
    534                        deflateUntilInputIsNeeded();
    535                    } else {
    536                        final int fullblocks = length / DEFLATER_BLOCK_SIZE;
    537                        for (int i = 0; i < fullblocks; i++) {
    538                            def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
    539                                         DEFLATER_BLOCK_SIZE);
    540                            deflateUntilInputIsNeeded();
    541                        }
    542                        final int done = fullblocks * DEFLATER_BLOCK_SIZE;
    543                        if (done < length) {
    544                            def.setInput(b, offset + done, length - done);
    545                            deflateUntilInputIsNeeded();
    546                        }
    547                    }
    548                }
    549            } else {
    550                writeOut(b, offset, length);
    551                written += length;
    552            }
    553            crc.update(b, offset, length);
    554            count(length);
    555        }
    556
    557        /**
    558         * Closes this output stream and releases any system resources
    559         * associated with the stream.
    560         *
    561         * @exception  IOException  if an I/O error occurs.
    562         */
    563        public void close() throws IOException {
    564            if (!finished) {
    565                finish();
    566            }
    567
    568            if (raf != null) {
    569                raf.close();
    570            }
    571            if (out != null) {
    572                out.close();
    573            }
    574        }
    575
    576        /**
    577         * Flushes this output stream and forces any buffered output bytes
    578         * to be written out to the stream.
    579         *
    580         * @exception  IOException  if an I/O error occurs.
    581         */
    582        public void flush() throws IOException {
    583            if (out != null) {
    584                out.flush();
    585            }
    586        }
    587
    588        /*
    589         * Various ZIP constants
    590         */
    591        /**
    592         * local file header signature
    593         */
    594        static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
    595        /**
    596         * data descriptor signature
    597         */
    598        static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes();
    599        /**
    600         * central file header signature
    601         */
    602        static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
    603        /**
    604         * end of central dir signature
    605         */
    606        static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
    607
    608        /**
    609         * Writes next block of compressed data to the output stream.
    610         * @throws IOException on error
    611         */
    612        protected final void deflate() throws IOException {
    613            int len = def.deflate(buf, 0, buf.length);
    614            if (len > 0) {
    615                writeOut(buf, 0, len);
    616            }
    617        }
    618
    619        /**
    620         * Writes the local file header entry
    621         * @param ze the entry to write
    622         * @throws IOException on error
    623         */
    624        protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException {
    625
    626            boolean encodable = zipEncoding.canEncode(ze.getName());
    627
    628            final ZipEncoding entryEncoding;
    629
    630            if (!encodable && fallbackToUTF8) {
    631                entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
    632            } else {
    633                entryEncoding = zipEncoding;
    634            }
    635
    636            ByteBuffer name = entryEncoding.encode(ze.getName());
    637
    638            if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
    639
    640                if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
    641                    || !encodable) {
    642                    ze.addExtraField(new UnicodePathExtraField(ze.getName(),
    643                                                               name.array(),
    644                                                               name.arrayOffset(),
    645                                                               name.limit()));
    646                }
    647
    648                String comm = ze.getComment();
    649                if (comm != null && !"".equals(comm)) {
    650
    651                    boolean commentEncodable = this.zipEncoding.canEncode(comm);
    652
    653                    if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
    654                        || !commentEncodable) {
    655                        ByteBuffer commentB = entryEncoding.encode(comm);
    656                        ze.addExtraField(new UnicodeCommentExtraField(comm,
    657                                                                      commentB.array(),
    658                                                                      commentB.arrayOffset(),
    659                                                                      commentB.limit())
    660                                         );
    661                    }
    662                }
    663            }
    664
    665            offsets.put(ze, ZipLong.getBytes(written));
    666
    667            writeOut(LFH_SIG);
    668            written += WORD;
    669
    670            //store method in local variable to prevent multiple method calls
    671            final int zipMethod = ze.getMethod();
    672
    673            writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
    674                                                             !encodable
    675                                                             && fallbackToUTF8);
    676            written += WORD;
    677
    678            // compression method
    679            writeOut(ZipShort.getBytes(zipMethod));
    680            written += SHORT;
    681
    682            // last mod. time and date
    683            writeOut(ZipUtil.toDosTime(ze.getTime()));
    684            written += WORD;
    685
    686            // CRC
    687            // compressed length
    688            // uncompressed length
    689            localDataStart = written;
    690            if (zipMethod == DEFLATED || raf != null) {
    691                writeOut(LZERO);
    692                writeOut(LZERO);
    693                writeOut(LZERO);
    694            } else {
    695                writeOut(ZipLong.getBytes(ze.getCrc()));
    696                writeOut(ZipLong.getBytes(ze.getSize()));
    697                writeOut(ZipLong.getBytes(ze.getSize()));
    698            }
    699            // CheckStyle:MagicNumber OFF
    700            written += 12;
    701            // CheckStyle:MagicNumber ON
    702
    703            // file name length
    704            writeOut(ZipShort.getBytes(name.limit()));
    705            written += SHORT;
    706
    707            // extra field length
    708            byte[] extra = ze.getLocalFileDataExtra();
    709            writeOut(ZipShort.getBytes(extra.length));
    710            written += SHORT;
    711
    712            // file name
    713            writeOut(name.array(), name.arrayOffset(), name.limit());
    714            written += name.limit();
    715
    716            // extra field
    717            writeOut(extra);
    718            written += extra.length;
    719
    720            dataStart = written;
    721        }
    722
    723        /**
    724         * Writes the data descriptor entry.
    725         * @param ze the entry to write
    726         * @throws IOException on error
    727         */
    728        protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException {
    729            if (ze.getMethod() != DEFLATED || raf != null) {
    730                return;
    731            }
    732            writeOut(DD_SIG);
    733            writeOut(ZipLong.getBytes(entry.getCrc()));
    734            writeOut(ZipLong.getBytes(entry.getCompressedSize()));
    735            writeOut(ZipLong.getBytes(entry.getSize()));
    736            // CheckStyle:MagicNumber OFF
    737            written += 16;
    738            // CheckStyle:MagicNumber ON
    739        }
    740
    741        /**
    742         * Writes the central file header entry.
    743         * @param ze the entry to write
    744         * @throws IOException on error
    745         */
    746        protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException {
    747            writeOut(CFH_SIG);
    748            written += WORD;
    749
    750            // version made by
    751            // CheckStyle:MagicNumber OFF
    752            writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
    753            written += SHORT;
    754
    755            final int zipMethod = ze.getMethod();
    756            final boolean encodable = zipEncoding.canEncode(ze.getName());
    757            writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
    758                                                             !encodable
    759                                                             && fallbackToUTF8);
    760            written += WORD;
    761
    762            // compression method
    763            writeOut(ZipShort.getBytes(zipMethod));
    764            written += SHORT;
    765
    766            // last mod. time and date
    767            writeOut(ZipUtil.toDosTime(ze.getTime()));
    768            written += WORD;
    769
    770            // CRC
    771            // compressed length
    772            // uncompressed length
    773            writeOut(ZipLong.getBytes(ze.getCrc()));
    774            writeOut(ZipLong.getBytes(ze.getCompressedSize()));
    775            writeOut(ZipLong.getBytes(ze.getSize()));
    776            // CheckStyle:MagicNumber OFF
    777            written += 12;
    778            // CheckStyle:MagicNumber ON
    779
    780            // file name length
    781            final ZipEncoding entryEncoding;
    782
    783            if (!encodable && fallbackToUTF8) {
    784                entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
    785            } else {
    786                entryEncoding = zipEncoding;
    787            }
    788
    789            ByteBuffer name = entryEncoding.encode(ze.getName());
    790
    791            writeOut(ZipShort.getBytes(name.limit()));
    792            written += SHORT;
    793
    794            // extra field length
    795            byte[] extra = ze.getCentralDirectoryExtra();
    796            writeOut(ZipShort.getBytes(extra.length));
    797            written += SHORT;
    798
    799            // file comment length
    800            String comm = ze.getComment();
    801            if (comm == null) {
    802                comm = "";
    803            }
    804
    805            ByteBuffer commentB = entryEncoding.encode(comm);
    806
    807            writeOut(ZipShort.getBytes(commentB.limit()));
    808            written += SHORT;
    809
    810            // disk number start
    811            writeOut(ZERO);
    812            written += SHORT;
    813
    814            // internal file attributes
    815            writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
    816            written += SHORT;
    817
    818            // external file attributes
    819            writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
    820            written += WORD;
    821
    822            // relative offset of LFH
    823            writeOut((byte[]) offsets.get(ze));
    824            written += WORD;
    825
    826            // file name
    827            writeOut(name.array(), name.arrayOffset(), name.limit());
    828            written += name.limit();
    829
    830            // extra field
    831            writeOut(extra);
    832            written += extra.length;
    833
    834            // file comment
    835            writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit());
    836            written += commentB.limit();
    837        }
    838
    839        /**
    840         * Writes the &quot;End of central dir record&quot;.
    841         * @throws IOException on error
    842         */
    843        protected void writeCentralDirectoryEnd() throws IOException {
    844            writeOut(EOCD_SIG);
    845
    846            // disk numbers
    847            writeOut(ZERO);
    848            writeOut(ZERO);
    849
    850            // number of entries
    851            byte[] num = ZipShort.getBytes(entries.size());
    852            writeOut(num);
    853            writeOut(num);
    854
    855            // length and location of CD
    856            writeOut(ZipLong.getBytes(cdLength));
    857            writeOut(ZipLong.getBytes(cdOffset));
    858
    859            // ZIP file comment
    860            ByteBuffer data = this.zipEncoding.encode(comment);
    861            writeOut(ZipShort.getBytes(data.limit()));
    862            writeOut(data.array(), data.arrayOffset(), data.limit());
    863        }
    864
    865        /**
    866         * Write bytes to output or random access file.
    867         * @param data the byte array to write
    868         * @throws IOException on error
    869         */
    870        protected final void writeOut(byte[] data) throws IOException {
    871            writeOut(data, 0, data.length);
    872        }
    873
    874        /**
    875         * Write bytes to output or random access file.
    876         * @param data the byte array to write
    877         * @param offset the start position to write from
    878         * @param length the number of bytes to write
    879         * @throws IOException on error
    880         */
    881        protected final void writeOut(byte[] data, int offset, int length)
    882            throws IOException {
    883            if (raf != null) {
    884                raf.write(data, offset, length);
    885            } else {
    886                out.write(data, offset, length);
    887            }
    888        }
    889
    890        private void deflateUntilInputIsNeeded() throws IOException {
    891            while (!def.needsInput()) {
    892                deflate();
    893            }
    894        }
    895
    896        private void writeVersionNeededToExtractAndGeneralPurposeBits(final int
    897                                                                      zipMethod,
    898                                                                      final boolean
    899                                                                      utfFallback)
    900            throws IOException {
    901
    902            // CheckStyle:MagicNumber OFF
    903            int versionNeededToExtract = 10;
    904            GeneralPurposeBit b = new GeneralPurposeBit();
    905            b.useUTF8ForNames(useUTF8Flag || utfFallback);
    906            if (zipMethod == DEFLATED && raf == null) {
    907                // requires version 2 as we are going to store length info
    908                // in the data descriptor
    909                versionNeededToExtract =  20;
    910                b.useDataDescriptor(true);
    911            }
    912            // CheckStyle:MagicNumber ON
    913
    914            // version needed to extract
    915            writeOut(ZipShort.getBytes(versionNeededToExtract));
    916            // general purpose bit flag
    917            writeOut(b.encode());
    918        }
    919
    920        /**
    921         * enum that represents the possible policies for creating Unicode
    922         * extra fields.
    923         */
    924        public static final class UnicodeExtraFieldPolicy {
    925            /**
    926             * Always create Unicode extra fields.
    927             */
    928            public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
    929            /**
    930             * Never create Unicode extra fields.
    931             */
    932            public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
    933            /**
    934             * Create Unicode extra fields for filenames that cannot be
    935             * encoded using the specified encoding.
    936             */
    937            public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
    938                new UnicodeExtraFieldPolicy("not encodeable");
    939
    940            private final String name;
    941            private UnicodeExtraFieldPolicy(String n) {
    942                name = n;
    943            }
    944            public String toString() {
    945                return name;
    946            }
    947        }
    948
    949        /**
    950         * Creates a new zip entry taking some information from the given
    951         * file and using the provided name.
    952         *
    953         * <p>The name will be adjusted to end with a forward slash "/" if
    954         * the file is a directory.  If the file is not a directory a
    955         * potential trailing forward slash will be stripped from the
    956         * entry name.</p>
    957         *
    958         * <p>Must not be used if the stream has already been closed.</p>
    959         */
    960        public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
    961                throws IOException {
    962            if (finished) {
    963                throw new IOException("Stream has already been finished");
    964            }
    965            return new ZipArchiveEntry(inputFile, entryName);
    966        }
    967    }