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