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