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