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