    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         */
    101        public static final int EFS_FLAG = 1 << 11;
    102
    103        /**
    104         * Current entry.
    105         */
    106        private ZipArchiveEntry entry;
    107
    108        /**
    109         * The file comment.
    110         */
    111        private String comment = "";
    112
    113        /**
    114         * Compression level for next entry.
    115         */
    116        private int level = DEFAULT_COMPRESSION;
    117
    118        /**
    119         * Has the compression level changed when compared to the last
    120         * entry?
    121         */
    122        private boolean hasCompressionLevelChanged = false;
    123
    124        /**
    125         * Default compression method for next entry.
    126         */
    127        private int method = java.util.zip.ZipEntry.DEFLATED;
    128
    129        /**
    130         * List of ZipArchiveEntries written so far.
    131         */
    132        private final List entries = new LinkedList();
    133
    134        /**
    135         * CRC instance to avoid parsing DEFLATED data twice.
    136         */
    137        private final CRC32 crc = new CRC32();
    138
    139        /**
    140         * Count the bytes written to out.
    141         */
    142        private long written = 0;
    143
    144        /**
    145         * Data for local header data
    146         */
    147        private long dataStart = 0;
    148
    149        /**
    150         * Offset for CRC entry in the local file header data for the
    151         * current entry starts here.
    152         */
    153        private long localDataStart = 0;
    154
    155        /**
    156         * Start of central directory.
    157         */
    158        private long cdOffset = 0;
    159
    160        /**
    161         * Length of central directory.
    162         */
    163        private long cdLength = 0;
    164
    165        /**
    166         * Helper, a 0 as ZipShort.
    167         */
    168        private static final byte[] ZERO = {0, 0};
    169
    170        /**
    171         * Helper, a 0 as ZipLong.
    172         */
    173        private static final byte[] LZERO = {0, 0, 0, 0};
    174
    175        /**
    176         * Holds the offsets of the LFH starts for each entry.
    177         */
    178        private final Map offsets = new HashMap();
    179
    180        /**
    181         * The encoding to use for filenames and the file comment.
    182         *
    183         * <p>For a list of possible values see <a
    184         * 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>.
    185         * Defaults to UTF-8.</p>
    186         */
    187        private String encoding = DEFAULT_ENCODING;
    188
    189        /**
    190         * The zip encoding to use for filenames and the file comment.
    191         *
    192         * This field is of internal use and will be set in {@link
    193         * #setEncoding(String)}.
    194         */
    195        private ZipEncoding zipEncoding =
    196            ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING);
    197
    198        /**
    199         * This Deflater object is used for output.
    200         *
    201         */
    202        protected final Deflater def = new Deflater(level, true);
    203
    204        /**
    205         * This buffer servers as a Deflater.
    206         *
    207         */
    208        private final byte[] buf = new byte[BUFFER_SIZE];
    209
    210        /**
    211         * Optional random access output.
    212         */
    213        private final RandomAccessFile raf;
    214
    215        private final OutputStream out;
    216
    217        /**
    218         * whether to use the EFS flag when writing UTF-8 filenames or not.
    219         */
    220        private boolean useEFS = true;
    221
    222        /**
    223         * Whether to encode non-encodable file names as UTF-8.
    224         */
    225        private boolean fallbackToUTF8 = false;
    226
    227        /**
    228         * whether to create UnicodePathExtraField-s for each entry.
    229         */
    230        private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER;
    231
    232        /**
    233         * Creates a new ZIP OutputStream filtering the underlying stream.
    234         * @param out the outputstream to zip
    235         */
    236        public ZipArchiveOutputStream(OutputStream out) {
    237            this.out = out;
    238            this.raf = null;
    239        }
    240
    241        /**
    242         * Creates a new ZIP OutputStream writing to a File.  Will use
    243         * random access if possible.
    244         * @param file the file to zip to
    245         * @throws IOException on error
    246         */
    247        public ZipArchiveOutputStream(File file) throws IOException {
    248            OutputStream o = null;
    249            RandomAccessFile _raf = null;
    250            try {
    251                _raf = new RandomAccessFile(file, "rw");
    252                _raf.setLength(0);
    253            } catch (IOException e) {
    254                if (_raf != null) {
    255                    try {
    256                        _raf.close();
    257                    } catch (IOException inner) {
    258                        // ignore
    259                    }
    260                    _raf = null;
    261                }
    262                o = new FileOutputStream(file);
    263            }
    264            out = o;
    265            raf = _raf;
    266        }
    267
    268        /**
    269         * This method indicates whether this archive is writing to a
    270         * seekable stream (i.e., to a random access file).
    271         *
    272         * <p>For seekable streams, you don't need to calculate the CRC or
    273         * uncompressed size for {@link #STORED} entries before
    274         * invoking {@link #putArchiveEntry(ArchiveEntry)}.
    275         * @return true if seekable
    276         */
    277        public boolean isSeekable() {
    278            return raf != null;
    279        }
    280
    281        /**
    282         * The encoding to use for filenames and the file comment.
    283         *
    284         * <p>For a list of possible values see <a
    285         * 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>.
    286         * Defaults to UTF-8.</p>
    287         * @param encoding the encoding to use for file names, use null
    288         * for the platform's default encoding
    289         */
    290        public void setEncoding(final String encoding) {
    291            this.encoding = encoding;
    292            this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding);
    293            useEFS &= ZipEncodingHelper.isUTF8(encoding);
    294        }
    295
    296        /**
    297         * The encoding to use for filenames and the file comment.
    298         *
    299         * @return null if using the platform's default character encoding.
    300         */
    301        public String getEncoding() {
    302            return encoding;
    303        }
    304
    305        /**
    306         * Whether to set the language encoding flag if the file name
    307         * encoding is UTF-8.
    308         *
    309         * <p>Defaults to true.</p>
    310         */
    311        public void setUseLanguageEncodingFlag(boolean b) {
    312            useEFS = b && ZipEncodingHelper.isUTF8(encoding);
    313        }
    314
    315        /**
    316         * Whether to create Unicode Extra Fields.
    317         *
    318         * <p>Defaults to NEVER.</p>
    319         */
    320        public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) {
    321            createUnicodeExtraFields = b;
    322        }
    323
    324        /**
    325         * Whether to fall back to UTF and the language encoding flag if
    326         * the file name cannot be encoded using the specified encoding.
    327         *
    328         * <p>Defaults to false.</p>
    329         */
    330        public void setFallbackToUTF8(boolean b) {
    331            fallbackToUTF8 = b;
    332        }
    333
    334        /* (non-Javadoc)
    335         * @see org.apache.commons.compress.archivers.ArchiveOutputStream#finish()
    336         */
    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        /** {@inheritDoc} */
    427     // @throws ClassCastException if entry is not an instance of ZipArchiveEntry
    428        public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException {
    429            if(finished) {
    430                throw new IOException("Stream has already been finished");
    431            }
    432
    433            if (entry != null) {
    434                closeArchiveEntry();
    435            }
    436
    437            entry = ((ZipArchiveEntry) archiveEntry);
    438            entries.add(entry);
    439
    440            if (entry.getMethod() == -1) { // not specified
    441                entry.setMethod(method);
    442            }
    443
    444            if (entry.getTime() == -1) { // not specified
    445                entry.setTime(System.currentTimeMillis());
    446            }
    447
    448            // Size/CRC not required if RandomAccessFile is used
    449            if (entry.getMethod() == STORED && raf == null) {
    450                if (entry.getSize() == -1) {
    451                    throw new ZipException("uncompressed size is required for"
    452                                           + " STORED method when not writing to a"
    453                                           + " file");
    454                }
    455                if (entry.getCrc() == -1) {
    456                    throw new ZipException("crc checksum is required for STORED"
    457                                           + " method when not writing to a file");
    458                }
    459                entry.setCompressedSize(entry.getSize());
    460            }
    461
    462            if (entry.getMethod() == DEFLATED && hasCompressionLevelChanged) {
    463                def.setLevel(level);
    464                hasCompressionLevelChanged = false;
    465            }
    466            writeLocalFileHeader(entry);
    467        }
    468
    469        /**
    470         * Set the file comment.
    471         * @param comment the comment
    472         */
    473        public void setComment(String comment) {
    474            this.comment = comment;
    475        }
    476
    477        /**
    478         * Sets the compression level for subsequent entries.
    479         *
    480         * <p>Default is Deflater.DEFAULT_COMPRESSION.</p>
    481         * @param level the compression level.
    482         * @throws IllegalArgumentException if an invalid compression
    483         * level is specified.
    484         */
    485        public void setLevel(int level) {
    486            if (level < Deflater.DEFAULT_COMPRESSION
    487                || level > Deflater.BEST_COMPRESSION) {
    488                throw new IllegalArgumentException("Invalid compression level: "
    489                                                   + level);
    490            }
    491            hasCompressionLevelChanged = (this.level != level);
    492            this.level = level;
    493        }
    494
    495        /**
    496         * Sets the default compression method for subsequent entries.
    497         *
    498         * <p>Default is DEFLATED.</p>
    499         * @param method an <code>int</code> from java.util.zip.ZipEntry
    500         */
    501        public void setMethod(int method) {
    502            this.method = method;
    503        }
    504
    505        /**
    506         * Writes bytes to ZIP entry.
    507         * @param b the byte array to write
    508         * @param offset the start position to write from
    509         * @param length the number of bytes to write
    510         * @throws IOException on error
    511         */
    512        public void write(byte[] b, int offset, int length) throws IOException {
    513            if (entry.getMethod() == DEFLATED) {
    514                if (length > 0) {
    515                    if (!def.finished()) {
    516                        if (length <= DEFLATER_BLOCK_SIZE) {
    517                            def.setInput(b, offset, length);
    518                            deflateUntilInputIsNeeded();
    519                        } else {
    520                            final int fullblocks = length / DEFLATER_BLOCK_SIZE;
    521                            for (int i = 0; i < fullblocks; i++) {
    522                                def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE,
    523                                             DEFLATER_BLOCK_SIZE);
    524                                deflateUntilInputIsNeeded();
    525                            }
    526                            final int done = fullblocks * DEFLATER_BLOCK_SIZE;
    527                            if (done < length) {
    528                                def.setInput(b, offset + done, length - done);
    529                                deflateUntilInputIsNeeded();
    530                            }
    531                        }
    532                    }
    533                }
    534            } else {
    535                writeOut(b, offset, length);
    536                written += length;
    537            }
    538            crc.update(b, offset, length);
    539            count(length);
    540        }
    541
    542        /**
    543         * Closes this output stream and releases any system resources
    544         * associated with the stream.
    545         *
    546         * @exception  IOException  if an I/O error occurs.
    547         */
    548        public void close() throws IOException {
    549            if(!finished) {
    550                finish();
    551            }
    552
    553            if (raf != null) {
    554                raf.close();
    555            }
    556            if (out != null) {
    557                out.close();
    558            }
    559        }
    560
    561        /**
    562         * Flushes this output stream and forces any buffered output bytes
    563         * to be written out to the stream.
    564         *
    565         * @exception  IOException  if an I/O error occurs.
    566         */
    567        public void flush() throws IOException {
    568            if (out != null) {
    569                out.flush();
    570            }
    571        }
    572
    573        /*
    574         * Various ZIP constants
    575         */
    576        /**
    577         * local file header signature
    578         */
    579        static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes();
    580        /**
    581         * data descriptor signature
    582         */
    583        static final byte[] DD_SIG = ZipLong.getBytes(0X08074B50L);
    584        /**
    585         * central file header signature
    586         */
    587        static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes();
    588        /**
    589         * end of central dir signature
    590         */
    591        static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L);
    592
    593        /**
    594         * Writes next block of compressed data to the output stream.
    595         * @throws IOException on error
    596         */
    597        protected final void deflate() throws IOException {
    598            int len = def.deflate(buf, 0, buf.length);
    599            if (len > 0) {
    600                writeOut(buf, 0, len);
    601            }
    602        }
    603
    604        /**
    605         * Writes the local file header entry
    606         * @param ze the entry to write
    607         * @throws IOException on error
    608         */
    609        protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException {
    610
    611            boolean encodable = zipEncoding.canEncode(ze.getName());
    612
    613            final ZipEncoding entryEncoding;
    614
    615            if (!encodable && fallbackToUTF8) {
    616                entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
    617            } else {
    618                entryEncoding = zipEncoding;
    619            }
    620
    621            ByteBuffer name = entryEncoding.encode(ze.getName());
    622
    623            if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) {
    624
    625                if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
    626                    || !encodable) {
    627                    ze.addExtraField(new UnicodePathExtraField(ze.getName(),
    628                                                               name.array(),
    629                                                               name.arrayOffset(),
    630                                                               name.limit()));
    631                }
    632
    633                String comm = ze.getComment();
    634                if (comm != null && !"".equals(comm)) {
    635
    636                    boolean commentEncodable = this.zipEncoding.canEncode(comm);
    637
    638                    if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS
    639                        || !commentEncodable) {
    640                        ByteBuffer commentB = entryEncoding.encode(comm);
    641                        ze.addExtraField(new UnicodeCommentExtraField(comm,
    642                                                                      commentB.array(),
    643                                                                      commentB.arrayOffset(),
    644                                                                      commentB.limit())
    645                                         );
    646                    }
    647                }
    648            }
    649
    650            offsets.put(ze, ZipLong.getBytes(written));
    651
    652            writeOut(LFH_SIG);
    653            written += WORD;
    654
    655            //store method in local variable to prevent multiple method calls
    656            final int zipMethod = ze.getMethod();
    657
    658            writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
    659                                                             !encodable
    660                                                             && fallbackToUTF8);
    661            written += WORD;
    662
    663            // compression method
    664            writeOut(ZipShort.getBytes(zipMethod));
    665            written += SHORT;
    666
    667            // last mod. time and date
    668            writeOut(ZipUtil.toDosTime(ze.getTime()));
    669            written += WORD;
    670
    671            // CRC
    672            // compressed length
    673            // uncompressed length
    674            localDataStart = written;
    675            if (zipMethod == DEFLATED || raf != null) {
    676                writeOut(LZERO);
    677                writeOut(LZERO);
    678                writeOut(LZERO);
    679            } else {
    680                writeOut(ZipLong.getBytes(ze.getCrc()));
    681                writeOut(ZipLong.getBytes(ze.getSize()));
    682                writeOut(ZipLong.getBytes(ze.getSize()));
    683            }
    684            // CheckStyle:MagicNumber OFF
    685            written += 12;
    686            // CheckStyle:MagicNumber ON
    687
    688            // file name length
    689            writeOut(ZipShort.getBytes(name.limit()));
    690            written += SHORT;
    691
    692            // extra field length
    693            byte[] extra = ze.getLocalFileDataExtra();
    694            writeOut(ZipShort.getBytes(extra.length));
    695            written += SHORT;
    696
    697            // file name
    698            writeOut(name.array(), name.arrayOffset(), name.limit());
    699            written += name.limit();
    700
    701            // extra field
    702            writeOut(extra);
    703            written += extra.length;
    704
    705            dataStart = written;
    706        }
    707
    708        /**
    709         * Writes the data descriptor entry.
    710         * @param ze the entry to write
    711         * @throws IOException on error
    712         */
    713        protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException {
    714            if (ze.getMethod() != DEFLATED || raf != null) {
    715                return;
    716            }
    717            writeOut(DD_SIG);
    718            writeOut(ZipLong.getBytes(entry.getCrc()));
    719            writeOut(ZipLong.getBytes(entry.getCompressedSize()));
    720            writeOut(ZipLong.getBytes(entry.getSize()));
    721            // CheckStyle:MagicNumber OFF
    722            written += 16;
    723            // CheckStyle:MagicNumber ON
    724        }
    725
    726        /**
    727         * Writes the central file header entry.
    728         * @param ze the entry to write
    729         * @throws IOException on error
    730         */
    731        protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException {
    732            writeOut(CFH_SIG);
    733            written += WORD;
    734
    735            // version made by
    736            // CheckStyle:MagicNumber OFF
    737            writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 20));
    738            written += SHORT;
    739
    740            final int zipMethod = ze.getMethod();
    741            final boolean encodable = zipEncoding.canEncode(ze.getName());
    742            writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod,
    743                                                             !encodable
    744                                                             && fallbackToUTF8);
    745            written += WORD;
    746
    747            // compression method
    748            writeOut(ZipShort.getBytes(zipMethod));
    749            written += SHORT;
    750
    751            // last mod. time and date
    752            writeOut(ZipUtil.toDosTime(ze.getTime()));
    753            written += WORD;
    754
    755            // CRC
    756            // compressed length
    757            // uncompressed length
    758            writeOut(ZipLong.getBytes(ze.getCrc()));
    759            writeOut(ZipLong.getBytes(ze.getCompressedSize()));
    760            writeOut(ZipLong.getBytes(ze.getSize()));
    761            // CheckStyle:MagicNumber OFF
    762            written += 12;
    763            // CheckStyle:MagicNumber ON
    764
    765            // file name length
    766            final ZipEncoding entryEncoding;
    767
    768            if (!encodable && fallbackToUTF8) {
    769                entryEncoding = ZipEncodingHelper.UTF8_ZIP_ENCODING;
    770            } else {
    771                entryEncoding = zipEncoding;
    772            }
    773
    774            ByteBuffer name = entryEncoding.encode(ze.getName());
    775
    776            writeOut(ZipShort.getBytes(name.limit()));
    777            written += SHORT;
    778
    779            // extra field length
    780            byte[] extra = ze.getCentralDirectoryExtra();
    781            writeOut(ZipShort.getBytes(extra.length));
    782            written += SHORT;
    783
    784            // file comment length
    785            String comm = ze.getComment();
    786            if (comm == null) {
    787                comm = "";
    788            }
    789
    790            ByteBuffer commentB = entryEncoding.encode(comm);
    791
    792            writeOut(ZipShort.getBytes(commentB.limit()));
    793            written += SHORT;
    794
    795            // disk number start
    796            writeOut(ZERO);
    797            written += SHORT;
    798
    799            // internal file attributes
    800            writeOut(ZipShort.getBytes(ze.getInternalAttributes()));
    801            written += SHORT;
    802
    803            // external file attributes
    804            writeOut(ZipLong.getBytes(ze.getExternalAttributes()));
    805            written += WORD;
    806
    807            // relative offset of LFH
    808            writeOut((byte[]) offsets.get(ze));
    809            written += WORD;
    810
    811            // file name
    812            writeOut(name.array(), name.arrayOffset(), name.limit());
    813            written += name.limit();
    814
    815            // extra field
    816            writeOut(extra);
    817            written += extra.length;
    818
    819            // file comment
    820            writeOut(commentB.array(), commentB.arrayOffset(), commentB.limit());
    821            written += commentB.limit();
    822        }
    823
    824        /**
    825         * Writes the &quot;End of central dir record&quot;.
    826         * @throws IOException on error
    827         */
    828        protected void writeCentralDirectoryEnd() throws IOException {
    829            writeOut(EOCD_SIG);
    830
    831            // disk numbers
    832            writeOut(ZERO);
    833            writeOut(ZERO);
    834
    835            // number of entries
    836            byte[] num = ZipShort.getBytes(entries.size());
    837            writeOut(num);
    838            writeOut(num);
    839
    840            // length and location of CD
    841            writeOut(ZipLong.getBytes(cdLength));
    842            writeOut(ZipLong.getBytes(cdOffset));
    843
    844            // ZIP file comment
    845            ByteBuffer data = this.zipEncoding.encode(comment);
    846            writeOut(ZipShort.getBytes(data.limit()));
    847            writeOut(data.array(), data.arrayOffset(), data.limit());
    848        }
    849
    850        /**
    851         * Write bytes to output or random access file.
    852         * @param data the byte array to write
    853         * @throws IOException on error
    854         */
    855        protected final void writeOut(byte[] data) throws IOException {
    856            writeOut(data, 0, data.length);
    857        }
    858
    859        /**
    860         * Write bytes to output or random access file.
    861         * @param data the byte array to write
    862         * @param offset the start position to write from
    863         * @param length the number of bytes to write
    864         * @throws IOException on error
    865         */
    866        protected final void writeOut(byte[] data, int offset, int length)
    867            throws IOException {
    868            if (raf != null) {
    869                raf.write(data, offset, length);
    870            } else {
    871                out.write(data, offset, length);
    872            }
    873        }
    874
    875        private void deflateUntilInputIsNeeded() throws IOException {
    876            while (!def.needsInput()) {
    877                deflate();
    878            }
    879        }
    880
    881        private void writeVersionNeededToExtractAndGeneralPurposeBits(final int
    882                                                                      zipMethod,
    883                                                                      final boolean
    884                                                                      utfFallback)
    885            throws IOException {
    886
    887            // CheckStyle:MagicNumber OFF
    888            int versionNeededToExtract = 10;
    889            int generalPurposeFlag = (useEFS || utfFallback) ? EFS_FLAG : 0;
    890            if (zipMethod == DEFLATED && raf == null) {
    891                // requires version 2 as we are going to store length info
    892                // in the data descriptor
    893                versionNeededToExtract =  20;
    894                // bit3 set to signal, we use a data descriptor
    895                generalPurposeFlag |= 8;
    896            }
    897            // CheckStyle:MagicNumber ON
    898
    899            // version needed to extract
    900            writeOut(ZipShort.getBytes(versionNeededToExtract));
    901            // general purpose bit flag
    902            writeOut(ZipShort.getBytes(generalPurposeFlag));
    903        }
    904
    905        /**
    906         * enum that represents the possible policies for creating Unicode
    907         * extra fields.
    908         */
    909        public static final class UnicodeExtraFieldPolicy {
    910            /**
    911             * Always create Unicode extra fields.
    912             */
    913            public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always");
    914            /**
    915             * Never create Unicode extra fields.
    916             */
    917            public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never");
    918            /**
    919             * Create Unicode extra fields for filenames that cannot be
    920             * encoded using the specified encoding.
    921             */
    922            public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE =
    923                new UnicodeExtraFieldPolicy("not encodeable");
    924
    925            private final String name;
    926            private UnicodeExtraFieldPolicy(String n) {
    927                name = n;
    928            }
    929            public String toString() {
    930                return name;
    931            }
    932        }
    933
    934        public ArchiveEntry createArchiveEntry(File inputFile, String entryName)
    935                throws IOException {
    936            if(finished) {
    937                throw new IOException("Stream has already been finished");
    938            }
    939            return new ZipArchiveEntry(inputFile, entryName);
    940        }
    941    }