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