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