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