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