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