Home | History | Annotate | Download | only in zip
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  * Copyright (c) 1996, 2015, Oracle and/or its affiliates. All rights reserved.
      4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      5  *
      6  * This code is free software; you can redistribute it and/or modify it
      7  * under the terms of the GNU General Public License version 2 only, as
      8  * published by the Free Software Foundation.  Oracle designates this
      9  * particular file as subject to the "Classpath" exception as provided
     10  * by Oracle in the LICENSE file that accompanied this code.
     11  *
     12  * This code is distributed in the hope that it will be useful, but WITHOUT
     13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     15  * version 2 for more details (a copy is included in the LICENSE file that
     16  * accompanied this code).
     17  *
     18  * You should have received a copy of the GNU General Public License version
     19  * 2 along with this work; if not, write to the Free Software Foundation,
     20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     21  *
     22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     23  * or visit www.oracle.com if you need additional information or have any
     24  * questions.
     25  */
     26 
     27 package java.util.zip;
     28 
     29 import java.io.OutputStream;
     30 import java.io.IOException;
     31 import java.nio.charset.Charset;
     32 import java.nio.charset.StandardCharsets;
     33 import java.util.Vector;
     34 import java.util.HashSet;
     35 import static java.util.zip.ZipConstants64.*;
     36 import static java.util.zip.ZipUtils.*;
     37 
     38 /**
     39  * This class implements an output stream filter for writing files in the
     40  * ZIP file format. Includes support for both compressed and uncompressed
     41  * entries.
     42  *
     43  * @author      David Connelly
     44  */
     45 public
     46 class ZipOutputStream extends DeflaterOutputStream implements ZipConstants {
     47 
     48     /**
     49      * Whether to use ZIP64 for zip files with more than 64k entries.
     50      * Until ZIP64 support in zip implementations is ubiquitous, this
     51      * system property allows the creation of zip files which can be
     52      * read by legacy zip implementations which tolerate "incorrect"
     53      * total entry count fields, such as the ones in jdk6, and even
     54      * some in jdk7.
     55      */
     56     // Android-changed: Force to false.
     57     private static final boolean inhibitZip64 = false;
     58     //  Boolean.parseBoolean(
     59     //      java.security.AccessController.doPrivileged(
     60     //          new sun.security.action.GetPropertyAction(
     61     //              "jdk.util.zip.inhibitZip64", "false")));
     62 
     63     private static class XEntry {
     64         final ZipEntry entry;
     65         final long offset;
     66         public XEntry(ZipEntry entry, long offset) {
     67             this.entry = entry;
     68             this.offset = offset;
     69         }
     70     }
     71 
     72     private XEntry current;
     73     private Vector<XEntry> xentries = new Vector<>();
     74     private HashSet<String> names = new HashSet<>();
     75     private CRC32 crc = new CRC32();
     76     private long written = 0;
     77     private long locoff = 0;
     78     private byte[] comment;
     79     private int method = DEFLATED;
     80     private boolean finished;
     81 
     82     private boolean closed = false;
     83 
     84     private final ZipCoder zc;
     85 
     86     private static int version(ZipEntry e) throws ZipException {
     87         switch (e.method) {
     88         case DEFLATED: return 20;
     89         case STORED:   return 10;
     90         default: throw new ZipException("unsupported compression method");
     91         }
     92     }
     93 
     94     /**
     95      * Checks to make sure that this stream has not been closed.
     96      */
     97     private void ensureOpen() throws IOException {
     98         if (closed) {
     99             throw new IOException("Stream closed");
    100         }
    101     }
    102     /**
    103      * Compression method for uncompressed (STORED) entries.
    104      */
    105     public static final int STORED = ZipEntry.STORED;
    106 
    107     /**
    108      * Compression method for compressed (DEFLATED) entries.
    109      */
    110     public static final int DEFLATED = ZipEntry.DEFLATED;
    111 
    112     /**
    113      * Creates a new ZIP output stream.
    114      *
    115      * <p>The UTF-8 {@link java.nio.charset.Charset charset} is used
    116      * to encode the entry names and comments.
    117      *
    118      * @param out the actual output stream
    119      */
    120     public ZipOutputStream(OutputStream out) {
    121         this(out, StandardCharsets.UTF_8);
    122     }
    123 
    124     /**
    125      * Creates a new ZIP output stream.
    126      *
    127      * @param out the actual output stream
    128      *
    129      * @param charset the {@linkplain java.nio.charset.Charset charset}
    130      *                to be used to encode the entry names and comments
    131      *
    132      * @since 1.7
    133      */
    134     public ZipOutputStream(OutputStream out, Charset charset) {
    135         super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true));
    136         if (charset == null)
    137             throw new NullPointerException("charset is null");
    138         this.zc = ZipCoder.get(charset);
    139         usesDefaultDeflater = true;
    140     }
    141 
    142     /**
    143      * Sets the ZIP file comment.
    144      * @param comment the comment string
    145      * @exception IllegalArgumentException if the length of the specified
    146      *            ZIP file comment is greater than 0xFFFF bytes
    147      */
    148     public void setComment(String comment) {
    149         if (comment != null) {
    150             this.comment = zc.getBytes(comment);
    151             if (this.comment.length > 0xffff)
    152                 throw new IllegalArgumentException("ZIP file comment too long.");
    153         }
    154     }
    155 
    156     /**
    157      * Sets the default compression method for subsequent entries. This
    158      * default will be used whenever the compression method is not specified
    159      * for an individual ZIP file entry, and is initially set to DEFLATED.
    160      * @param method the default compression method
    161      * @exception IllegalArgumentException if the specified compression method
    162      *            is invalid
    163      */
    164     public void setMethod(int method) {
    165         if (method != DEFLATED && method != STORED) {
    166             throw new IllegalArgumentException("invalid compression method");
    167         }
    168         this.method = method;
    169     }
    170 
    171     /**
    172      * Sets the compression level for subsequent entries which are DEFLATED.
    173      * The default setting is DEFAULT_COMPRESSION.
    174      * @param level the compression level (0-9)
    175      * @exception IllegalArgumentException if the compression level is invalid
    176      */
    177     public void setLevel(int level) {
    178         def.setLevel(level);
    179     }
    180 
    181     /**
    182      * Begins writing a new ZIP file entry and positions the stream to the
    183      * start of the entry data. Closes the current entry if still active.
    184      * The default compression method will be used if no compression method
    185      * was specified for the entry, and the current time will be used if
    186      * the entry has no set modification time.
    187      * @param e the ZIP entry to be written
    188      * @exception ZipException if a ZIP format error has occurred
    189      * @exception IOException if an I/O error has occurred
    190      */
    191     public void putNextEntry(ZipEntry e) throws IOException {
    192         ensureOpen();
    193         if (current != null) {
    194             closeEntry();       // close previous entry
    195         }
    196         if (e.xdostime == -1) {
    197             // by default, do NOT use extended timestamps in extra
    198             // data, for now.
    199             e.setTime(System.currentTimeMillis());
    200         }
    201         if (e.method == -1) {
    202             e.method = method;  // use default method
    203         }
    204         // store size, compressed size, and crc-32 in LOC header
    205         e.flag = 0;
    206         switch (e.method) {
    207         case DEFLATED:
    208             // store size, compressed size, and crc-32 in data descriptor
    209             // immediately following the compressed entry data
    210             if (e.size  == -1 || e.csize == -1 || e.crc   == -1)
    211                 e.flag = 8;
    212 
    213             break;
    214         case STORED:
    215             // compressed size, uncompressed size, and crc-32 must all be
    216             // set for entries using STORED compression method
    217             if (e.size == -1) {
    218                 e.size = e.csize;
    219             } else if (e.csize == -1) {
    220                 e.csize = e.size;
    221             } else if (e.size != e.csize) {
    222                 throw new ZipException(
    223                     "STORED entry where compressed != uncompressed size");
    224             }
    225             if (e.size == -1 || e.crc == -1) {
    226                 throw new ZipException(
    227                     "STORED entry missing size, compressed size, or crc-32");
    228             }
    229             break;
    230         default:
    231             throw new ZipException("unsupported compression method");
    232         }
    233         if (! names.add(e.name)) {
    234             throw new ZipException("duplicate entry: " + e.name);
    235         }
    236         if (zc.isUTF8())
    237             e.flag |= EFS;
    238         current = new XEntry(e, written);
    239         xentries.add(current);
    240         writeLOC(current);
    241     }
    242 
    243     /**
    244      * Closes the current ZIP entry and positions the stream for writing
    245      * the next entry.
    246      * @exception ZipException if a ZIP format error has occurred
    247      * @exception IOException if an I/O error has occurred
    248      */
    249     public void closeEntry() throws IOException {
    250         ensureOpen();
    251         if (current != null) {
    252             ZipEntry e = current.entry;
    253             switch (e.method) {
    254             case DEFLATED:
    255                 def.finish();
    256                 while (!def.finished()) {
    257                     deflate();
    258                 }
    259                 if ((e.flag & 8) == 0) {
    260                     // verify size, compressed size, and crc-32 settings
    261                     if (e.size != def.getBytesRead()) {
    262                         throw new ZipException(
    263                             "invalid entry size (expected " + e.size +
    264                             " but got " + def.getBytesRead() + " bytes)");
    265                     }
    266                     if (e.csize != def.getBytesWritten()) {
    267                         throw new ZipException(
    268                             "invalid entry compressed size (expected " +
    269                             e.csize + " but got " + def.getBytesWritten() + " bytes)");
    270                     }
    271                     if (e.crc != crc.getValue()) {
    272                         throw new ZipException(
    273                             "invalid entry CRC-32 (expected 0x" +
    274                             Long.toHexString(e.crc) + " but got 0x" +
    275                             Long.toHexString(crc.getValue()) + ")");
    276                     }
    277                 } else {
    278                     e.size  = def.getBytesRead();
    279                     e.csize = def.getBytesWritten();
    280                     e.crc = crc.getValue();
    281                     writeEXT(e);
    282                 }
    283                 def.reset();
    284                 written += e.csize;
    285                 break;
    286             case STORED:
    287                 // we already know that both e.size and e.csize are the same
    288                 if (e.size != written - locoff) {
    289                     throw new ZipException(
    290                         "invalid entry size (expected " + e.size +
    291                         " but got " + (written - locoff) + " bytes)");
    292                 }
    293                 if (e.crc != crc.getValue()) {
    294                     throw new ZipException(
    295                          "invalid entry crc-32 (expected 0x" +
    296                          Long.toHexString(e.crc) + " but got 0x" +
    297                          Long.toHexString(crc.getValue()) + ")");
    298                 }
    299                 break;
    300             default:
    301                 throw new ZipException("invalid compression method");
    302             }
    303             crc.reset();
    304             current = null;
    305         }
    306     }
    307 
    308     /**
    309      * Writes an array of bytes to the current ZIP entry data. This method
    310      * will block until all the bytes are written.
    311      * @param b the data to be written
    312      * @param off the start offset in the data
    313      * @param len the number of bytes that are written
    314      * @exception ZipException if a ZIP file error has occurred
    315      * @exception IOException if an I/O error has occurred
    316      */
    317     public synchronized void write(byte[] b, int off, int len)
    318         throws IOException
    319     {
    320         ensureOpen();
    321         if (off < 0 || len < 0 || off > b.length - len) {
    322             throw new IndexOutOfBoundsException();
    323         } else if (len == 0) {
    324             return;
    325         }
    326 
    327         if (current == null) {
    328             throw new ZipException("no current ZIP entry");
    329         }
    330         ZipEntry entry = current.entry;
    331         switch (entry.method) {
    332         case DEFLATED:
    333             super.write(b, off, len);
    334             break;
    335         case STORED:
    336             written += len;
    337             if (written - locoff > entry.size) {
    338                 throw new ZipException(
    339                     "attempt to write past end of STORED entry");
    340             }
    341             out.write(b, off, len);
    342             break;
    343         default:
    344             throw new ZipException("invalid compression method");
    345         }
    346         crc.update(b, off, len);
    347     }
    348 
    349     /**
    350      * Finishes writing the contents of the ZIP output stream without closing
    351      * the underlying stream. Use this method when applying multiple filters
    352      * in succession to the same output stream.
    353      * @exception ZipException if a ZIP file error has occurred
    354      * @exception IOException if an I/O exception has occurred
    355      */
    356     public void finish() throws IOException {
    357         ensureOpen();
    358         if (finished) {
    359             return;
    360         }
    361         // Android-changed: Fix for ZipOutputStreamTest#testCreateEmpty
    362         if (xentries.isEmpty()) {
    363             throw new ZipException("No entries");
    364         }
    365         if (current != null) {
    366             closeEntry();
    367         }
    368         // write central directory
    369         long off = written;
    370         for (XEntry xentry : xentries)
    371             writeCEN(xentry);
    372         writeEND(off, written - off);
    373         finished = true;
    374     }
    375 
    376     /**
    377      * Closes the ZIP output stream as well as the stream being filtered.
    378      * @exception ZipException if a ZIP file error has occurred
    379      * @exception IOException if an I/O error has occurred
    380      */
    381     public void close() throws IOException {
    382         if (!closed) {
    383             super.close();
    384             closed = true;
    385         }
    386     }
    387 
    388     /*
    389      * Writes local file (LOC) header for specified entry.
    390      */
    391     private void writeLOC(XEntry xentry) throws IOException {
    392         ZipEntry e = xentry.entry;
    393         int flag = e.flag;
    394         boolean hasZip64 = false;
    395         int elen = getExtraLen(e.extra);
    396 
    397         writeInt(LOCSIG);               // LOC header signature
    398         if ((flag & 8) == 8) {
    399             writeShort(version(e));     // version needed to extract
    400             writeShort(flag);           // general purpose bit flag
    401             writeShort(e.method);       // compression method
    402             writeInt(e.xdostime);       // last modification time
    403             // store size, uncompressed size, and crc-32 in data descriptor
    404             // immediately following compressed entry data
    405             writeInt(0);
    406             writeInt(0);
    407             writeInt(0);
    408         } else {
    409             if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
    410                 hasZip64 = true;
    411                 writeShort(45);         // ver 4.5 for zip64
    412             } else {
    413                 writeShort(version(e)); // version needed to extract
    414             }
    415             writeShort(flag);           // general purpose bit flag
    416             writeShort(e.method);       // compression method
    417             writeInt(e.xdostime);       // last modification time
    418             writeInt(e.crc);            // crc-32
    419             if (hasZip64) {
    420                 writeInt(ZIP64_MAGICVAL);
    421                 writeInt(ZIP64_MAGICVAL);
    422                 elen += 20;        //headid(2) + size(2) + size(8) + csize(8)
    423             } else {
    424                 writeInt(e.csize);  // compressed size
    425                 writeInt(e.size);   // uncompressed size
    426             }
    427         }
    428         byte[] nameBytes = zc.getBytes(e.name);
    429         writeShort(nameBytes.length);
    430 
    431         int elenEXTT = 0;               // info-zip extended timestamp
    432         int flagEXTT = 0;
    433         if (e.mtime != null) {
    434             elenEXTT += 4;
    435             flagEXTT |= EXTT_FLAG_LMT;
    436         }
    437         if (e.atime != null) {
    438             elenEXTT += 4;
    439             flagEXTT |= EXTT_FLAG_LAT;
    440         }
    441         if (e.ctime != null) {
    442             elenEXTT += 4;
    443             flagEXTT |= EXTT_FLAT_CT;
    444         }
    445         if (flagEXTT != 0)
    446             elen += (elenEXTT + 5);    // headid(2) + size(2) + flag(1) + data
    447         writeShort(elen);
    448         writeBytes(nameBytes, 0, nameBytes.length);
    449         if (hasZip64) {
    450             writeShort(ZIP64_EXTID);
    451             writeShort(16);
    452             writeLong(e.size);
    453             writeLong(e.csize);
    454         }
    455         if (flagEXTT != 0) {
    456             writeShort(EXTID_EXTT);
    457             writeShort(elenEXTT + 1);      // flag + data
    458             writeByte(flagEXTT);
    459             if (e.mtime != null)
    460                 writeInt(fileTimeToUnixTime(e.mtime));
    461             if (e.atime != null)
    462                 writeInt(fileTimeToUnixTime(e.atime));
    463             if (e.ctime != null)
    464                 writeInt(fileTimeToUnixTime(e.ctime));
    465         }
    466         writeExtra(e.extra);
    467         locoff = written;
    468     }
    469 
    470     /*
    471      * Writes extra data descriptor (EXT) for specified entry.
    472      */
    473     private void writeEXT(ZipEntry e) throws IOException {
    474         writeInt(EXTSIG);           // EXT header signature
    475         writeInt(e.crc);            // crc-32
    476         if (e.csize >= ZIP64_MAGICVAL || e.size >= ZIP64_MAGICVAL) {
    477             writeLong(e.csize);
    478             writeLong(e.size);
    479         } else {
    480             writeInt(e.csize);          // compressed size
    481             writeInt(e.size);           // uncompressed size
    482         }
    483     }
    484 
    485     /*
    486      * Write central directory (CEN) header for specified entry.
    487      * REMIND: add support for file attributes
    488      */
    489     private void writeCEN(XEntry xentry) throws IOException {
    490         ZipEntry e  = xentry.entry;
    491         int flag = e.flag;
    492         int version = version(e);
    493         long csize = e.csize;
    494         long size = e.size;
    495         long offset = xentry.offset;
    496         int elenZIP64 = 0;
    497         boolean hasZip64 = false;
    498 
    499         if (e.csize >= ZIP64_MAGICVAL) {
    500             csize = ZIP64_MAGICVAL;
    501             elenZIP64 += 8;              // csize(8)
    502             hasZip64 = true;
    503         }
    504         if (e.size >= ZIP64_MAGICVAL) {
    505             size = ZIP64_MAGICVAL;    // size(8)
    506             elenZIP64 += 8;
    507             hasZip64 = true;
    508         }
    509         if (xentry.offset >= ZIP64_MAGICVAL) {
    510             offset = ZIP64_MAGICVAL;
    511             elenZIP64 += 8;              // offset(8)
    512             hasZip64 = true;
    513         }
    514         writeInt(CENSIG);           // CEN header signature
    515         if (hasZip64) {
    516             writeShort(45);         // ver 4.5 for zip64
    517             writeShort(45);
    518         } else {
    519             writeShort(version);    // version made by
    520             writeShort(version);    // version needed to extract
    521         }
    522         writeShort(flag);           // general purpose bit flag
    523         writeShort(e.method);       // compression method
    524         writeInt(e.xdostime);       // last modification time
    525         writeInt(e.crc);            // crc-32
    526         writeInt(csize);            // compressed size
    527         writeInt(size);             // uncompressed size
    528         byte[] nameBytes = zc.getBytes(e.name);
    529         writeShort(nameBytes.length);
    530 
    531         int elen = getExtraLen(e.extra);
    532         if (hasZip64) {
    533             elen += (elenZIP64 + 4);// + headid(2) + datasize(2)
    534         }
    535         // cen info-zip extended timestamp only outputs mtime
    536         // but set the flag for a/ctime, if present in loc
    537         int flagEXTT = 0;
    538         if (e.mtime != null) {
    539             elen += 4;              // + mtime(4)
    540             flagEXTT |= EXTT_FLAG_LMT;
    541         }
    542         if (e.atime != null) {
    543             flagEXTT |= EXTT_FLAG_LAT;
    544         }
    545         if (e.ctime != null) {
    546             flagEXTT |= EXTT_FLAT_CT;
    547         }
    548         if (flagEXTT != 0) {
    549             elen += 5;             // headid + sz + flag
    550         }
    551         writeShort(elen);
    552         byte[] commentBytes;
    553         if (e.comment != null) {
    554             commentBytes = zc.getBytes(e.comment);
    555             writeShort(Math.min(commentBytes.length, 0xffff));
    556         } else {
    557             commentBytes = null;
    558             writeShort(0);
    559         }
    560         writeShort(0);              // starting disk number
    561         writeShort(0);              // internal file attributes (unused)
    562         writeInt(0);                // external file attributes (unused)
    563         writeInt(offset);           // relative offset of local header
    564         writeBytes(nameBytes, 0, nameBytes.length);
    565 
    566         // take care of EXTID_ZIP64 and EXTID_EXTT
    567         if (hasZip64) {
    568             writeShort(ZIP64_EXTID);// Zip64 extra
    569             writeShort(elenZIP64);
    570             if (size == ZIP64_MAGICVAL)
    571                 writeLong(e.size);
    572             if (csize == ZIP64_MAGICVAL)
    573                 writeLong(e.csize);
    574             if (offset == ZIP64_MAGICVAL)
    575                 writeLong(xentry.offset);
    576         }
    577         if (flagEXTT != 0) {
    578             writeShort(EXTID_EXTT);
    579             if (e.mtime != null) {
    580                 writeShort(5);      // flag + mtime
    581                 writeByte(flagEXTT);
    582                 writeInt(fileTimeToUnixTime(e.mtime));
    583             } else {
    584                 writeShort(1);      // flag only
    585                 writeByte(flagEXTT);
    586             }
    587         }
    588         writeExtra(e.extra);
    589         if (commentBytes != null) {
    590             writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff));
    591         }
    592     }
    593 
    594     /*
    595      * Writes end of central directory (END) header.
    596      */
    597     private void writeEND(long off, long len) throws IOException {
    598         boolean hasZip64 = false;
    599         long xlen = len;
    600         long xoff = off;
    601         if (xlen >= ZIP64_MAGICVAL) {
    602             xlen = ZIP64_MAGICVAL;
    603             hasZip64 = true;
    604         }
    605         if (xoff >= ZIP64_MAGICVAL) {
    606             xoff = ZIP64_MAGICVAL;
    607             hasZip64 = true;
    608         }
    609         int count = xentries.size();
    610         if (count >= ZIP64_MAGICCOUNT) {
    611             hasZip64 |= !inhibitZip64;
    612             if (hasZip64) {
    613                 count = ZIP64_MAGICCOUNT;
    614             }
    615         }
    616         if (hasZip64) {
    617             long off64 = written;
    618             //zip64 end of central directory record
    619             writeInt(ZIP64_ENDSIG);        // zip64 END record signature
    620             writeLong(ZIP64_ENDHDR - 12);  // size of zip64 end
    621             writeShort(45);                // version made by
    622             writeShort(45);                // version needed to extract
    623             writeInt(0);                   // number of this disk
    624             writeInt(0);                   // central directory start disk
    625             writeLong(xentries.size());    // number of directory entires on disk
    626             writeLong(xentries.size());    // number of directory entires
    627             writeLong(len);                // length of central directory
    628             writeLong(off);                // offset of central directory
    629 
    630             //zip64 end of central directory locator
    631             writeInt(ZIP64_LOCSIG);        // zip64 END locator signature
    632             writeInt(0);                   // zip64 END start disk
    633             writeLong(off64);              // offset of zip64 END
    634             writeInt(1);                   // total number of disks (?)
    635         }
    636         writeInt(ENDSIG);                 // END record signature
    637         writeShort(0);                    // number of this disk
    638         writeShort(0);                    // central directory start disk
    639         writeShort(count);                // number of directory entries on disk
    640         writeShort(count);                // total number of directory entries
    641         writeInt(xlen);                   // length of central directory
    642         writeInt(xoff);                   // offset of central directory
    643         if (comment != null) {            // zip file comment
    644             writeShort(comment.length);
    645             writeBytes(comment, 0, comment.length);
    646         } else {
    647             writeShort(0);
    648         }
    649     }
    650 
    651     /*
    652      * Returns the length of extra data without EXTT and ZIP64.
    653      */
    654     private int getExtraLen(byte[] extra) {
    655         if (extra == null)
    656             return 0;
    657         int skipped = 0;
    658         int len = extra.length;
    659         int off = 0;
    660         while (off + 4 <= len) {
    661             int tag = get16(extra, off);
    662             int sz = get16(extra, off + 2);
    663             if (sz < 0 || (off + 4 + sz) > len) {
    664                 break;
    665             }
    666             if (tag == EXTID_EXTT || tag == EXTID_ZIP64) {
    667                 skipped += (sz + 4);
    668             }
    669             off += (sz + 4);
    670         }
    671         return len - skipped;
    672     }
    673 
    674     /*
    675      * Writes extra data without EXTT and ZIP64.
    676      *
    677      * Extra timestamp and ZIP64 data is handled/output separately
    678      * in writeLOC and writeCEN.
    679      */
    680     private void writeExtra(byte[] extra) throws IOException {
    681         if (extra != null) {
    682             int len = extra.length;
    683             int off = 0;
    684             while (off + 4 <= len) {
    685                 int tag = get16(extra, off);
    686                 int sz = get16(extra, off + 2);
    687                 if (sz < 0 || (off + 4 + sz) > len) {
    688                     writeBytes(extra, off, len - off);
    689                     return;
    690                 }
    691                 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) {
    692                     writeBytes(extra, off, sz + 4);
    693                 }
    694                 off += (sz + 4);
    695             }
    696             if (off < len) {
    697                 writeBytes(extra, off, len - off);
    698             }
    699         }
    700     }
    701 
    702     /*
    703      * Writes a 8-bit byte to the output stream.
    704      */
    705     private void writeByte(int v) throws IOException {
    706         OutputStream out = this.out;
    707         out.write(v & 0xff);
    708         written += 1;
    709     }
    710 
    711     /*
    712      * Writes a 16-bit short to the output stream in little-endian byte order.
    713      */
    714     private void writeShort(int v) throws IOException {
    715         OutputStream out = this.out;
    716         out.write((v >>> 0) & 0xff);
    717         out.write((v >>> 8) & 0xff);
    718         written += 2;
    719     }
    720 
    721     /*
    722      * Writes a 32-bit int to the output stream in little-endian byte order.
    723      */
    724     private void writeInt(long v) throws IOException {
    725         OutputStream out = this.out;
    726         out.write((int)((v >>>  0) & 0xff));
    727         out.write((int)((v >>>  8) & 0xff));
    728         out.write((int)((v >>> 16) & 0xff));
    729         out.write((int)((v >>> 24) & 0xff));
    730         written += 4;
    731     }
    732 
    733     /*
    734      * Writes a 64-bit int to the output stream in little-endian byte order.
    735      */
    736     private void writeLong(long v) throws IOException {
    737         OutputStream out = this.out;
    738         out.write((int)((v >>>  0) & 0xff));
    739         out.write((int)((v >>>  8) & 0xff));
    740         out.write((int)((v >>> 16) & 0xff));
    741         out.write((int)((v >>> 24) & 0xff));
    742         out.write((int)((v >>> 32) & 0xff));
    743         out.write((int)((v >>> 40) & 0xff));
    744         out.write((int)((v >>> 48) & 0xff));
    745         out.write((int)((v >>> 56) & 0xff));
    746         written += 8;
    747     }
    748 
    749     /*
    750      * Writes an array of bytes to the output stream.
    751      */
    752     private void writeBytes(byte[] b, int off, int len) throws IOException {
    753         super.out.write(b, off, len);
    754         written += len;
    755     }
    756 }
    757