001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018 package org.apache.commons.compress.archivers.zip; 019 020 import java.io.File; 021 import java.io.FileOutputStream; 022 import java.io.IOException; 023 import java.io.OutputStream; 024 import java.io.RandomAccessFile; 025 import java.nio.ByteBuffer; 026 import java.util.HashMap; 027 import java.util.LinkedList; 028 import java.util.List; 029 import java.util.Map; 030 import java.util.zip.CRC32; 031 import java.util.zip.Deflater; 032 import java.util.zip.ZipException; 033 034 import org.apache.commons.compress.archivers.ArchiveEntry; 035 import org.apache.commons.compress.archivers.ArchiveOutputStream; 036 037 import static org.apache.commons.compress.archivers.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION; 038 import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD; 039 import static org.apache.commons.compress.archivers.zip.ZipConstants.INITIAL_VERSION; 040 import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT; 041 import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD; 042 import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC; 043 import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT; 044 import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MIN_VERSION; 045 046 /** 047 * Reimplementation of {@link java.util.zip.ZipOutputStream 048 * java.util.zip.ZipOutputStream} that does handle the extended 049 * functionality of this package, especially internal/external file 050 * attributes and extra fields with different layouts for local file 051 * data and central directory entries. 052 * 053 * <p>This class will try to use {@link java.io.RandomAccessFile 054 * RandomAccessFile} when you know that the output is going to go to a 055 * file.</p> 056 * 057 * <p>If RandomAccessFile cannot be used, this implementation will use 058 * a Data Descriptor to store size and CRC information for {@link 059 * #DEFLATED DEFLATED} entries, this means, you don't need to 060 * calculate them yourself. Unfortunately this is not possible for 061 * the {@link #STORED STORED} method, here setting the CRC and 062 * uncompressed size information is required before {@link 063 * #putArchiveEntry(ArchiveEntry)} can be called.</p> 064 * 065 * <p>As of Apache Commons Compress 1.3 it transparently supports Zip64 066 * extensions and thus individual entries and archives larger than 4 067 * GB or with more than 65536 entries in most cases but explicit 068 * control is provided via {@link #setUseZip64}. If the stream can not 069 * user RandomAccessFile and you try to write a ZipArchiveEntry of 070 * unknown size then Zip64 extensions will be disabled by default.</p> 071 * 072 * @NotThreadSafe 073 */ 074 public class ZipArchiveOutputStream extends ArchiveOutputStream { 075 076 static final int BUFFER_SIZE = 512; 077 078 /** indicates if this archive is finished. protected for use in Jar implementation */ 079 protected boolean finished = false; 080 081 /* 082 * Apparently Deflater.setInput gets slowed down a lot on Sun JVMs 083 * when it gets handed a really big buffer. See 084 * https://issues.apache.org/bugzilla/show_bug.cgi?id=45396 085 * 086 * Using a buffer size of 8 kB proved to be a good compromise 087 */ 088 private static final int DEFLATER_BLOCK_SIZE = 8192; 089 090 /** 091 * Compression method for deflated entries. 092 */ 093 public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED; 094 095 /** 096 * Default compression level for deflated entries. 097 */ 098 public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION; 099 100 /** 101 * Compression method for stored entries. 102 */ 103 public static final int STORED = java.util.zip.ZipEntry.STORED; 104 105 /** 106 * default encoding for file names and comment. 107 */ 108 static final String DEFAULT_ENCODING = ZipEncodingHelper.UTF8; 109 110 /** 111 * General purpose flag, which indicates that filenames are 112 * written in utf-8. 113 * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead 114 */ 115 @Deprecated 116 public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG; 117 118 private static final byte[] EMPTY = new byte[0]; 119 120 /** 121 * Current entry. 122 */ 123 private CurrentEntry entry; 124 125 /** 126 * The file comment. 127 */ 128 private String comment = ""; 129 130 /** 131 * Compression level for next entry. 132 */ 133 private int level = DEFAULT_COMPRESSION; 134 135 /** 136 * Has the compression level changed when compared to the last 137 * entry? 138 */ 139 private boolean hasCompressionLevelChanged = false; 140 141 /** 142 * Default compression method for next entry. 143 */ 144 private int method = java.util.zip.ZipEntry.DEFLATED; 145 146 /** 147 * List of ZipArchiveEntries written so far. 148 */ 149 private final List<ZipArchiveEntry> entries = 150 new LinkedList<ZipArchiveEntry>(); 151 152 /** 153 * CRC instance to avoid parsing DEFLATED data twice. 154 */ 155 private final CRC32 crc = new CRC32(); 156 157 /** 158 * Count the bytes written to out. 159 */ 160 private long written = 0; 161 162 /** 163 * Start of central directory. 164 */ 165 private long cdOffset = 0; 166 167 /** 168 * Length of central directory. 169 */ 170 private long cdLength = 0; 171 172 /** 173 * Helper, a 0 as ZipShort. 174 */ 175 private static final byte[] ZERO = {0, 0}; 176 177 /** 178 * Helper, a 0 as ZipLong. 179 */ 180 private static final byte[] LZERO = {0, 0, 0, 0}; 181 182 /** 183 * Holds the offsets of the LFH starts for each entry. 184 */ 185 private final Map<ZipArchiveEntry, Long> offsets = 186 new HashMap<ZipArchiveEntry, Long>(); 187 188 /** 189 * The encoding to use for filenames and the file comment. 190 * 191 * <p>For a list of possible values see <a 192 * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. 193 * Defaults to UTF-8.</p> 194 */ 195 private String encoding = DEFAULT_ENCODING; 196 197 /** 198 * The zip encoding to use for filenames and the file comment. 199 * 200 * This field is of internal use and will be set in {@link 201 * #setEncoding(String)}. 202 */ 203 private ZipEncoding zipEncoding = 204 ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING); 205 206 /** 207 * This Deflater object is used for output. 208 * 209 */ 210 protected final Deflater def = new Deflater(level, true); 211 212 /** 213 * This buffer serves as a Deflater. 214 * 215 */ 216 private final byte[] buf = new byte[BUFFER_SIZE]; 217 218 /** 219 * Optional random access output. 220 */ 221 private final RandomAccessFile raf; 222 223 private final OutputStream out; 224 225 /** 226 * whether to use the general purpose bit flag when writing UTF-8 227 * filenames or not. 228 */ 229 private boolean useUTF8Flag = true; 230 231 /** 232 * Whether to encode non-encodable file names as UTF-8. 233 */ 234 private boolean fallbackToUTF8 = false; 235 236 /** 237 * whether to create UnicodePathExtraField-s for each entry. 238 */ 239 private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER; 240 241 /** 242 * Whether anything inside this archive has used a ZIP64 feature. 243 * 244 * @since 1.3 245 */ 246 private boolean hasUsedZip64 = false; 247 248 private Zip64Mode zip64Mode = Zip64Mode.AsNeeded; 249 250 /** 251 * Creates a new ZIP OutputStream filtering the underlying stream. 252 * @param out the outputstream to zip 253 */ 254 public ZipArchiveOutputStream(OutputStream out) { 255 this.out = out; 256 this.raf = null; 257 } 258 259 /** 260 * Creates a new ZIP OutputStream writing to a File. Will use 261 * random access if possible. 262 * @param file the file to zip to 263 * @throws IOException on error 264 */ 265 public ZipArchiveOutputStream(File file) throws IOException { 266 OutputStream o = null; 267 RandomAccessFile _raf = null; 268 try { 269 _raf = new RandomAccessFile(file, "rw"); 270 _raf.setLength(0); 271 } catch (IOException e) { 272 if (_raf != null) { 273 try { 274 _raf.close(); 275 } catch (IOException inner) { // NOPMD 276 // ignore 277 } 278 _raf = null; 279 } 280 o = new FileOutputStream(file); 281 } 282 out = o; 283 raf = _raf; 284 } 285 286 /** 287 * This method indicates whether this archive is writing to a 288 * seekable stream (i.e., to a random access file). 289 * 290 * <p>For seekable streams, you don't need to calculate the CRC or 291 * uncompressed size for {@link #STORED} entries before 292 * invoking {@link #putArchiveEntry(ArchiveEntry)}. 293 * @return true if seekable 294 */ 295 public boolean isSeekable() { 296 return raf != null; 297 } 298 299 /** 300 * The encoding to use for filenames and the file comment. 301 * 302 * <p>For a list of possible values see <a 303 * href="http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html">http://java.sun.com/j2se/1.5.0/docs/guide/intl/encoding.doc.html</a>. 304 * Defaults to UTF-8.</p> 305 * @param encoding the encoding to use for file names, use null 306 * for the platform's default encoding 307 */ 308 public void setEncoding(final String encoding) { 309 this.encoding = encoding; 310 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); 311 if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) { 312 useUTF8Flag = false; 313 } 314 } 315 316 /** 317 * The encoding to use for filenames and the file comment. 318 * 319 * @return null if using the platform's default character encoding. 320 */ 321 public String getEncoding() { 322 return encoding; 323 } 324 325 /** 326 * Whether to set the language encoding flag if the file name 327 * encoding is UTF-8. 328 * 329 * <p>Defaults to true.</p> 330 */ 331 public void setUseLanguageEncodingFlag(boolean b) { 332 useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding); 333 } 334 335 /** 336 * Whether to create Unicode Extra Fields. 337 * 338 * <p>Defaults to NEVER.</p> 339 */ 340 public void setCreateUnicodeExtraFields(UnicodeExtraFieldPolicy b) { 341 createUnicodeExtraFields = b; 342 } 343 344 /** 345 * Whether to fall back to UTF and the language encoding flag if 346 * the file name cannot be encoded using the specified encoding. 347 * 348 * <p>Defaults to false.</p> 349 */ 350 public void setFallbackToUTF8(boolean b) { 351 fallbackToUTF8 = b; 352 } 353 354 /** 355 * Whether Zip64 extensions will be used. 356 * 357 * <p>When setting the mode to {@link Zip64Mode#Never Never}, 358 * {@link #putArchiveEntry}, {@link #closeArchiveEntry}, {@link 359 * #finish} or {@link #close} may throw a {@link 360 * Zip64RequiredException} if the entry's size or the total size 361 * of the archive exceeds 4GB or there are more than 65536 entries 362 * inside the archive. Any archive created in this mode will be 363 * readable by implementations that don't support Zip64.</p> 364 * 365 * <p>When setting the mode to {@link Zip64Mode#Always Always}, 366 * Zip64 extensions will be used for all entries. Any archive 367 * created in this mode may be unreadable by implementations that 368 * don't support Zip64 even if all its contents would be.</p> 369 * 370 * <p>When setting the mode to {@link Zip64Mode#AsNeeded 371 * AsNeeded}, Zip64 extensions will transparently be used for 372 * those entries that require them. This mode can only be used if 373 * the uncompressed size of the {@link ZipArchiveEntry} is known 374 * when calling {@link #putArchiveEntry} or the archive is written 375 * to a seekable output (i.e. you have used the {@link 376 * #ZipArchiveOutputStream(java.io.File) File-arg constructor}) - 377 * this mode is not valid when the output stream is not seekable 378 * and the uncompressed size is unknown when {@link 379 * #putArchiveEntry} is called.</p> 380 * 381 * <p>If no entry inside the resulting archive requires Zip64 382 * extensions then {@link Zip64Mode#Never Never} will create the 383 * smallest archive. {@link Zip64Mode#AsNeeded AsNeeded} will 384 * create a slightly bigger archive if the uncompressed size of 385 * any entry has initially been unknown and create an archive 386 * identical to {@link Zip64Mode#Never Never} otherwise. {@link 387 * Zip64Mode#Always Always} will create an archive that is at 388 * least 24 bytes per entry bigger than the one {@link 389 * Zip64Mode#Never Never} would create.</p> 390 * 391 * <p>Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless 392 * {@link #putArchiveEntry} is called with an entry of unknown 393 * size and data is written to a non-seekable stream - in this 394 * case the default is {@link Zip64Mode#Never Never}.</p> 395 * 396 * @since 1.3 397 */ 398 public void setUseZip64(Zip64Mode mode) { 399 zip64Mode = mode; 400 } 401 402 /** 403 * {@inheritDoc} 404 * @throws Zip64RequiredException if the archive's size exceeds 4 405 * GByte or there are more than 65535 entries inside the archive 406 * and {@link #setUseZip64} is {@link Zip64Mode#Never}. 407 */ 408 @Override 409 public void finish() throws IOException { 410 if (finished) { 411 throw new IOException("This archive has already been finished"); 412 } 413 414 if (entry != null) { 415 throw new IOException("This archive contains unclosed entries."); 416 } 417 418 cdOffset = written; 419 for (ZipArchiveEntry ze : entries) { 420 writeCentralFileHeader(ze); 421 } 422 cdLength = written - cdOffset; 423 writeZip64CentralDirectory(); 424 writeCentralDirectoryEnd(); 425 offsets.clear(); 426 entries.clear(); 427 def.end(); 428 finished = true; 429 } 430 431 /** 432 * Writes all necessary data for this entry. 433 * @throws IOException on error 434 * @throws Zip64RequiredException if the entry's uncompressed or 435 * compressed size exceeds 4 GByte and {@link #setUseZip64} 436 * is {@link Zip64Mode#Never}. 437 */ 438 @Override 439 public void closeArchiveEntry() throws IOException { 440 if (finished) { 441 throw new IOException("Stream has already been finished"); 442 } 443 444 if (entry == null) { 445 throw new IOException("No current entry to close"); 446 } 447 448 if (!entry.hasWritten) { 449 write(EMPTY, 0, 0); 450 } 451 452 flushDeflater(); 453 454 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); 455 long bytesWritten = written - entry.dataStart; 456 long realCrc = crc.getValue(); 457 crc.reset(); 458 459 final boolean actuallyNeedsZip64 = 460 handleSizesAndCrc(bytesWritten, realCrc, effectiveMode); 461 462 if (raf != null) { 463 rewriteSizesAndCrc(actuallyNeedsZip64); 464 } 465 466 writeDataDescriptor(entry.entry); 467 entry = null; 468 } 469 470 /** 471 * Ensures all bytes sent to the deflater are written to the stream. 472 */ 473 private void flushDeflater() throws IOException { 474 if (entry.entry.getMethod() == DEFLATED) { 475 def.finish(); 476 while (!def.finished()) { 477 deflate(); 478 } 479 } 480 } 481 482 /** 483 * Ensures the current entry's size and CRC information is set to 484 * the values just written, verifies it isn't too big in the 485 * Zip64Mode.Never case and returns whether the entry would 486 * require a Zip64 extra field. 487 */ 488 private boolean handleSizesAndCrc(long bytesWritten, long crc, 489 Zip64Mode effectiveMode) 490 throws ZipException { 491 if (entry.entry.getMethod() == DEFLATED) { 492 /* It turns out def.getBytesRead() returns wrong values if 493 * the size exceeds 4 GB on Java < Java7 494 entry.entry.setSize(def.getBytesRead()); 495 */ 496 entry.entry.setSize(entry.bytesRead); 497 entry.entry.setCompressedSize(bytesWritten); 498 entry.entry.setCrc(crc); 499 500 def.reset(); 501 } else if (raf == null) { 502 if (entry.entry.getCrc() != crc) { 503 throw new ZipException("bad CRC checksum for entry " 504 + entry.entry.getName() + ": " 505 + Long.toHexString(entry.entry.getCrc()) 506 + " instead of " 507 + Long.toHexString(crc)); 508 } 509 510 if (entry.entry.getSize() != bytesWritten) { 511 throw new ZipException("bad size for entry " 512 + entry.entry.getName() + ": " 513 + entry.entry.getSize() 514 + " instead of " 515 + bytesWritten); 516 } 517 } else { /* method is STORED and we used RandomAccessFile */ 518 entry.entry.setSize(bytesWritten); 519 entry.entry.setCompressedSize(bytesWritten); 520 entry.entry.setCrc(crc); 521 } 522 523 final boolean actuallyNeedsZip64 = effectiveMode == Zip64Mode.Always 524 || entry.entry.getSize() >= ZIP64_MAGIC 525 || entry.entry.getCompressedSize() >= ZIP64_MAGIC; 526 if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) { 527 throw new Zip64RequiredException(Zip64RequiredException 528 .getEntryTooBigMessage(entry.entry)); 529 } 530 return actuallyNeedsZip64; 531 } 532 533 /** 534 * When using random access output, write the local file header 535 * and potentiall the ZIP64 extra containing the correct CRC and 536 * compressed/uncompressed sizes. 537 */ 538 private void rewriteSizesAndCrc(boolean actuallyNeedsZip64) 539 throws IOException { 540 long save = raf.getFilePointer(); 541 542 raf.seek(entry.localDataStart); 543 writeOut(ZipLong.getBytes(entry.entry.getCrc())); 544 if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) { 545 writeOut(ZipLong.getBytes(entry.entry.getCompressedSize())); 546 writeOut(ZipLong.getBytes(entry.entry.getSize())); 547 } else { 548 writeOut(ZipLong.ZIP64_MAGIC.getBytes()); 549 writeOut(ZipLong.ZIP64_MAGIC.getBytes()); 550 } 551 552 if (hasZip64Extra(entry.entry)) { 553 // seek to ZIP64 extra, skip header and size information 554 raf.seek(entry.localDataStart + 3 * WORD + 2 * SHORT 555 + getName(entry.entry).limit() + 2 * SHORT); 556 // inside the ZIP64 extra uncompressed size comes 557 // first, unlike the LFH, CD or data descriptor 558 writeOut(ZipEightByteInteger.getBytes(entry.entry.getSize())); 559 writeOut(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize())); 560 561 if (!actuallyNeedsZip64) { 562 // do some cleanup: 563 // * rewrite version needed to extract 564 raf.seek(entry.localDataStart - 5 * SHORT); 565 writeOut(ZipShort.getBytes(INITIAL_VERSION)); 566 567 // * remove ZIP64 extra so it doesn't get written 568 // to the central directory 569 entry.entry.removeExtraField(Zip64ExtendedInformationExtraField 570 .HEADER_ID); 571 entry.entry.setExtra(); 572 573 // * reset hasUsedZip64 if it has been set because 574 // of this entry 575 if (entry.causedUseOfZip64) { 576 hasUsedZip64 = false; 577 } 578 } 579 } 580 raf.seek(save); 581 } 582 583 /** 584 * {@inheritDoc} 585 * @throws ClassCastException if entry is not an instance of ZipArchiveEntry 586 * @throws Zip64RequiredException if the entry's uncompressed or 587 * compressed size is known to exceed 4 GByte and {@link #setUseZip64} 588 * is {@link Zip64Mode#Never}. 589 */ 590 @Override 591 public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException { 592 if (finished) { 593 throw new IOException("Stream has already been finished"); 594 } 595 596 if (entry != null) { 597 closeArchiveEntry(); 598 } 599 600 entry = new CurrentEntry((ZipArchiveEntry) archiveEntry); 601 entries.add(entry.entry); 602 603 setDefaults(entry.entry); 604 605 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); 606 validateSizeInformation(effectiveMode); 607 608 if (shouldAddZip64Extra(entry.entry, effectiveMode)) { 609 610 Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry); 611 612 // just a placeholder, real data will be in data 613 // descriptor or inserted later via RandomAccessFile 614 ZipEightByteInteger size = ZipEightByteInteger.ZERO; 615 if (entry.entry.getMethod() == STORED 616 && entry.entry.getSize() != ArchiveEntry.SIZE_UNKNOWN) { 617 // actually, we already know the sizes 618 size = new ZipEightByteInteger(entry.entry.getSize()); 619 } 620 z64.setSize(size); 621 z64.setCompressedSize(size); 622 entry.entry.setExtra(); 623 } 624 625 if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) { 626 def.setLevel(level); 627 hasCompressionLevelChanged = false; 628 } 629 writeLocalFileHeader(entry.entry); 630 } 631 632 /** 633 * Provides default values for compression method and last 634 * modification time. 635 */ 636 private void setDefaults(ZipArchiveEntry entry) { 637 if (entry.getMethod() == -1) { // not specified 638 entry.setMethod(method); 639 } 640 641 if (entry.getTime() == -1) { // not specified 642 entry.setTime(System.currentTimeMillis()); 643 } 644 } 645 646 /** 647 * Throws an exception if the size is unknown for a stored entry 648 * that is written to a non-seekable output or the entry is too 649 * big to be written without Zip64 extra but the mode has been set 650 * to Never. 651 */ 652 private void validateSizeInformation(Zip64Mode effectiveMode) 653 throws ZipException { 654 // Size/CRC not required if RandomAccessFile is used 655 if (entry.entry.getMethod() == STORED && raf == null) { 656 if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) { 657 throw new ZipException("uncompressed size is required for" 658 + " STORED method when not writing to a" 659 + " file"); 660 } 661 if (entry.entry.getCrc() == -1) { 662 throw new ZipException("crc checksum is required for STORED" 663 + " method when not writing to a file"); 664 } 665 entry.entry.setCompressedSize(entry.entry.getSize()); 666 } 667 668 if ((entry.entry.getSize() >= ZIP64_MAGIC 669 || entry.entry.getCompressedSize() >= ZIP64_MAGIC) 670 && effectiveMode == Zip64Mode.Never) { 671 throw new Zip64RequiredException(Zip64RequiredException 672 .getEntryTooBigMessage(entry.entry)); 673 } 674 } 675 676 /** 677 * Whether to addd a Zip64 extended information extra field to the 678 * local file header. 679 * 680 * <p>Returns true if</p> 681 * 682 * <ul> 683 * <li>mode is Always</li> 684 * <li>or we already know it is going to be needed</li> 685 * <li>or the size is unknown and we can ensure it won't hurt 686 * other implementations if we add it (i.e. we can erase its 687 * usage</li> 688 * </ul> 689 */ 690 private boolean shouldAddZip64Extra(ZipArchiveEntry entry, Zip64Mode mode) { 691 return mode == Zip64Mode.Always 692 || entry.getSize() >= ZIP64_MAGIC 693 || entry.getCompressedSize() >= ZIP64_MAGIC 694 || (entry.getSize() == ArchiveEntry.SIZE_UNKNOWN 695 && raf != null && mode != Zip64Mode.Never); 696 } 697 698 /** 699 * Set the file comment. 700 * @param comment the comment 701 */ 702 public void setComment(String comment) { 703 this.comment = comment; 704 } 705 706 /** 707 * Sets the compression level for subsequent entries. 708 * 709 * <p>Default is Deflater.DEFAULT_COMPRESSION.</p> 710 * @param level the compression level. 711 * @throws IllegalArgumentException if an invalid compression 712 * level is specified. 713 */ 714 public void setLevel(int level) { 715 if (level < Deflater.DEFAULT_COMPRESSION 716 || level > Deflater.BEST_COMPRESSION) { 717 throw new IllegalArgumentException("Invalid compression level: " 718 + level); 719 } 720 hasCompressionLevelChanged = (this.level != level); 721 this.level = level; 722 } 723 724 /** 725 * Sets the default compression method for subsequent entries. 726 * 727 * <p>Default is DEFLATED.</p> 728 * @param method an <code>int</code> from java.util.zip.ZipEntry 729 */ 730 public void setMethod(int method) { 731 this.method = method; 732 } 733 734 /** 735 * Whether this stream is able to write the given entry. 736 * 737 * <p>May return false if it is set up to use encryption or a 738 * compression method that hasn't been implemented yet.</p> 739 * @since 1.1 740 */ 741 @Override 742 public boolean canWriteEntryData(ArchiveEntry ae) { 743 if (ae instanceof ZipArchiveEntry) { 744 return ZipUtil.canHandleEntryData((ZipArchiveEntry) ae); 745 } 746 return false; 747 } 748 749 /** 750 * Writes bytes to ZIP entry. 751 * @param b the byte array to write 752 * @param offset the start position to write from 753 * @param length the number of bytes to write 754 * @throws IOException on error 755 */ 756 @Override 757 public void write(byte[] b, int offset, int length) throws IOException { 758 ZipUtil.checkRequestedFeatures(entry.entry); 759 entry.hasWritten = true; 760 if (entry.entry.getMethod() == DEFLATED) { 761 writeDeflated(b, offset, length); 762 } else { 763 writeOut(b, offset, length); 764 written += length; 765 } 766 crc.update(b, offset, length); 767 count(length); 768 } 769 770 /** 771 * write implementation for DEFLATED entries. 772 */ 773 private void writeDeflated(byte[]b, int offset, int length) 774 throws IOException { 775 if (length > 0 && !def.finished()) { 776 entry.bytesRead += length; 777 if (length <= DEFLATER_BLOCK_SIZE) { 778 def.setInput(b, offset, length); 779 deflateUntilInputIsNeeded(); 780 } else { 781 final int fullblocks = length / DEFLATER_BLOCK_SIZE; 782 for (int i = 0; i < fullblocks; i++) { 783 def.setInput(b, offset + i * DEFLATER_BLOCK_SIZE, 784 DEFLATER_BLOCK_SIZE); 785 deflateUntilInputIsNeeded(); 786 } 787 final int done = fullblocks * DEFLATER_BLOCK_SIZE; 788 if (done < length) { 789 def.setInput(b, offset + done, length - done); 790 deflateUntilInputIsNeeded(); 791 } 792 } 793 } 794 } 795 796 /** 797 * Closes this output stream and releases any system resources 798 * associated with the stream. 799 * 800 * @exception IOException if an I/O error occurs. 801 * @throws Zip64RequiredException if the archive's size exceeds 4 802 * GByte or there are more than 65535 entries inside the archive 803 * and {@link #setUseZip64} is {@link Zip64Mode#Never}. 804 */ 805 @Override 806 public void close() throws IOException { 807 if (!finished) { 808 finish(); 809 } 810 destroy(); 811 } 812 813 /** 814 * Flushes this output stream and forces any buffered output bytes 815 * to be written out to the stream. 816 * 817 * @exception IOException if an I/O error occurs. 818 */ 819 @Override 820 public void flush() throws IOException { 821 if (out != null) { 822 out.flush(); 823 } 824 } 825 826 /* 827 * Various ZIP constants 828 */ 829 /** 830 * local file header signature 831 */ 832 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); 833 /** 834 * data descriptor signature 835 */ 836 static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes(); 837 /** 838 * central file header signature 839 */ 840 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); 841 /** 842 * end of central dir signature 843 */ 844 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); 845 /** 846 * ZIP64 end of central dir signature 847 */ 848 static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L); 849 /** 850 * ZIP64 end of central dir locator signature 851 */ 852 static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L); 853 854 /** 855 * Writes next block of compressed data to the output stream. 856 * @throws IOException on error 857 */ 858 protected final void deflate() throws IOException { 859 int len = def.deflate(buf, 0, buf.length); 860 if (len > 0) { 861 writeOut(buf, 0, len); 862 written += len; 863 } 864 } 865 866 /** 867 * Writes the local file header entry 868 * @param ze the entry to write 869 * @throws IOException on error 870 */ 871 protected void writeLocalFileHeader(ZipArchiveEntry ze) throws IOException { 872 873 boolean encodable = zipEncoding.canEncode(ze.getName()); 874 ByteBuffer name = getName(ze); 875 876 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) { 877 addUnicodeExtraFields(ze, encodable, name); 878 } 879 880 offsets.put(ze, Long.valueOf(written)); 881 882 writeOut(LFH_SIG); 883 written += WORD; 884 885 //store method in local variable to prevent multiple method calls 886 final int zipMethod = ze.getMethod(); 887 888 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, 889 !encodable 890 && fallbackToUTF8, 891 hasZip64Extra(ze)); 892 written += WORD; 893 894 // compression method 895 writeOut(ZipShort.getBytes(zipMethod)); 896 written += SHORT; 897 898 // last mod. time and date 899 writeOut(ZipUtil.toDosTime(ze.getTime())); 900 written += WORD; 901 902 // CRC 903 // compressed length 904 // uncompressed length 905 entry.localDataStart = written; 906 if (zipMethod == DEFLATED || raf != null) { 907 writeOut(LZERO); 908 if (hasZip64Extra(entry.entry)) { 909 // point to ZIP64 extended information extra field for 910 // sizes, may get rewritten once sizes are known if 911 // stream is seekable 912 writeOut(ZipLong.ZIP64_MAGIC.getBytes()); 913 writeOut(ZipLong.ZIP64_MAGIC.getBytes()); 914 } else { 915 writeOut(LZERO); 916 writeOut(LZERO); 917 } 918 } else { 919 writeOut(ZipLong.getBytes(ze.getCrc())); 920 byte[] size = ZipLong.ZIP64_MAGIC.getBytes(); 921 if (!hasZip64Extra(ze)) { 922 size = ZipLong.getBytes(ze.getSize()); 923 } 924 writeOut(size); 925 writeOut(size); 926 } 927 // CheckStyle:MagicNumber OFF 928 written += 12; 929 // CheckStyle:MagicNumber ON 930 931 // file name length 932 writeOut(ZipShort.getBytes(name.limit())); 933 written += SHORT; 934 935 // extra field length 936 byte[] extra = ze.getLocalFileDataExtra(); 937 writeOut(ZipShort.getBytes(extra.length)); 938 written += SHORT; 939 940 // file name 941 writeOut(name.array(), name.arrayOffset(), 942 name.limit() - name.position()); 943 written += name.limit(); 944 945 // extra field 946 writeOut(extra); 947 written += extra.length; 948 949 entry.dataStart = written; 950 } 951 952 /** 953 * Adds UnicodeExtra fields for name and file comment if mode is 954 * ALWAYS or the data cannot be encoded using the configured 955 * encoding. 956 */ 957 private void addUnicodeExtraFields(ZipArchiveEntry ze, boolean encodable, 958 ByteBuffer name) 959 throws IOException { 960 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 961 || !encodable) { 962 ze.addExtraField(new UnicodePathExtraField(ze.getName(), 963 name.array(), 964 name.arrayOffset(), 965 name.limit() 966 - name.position())); 967 } 968 969 String comm = ze.getComment(); 970 if (comm != null && !"".equals(comm)) { 971 972 boolean commentEncodable = zipEncoding.canEncode(comm); 973 974 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 975 || !commentEncodable) { 976 ByteBuffer commentB = getEntryEncoding(ze).encode(comm); 977 ze.addExtraField(new UnicodeCommentExtraField(comm, 978 commentB.array(), 979 commentB.arrayOffset(), 980 commentB.limit() 981 - commentB.position()) 982 ); 983 } 984 } 985 } 986 987 /** 988 * Writes the data descriptor entry. 989 * @param ze the entry to write 990 * @throws IOException on error 991 */ 992 protected void writeDataDescriptor(ZipArchiveEntry ze) throws IOException { 993 if (ze.getMethod() != DEFLATED || raf != null) { 994 return; 995 } 996 writeOut(DD_SIG); 997 writeOut(ZipLong.getBytes(ze.getCrc())); 998 int sizeFieldSize = WORD; 999 if (!hasZip64Extra(ze)) { 1000 writeOut(ZipLong.getBytes(ze.getCompressedSize())); 1001 writeOut(ZipLong.getBytes(ze.getSize())); 1002 } else { 1003 sizeFieldSize = DWORD; 1004 writeOut(ZipEightByteInteger.getBytes(ze.getCompressedSize())); 1005 writeOut(ZipEightByteInteger.getBytes(ze.getSize())); 1006 } 1007 written += 2 * WORD + 2 * sizeFieldSize; 1008 } 1009 1010 /** 1011 * Writes the central file header entry. 1012 * @param ze the entry to write 1013 * @throws IOException on error 1014 * @throws Zip64RequiredException if the archive's size exceeds 4 1015 * GByte and {@link Zip64Mode #setUseZip64} is {@link 1016 * Zip64Mode#Never}. 1017 */ 1018 protected void writeCentralFileHeader(ZipArchiveEntry ze) throws IOException { 1019 writeOut(CFH_SIG); 1020 written += WORD; 1021 1022 final long lfhOffset = offsets.get(ze).longValue(); 1023 final boolean needsZip64Extra = hasZip64Extra(ze) 1024 || ze.getCompressedSize() >= ZIP64_MAGIC 1025 || ze.getSize() >= ZIP64_MAGIC 1026 || lfhOffset >= ZIP64_MAGIC; 1027 1028 if (needsZip64Extra && zip64Mode == Zip64Mode.Never) { 1029 // must be the offset that is too big, otherwise an 1030 // exception would have been throw in putArchiveEntry or 1031 // closeArchiveEntry 1032 throw new Zip64RequiredException(Zip64RequiredException 1033 .ARCHIVE_TOO_BIG_MESSAGE); 1034 } 1035 1036 handleZip64Extra(ze, lfhOffset, needsZip64Extra); 1037 1038 // version made by 1039 // CheckStyle:MagicNumber OFF 1040 writeOut(ZipShort.getBytes((ze.getPlatform() << 8) | 1041 (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION 1042 : ZIP64_MIN_VERSION))); 1043 written += SHORT; 1044 1045 final int zipMethod = ze.getMethod(); 1046 final boolean encodable = zipEncoding.canEncode(ze.getName()); 1047 writeVersionNeededToExtractAndGeneralPurposeBits(zipMethod, 1048 !encodable 1049 && fallbackToUTF8, 1050 needsZip64Extra); 1051 written += WORD; 1052 1053 // compression method 1054 writeOut(ZipShort.getBytes(zipMethod)); 1055 written += SHORT; 1056 1057 // last mod. time and date 1058 writeOut(ZipUtil.toDosTime(ze.getTime())); 1059 written += WORD; 1060 1061 // CRC 1062 // compressed length 1063 // uncompressed length 1064 writeOut(ZipLong.getBytes(ze.getCrc())); 1065 if (ze.getCompressedSize() >= ZIP64_MAGIC 1066 || ze.getSize() >= ZIP64_MAGIC) { 1067 writeOut(ZipLong.ZIP64_MAGIC.getBytes()); 1068 writeOut(ZipLong.ZIP64_MAGIC.getBytes()); 1069 } else { 1070 writeOut(ZipLong.getBytes(ze.getCompressedSize())); 1071 writeOut(ZipLong.getBytes(ze.getSize())); 1072 } 1073 // CheckStyle:MagicNumber OFF 1074 written += 12; 1075 // CheckStyle:MagicNumber ON 1076 1077 ByteBuffer name = getName(ze); 1078 1079 writeOut(ZipShort.getBytes(name.limit())); 1080 written += SHORT; 1081 1082 // extra field length 1083 byte[] extra = ze.getCentralDirectoryExtra(); 1084 writeOut(ZipShort.getBytes(extra.length)); 1085 written += SHORT; 1086 1087 // file comment length 1088 String comm = ze.getComment(); 1089 if (comm == null) { 1090 comm = ""; 1091 } 1092 1093 ByteBuffer commentB = getEntryEncoding(ze).encode(comm); 1094 1095 writeOut(ZipShort.getBytes(commentB.limit())); 1096 written += SHORT; 1097 1098 // disk number start 1099 writeOut(ZERO); 1100 written += SHORT; 1101 1102 // internal file attributes 1103 writeOut(ZipShort.getBytes(ze.getInternalAttributes())); 1104 written += SHORT; 1105 1106 // external file attributes 1107 writeOut(ZipLong.getBytes(ze.getExternalAttributes())); 1108 written += WORD; 1109 1110 // relative offset of LFH 1111 writeOut(ZipLong.getBytes(Math.min(lfhOffset, ZIP64_MAGIC))); 1112 written += WORD; 1113 1114 // file name 1115 writeOut(name.array(), name.arrayOffset(), 1116 name.limit() - name.position()); 1117 written += name.limit(); 1118 1119 // extra field 1120 writeOut(extra); 1121 written += extra.length; 1122 1123 // file comment 1124 writeOut(commentB.array(), commentB.arrayOffset(), 1125 commentB.limit() - commentB.position()); 1126 written += commentB.limit(); 1127 } 1128 1129 /** 1130 * If the entry needs Zip64 extra information inside the central 1131 * directory then configure its data. 1132 */ 1133 private void handleZip64Extra(ZipArchiveEntry ze, long lfhOffset, 1134 boolean needsZip64Extra) { 1135 if (needsZip64Extra) { 1136 Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze); 1137 if (ze.getCompressedSize() >= ZIP64_MAGIC 1138 || ze.getSize() >= ZIP64_MAGIC) { 1139 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize())); 1140 z64.setSize(new ZipEightByteInteger(ze.getSize())); 1141 } else { 1142 // reset value that may have been set for LFH 1143 z64.setCompressedSize(null); 1144 z64.setSize(null); 1145 } 1146 if (lfhOffset >= ZIP64_MAGIC) { 1147 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset)); 1148 } 1149 ze.setExtra(); 1150 } 1151 } 1152 1153 /** 1154 * Writes the "End of central dir record". 1155 * @throws IOException on error 1156 * @throws Zip64RequiredException if the archive's size exceeds 4 1157 * GByte or there are more than 65535 entries inside the archive 1158 * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}. 1159 */ 1160 protected void writeCentralDirectoryEnd() throws IOException { 1161 writeOut(EOCD_SIG); 1162 1163 // disk numbers 1164 writeOut(ZERO); 1165 writeOut(ZERO); 1166 1167 // number of entries 1168 int numberOfEntries = entries.size(); 1169 if (numberOfEntries > ZIP64_MAGIC_SHORT 1170 && zip64Mode == Zip64Mode.Never) { 1171 throw new Zip64RequiredException(Zip64RequiredException 1172 .TOO_MANY_ENTRIES_MESSAGE); 1173 } 1174 if (cdOffset > ZIP64_MAGIC && zip64Mode == Zip64Mode.Never) { 1175 throw new Zip64RequiredException(Zip64RequiredException 1176 .ARCHIVE_TOO_BIG_MESSAGE); 1177 } 1178 1179 byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, 1180 ZIP64_MAGIC_SHORT)); 1181 writeOut(num); 1182 writeOut(num); 1183 1184 // length and location of CD 1185 writeOut(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC))); 1186 writeOut(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC))); 1187 1188 // ZIP file comment 1189 ByteBuffer data = this.zipEncoding.encode(comment); 1190 writeOut(ZipShort.getBytes(data.limit())); 1191 writeOut(data.array(), data.arrayOffset(), 1192 data.limit() - data.position()); 1193 } 1194 1195 private static final byte[] ONE = ZipLong.getBytes(1L); 1196 1197 /** 1198 * Writes the "ZIP64 End of central dir record" and 1199 * "ZIP64 End of central dir locator". 1200 * @throws IOException on error 1201 * @since 1.3 1202 */ 1203 protected void writeZip64CentralDirectory() throws IOException { 1204 if (zip64Mode == Zip64Mode.Never) { 1205 return; 1206 } 1207 1208 if (!hasUsedZip64 1209 && (cdOffset >= ZIP64_MAGIC || cdLength >= ZIP64_MAGIC 1210 || entries.size() >= ZIP64_MAGIC_SHORT)) { 1211 // actually "will use" 1212 hasUsedZip64 = true; 1213 } 1214 1215 if (!hasUsedZip64) { 1216 return; 1217 } 1218 1219 long offset = written; 1220 1221 writeOut(ZIP64_EOCD_SIG); 1222 // size, we don't have any variable length as we don't support 1223 // the extensible data sector, yet 1224 writeOut(ZipEightByteInteger 1225 .getBytes(SHORT /* version made by */ 1226 + SHORT /* version needed to extract */ 1227 + WORD /* disk number */ 1228 + WORD /* disk with central directory */ 1229 + DWORD /* number of entries in CD on this disk */ 1230 + DWORD /* total number of entries */ 1231 + DWORD /* size of CD */ 1232 + DWORD /* offset of CD */ 1233 )); 1234 1235 // version made by and version needed to extract 1236 writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); 1237 writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); 1238 1239 // disk numbers - four bytes this time 1240 writeOut(LZERO); 1241 writeOut(LZERO); 1242 1243 // number of entries 1244 byte[] num = ZipEightByteInteger.getBytes(entries.size()); 1245 writeOut(num); 1246 writeOut(num); 1247 1248 // length and location of CD 1249 writeOut(ZipEightByteInteger.getBytes(cdLength)); 1250 writeOut(ZipEightByteInteger.getBytes(cdOffset)); 1251 1252 // no "zip64 extensible data sector" for now 1253 1254 // and now the "ZIP64 end of central directory locator" 1255 writeOut(ZIP64_EOCD_LOC_SIG); 1256 1257 // disk number holding the ZIP64 EOCD record 1258 writeOut(LZERO); 1259 // relative offset of ZIP64 EOCD record 1260 writeOut(ZipEightByteInteger.getBytes(offset)); 1261 // total number of disks 1262 writeOut(ONE); 1263 } 1264 1265 /** 1266 * Write bytes to output or random access file. 1267 * @param data the byte array to write 1268 * @throws IOException on error 1269 */ 1270 protected final void writeOut(byte[] data) throws IOException { 1271 writeOut(data, 0, data.length); 1272 } 1273 1274 /** 1275 * Write bytes to output or random access file. 1276 * @param data the byte array to write 1277 * @param offset the start position to write from 1278 * @param length the number of bytes to write 1279 * @throws IOException on error 1280 */ 1281 protected final void writeOut(byte[] data, int offset, int length) 1282 throws IOException { 1283 if (raf != null) { 1284 raf.write(data, offset, length); 1285 } else { 1286 out.write(data, offset, length); 1287 } 1288 } 1289 1290 private void deflateUntilInputIsNeeded() throws IOException { 1291 while (!def.needsInput()) { 1292 deflate(); 1293 } 1294 } 1295 1296 private void writeVersionNeededToExtractAndGeneralPurposeBits(final int 1297 zipMethod, 1298 final boolean 1299 utfFallback, 1300 final boolean 1301 zip64) 1302 throws IOException { 1303 1304 // CheckStyle:MagicNumber OFF 1305 int versionNeededToExtract = INITIAL_VERSION; 1306 GeneralPurposeBit b = new GeneralPurposeBit(); 1307 b.useUTF8ForNames(useUTF8Flag || utfFallback); 1308 if (zipMethod == DEFLATED && raf == null) { 1309 // requires version 2 as we are going to store length info 1310 // in the data descriptor 1311 versionNeededToExtract = DATA_DESCRIPTOR_MIN_VERSION; 1312 b.useDataDescriptor(true); 1313 } 1314 if (zip64) { 1315 versionNeededToExtract = ZIP64_MIN_VERSION; 1316 } 1317 // CheckStyle:MagicNumber ON 1318 1319 // version needed to extract 1320 writeOut(ZipShort.getBytes(versionNeededToExtract)); 1321 // general purpose bit flag 1322 writeOut(b.encode()); 1323 } 1324 1325 /** 1326 * Creates a new zip entry taking some information from the given 1327 * file and using the provided name. 1328 * 1329 * <p>The name will be adjusted to end with a forward slash "/" if 1330 * the file is a directory. If the file is not a directory a 1331 * potential trailing forward slash will be stripped from the 1332 * entry name.</p> 1333 * 1334 * <p>Must not be used if the stream has already been closed.</p> 1335 */ 1336 @Override 1337 public ArchiveEntry createArchiveEntry(File inputFile, String entryName) 1338 throws IOException { 1339 if (finished) { 1340 throw new IOException("Stream has already been finished"); 1341 } 1342 return new ZipArchiveEntry(inputFile, entryName); 1343 } 1344 1345 /** 1346 * Get the existing ZIP64 extended information extra field or 1347 * create a new one and add it to the entry. 1348 * 1349 * @since 1.3 1350 */ 1351 private Zip64ExtendedInformationExtraField 1352 getZip64Extra(ZipArchiveEntry ze) { 1353 if (entry != null) { 1354 entry.causedUseOfZip64 = !hasUsedZip64; 1355 } 1356 hasUsedZip64 = true; 1357 Zip64ExtendedInformationExtraField z64 = 1358 (Zip64ExtendedInformationExtraField) 1359 ze.getExtraField(Zip64ExtendedInformationExtraField 1360 .HEADER_ID); 1361 if (z64 == null) { 1362 /* 1363 System.err.println("Adding z64 for " + ze.getName() 1364 + ", method: " + ze.getMethod() 1365 + " (" + (ze.getMethod() == STORED) + ")" 1366 + ", raf: " + (raf != null)); 1367 */ 1368 z64 = new Zip64ExtendedInformationExtraField(); 1369 } 1370 1371 // even if the field is there already, make sure it is the first one 1372 ze.addAsFirstExtraField(z64); 1373 1374 return z64; 1375 } 1376 1377 /** 1378 * Is there a ZIP64 extended information extra field for the 1379 * entry? 1380 * 1381 * @since 1.3 1382 */ 1383 private boolean hasZip64Extra(ZipArchiveEntry ze) { 1384 return ze.getExtraField(Zip64ExtendedInformationExtraField 1385 .HEADER_ID) 1386 != null; 1387 } 1388 1389 /** 1390 * If the mode is AsNeeded and the entry is a compressed entry of 1391 * unknown size that gets written to a non-seekable stream the 1392 * change the default to Never. 1393 * 1394 * @since 1.3 1395 */ 1396 private Zip64Mode getEffectiveZip64Mode(ZipArchiveEntry ze) { 1397 if (zip64Mode != Zip64Mode.AsNeeded 1398 || raf != null 1399 || ze.getMethod() != DEFLATED 1400 || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) { 1401 return zip64Mode; 1402 } 1403 return Zip64Mode.Never; 1404 } 1405 1406 private ZipEncoding getEntryEncoding(ZipArchiveEntry ze) { 1407 boolean encodable = zipEncoding.canEncode(ze.getName()); 1408 return !encodable && fallbackToUTF8 1409 ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; 1410 } 1411 1412 private ByteBuffer getName(ZipArchiveEntry ze) throws IOException { 1413 return getEntryEncoding(ze).encode(ze.getName()); 1414 } 1415 1416 /** 1417 * Closes the underlying stream/file without finishing the 1418 * archive, the result will likely be a corrupt archive. 1419 * 1420 * <p>This method only exists to support tests that generate 1421 * corrupt archives so they can clean up any temporary files.</p> 1422 */ 1423 void destroy() throws IOException { 1424 if (raf != null) { 1425 raf.close(); 1426 } 1427 if (out != null) { 1428 out.close(); 1429 } 1430 } 1431 1432 /** 1433 * enum that represents the possible policies for creating Unicode 1434 * extra fields. 1435 */ 1436 public static final class UnicodeExtraFieldPolicy { 1437 /** 1438 * Always create Unicode extra fields. 1439 */ 1440 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always"); 1441 /** 1442 * Never create Unicode extra fields. 1443 */ 1444 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never"); 1445 /** 1446 * Create Unicode extra fields for filenames that cannot be 1447 * encoded using the specified encoding. 1448 */ 1449 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = 1450 new UnicodeExtraFieldPolicy("not encodeable"); 1451 1452 private final String name; 1453 private UnicodeExtraFieldPolicy(String n) { 1454 name = n; 1455 } 1456 @Override 1457 public String toString() { 1458 return name; 1459 } 1460 } 1461 1462 /** 1463 * Structure collecting information for the entry that is 1464 * currently being written. 1465 */ 1466 private static final class CurrentEntry { 1467 private CurrentEntry(ZipArchiveEntry entry) { 1468 this.entry = entry; 1469 } 1470 /** 1471 * Current ZIP entry. 1472 */ 1473 private final ZipArchiveEntry entry; 1474 /** 1475 * Offset for CRC entry in the local file header data for the 1476 * current entry starts here. 1477 */ 1478 private long localDataStart = 0; 1479 /** 1480 * Data for local header data 1481 */ 1482 private long dataStart = 0; 1483 /** 1484 * Number of bytes read for the current entry (can't rely on 1485 * Deflater#getBytesRead) when using DEFLATED. 1486 */ 1487 private long bytesRead = 0; 1488 /** 1489 * Whether current entry was the first one using ZIP64 features. 1490 */ 1491 private boolean causedUseOfZip64 = false; 1492 /** 1493 * Whether write() has been called at all. 1494 * 1495 * <p>In order to create a valid archive {@link 1496 * #closeArchiveEntry closeArchiveEntry} will write an empty 1497 * array to get the CRC right if nothing has been written to 1498 * the stream at all.</p> 1499 */ 1500 private boolean hasWritten; 1501 } 1502 1503 }