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