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 += 5; // headid(2) + sz(2) + flag(1) 586 if (e.mtime != null) 587 elen += 4; // + mtime (4) 588 } 589 } 590 writeShort(elen); 591 byte[] commentBytes; 592 if (e.comment != null) { 593 commentBytes = zc.getBytes(e.comment); 594 writeShort(Math.min(commentBytes.length, 0xffff)); 595 } else { 596 commentBytes = null; 597 writeShort(0); 598 } 599 writeShort(0); // starting disk number 600 writeShort(0); // internal file attributes (unused) 601 writeInt(0); // external file attributes (unused) 602 writeInt(offset); // relative offset of local header 603 writeBytes(nameBytes, 0, nameBytes.length); 604 605 // take care of EXTID_ZIP64 and EXTID_EXTT 606 if (hasZip64) { 607 writeShort(ZIP64_EXTID);// Zip64 extra 608 writeShort(elenZIP64); 609 if (size == ZIP64_MAGICVAL) 610 writeLong(e.size); 611 if (csize == ZIP64_MAGICVAL) 612 writeLong(e.csize); 613 if (offset == ZIP64_MAGICVAL) 614 writeLong(xentry.offset); 615 } 616 if (flagEXTT != 0) { 617 if (umtime > UPPER_UNIXTIME_BOUND || 618 uatime > UPPER_UNIXTIME_BOUND || 619 uctime > UPPER_UNIXTIME_BOUND) { 620 writeShort(EXTID_NTFS); // id 621 writeShort(32); // data size 622 writeInt(0); // reserved 623 writeShort(0x0001); // NTFS attr tag 624 writeShort(24); 625 writeLong(e.mtime == null ? WINDOWS_TIME_NOT_AVAILABLE 626 : fileTimeToWinTime(e.mtime)); 627 writeLong(e.atime == null ? WINDOWS_TIME_NOT_AVAILABLE 628 : fileTimeToWinTime(e.atime)); 629 writeLong(e.ctime == null ? WINDOWS_TIME_NOT_AVAILABLE 630 : fileTimeToWinTime(e.ctime)); 631 } else { 632 writeShort(EXTID_EXTT); 633 if (e.mtime != null) { 634 writeShort(5); // flag + mtime 635 writeByte(flagEXTT); 636 writeInt(umtime); 637 } else { 638 writeShort(1); // flag only 639 writeByte(flagEXTT); 640 } 641 } 642 } 643 writeExtra(e.extra); 644 if (commentBytes != null) { 645 writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); 646 } 647 } 648 649 /* 650 * Writes end of central directory (END) header. 651 */ 652 private void writeEND(long off, long len) throws IOException { 653 boolean hasZip64 = false; 654 long xlen = len; 655 long xoff = off; 656 if (xlen >= ZIP64_MAGICVAL) { 657 xlen = ZIP64_MAGICVAL; 658 hasZip64 = true; 659 } 660 if (xoff >= ZIP64_MAGICVAL) { 661 xoff = ZIP64_MAGICVAL; 662 hasZip64 = true; 663 } 664 int count = xentries.size(); 665 if (count >= ZIP64_MAGICCOUNT) { 666 hasZip64 |= !inhibitZip64; 667 if (hasZip64) { 668 count = ZIP64_MAGICCOUNT; 669 } 670 } 671 if (hasZip64) { 672 long off64 = written; 673 //zip64 end of central directory record 674 writeInt(ZIP64_ENDSIG); // zip64 END record signature 675 writeLong(ZIP64_ENDHDR - 12); // size of zip64 end 676 writeShort(45); // version made by 677 writeShort(45); // version needed to extract 678 writeInt(0); // number of this disk 679 writeInt(0); // central directory start disk 680 writeLong(xentries.size()); // number of directory entires on disk 681 writeLong(xentries.size()); // number of directory entires 682 writeLong(len); // length of central directory 683 writeLong(off); // offset of central directory 684 685 //zip64 end of central directory locator 686 writeInt(ZIP64_LOCSIG); // zip64 END locator signature 687 writeInt(0); // zip64 END start disk 688 writeLong(off64); // offset of zip64 END 689 writeInt(1); // total number of disks (?) 690 } 691 writeInt(ENDSIG); // END record signature 692 writeShort(0); // number of this disk 693 writeShort(0); // central directory start disk 694 writeShort(count); // number of directory entries on disk 695 writeShort(count); // total number of directory entries 696 writeInt(xlen); // length of central directory 697 writeInt(xoff); // offset of central directory 698 if (comment != null) { // zip file comment 699 writeShort(comment.length); 700 writeBytes(comment, 0, comment.length); 701 } else { 702 writeShort(0); 703 } 704 } 705 706 /* 707 * Returns the length of extra data without EXTT and ZIP64. 708 */ 709 private int getExtraLen(byte[] extra) { 710 if (extra == null) 711 return 0; 712 int skipped = 0; 713 int len = extra.length; 714 int off = 0; 715 while (off + 4 <= len) { 716 int tag = get16(extra, off); 717 int sz = get16(extra, off + 2); 718 if (sz < 0 || (off + 4 + sz) > len) { 719 break; 720 } 721 if (tag == EXTID_EXTT || tag == EXTID_ZIP64) { 722 skipped += (sz + 4); 723 } 724 off += (sz + 4); 725 } 726 return len - skipped; 727 } 728 729 /* 730 * Writes extra data without EXTT and ZIP64. 731 * 732 * Extra timestamp and ZIP64 data is handled/output separately 733 * in writeLOC and writeCEN. 734 */ 735 private void writeExtra(byte[] extra) throws IOException { 736 if (extra != null) { 737 int len = extra.length; 738 int off = 0; 739 while (off + 4 <= len) { 740 int tag = get16(extra, off); 741 int sz = get16(extra, off + 2); 742 if (sz < 0 || (off + 4 + sz) > len) { 743 writeBytes(extra, off, len - off); 744 return; 745 } 746 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) { 747 writeBytes(extra, off, sz + 4); 748 } 749 off += (sz + 4); 750 } 751 if (off < len) { 752 writeBytes(extra, off, len - off); 753 } 754 } 755 } 756 757 /* 758 * Writes a 8-bit byte to the output stream. 759 */ 760 private void writeByte(int v) throws IOException { 761 OutputStream out = this.out; 762 out.write(v & 0xff); 763 written += 1; 764 } 765 766 /* 767 * Writes a 16-bit short to the output stream in little-endian byte order. 768 */ 769 private void writeShort(int v) throws IOException { 770 OutputStream out = this.out; 771 out.write((v >>> 0) & 0xff); 772 out.write((v >>> 8) & 0xff); 773 written += 2; 774 } 775 776 /* 777 * Writes a 32-bit int to the output stream in little-endian byte order. 778 */ 779 private void writeInt(long v) throws IOException { 780 OutputStream out = this.out; 781 out.write((int)((v >>> 0) & 0xff)); 782 out.write((int)((v >>> 8) & 0xff)); 783 out.write((int)((v >>> 16) & 0xff)); 784 out.write((int)((v >>> 24) & 0xff)); 785 written += 4; 786 } 787 788 /* 789 * Writes a 64-bit int to the output stream in little-endian byte order. 790 */ 791 private void writeLong(long v) throws IOException { 792 OutputStream out = this.out; 793 out.write((int)((v >>> 0) & 0xff)); 794 out.write((int)((v >>> 8) & 0xff)); 795 out.write((int)((v >>> 16) & 0xff)); 796 out.write((int)((v >>> 24) & 0xff)); 797 out.write((int)((v >>> 32) & 0xff)); 798 out.write((int)((v >>> 40) & 0xff)); 799 out.write((int)((v >>> 48) & 0xff)); 800 out.write((int)((v >>> 56) & 0xff)); 801 written += 8; 802 } 803 804 /* 805 * Writes an array of bytes to the output stream. 806 */ 807 private void writeBytes(byte[] b, int off, int len) throws IOException { 808 super.out.write(b, off, len); 809 written += len; 810 } 811 }