1 /* 2 * Copyright (c) 1996, 2019, 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.util.Vector; 32 import java.util.HashSet; 33 import static java.util.zip.ZipConstants64.*; 34 import static java.util.zip.ZipUtils.*; 35 import sun.nio.cs.UTF_8; 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 class ZipOutputStream extends DeflaterOutputStream implements ZipConstants { 47 48 /** 49 * Whether to use ZIP64 for zip files with more than 64k entries. 50 * Until ZIP64 support in zip implementations is ubiquitous, this 51 * system property allows the creation of zip files which can be 52 * read by legacy zip implementations which tolerate "incorrect" 53 * total entry count fields, such as the ones in jdk6, and even 54 * some in jdk7. 55 */ 56 private static final boolean inhibitZip64 = 57 Boolean.parseBoolean( 58 GetPropertyAction.privilegedGetProperty("jdk.util.zip.inhibitZip64")); 59 60 private static class XEntry { 61 final ZipEntry entry; 62 final long offset; 63 public XEntry(ZipEntry entry, long offset) { 64 this.entry = entry; 65 this.offset = offset; 66 } 67 } 68 69 private XEntry current; 70 private Vector<XEntry> xentries = new Vector<>(); 71 private HashSet<String> names = new HashSet<>(); 72 private CRC32 crc = new CRC32(); 73 private long written = 0; 74 private long locoff = 0; 75 private byte[] comment; 76 private int method = DEFLATED; 77 private boolean finished; 78 79 private boolean closed = false; 80 81 private final ZipCoder zc; 82 83 private static int version(ZipEntry e) throws ZipException { 84 switch (e.method) { 85 case DEFLATED: return 20; 86 case STORED: return 10; 87 default: throw new ZipException("unsupported compression method"); 88 } 89 } 90 91 /** 92 * Checks to make sure that this stream has not been closed. 93 */ 94 private void ensureOpen() throws IOException { 95 if (closed) { 96 throw new IOException("Stream closed"); 97 } 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, UTF_8.INSTANCE); 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 * @throws 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 * @throws 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 * @throws 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 * @throws ZipException if a ZIP format error has occurred 187 * @throws 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 |= USE_UTF8; 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 * @throws ZipException if a ZIP format error has occurred 245 * @throws 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 * @throws ZipException if a ZIP file error has occurred 313 * @throws 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 * @throws ZipException if a ZIP file error has occurred 352 * @throws 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 * @throws ZipException if a ZIP file error has occurred 373 * @throws 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 * Adds information about compatibility of file attribute information 511 * to a version value. 512 */ 513 private int versionMadeBy(ZipEntry e, int version) { 514 return (e.posixPerms < 0) ? version : 515 VERSION_MADE_BY_BASE_UNIX | (version & 0xff); 516 } 517 518 /* 519 * Write central directory (CEN) header for specified entry. 520 * REMIND: add support for file attributes 521 */ 522 private void writeCEN(XEntry xentry) throws IOException { 523 ZipEntry e = xentry.entry; 524 int flag = e.flag; 525 int version = version(e); 526 long csize = e.csize; 527 long size = e.size; 528 long offset = xentry.offset; 529 int elenZIP64 = 0; 530 boolean hasZip64 = false; 531 532 if (e.csize >= ZIP64_MAGICVAL) { 533 csize = ZIP64_MAGICVAL; 534 elenZIP64 += 8; // csize(8) 535 hasZip64 = true; 536 } 537 if (e.size >= ZIP64_MAGICVAL) { 538 size = ZIP64_MAGICVAL; // size(8) 539 elenZIP64 += 8; 540 hasZip64 = true; 541 } 542 if (xentry.offset >= ZIP64_MAGICVAL) { 543 offset = ZIP64_MAGICVAL; 544 elenZIP64 += 8; // offset(8) 545 hasZip64 = true; 546 } 547 writeInt(CENSIG); // CEN header signature 548 if (hasZip64) { 549 writeShort(versionMadeBy(e,45)); // ver 4.5 for zip64 550 writeShort(45); 551 } else { 552 writeShort(versionMadeBy(e, version)); // version made by 553 writeShort(version); // version needed to extract 554 } 555 writeShort(flag); // general purpose bit flag 556 writeShort(e.method); // compression method 557 writeInt(e.xdostime); // last modification time 558 writeInt(e.crc); // crc-32 559 writeInt(csize); // compressed size 560 writeInt(size); // uncompressed size 561 byte[] nameBytes = zc.getBytes(e.name); 562 writeShort(nameBytes.length); 563 564 int elen = getExtraLen(e.extra); 565 if (hasZip64) { 566 elen += (elenZIP64 + 4);// + headid(2) + datasize(2) 567 } 568 // cen info-zip extended timestamp only outputs mtime 569 // but set the flag for a/ctime, if present in loc 570 int flagEXTT = 0; 571 long umtime = -1; 572 long uatime = -1; 573 long uctime = -1; 574 if (e.mtime != null) { 575 flagEXTT |= EXTT_FLAG_LMT; 576 umtime = fileTimeToUnixTime(e.mtime); 577 } 578 if (e.atime != null) { 579 flagEXTT |= EXTT_FLAG_LAT; 580 uatime = fileTimeToUnixTime(e.atime); 581 } 582 if (e.ctime != null) { 583 flagEXTT |= EXTT_FLAT_CT; 584 uctime = fileTimeToUnixTime(e.ctime); 585 } 586 if (flagEXTT != 0) { 587 // to use ntfs time if any m/a/ctime is beyond unixtime upper bound 588 if (umtime > UPPER_UNIXTIME_BOUND || 589 uatime > UPPER_UNIXTIME_BOUND || 590 uctime > UPPER_UNIXTIME_BOUND) { 591 elen += 36; // NTFS time total 36 bytes 592 } else { 593 elen += 5; // headid(2) + sz(2) + flag(1) 594 if (e.mtime != null) 595 elen += 4; // + mtime (4) 596 } 597 } 598 writeShort(elen); 599 byte[] commentBytes; 600 if (e.comment != null) { 601 commentBytes = zc.getBytes(e.comment); 602 writeShort(Math.min(commentBytes.length, 0xffff)); 603 } else { 604 commentBytes = null; 605 writeShort(0); 606 } 607 writeShort(0); // starting disk number 608 writeShort(0); // internal file attributes (unused) 609 // external file attributes, used for storing posix permissions 610 writeInt(e.posixPerms > 0 ? e.posixPerms << 16 : 0); 611 writeInt(offset); // relative offset of local header 612 writeBytes(nameBytes, 0, nameBytes.length); 613 614 // take care of EXTID_ZIP64 and EXTID_EXTT 615 if (hasZip64) { 616 writeShort(ZIP64_EXTID);// Zip64 extra 617 writeShort(elenZIP64); 618 if (size == ZIP64_MAGICVAL) 619 writeLong(e.size); 620 if (csize == ZIP64_MAGICVAL) 621 writeLong(e.csize); 622 if (offset == ZIP64_MAGICVAL) 623 writeLong(xentry.offset); 624 } 625 if (flagEXTT != 0) { 626 if (umtime > UPPER_UNIXTIME_BOUND || 627 uatime > UPPER_UNIXTIME_BOUND || 628 uctime > UPPER_UNIXTIME_BOUND) { 629 writeShort(EXTID_NTFS); // id 630 writeShort(32); // data size 631 writeInt(0); // reserved 632 writeShort(0x0001); // NTFS attr tag 633 writeShort(24); 634 writeLong(e.mtime == null ? WINDOWS_TIME_NOT_AVAILABLE 635 : fileTimeToWinTime(e.mtime)); 636 writeLong(e.atime == null ? WINDOWS_TIME_NOT_AVAILABLE 637 : fileTimeToWinTime(e.atime)); 638 writeLong(e.ctime == null ? WINDOWS_TIME_NOT_AVAILABLE 639 : fileTimeToWinTime(e.ctime)); 640 } else { 641 writeShort(EXTID_EXTT); 642 if (e.mtime != null) { 643 writeShort(5); // flag + mtime 644 writeByte(flagEXTT); 645 writeInt(umtime); 646 } else { 647 writeShort(1); // flag only 648 writeByte(flagEXTT); 649 } 650 } 651 } 652 writeExtra(e.extra); 653 if (commentBytes != null) { 654 writeBytes(commentBytes, 0, Math.min(commentBytes.length, 0xffff)); 655 } 656 } 657 658 /* 659 * Writes end of central directory (END) header. 660 */ 661 private void writeEND(long off, long len) throws IOException { 662 boolean hasZip64 = false; 663 long xlen = len; 664 long xoff = off; 665 if (xlen >= ZIP64_MAGICVAL) { 666 xlen = ZIP64_MAGICVAL; 667 hasZip64 = true; 668 } 669 if (xoff >= ZIP64_MAGICVAL) { 670 xoff = ZIP64_MAGICVAL; 671 hasZip64 = true; 672 } 673 int count = xentries.size(); 674 if (count >= ZIP64_MAGICCOUNT) { 675 hasZip64 |= !inhibitZip64; 676 if (hasZip64) { 677 count = ZIP64_MAGICCOUNT; 678 } 679 } 680 if (hasZip64) { 681 long off64 = written; 682 //zip64 end of central directory record 683 writeInt(ZIP64_ENDSIG); // zip64 END record signature 684 writeLong(ZIP64_ENDHDR - 12); // size of zip64 end 685 writeShort(45); // version made by 686 writeShort(45); // version needed to extract 687 writeInt(0); // number of this disk 688 writeInt(0); // central directory start disk 689 writeLong(xentries.size()); // number of directory entires on disk 690 writeLong(xentries.size()); // number of directory entires 691 writeLong(len); // length of central directory 692 writeLong(off); // offset of central directory 693 694 //zip64 end of central directory locator 695 writeInt(ZIP64_LOCSIG); // zip64 END locator signature 696 writeInt(0); // zip64 END start disk 697 writeLong(off64); // offset of zip64 END 698 writeInt(1); // total number of disks (?) 699 } 700 writeInt(ENDSIG); // END record signature 701 writeShort(0); // number of this disk 702 writeShort(0); // central directory start disk 703 writeShort(count); // number of directory entries on disk 704 writeShort(count); // total number of directory entries 705 writeInt(xlen); // length of central directory 706 writeInt(xoff); // offset of central directory 707 if (comment != null) { // zip file comment 708 writeShort(comment.length); 709 writeBytes(comment, 0, comment.length); 710 } else { 711 writeShort(0); 712 } 713 } 714 715 /* 716 * Returns the length of extra data without EXTT and ZIP64. 717 */ 718 private int getExtraLen(byte[] extra) { 719 if (extra == null) 720 return 0; 721 int skipped = 0; 722 int len = extra.length; 723 int off = 0; 724 while (off + 4 <= len) { 725 int tag = get16(extra, off); 726 int sz = get16(extra, off + 2); 727 if (sz < 0 || (off + 4 + sz) > len) { 728 break; 729 } 730 if (tag == EXTID_EXTT || tag == EXTID_ZIP64) { 731 skipped += (sz + 4); 732 } 733 off += (sz + 4); 734 } 735 return len - skipped; 736 } 737 738 /* 739 * Writes extra data without EXTT and ZIP64. 740 * 741 * Extra timestamp and ZIP64 data is handled/output separately 742 * in writeLOC and writeCEN. 743 */ 744 private void writeExtra(byte[] extra) throws IOException { 745 if (extra != null) { 746 int len = extra.length; 747 int off = 0; 748 while (off + 4 <= len) { 749 int tag = get16(extra, off); 750 int sz = get16(extra, off + 2); 751 if (sz < 0 || (off + 4 + sz) > len) { 752 writeBytes(extra, off, len - off); 753 return; 754 } 755 if (tag != EXTID_EXTT && tag != EXTID_ZIP64) { 756 writeBytes(extra, off, sz + 4); 757 } 758 off += (sz + 4); 759 } 760 if (off < len) { 761 writeBytes(extra, off, len - off); 762 } 763 } 764 } 765 766 /* 767 * Writes a 8-bit byte to the output stream. 768 */ 769 private void writeByte(int v) throws IOException { 770 OutputStream out = this.out; 771 out.write(v & 0xff); 772 written += 1; 773 } 774 775 /* 776 * Writes a 16-bit short to the output stream in little-endian byte order. 777 */ 778 private void writeShort(int v) throws IOException { 779 OutputStream out = this.out; 780 out.write((v >>> 0) & 0xff); 781 out.write((v >>> 8) & 0xff); 782 written += 2; 783 } 784 785 /* 786 * Writes a 32-bit int to the output stream in little-endian byte order. 787 */ 788 private void writeInt(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 written += 4; 795 } 796 797 /* 798 * Writes a 64-bit int to the output stream in little-endian byte order. 799 */ 800 private void writeLong(long v) throws IOException { 801 OutputStream out = this.out; 802 out.write((int)((v >>> 0) & 0xff)); 803 out.write((int)((v >>> 8) & 0xff)); 804 out.write((int)((v >>> 16) & 0xff)); 805 out.write((int)((v >>> 24) & 0xff)); 806 out.write((int)((v >>> 32) & 0xff)); 807 out.write((int)((v >>> 40) & 0xff)); 808 out.write((int)((v >>> 48) & 0xff)); 809 out.write((int)((v >>> 56) & 0xff)); 810 written += 8; 811 } 812 813 /* 814 * Writes an array of bytes to the output stream. 815 */ 816 private void writeBytes(byte[] b, int off, int len) throws IOException { 817 super.out.write(b, off, len); 818 written += len; 819 } 820 }