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