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.ByteArrayOutputStream; 021import java.io.File; 022import java.io.FileOutputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.io.OutputStream; 026import java.nio.ByteBuffer; 027import java.nio.channels.SeekableByteChannel; 028import java.nio.file.Files; 029import java.nio.file.StandardOpenOption; 030import java.util.Calendar; 031import java.util.EnumSet; 032import java.util.HashMap; 033import java.util.LinkedList; 034import java.util.List; 035import java.util.Map; 036import java.util.zip.Deflater; 037import java.util.zip.ZipException; 038 039import org.apache.commons.compress.archivers.ArchiveEntry; 040import org.apache.commons.compress.archivers.ArchiveOutputStream; 041import org.apache.commons.compress.utils.IOUtils; 042 043import static org.apache.commons.compress.archivers.zip.ZipConstants.DATA_DESCRIPTOR_MIN_VERSION; 044import static org.apache.commons.compress.archivers.zip.ZipConstants.DEFLATE_MIN_VERSION; 045import static org.apache.commons.compress.archivers.zip.ZipConstants.DWORD; 046import static org.apache.commons.compress.archivers.zip.ZipConstants.INITIAL_VERSION; 047import static org.apache.commons.compress.archivers.zip.ZipConstants.SHORT; 048import static org.apache.commons.compress.archivers.zip.ZipConstants.WORD; 049import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC; 050import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MAGIC_SHORT; 051import static org.apache.commons.compress.archivers.zip.ZipConstants.ZIP64_MIN_VERSION; 052import static org.apache.commons.compress.archivers.zip.ZipLong.putLong; 053import static org.apache.commons.compress.archivers.zip.ZipShort.putShort; 054 055/** 056 * Reimplementation of {@link java.util.zip.ZipOutputStream 057 * java.util.zip.ZipOutputStream} that does handle the extended 058 * functionality of this package, especially internal/external file 059 * attributes and extra fields with different layouts for local file 060 * data and central directory entries. 061 * 062 * <p>This class will try to use {@link 063 * java.nio.channels.SeekableByteChannel} when it knows that the 064 * output is going to go to a file and no split archive shall be 065 * created.</p> 066 * 067 * <p>If SeekableByteChannel cannot be used, this implementation will use 068 * a Data Descriptor to store size and CRC information for {@link 069 * #DEFLATED DEFLATED} entries, this means, you don't need to 070 * calculate them yourself. Unfortunately this is not possible for 071 * the {@link #STORED STORED} method, here setting the CRC and 072 * uncompressed size information is required before {@link 073 * #putArchiveEntry(ArchiveEntry)} can be called.</p> 074 * 075 * <p>As of Apache Commons Compress 1.3 it transparently supports Zip64 076 * extensions and thus individual entries and archives larger than 4 077 * GB or with more than 65536 entries in most cases but explicit 078 * control is provided via {@link #setUseZip64}. If the stream can not 079 * use SeekableByteChannel and you try to write a ZipArchiveEntry of 080 * unknown size then Zip64 extensions will be disabled by default.</p> 081 * 082 * @NotThreadSafe 083 */ 084public class ZipArchiveOutputStream extends ArchiveOutputStream { 085 086 static final int BUFFER_SIZE = 512; 087 private static final int LFH_SIG_OFFSET = 0; 088 private static final int LFH_VERSION_NEEDED_OFFSET = 4; 089 private static final int LFH_GPB_OFFSET = 6; 090 private static final int LFH_METHOD_OFFSET = 8; 091 private static final int LFH_TIME_OFFSET = 10; 092 private static final int LFH_CRC_OFFSET = 14; 093 private static final int LFH_COMPRESSED_SIZE_OFFSET = 18; 094 private static final int LFH_ORIGINAL_SIZE_OFFSET = 22; 095 private static final int LFH_FILENAME_LENGTH_OFFSET = 26; 096 private static final int LFH_EXTRA_LENGTH_OFFSET = 28; 097 private static final int LFH_FILENAME_OFFSET = 30; 098 private static final int CFH_SIG_OFFSET = 0; 099 private static final int CFH_VERSION_MADE_BY_OFFSET = 4; 100 private static final int CFH_VERSION_NEEDED_OFFSET = 6; 101 private static final int CFH_GPB_OFFSET = 8; 102 private static final int CFH_METHOD_OFFSET = 10; 103 private static final int CFH_TIME_OFFSET = 12; 104 private static final int CFH_CRC_OFFSET = 16; 105 private static final int CFH_COMPRESSED_SIZE_OFFSET = 20; 106 private static final int CFH_ORIGINAL_SIZE_OFFSET = 24; 107 private static final int CFH_FILENAME_LENGTH_OFFSET = 28; 108 private static final int CFH_EXTRA_LENGTH_OFFSET = 30; 109 private static final int CFH_COMMENT_LENGTH_OFFSET = 32; 110 private static final int CFH_DISK_NUMBER_OFFSET = 34; 111 private static final int CFH_INTERNAL_ATTRIBUTES_OFFSET = 36; 112 private static final int CFH_EXTERNAL_ATTRIBUTES_OFFSET = 38; 113 private static final int CFH_LFH_OFFSET = 42; 114 private static final int CFH_FILENAME_OFFSET = 46; 115 116 /** indicates if this archive is finished. protected for use in Jar implementation */ 117 protected boolean finished = false; 118 119 /** 120 * Compression method for deflated entries. 121 */ 122 public static final int DEFLATED = java.util.zip.ZipEntry.DEFLATED; 123 124 /** 125 * Default compression level for deflated entries. 126 */ 127 public static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION; 128 129 /** 130 * Compression method for stored entries. 131 */ 132 public static final int STORED = java.util.zip.ZipEntry.STORED; 133 134 /** 135 * default encoding for file names and comment. 136 */ 137 static final String DEFAULT_ENCODING = ZipEncodingHelper.UTF8; 138 139 /** 140 * General purpose flag, which indicates that file names are 141 * written in UTF-8. 142 * @deprecated use {@link GeneralPurposeBit#UFT8_NAMES_FLAG} instead 143 */ 144 @Deprecated 145 public static final int EFS_FLAG = GeneralPurposeBit.UFT8_NAMES_FLAG; 146 147 private static final byte[] EMPTY = new byte[0]; 148 149 /** 150 * Current entry. 151 */ 152 private CurrentEntry entry; 153 154 /** 155 * The file comment. 156 */ 157 private String comment = ""; 158 159 /** 160 * Compression level for next entry. 161 */ 162 private int level = DEFAULT_COMPRESSION; 163 164 /** 165 * Has the compression level changed when compared to the last 166 * entry? 167 */ 168 private boolean hasCompressionLevelChanged = false; 169 170 /** 171 * Default compression method for next entry. 172 */ 173 private int method = java.util.zip.ZipEntry.DEFLATED; 174 175 /** 176 * List of ZipArchiveEntries written so far. 177 */ 178 private final List<ZipArchiveEntry> entries = 179 new LinkedList<>(); 180 181 private final StreamCompressor streamCompressor; 182 183 /** 184 * Start of central directory. 185 */ 186 private long cdOffset = 0; 187 188 /** 189 * Length of central directory. 190 */ 191 private long cdLength = 0; 192 193 /** 194 * Disk number start of central directory. 195 */ 196 private long cdDiskNumberStart = 0; 197 198 /** 199 * Length of end of central directory 200 */ 201 private long eocdLength = 0; 202 203 /** 204 * Helper, a 0 as ZipShort. 205 */ 206 private static final byte[] ZERO = {0, 0}; 207 208 /** 209 * Helper, a 0 as ZipLong. 210 */ 211 private static final byte[] LZERO = {0, 0, 0, 0}; 212 213 private static final byte[] ONE = ZipLong.getBytes(1L); 214 215 /** 216 * Holds some book-keeping data for each entry. 217 */ 218 private final Map<ZipArchiveEntry, EntryMetaData> metaData = 219 new HashMap<>(); 220 221 /** 222 * The encoding to use for file names and the file comment. 223 * 224 * <p>For a list of possible values see <a 225 * 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>. 226 * Defaults to UTF-8.</p> 227 */ 228 private String encoding = DEFAULT_ENCODING; 229 230 /** 231 * The zip encoding to use for file names and the file comment. 232 * 233 * This field is of internal use and will be set in {@link 234 * #setEncoding(String)}. 235 */ 236 private ZipEncoding zipEncoding = 237 ZipEncodingHelper.getZipEncoding(DEFAULT_ENCODING); 238 239 240 /** 241 * This Deflater object is used for output. 242 * 243 */ 244 protected final Deflater def; 245 /** 246 * Optional random access output. 247 */ 248 private final SeekableByteChannel channel; 249 250 private final OutputStream out; 251 252 /** 253 * whether to use the general purpose bit flag when writing UTF-8 254 * file names or not. 255 */ 256 private boolean useUTF8Flag = true; 257 258 /** 259 * Whether to encode non-encodable file names as UTF-8. 260 */ 261 private boolean fallbackToUTF8 = false; 262 263 /** 264 * whether to create UnicodePathExtraField-s for each entry. 265 */ 266 private UnicodeExtraFieldPolicy createUnicodeExtraFields = UnicodeExtraFieldPolicy.NEVER; 267 268 /** 269 * Whether anything inside this archive has used a ZIP64 feature. 270 * 271 * @since 1.3 272 */ 273 private boolean hasUsedZip64 = false; 274 275 private Zip64Mode zip64Mode = Zip64Mode.AsNeeded; 276 277 private final byte[] copyBuffer = new byte[32768]; 278 private final Calendar calendarInstance = Calendar.getInstance(); 279 280 /** 281 * Whether we are creating a split zip 282 */ 283 private final boolean isSplitZip; 284 285 /** 286 * Holds the number of Central Directories on each disk, this is used 287 * when writing Zip64 End Of Central Directory and End Of Central Directory 288 */ 289 private final Map<Integer, Integer> numberOfCDInDiskData = new HashMap<>(); 290 291 /** 292 * Creates a new ZIP OutputStream filtering the underlying stream. 293 * @param out the outputstream to zip 294 */ 295 public ZipArchiveOutputStream(final OutputStream out) { 296 this.out = out; 297 this.channel = null; 298 def = new Deflater(level, true); 299 streamCompressor = StreamCompressor.create(out, def); 300 isSplitZip = false; 301 } 302 303 /** 304 * Creates a new ZIP OutputStream writing to a File. Will use 305 * random access if possible. 306 * @param file the file to zip to 307 * @throws IOException on error 308 */ 309 public ZipArchiveOutputStream(final File file) throws IOException { 310 def = new Deflater(level, true); 311 OutputStream o = null; 312 SeekableByteChannel _channel = null; 313 StreamCompressor _streamCompressor = null; 314 try { 315 _channel = Files.newByteChannel(file.toPath(), 316 EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, 317 StandardOpenOption.READ, 318 StandardOpenOption.TRUNCATE_EXISTING)); 319 // will never get opened properly when an exception is thrown so doesn't need to get closed 320 _streamCompressor = StreamCompressor.create(_channel, def); //NOSONAR 321 } catch (final IOException e) { // NOSONAR 322 IOUtils.closeQuietly(_channel); 323 _channel = null; 324 o = new FileOutputStream(file); 325 _streamCompressor = StreamCompressor.create(o, def); 326 } 327 out = o; 328 channel = _channel; 329 streamCompressor = _streamCompressor; 330 isSplitZip = false; 331 } 332 333 /** 334 * Creates a split ZIP Archive. 335 * 336 * <p>The files making up the archive will use Z01, Z02, 337 * ... extensions and the last part of it will be the given {@code 338 * file}.</p> 339 * 340 * <p>Even though the stream writes to a file this stream will 341 * behave as if no random access was possible. This means the 342 * sizes of stored entries need to be known before the actual 343 * entry data is written.</p> 344 * 345 * @param file the file that will become the last part of the split archive 346 * @param zipSplitSize maximum size of a single part of the split 347 * archive created by this stream. Must be between 64kB and about 348 * 4GB. 349 * 350 * @throws IOException on error 351 * @throws IllegalArgumentException if zipSplitSize is not in the required range 352 * @since 1.20 353 */ 354 public ZipArchiveOutputStream(final File file, final long zipSplitSize) throws IOException { 355 def = new Deflater(level, true); 356 this.out = new ZipSplitOutputStream(file, zipSplitSize); 357 streamCompressor = StreamCompressor.create(this.out, def); 358 channel = null; 359 isSplitZip = true; 360 } 361 362 /** 363 * Creates a new ZIP OutputStream writing to a SeekableByteChannel. 364 * 365 * <p>{@link 366 * org.apache.commons.compress.utils.SeekableInMemoryByteChannel} 367 * allows you to write to an in-memory archive using random 368 * access.</p> 369 * 370 * @param channel the channel to zip to 371 * @throws IOException on error 372 * @since 1.13 373 */ 374 public ZipArchiveOutputStream(SeekableByteChannel channel) throws IOException { 375 this.channel = channel; 376 def = new Deflater(level, true); 377 streamCompressor = StreamCompressor.create(channel, def); 378 out = null; 379 isSplitZip = false; 380 } 381 382 /** 383 * This method indicates whether this archive is writing to a 384 * seekable stream (i.e., to a random access file). 385 * 386 * <p>For seekable streams, you don't need to calculate the CRC or 387 * uncompressed size for {@link #STORED} entries before 388 * invoking {@link #putArchiveEntry(ArchiveEntry)}. 389 * @return true if seekable 390 */ 391 public boolean isSeekable() { 392 return channel != null; 393 } 394 395 /** 396 * The encoding to use for file names and the file comment. 397 * 398 * <p>For a list of possible values see <a 399 * 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>. 400 * Defaults to UTF-8.</p> 401 * @param encoding the encoding to use for file names, use null 402 * for the platform's default encoding 403 */ 404 public void setEncoding(final String encoding) { 405 this.encoding = encoding; 406 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); 407 if (useUTF8Flag && !ZipEncodingHelper.isUTF8(encoding)) { 408 useUTF8Flag = false; 409 } 410 } 411 412 /** 413 * The encoding to use for file names and the file comment. 414 * 415 * @return null if using the platform's default character encoding. 416 */ 417 public String getEncoding() { 418 return encoding; 419 } 420 421 /** 422 * Whether to set the language encoding flag if the file name 423 * encoding is UTF-8. 424 * 425 * <p>Defaults to true.</p> 426 * 427 * @param b whether to set the language encoding flag if the file 428 * name encoding is UTF-8 429 */ 430 public void setUseLanguageEncodingFlag(final boolean b) { 431 useUTF8Flag = b && ZipEncodingHelper.isUTF8(encoding); 432 } 433 434 /** 435 * Whether to create Unicode Extra Fields. 436 * 437 * <p>Defaults to NEVER.</p> 438 * 439 * @param b whether to create Unicode Extra Fields. 440 */ 441 public void setCreateUnicodeExtraFields(final UnicodeExtraFieldPolicy b) { 442 createUnicodeExtraFields = b; 443 } 444 445 /** 446 * Whether to fall back to UTF and the language encoding flag if 447 * the file name cannot be encoded using the specified encoding. 448 * 449 * <p>Defaults to false.</p> 450 * 451 * @param b whether to fall back to UTF and the language encoding 452 * flag if the file name cannot be encoded using the specified 453 * encoding. 454 */ 455 public void setFallbackToUTF8(final boolean b) { 456 fallbackToUTF8 = b; 457 } 458 459 /** 460 * Whether Zip64 extensions will be used. 461 * 462 * <p>When setting the mode to {@link Zip64Mode#Never Never}, 463 * {@link #putArchiveEntry}, {@link #closeArchiveEntry}, {@link 464 * #finish} or {@link #close} may throw a {@link 465 * Zip64RequiredException} if the entry's size or the total size 466 * of the archive exceeds 4GB or there are more than 65536 entries 467 * inside the archive. Any archive created in this mode will be 468 * readable by implementations that don't support Zip64.</p> 469 * 470 * <p>When setting the mode to {@link Zip64Mode#Always Always}, 471 * Zip64 extensions will be used for all entries. Any archive 472 * created in this mode may be unreadable by implementations that 473 * don't support Zip64 even if all its contents would be.</p> 474 * 475 * <p>When setting the mode to {@link Zip64Mode#AsNeeded 476 * AsNeeded}, Zip64 extensions will transparently be used for 477 * those entries that require them. This mode can only be used if 478 * the uncompressed size of the {@link ZipArchiveEntry} is known 479 * when calling {@link #putArchiveEntry} or the archive is written 480 * to a seekable output (i.e. you have used the {@link 481 * #ZipArchiveOutputStream(java.io.File) File-arg constructor}) - 482 * this mode is not valid when the output stream is not seekable 483 * and the uncompressed size is unknown when {@link 484 * #putArchiveEntry} is called.</p> 485 * 486 * <p>If no entry inside the resulting archive requires Zip64 487 * extensions then {@link Zip64Mode#Never Never} will create the 488 * smallest archive. {@link Zip64Mode#AsNeeded AsNeeded} will 489 * create a slightly bigger archive if the uncompressed size of 490 * any entry has initially been unknown and create an archive 491 * identical to {@link Zip64Mode#Never Never} otherwise. {@link 492 * Zip64Mode#Always Always} will create an archive that is at 493 * least 24 bytes per entry bigger than the one {@link 494 * Zip64Mode#Never Never} would create.</p> 495 * 496 * <p>Defaults to {@link Zip64Mode#AsNeeded AsNeeded} unless 497 * {@link #putArchiveEntry} is called with an entry of unknown 498 * size and data is written to a non-seekable stream - in this 499 * case the default is {@link Zip64Mode#Never Never}.</p> 500 * 501 * @since 1.3 502 * @param mode Whether Zip64 extensions will be used. 503 */ 504 public void setUseZip64(final Zip64Mode mode) { 505 zip64Mode = mode; 506 } 507 508 /** 509 * {@inheritDoc} 510 * @throws Zip64RequiredException if the archive's size exceeds 4 511 * GByte or there are more than 65535 entries inside the archive 512 * and {@link #setUseZip64} is {@link Zip64Mode#Never}. 513 */ 514 @Override 515 public void finish() throws IOException { 516 if (finished) { 517 throw new IOException("This archive has already been finished"); 518 } 519 520 if (entry != null) { 521 throw new IOException("This archive contains unclosed entries."); 522 } 523 524 long cdOverallOffset = streamCompressor.getTotalBytesWritten(); 525 cdOffset = cdOverallOffset; 526 if (isSplitZip) { 527 // when creating a split zip, the offset should be 528 // the offset to the corresponding segment disk 529 ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream)this.out; 530 cdOffset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten(); 531 cdDiskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex(); 532 } 533 writeCentralDirectoryInChunks(); 534 535 cdLength = streamCompressor.getTotalBytesWritten() - cdOverallOffset; 536 537 // calculate the length of end of central directory, as it may be used in writeZip64CentralDirectory 538 final ByteBuffer commentData = this.zipEncoding.encode(comment); 539 final long commentLength = (long) commentData.limit() - commentData.position(); 540 eocdLength = WORD /* length of EOCD_SIG */ 541 + SHORT /* number of this disk */ 542 + SHORT /* disk number of start of central directory */ 543 + SHORT /* total number of entries on this disk */ 544 + SHORT /* total number of entries */ 545 + WORD /* size of central directory */ 546 + WORD /* offset of start of central directory */ 547 + SHORT /* zip comment length */ 548 + commentLength /* zip comment */; 549 550 writeZip64CentralDirectory(); 551 writeCentralDirectoryEnd(); 552 metaData.clear(); 553 entries.clear(); 554 streamCompressor.close(); 555 if (isSplitZip) { 556 // trigger the ZipSplitOutputStream to write the final split segment 557 out.close(); 558 } 559 finished = true; 560 } 561 562 private void writeCentralDirectoryInChunks() throws IOException { 563 final int NUM_PER_WRITE = 1000; 564 final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(70 * NUM_PER_WRITE); 565 int count = 0; 566 for (final ZipArchiveEntry ze : entries) { 567 byteArrayOutputStream.write(createCentralFileHeader(ze)); 568 if (++count > NUM_PER_WRITE){ 569 writeCounted(byteArrayOutputStream.toByteArray()); 570 byteArrayOutputStream.reset(); 571 count = 0; 572 } 573 } 574 writeCounted(byteArrayOutputStream.toByteArray()); 575 } 576 577 /** 578 * Writes all necessary data for this entry. 579 * @throws IOException on error 580 * @throws Zip64RequiredException if the entry's uncompressed or 581 * compressed size exceeds 4 GByte and {@link #setUseZip64} 582 * is {@link Zip64Mode#Never}. 583 */ 584 @Override 585 public void closeArchiveEntry() throws IOException { 586 preClose(); 587 588 flushDeflater(); 589 590 final long bytesWritten = streamCompressor.getTotalBytesWritten() - entry.dataStart; 591 final long realCrc = streamCompressor.getCrc32(); 592 entry.bytesRead = streamCompressor.getBytesRead(); 593 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); 594 final boolean actuallyNeedsZip64 = handleSizesAndCrc(bytesWritten, realCrc, effectiveMode); 595 closeEntry(actuallyNeedsZip64, false); 596 streamCompressor.reset(); 597 } 598 599 /** 600 * Writes all necessary data for this entry. 601 * 602 * @param phased This entry is second phase of a 2-phase zip creation, size, compressed size and crc 603 * are known in ZipArchiveEntry 604 * @throws IOException on error 605 * @throws Zip64RequiredException if the entry's uncompressed or 606 * compressed size exceeds 4 GByte and {@link #setUseZip64} 607 * is {@link Zip64Mode#Never}. 608 */ 609 private void closeCopiedEntry(final boolean phased) throws IOException { 610 preClose(); 611 entry.bytesRead = entry.entry.getSize(); 612 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); 613 final boolean actuallyNeedsZip64 = checkIfNeedsZip64(effectiveMode); 614 closeEntry(actuallyNeedsZip64, phased); 615 } 616 617 private void closeEntry(final boolean actuallyNeedsZip64, final boolean phased) throws IOException { 618 if (!phased && channel != null) { 619 rewriteSizesAndCrc(actuallyNeedsZip64); 620 } 621 622 if (!phased) { 623 writeDataDescriptor(entry.entry); 624 } 625 entry = null; 626 } 627 628 private void preClose() throws IOException { 629 if (finished) { 630 throw new IOException("Stream has already been finished"); 631 } 632 633 if (entry == null) { 634 throw new IOException("No current entry to close"); 635 } 636 637 if (!entry.hasWritten) { 638 write(EMPTY, 0, 0); 639 } 640 } 641 642 /** 643 * Adds an archive entry with a raw input stream. 644 * 645 * If crc, size and compressed size are supplied on the entry, these values will be used as-is. 646 * Zip64 status is re-established based on the settings in this stream, and the supplied value 647 * is ignored. 648 * 649 * The entry is put and closed immediately. 650 * 651 * @param entry The archive entry to add 652 * @param rawStream The raw input stream of a different entry. May be compressed/encrypted. 653 * @throws IOException If copying fails 654 */ 655 public void addRawArchiveEntry(final ZipArchiveEntry entry, final InputStream rawStream) 656 throws IOException { 657 final ZipArchiveEntry ae = new ZipArchiveEntry(entry); 658 if (hasZip64Extra(ae)) { 659 // Will be re-added as required. this may make the file generated with this method 660 // somewhat smaller than standard mode, 661 // since standard mode is unable to remove the zip 64 header. 662 ae.removeExtraField(Zip64ExtendedInformationExtraField.HEADER_ID); 663 } 664 final boolean is2PhaseSource = ae.getCrc() != ZipArchiveEntry.CRC_UNKNOWN 665 && ae.getSize() != ArchiveEntry.SIZE_UNKNOWN 666 && ae.getCompressedSize() != ArchiveEntry.SIZE_UNKNOWN; 667 putArchiveEntry(ae, is2PhaseSource); 668 copyFromZipInputStream(rawStream); 669 closeCopiedEntry(is2PhaseSource); 670 } 671 672 /** 673 * Ensures all bytes sent to the deflater are written to the stream. 674 */ 675 private void flushDeflater() throws IOException { 676 if (entry.entry.getMethod() == DEFLATED) { 677 streamCompressor.flushDeflater(); 678 } 679 } 680 681 /** 682 * Ensures the current entry's size and CRC information is set to 683 * the values just written, verifies it isn't too big in the 684 * Zip64Mode.Never case and returns whether the entry would 685 * require a Zip64 extra field. 686 */ 687 private boolean handleSizesAndCrc(final long bytesWritten, final long crc, 688 final Zip64Mode effectiveMode) 689 throws ZipException { 690 if (entry.entry.getMethod() == DEFLATED) { 691 /* It turns out def.getBytesRead() returns wrong values if 692 * the size exceeds 4 GB on Java < Java7 693 entry.entry.setSize(def.getBytesRead()); 694 */ 695 entry.entry.setSize(entry.bytesRead); 696 entry.entry.setCompressedSize(bytesWritten); 697 entry.entry.setCrc(crc); 698 699 } else if (channel == null) { 700 if (entry.entry.getCrc() != crc) { 701 throw new ZipException("Bad CRC checksum for entry " 702 + entry.entry.getName() + ": " 703 + Long.toHexString(entry.entry.getCrc()) 704 + " instead of " 705 + Long.toHexString(crc)); 706 } 707 708 if (entry.entry.getSize() != bytesWritten) { 709 throw new ZipException("Bad size for entry " 710 + entry.entry.getName() + ": " 711 + entry.entry.getSize() 712 + " instead of " 713 + bytesWritten); 714 } 715 } else { /* method is STORED and we used SeekableByteChannel */ 716 entry.entry.setSize(bytesWritten); 717 entry.entry.setCompressedSize(bytesWritten); 718 entry.entry.setCrc(crc); 719 } 720 721 return checkIfNeedsZip64(effectiveMode); 722 } 723 724 /** 725 * Verifies the sizes aren't too big in the Zip64Mode.Never case 726 * and returns whether the entry would require a Zip64 extra 727 * field. 728 */ 729 private boolean checkIfNeedsZip64(final Zip64Mode effectiveMode) 730 throws ZipException { 731 final boolean actuallyNeedsZip64 = isZip64Required(entry.entry, effectiveMode); 732 if (actuallyNeedsZip64 && effectiveMode == Zip64Mode.Never) { 733 throw new Zip64RequiredException(Zip64RequiredException.getEntryTooBigMessage(entry.entry)); 734 } 735 return actuallyNeedsZip64; 736 } 737 738 private boolean isZip64Required(final ZipArchiveEntry entry1, final Zip64Mode requestedMode) { 739 return requestedMode == Zip64Mode.Always || isTooLageForZip32(entry1); 740 } 741 742 private boolean isTooLageForZip32(final ZipArchiveEntry zipArchiveEntry){ 743 return zipArchiveEntry.getSize() >= ZIP64_MAGIC || zipArchiveEntry.getCompressedSize() >= ZIP64_MAGIC; 744 } 745 746 /** 747 * When using random access output, write the local file header 748 * and potentiall the ZIP64 extra containing the correct CRC and 749 * compressed/uncompressed sizes. 750 */ 751 private void rewriteSizesAndCrc(final boolean actuallyNeedsZip64) 752 throws IOException { 753 final long save = channel.position(); 754 755 channel.position(entry.localDataStart); 756 writeOut(ZipLong.getBytes(entry.entry.getCrc())); 757 if (!hasZip64Extra(entry.entry) || !actuallyNeedsZip64) { 758 writeOut(ZipLong.getBytes(entry.entry.getCompressedSize())); 759 writeOut(ZipLong.getBytes(entry.entry.getSize())); 760 } else { 761 writeOut(ZipLong.ZIP64_MAGIC.getBytes()); 762 writeOut(ZipLong.ZIP64_MAGIC.getBytes()); 763 } 764 765 if (hasZip64Extra(entry.entry)) { 766 final ByteBuffer name = getName(entry.entry); 767 final int nameLen = name.limit() - name.position(); 768 // seek to ZIP64 extra, skip header and size information 769 channel.position(entry.localDataStart + 3 * WORD + 2 * SHORT 770 + nameLen + 2 * SHORT); 771 // inside the ZIP64 extra uncompressed size comes 772 // first, unlike the LFH, CD or data descriptor 773 writeOut(ZipEightByteInteger.getBytes(entry.entry.getSize())); 774 writeOut(ZipEightByteInteger.getBytes(entry.entry.getCompressedSize())); 775 776 if (!actuallyNeedsZip64) { 777 // do some cleanup: 778 // * rewrite version needed to extract 779 channel.position(entry.localDataStart - 5 * SHORT); 780 writeOut(ZipShort.getBytes(versionNeededToExtract(entry.entry.getMethod(), false, false))); 781 782 // * remove ZIP64 extra so it doesn't get written 783 // to the central directory 784 entry.entry.removeExtraField(Zip64ExtendedInformationExtraField 785 .HEADER_ID); 786 entry.entry.setExtra(); 787 788 // * reset hasUsedZip64 if it has been set because 789 // of this entry 790 if (entry.causedUseOfZip64) { 791 hasUsedZip64 = false; 792 } 793 } 794 } 795 channel.position(save); 796 } 797 798 /** 799 * {@inheritDoc} 800 * @throws ClassCastException if entry is not an instance of ZipArchiveEntry 801 * @throws Zip64RequiredException if the entry's uncompressed or 802 * compressed size is known to exceed 4 GByte and {@link #setUseZip64} 803 * is {@link Zip64Mode#Never}. 804 */ 805 @Override 806 public void putArchiveEntry(final ArchiveEntry archiveEntry) throws IOException { 807 putArchiveEntry(archiveEntry, false); 808 } 809 810 /** 811 * Writes the headers for an archive entry to the output stream. 812 * The caller must then write the content to the stream and call 813 * {@link #closeArchiveEntry()} to complete the process. 814 815 * @param archiveEntry The archiveEntry 816 * @param phased If true size, compressedSize and crc required to be known up-front in the archiveEntry 817 * @throws ClassCastException if entry is not an instance of ZipArchiveEntry 818 * @throws Zip64RequiredException if the entry's uncompressed or 819 * compressed size is known to exceed 4 GByte and {@link #setUseZip64} 820 * is {@link Zip64Mode#Never}. 821 */ 822 private void putArchiveEntry(final ArchiveEntry archiveEntry, final boolean phased) throws IOException { 823 if (finished) { 824 throw new IOException("Stream has already been finished"); 825 } 826 827 if (entry != null) { 828 closeArchiveEntry(); 829 } 830 831 entry = new CurrentEntry((ZipArchiveEntry) archiveEntry); 832 entries.add(entry.entry); 833 834 setDefaults(entry.entry); 835 836 final Zip64Mode effectiveMode = getEffectiveZip64Mode(entry.entry); 837 validateSizeInformation(effectiveMode); 838 839 if (shouldAddZip64Extra(entry.entry, effectiveMode)) { 840 841 final Zip64ExtendedInformationExtraField z64 = getZip64Extra(entry.entry); 842 843 ZipEightByteInteger size; 844 ZipEightByteInteger compressedSize; 845 if (phased) { 846 // sizes are already known 847 size = new ZipEightByteInteger(entry.entry.getSize()); 848 compressedSize = new ZipEightByteInteger(entry.entry.getCompressedSize()); 849 } else if (entry.entry.getMethod() == STORED 850 && entry.entry.getSize() != ArchiveEntry.SIZE_UNKNOWN) { 851 // actually, we already know the sizes 852 compressedSize = size = new ZipEightByteInteger(entry.entry.getSize()); 853 } else { 854 // just a placeholder, real data will be in data 855 // descriptor or inserted later via SeekableByteChannel 856 compressedSize = size = ZipEightByteInteger.ZERO; 857 } 858 z64.setSize(size); 859 z64.setCompressedSize(compressedSize); 860 entry.entry.setExtra(); 861 } 862 863 if (entry.entry.getMethod() == DEFLATED && hasCompressionLevelChanged) { 864 def.setLevel(level); 865 hasCompressionLevelChanged = false; 866 } 867 writeLocalFileHeader((ZipArchiveEntry) archiveEntry, phased); 868 } 869 870 /** 871 * Provides default values for compression method and last 872 * modification time. 873 */ 874 private void setDefaults(final ZipArchiveEntry entry) { 875 if (entry.getMethod() == -1) { // not specified 876 entry.setMethod(method); 877 } 878 879 if (entry.getTime() == -1) { // not specified 880 entry.setTime(System.currentTimeMillis()); 881 } 882 } 883 884 /** 885 * Throws an exception if the size is unknown for a stored entry 886 * that is written to a non-seekable output or the entry is too 887 * big to be written without Zip64 extra but the mode has been set 888 * to Never. 889 */ 890 private void validateSizeInformation(final Zip64Mode effectiveMode) 891 throws ZipException { 892 // Size/CRC not required if SeekableByteChannel is used 893 if (entry.entry.getMethod() == STORED && channel == null) { 894 if (entry.entry.getSize() == ArchiveEntry.SIZE_UNKNOWN) { 895 throw new ZipException("Uncompressed size is required for" 896 + " STORED method when not writing to a" 897 + " file"); 898 } 899 if (entry.entry.getCrc() == ZipArchiveEntry.CRC_UNKNOWN) { 900 throw new ZipException("CRC checksum is required for STORED" 901 + " method when not writing to a file"); 902 } 903 entry.entry.setCompressedSize(entry.entry.getSize()); 904 } 905 906 if ((entry.entry.getSize() >= ZIP64_MAGIC 907 || entry.entry.getCompressedSize() >= ZIP64_MAGIC) 908 && effectiveMode == Zip64Mode.Never) { 909 throw new Zip64RequiredException(Zip64RequiredException 910 .getEntryTooBigMessage(entry.entry)); 911 } 912 } 913 914 /** 915 * Whether to addd a Zip64 extended information extra field to the 916 * local file header. 917 * 918 * <p>Returns true if</p> 919 * 920 * <ul> 921 * <li>mode is Always</li> 922 * <li>or we already know it is going to be needed</li> 923 * <li>or the size is unknown and we can ensure it won't hurt 924 * other implementations if we add it (i.e. we can erase its 925 * usage</li> 926 * </ul> 927 */ 928 private boolean shouldAddZip64Extra(final ZipArchiveEntry entry, final Zip64Mode mode) { 929 return mode == Zip64Mode.Always 930 || entry.getSize() >= ZIP64_MAGIC 931 || entry.getCompressedSize() >= ZIP64_MAGIC 932 || (entry.getSize() == ArchiveEntry.SIZE_UNKNOWN 933 && channel != null && mode != Zip64Mode.Never); 934 } 935 936 /** 937 * Set the file comment. 938 * @param comment the comment 939 */ 940 public void setComment(final String comment) { 941 this.comment = comment; 942 } 943 944 /** 945 * Sets the compression level for subsequent entries. 946 * 947 * <p>Default is Deflater.DEFAULT_COMPRESSION.</p> 948 * @param level the compression level. 949 * @throws IllegalArgumentException if an invalid compression 950 * level is specified. 951 */ 952 public void setLevel(final int level) { 953 if (level < Deflater.DEFAULT_COMPRESSION 954 || level > Deflater.BEST_COMPRESSION) { 955 throw new IllegalArgumentException("Invalid compression level: " 956 + level); 957 } 958 if (this.level == level) { 959 return; 960 } 961 hasCompressionLevelChanged = true; 962 this.level = level; 963 } 964 965 /** 966 * Sets the default compression method for subsequent entries. 967 * 968 * <p>Default is DEFLATED.</p> 969 * @param method an <code>int</code> from java.util.zip.ZipEntry 970 */ 971 public void setMethod(final int method) { 972 this.method = method; 973 } 974 975 /** 976 * Whether this stream is able to write the given entry. 977 * 978 * <p>May return false if it is set up to use encryption or a 979 * compression method that hasn't been implemented yet.</p> 980 * @since 1.1 981 */ 982 @Override 983 public boolean canWriteEntryData(final ArchiveEntry ae) { 984 if (ae instanceof ZipArchiveEntry) { 985 final ZipArchiveEntry zae = (ZipArchiveEntry) ae; 986 return zae.getMethod() != ZipMethod.IMPLODING.getCode() 987 && zae.getMethod() != ZipMethod.UNSHRINKING.getCode() 988 && ZipUtil.canHandleEntryData(zae); 989 } 990 return false; 991 } 992 993 /** 994 * Writes bytes to ZIP entry. 995 * @param b the byte array to write 996 * @param offset the start position to write from 997 * @param length the number of bytes to write 998 * @throws IOException on error 999 */ 1000 @Override 1001 public void write(final byte[] b, final int offset, final int length) throws IOException { 1002 if (entry == null) { 1003 throw new IllegalStateException("No current entry"); 1004 } 1005 ZipUtil.checkRequestedFeatures(entry.entry); 1006 final long writtenThisTime = streamCompressor.write(b, offset, length, entry.entry.getMethod()); 1007 count(writtenThisTime); 1008 } 1009 1010 /** 1011 * Write bytes to output or random access file. 1012 * @param data the byte array to write 1013 * @throws IOException on error 1014 */ 1015 private void writeCounted(final byte[] data) throws IOException { 1016 streamCompressor.writeCounted(data); 1017 } 1018 1019 private void copyFromZipInputStream(final InputStream src) throws IOException { 1020 if (entry == null) { 1021 throw new IllegalStateException("No current entry"); 1022 } 1023 ZipUtil.checkRequestedFeatures(entry.entry); 1024 entry.hasWritten = true; 1025 int length; 1026 while ((length = src.read(copyBuffer)) >= 0 ) 1027 { 1028 streamCompressor.writeCounted(copyBuffer, 0, length); 1029 count( length ); 1030 } 1031 } 1032 1033 /** 1034 * Closes this output stream and releases any system resources 1035 * associated with the stream. 1036 * 1037 * @throws IOException if an I/O error occurs. 1038 * @throws Zip64RequiredException if the archive's size exceeds 4 1039 * GByte or there are more than 65535 entries inside the archive 1040 * and {@link #setUseZip64} is {@link Zip64Mode#Never}. 1041 */ 1042 @Override 1043 public void close() throws IOException { 1044 try { 1045 if (!finished) { 1046 finish(); 1047 } 1048 } finally { 1049 destroy(); 1050 } 1051 } 1052 1053 /** 1054 * Flushes this output stream and forces any buffered output bytes 1055 * to be written out to the stream. 1056 * 1057 * @throws IOException if an I/O error occurs. 1058 */ 1059 @Override 1060 public void flush() throws IOException { 1061 if (out != null) { 1062 out.flush(); 1063 } 1064 } 1065 1066 /* 1067 * Various ZIP constants shared between this class, ZipArchiveInputStream and ZipFile 1068 */ 1069 /** 1070 * local file header signature 1071 */ 1072 static final byte[] LFH_SIG = ZipLong.LFH_SIG.getBytes(); //NOSONAR 1073 /** 1074 * data descriptor signature 1075 */ 1076 static final byte[] DD_SIG = ZipLong.DD_SIG.getBytes(); //NOSONAR 1077 /** 1078 * central file header signature 1079 */ 1080 static final byte[] CFH_SIG = ZipLong.CFH_SIG.getBytes(); //NOSONAR 1081 /** 1082 * end of central dir signature 1083 */ 1084 static final byte[] EOCD_SIG = ZipLong.getBytes(0X06054B50L); //NOSONAR 1085 /** 1086 * ZIP64 end of central dir signature 1087 */ 1088 static final byte[] ZIP64_EOCD_SIG = ZipLong.getBytes(0X06064B50L); //NOSONAR 1089 /** 1090 * ZIP64 end of central dir locator signature 1091 */ 1092 static final byte[] ZIP64_EOCD_LOC_SIG = ZipLong.getBytes(0X07064B50L); //NOSONAR 1093 1094 /** 1095 * Writes next block of compressed data to the output stream. 1096 * @throws IOException on error 1097 */ 1098 protected final void deflate() throws IOException { 1099 streamCompressor.deflate(); 1100 } 1101 1102 /** 1103 * Writes the local file header entry 1104 * @param ze the entry to write 1105 * @throws IOException on error 1106 */ 1107 protected void writeLocalFileHeader(final ZipArchiveEntry ze) throws IOException { 1108 writeLocalFileHeader(ze, false); 1109 } 1110 1111 private void writeLocalFileHeader(final ZipArchiveEntry ze, final boolean phased) throws IOException { 1112 final boolean encodable = zipEncoding.canEncode(ze.getName()); 1113 final ByteBuffer name = getName(ze); 1114 1115 if (createUnicodeExtraFields != UnicodeExtraFieldPolicy.NEVER) { 1116 addUnicodeExtraFields(ze, encodable, name); 1117 } 1118 1119 long localHeaderStart = streamCompressor.getTotalBytesWritten(); 1120 if (isSplitZip) { 1121 // when creating a split zip, the offset should be 1122 // the offset to the corresponding segment disk 1123 ZipSplitOutputStream splitOutputStream = (ZipSplitOutputStream)this.out; 1124 ze.setDiskNumberStart(splitOutputStream.getCurrentSplitSegmentIndex()); 1125 localHeaderStart = splitOutputStream.getCurrentSplitSegmentBytesWritten(); 1126 } 1127 1128 final byte[] localHeader = createLocalFileHeader(ze, name, encodable, phased, localHeaderStart); 1129 metaData.put(ze, new EntryMetaData(localHeaderStart, usesDataDescriptor(ze.getMethod(), phased))); 1130 entry.localDataStart = localHeaderStart + LFH_CRC_OFFSET; // At crc offset 1131 writeCounted(localHeader); 1132 entry.dataStart = streamCompressor.getTotalBytesWritten(); 1133 } 1134 1135 1136 private byte[] createLocalFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final boolean encodable, 1137 final boolean phased, long archiveOffset) { 1138 ResourceAlignmentExtraField oldAlignmentEx = 1139 (ResourceAlignmentExtraField) ze.getExtraField(ResourceAlignmentExtraField.ID); 1140 if (oldAlignmentEx != null) { 1141 ze.removeExtraField(ResourceAlignmentExtraField.ID); 1142 } 1143 1144 int alignment = ze.getAlignment(); 1145 if (alignment <= 0 && oldAlignmentEx != null) { 1146 alignment = oldAlignmentEx.getAlignment(); 1147 } 1148 1149 if (alignment > 1 || (oldAlignmentEx != null && !oldAlignmentEx.allowMethodChange())) { 1150 int oldLength = LFH_FILENAME_OFFSET + 1151 name.limit() - name.position() + 1152 ze.getLocalFileDataExtra().length; 1153 1154 int padding = (int) ((-archiveOffset - oldLength - ZipExtraField.EXTRAFIELD_HEADER_SIZE 1155 - ResourceAlignmentExtraField.BASE_SIZE) & 1156 (alignment - 1)); 1157 ze.addExtraField(new ResourceAlignmentExtraField(alignment, 1158 oldAlignmentEx != null && oldAlignmentEx.allowMethodChange(), padding)); 1159 } 1160 1161 final byte[] extra = ze.getLocalFileDataExtra(); 1162 final int nameLen = name.limit() - name.position(); 1163 final int len = LFH_FILENAME_OFFSET + nameLen + extra.length; 1164 final byte[] buf = new byte[len]; 1165 1166 System.arraycopy(LFH_SIG, 0, buf, LFH_SIG_OFFSET, WORD); 1167 1168 //store method in local variable to prevent multiple method calls 1169 final int zipMethod = ze.getMethod(); 1170 final boolean dataDescriptor = usesDataDescriptor(zipMethod, phased); 1171 1172 putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze), dataDescriptor), buf, LFH_VERSION_NEEDED_OFFSET); 1173 1174 final GeneralPurposeBit generalPurposeBit = getGeneralPurposeBits(!encodable && fallbackToUTF8, dataDescriptor); 1175 generalPurposeBit.encode(buf, LFH_GPB_OFFSET); 1176 1177 // compression method 1178 putShort(zipMethod, buf, LFH_METHOD_OFFSET); 1179 1180 ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, LFH_TIME_OFFSET); 1181 1182 // CRC 1183 if (phased){ 1184 putLong(ze.getCrc(), buf, LFH_CRC_OFFSET); 1185 } else if (zipMethod == DEFLATED || channel != null) { 1186 System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, WORD); 1187 } else { 1188 putLong(ze.getCrc(), buf, LFH_CRC_OFFSET); 1189 } 1190 1191 // compressed length 1192 // uncompressed length 1193 if (hasZip64Extra(entry.entry)){ 1194 // point to ZIP64 extended information extra field for 1195 // sizes, may get rewritten once sizes are known if 1196 // stream is seekable 1197 ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET); 1198 ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET); 1199 } else if (phased) { 1200 putLong(ze.getCompressedSize(), buf, LFH_COMPRESSED_SIZE_OFFSET); 1201 putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET); 1202 } else if (zipMethod == DEFLATED || channel != null) { 1203 System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, WORD); 1204 System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, WORD); 1205 } else { // Stored 1206 putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET); 1207 putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET); 1208 } 1209 // file name length 1210 putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET); 1211 1212 // extra field length 1213 putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET); 1214 1215 // file name 1216 System.arraycopy( name.array(), name.arrayOffset(), buf, LFH_FILENAME_OFFSET, nameLen); 1217 1218 // extra fields 1219 System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length); 1220 1221 return buf; 1222 } 1223 1224 1225 /** 1226 * Adds UnicodeExtra fields for name and file comment if mode is 1227 * ALWAYS or the data cannot be encoded using the configured 1228 * encoding. 1229 */ 1230 private void addUnicodeExtraFields(final ZipArchiveEntry ze, final boolean encodable, 1231 final ByteBuffer name) 1232 throws IOException { 1233 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 1234 || !encodable) { 1235 ze.addExtraField(new UnicodePathExtraField(ze.getName(), 1236 name.array(), 1237 name.arrayOffset(), 1238 name.limit() 1239 - name.position())); 1240 } 1241 1242 final String comm = ze.getComment(); 1243 if (comm != null && !"".equals(comm)) { 1244 1245 final boolean commentEncodable = zipEncoding.canEncode(comm); 1246 1247 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 1248 || !commentEncodable) { 1249 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm); 1250 ze.addExtraField(new UnicodeCommentExtraField(comm, 1251 commentB.array(), 1252 commentB.arrayOffset(), 1253 commentB.limit() 1254 - commentB.position()) 1255 ); 1256 } 1257 } 1258 } 1259 1260 /** 1261 * Writes the data descriptor entry. 1262 * @param ze the entry to write 1263 * @throws IOException on error 1264 */ 1265 protected void writeDataDescriptor(final ZipArchiveEntry ze) throws IOException { 1266 if (!usesDataDescriptor(ze.getMethod(), false)) { 1267 return; 1268 } 1269 writeCounted(DD_SIG); 1270 writeCounted(ZipLong.getBytes(ze.getCrc())); 1271 if (!hasZip64Extra(ze)) { 1272 writeCounted(ZipLong.getBytes(ze.getCompressedSize())); 1273 writeCounted(ZipLong.getBytes(ze.getSize())); 1274 } else { 1275 writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize())); 1276 writeCounted(ZipEightByteInteger.getBytes(ze.getSize())); 1277 } 1278 } 1279 1280 /** 1281 * Writes the central file header entry. 1282 * @param ze the entry to write 1283 * @throws IOException on error 1284 * @throws Zip64RequiredException if the archive's size exceeds 4 1285 * GByte and {@link Zip64Mode #setUseZip64} is {@link 1286 * Zip64Mode#Never}. 1287 */ 1288 protected void writeCentralFileHeader(final ZipArchiveEntry ze) throws IOException { 1289 final byte[] centralFileHeader = createCentralFileHeader(ze); 1290 writeCounted(centralFileHeader); 1291 } 1292 1293 private byte[] createCentralFileHeader(final ZipArchiveEntry ze) throws IOException { 1294 1295 final EntryMetaData entryMetaData = metaData.get(ze); 1296 final boolean needsZip64Extra = hasZip64Extra(ze) 1297 || ze.getCompressedSize() >= ZIP64_MAGIC 1298 || ze.getSize() >= ZIP64_MAGIC 1299 || entryMetaData.offset >= ZIP64_MAGIC 1300 || ze.getDiskNumberStart() >= ZIP64_MAGIC_SHORT 1301 || zip64Mode == Zip64Mode.Always; 1302 1303 if (needsZip64Extra && zip64Mode == Zip64Mode.Never) { 1304 // must be the offset that is too big, otherwise an 1305 // exception would have been throw in putArchiveEntry or 1306 // closeArchiveEntry 1307 throw new Zip64RequiredException(Zip64RequiredException 1308 .ARCHIVE_TOO_BIG_MESSAGE); 1309 } 1310 1311 1312 handleZip64Extra(ze, entryMetaData.offset, needsZip64Extra); 1313 1314 return createCentralFileHeader(ze, getName(ze), entryMetaData, needsZip64Extra); 1315 } 1316 1317 /** 1318 * Writes the central file header entry. 1319 * @param ze the entry to write 1320 * @param name The encoded name 1321 * @param entryMetaData meta data for this file 1322 * @throws IOException on error 1323 */ 1324 private byte[] createCentralFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, 1325 final EntryMetaData entryMetaData, 1326 final boolean needsZip64Extra) throws IOException { 1327 if(isSplitZip) { 1328 // calculate the disk number for every central file header, 1329 // this will be used in writing End Of Central Directory and Zip64 End Of Central Directory 1330 int currentSplitSegment = ((ZipSplitOutputStream)this.out).getCurrentSplitSegmentIndex(); 1331 if(numberOfCDInDiskData.get(currentSplitSegment) == null) { 1332 numberOfCDInDiskData.put(currentSplitSegment, 1); 1333 } else { 1334 int originalNumberOfCD = numberOfCDInDiskData.get(currentSplitSegment); 1335 numberOfCDInDiskData.put(currentSplitSegment, originalNumberOfCD + 1); 1336 } 1337 } 1338 1339 final byte[] extra = ze.getCentralDirectoryExtra(); 1340 1341 // file comment length 1342 String comm = ze.getComment(); 1343 if (comm == null) { 1344 comm = ""; 1345 } 1346 1347 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm); 1348 final int nameLen = name.limit() - name.position(); 1349 final int commentLen = commentB.limit() - commentB.position(); 1350 final int len= CFH_FILENAME_OFFSET + nameLen + extra.length + commentLen; 1351 final byte[] buf = new byte[len]; 1352 1353 System.arraycopy(CFH_SIG, 0, buf, CFH_SIG_OFFSET, WORD); 1354 1355 // version made by 1356 // CheckStyle:MagicNumber OFF 1357 putShort((ze.getPlatform() << 8) | (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION : ZIP64_MIN_VERSION), 1358 buf, CFH_VERSION_MADE_BY_OFFSET); 1359 1360 final int zipMethod = ze.getMethod(); 1361 final boolean encodable = zipEncoding.canEncode(ze.getName()); 1362 putShort(versionNeededToExtract(zipMethod, needsZip64Extra, entryMetaData.usesDataDescriptor), 1363 buf, CFH_VERSION_NEEDED_OFFSET); 1364 getGeneralPurposeBits(!encodable && fallbackToUTF8, entryMetaData.usesDataDescriptor).encode(buf, CFH_GPB_OFFSET); 1365 1366 // compression method 1367 putShort(zipMethod, buf, CFH_METHOD_OFFSET); 1368 1369 1370 // last mod. time and date 1371 ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, CFH_TIME_OFFSET); 1372 1373 // CRC 1374 // compressed length 1375 // uncompressed length 1376 putLong(ze.getCrc(), buf, CFH_CRC_OFFSET); 1377 if (ze.getCompressedSize() >= ZIP64_MAGIC 1378 || ze.getSize() >= ZIP64_MAGIC 1379 || zip64Mode == Zip64Mode.Always) { 1380 ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET); 1381 ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET); 1382 } else { 1383 putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET); 1384 putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET); 1385 } 1386 1387 putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET); 1388 1389 // extra field length 1390 putShort(extra.length, buf, CFH_EXTRA_LENGTH_OFFSET); 1391 1392 putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET); 1393 1394 // disk number start 1395 if(isSplitZip) { 1396 if (ze.getDiskNumberStart() >= ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always) { 1397 putShort(ZIP64_MAGIC_SHORT, buf, CFH_DISK_NUMBER_OFFSET); 1398 } else { 1399 putShort((int) ze.getDiskNumberStart(), buf, CFH_DISK_NUMBER_OFFSET); 1400 } 1401 } else { 1402 System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, SHORT); 1403 } 1404 1405 // internal file attributes 1406 putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET); 1407 1408 // external file attributes 1409 putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET); 1410 1411 // relative offset of LFH 1412 if (entryMetaData.offset >= ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) { 1413 putLong(ZIP64_MAGIC, buf, CFH_LFH_OFFSET); 1414 } else { 1415 putLong(Math.min(entryMetaData.offset, ZIP64_MAGIC), buf, CFH_LFH_OFFSET); 1416 } 1417 1418 // file name 1419 System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen); 1420 1421 final int extraStart = CFH_FILENAME_OFFSET + nameLen; 1422 System.arraycopy(extra, 0, buf, extraStart, extra.length); 1423 1424 final int commentStart = extraStart + extra.length; 1425 1426 // file comment 1427 System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen); 1428 return buf; 1429 } 1430 1431 /** 1432 * If the entry needs Zip64 extra information inside the central 1433 * directory then configure its data. 1434 */ 1435 private void handleZip64Extra(final ZipArchiveEntry ze, final long lfhOffset, 1436 final boolean needsZip64Extra) { 1437 if (needsZip64Extra) { 1438 final Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze); 1439 if (ze.getCompressedSize() >= ZIP64_MAGIC 1440 || ze.getSize() >= ZIP64_MAGIC 1441 || zip64Mode == Zip64Mode.Always) { 1442 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize())); 1443 z64.setSize(new ZipEightByteInteger(ze.getSize())); 1444 } else { 1445 // reset value that may have been set for LFH 1446 z64.setCompressedSize(null); 1447 z64.setSize(null); 1448 } 1449 if (lfhOffset >= ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) { 1450 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset)); 1451 } 1452 if (ze.getDiskNumberStart() >= ZIP64_MAGIC_SHORT || zip64Mode == Zip64Mode.Always) { 1453 z64.setDiskStartNumber(new ZipLong(ze.getDiskNumberStart())); 1454 } 1455 ze.setExtra(); 1456 } 1457 } 1458 1459 /** 1460 * Writes the "End of central dir record". 1461 * @throws IOException on error 1462 * @throws Zip64RequiredException if the archive's size exceeds 4 1463 * GByte or there are more than 65535 entries inside the archive 1464 * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}. 1465 */ 1466 protected void writeCentralDirectoryEnd() throws IOException { 1467 if(!hasUsedZip64 && isSplitZip) { 1468 ((ZipSplitOutputStream)this.out).prepareToWriteUnsplittableContent(eocdLength); 1469 } 1470 1471 validateIfZip64IsNeededInEOCD(); 1472 1473 writeCounted(EOCD_SIG); 1474 1475 // number of this disk 1476 int numberOfThisDisk = 0; 1477 if(isSplitZip) { 1478 numberOfThisDisk = ((ZipSplitOutputStream)this.out).getCurrentSplitSegmentIndex(); 1479 } 1480 writeCounted(ZipShort.getBytes(numberOfThisDisk)); 1481 1482 // disk number of the start of central directory 1483 writeCounted(ZipShort.getBytes((int)cdDiskNumberStart)); 1484 1485 // number of entries 1486 final int numberOfEntries = entries.size(); 1487 1488 // total number of entries in the central directory on this disk 1489 int numOfEntriesOnThisDisk = isSplitZip 1490 ? (numberOfCDInDiskData.get(numberOfThisDisk) == null ? 0 : numberOfCDInDiskData.get(numberOfThisDisk)) 1491 : numberOfEntries; 1492 final byte[] numOfEntriesOnThisDiskData = ZipShort 1493 .getBytes(Math.min(numOfEntriesOnThisDisk, ZIP64_MAGIC_SHORT)); 1494 writeCounted(numOfEntriesOnThisDiskData); 1495 1496 // number of entries 1497 final byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, 1498 ZIP64_MAGIC_SHORT)); 1499 writeCounted(num); 1500 1501 // length and location of CD 1502 writeCounted(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC))); 1503 writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC))); 1504 1505 // ZIP file comment 1506 final ByteBuffer data = this.zipEncoding.encode(comment); 1507 final int dataLen = data.limit() - data.position(); 1508 writeCounted(ZipShort.getBytes(dataLen)); 1509 streamCompressor.writeCounted(data.array(), data.arrayOffset(), dataLen); 1510 } 1511 1512 /** 1513 * If the Zip64 mode is set to never, then all the data in End Of Central Directory 1514 * should not exceed their limits. 1515 * @throws Zip64RequiredException if Zip64 is actually needed 1516 */ 1517 private void validateIfZip64IsNeededInEOCD() throws Zip64RequiredException { 1518 // exception will only be thrown if the Zip64 mode is never while Zip64 is actually needed 1519 if (zip64Mode != Zip64Mode.Never) { 1520 return; 1521 } 1522 1523 int numberOfThisDisk = 0; 1524 if (isSplitZip) { 1525 numberOfThisDisk = ((ZipSplitOutputStream)this.out).getCurrentSplitSegmentIndex(); 1526 } 1527 if (numberOfThisDisk >= ZIP64_MAGIC_SHORT) { 1528 throw new Zip64RequiredException(Zip64RequiredException 1529 .NUMBER_OF_THIS_DISK_TOO_BIG_MESSAGE); 1530 } 1531 1532 if (cdDiskNumberStart >= ZIP64_MAGIC_SHORT) { 1533 throw new Zip64RequiredException(Zip64RequiredException 1534 .NUMBER_OF_THE_DISK_OF_CENTRAL_DIRECTORY_TOO_BIG_MESSAGE); 1535 } 1536 1537 final int numOfEntriesOnThisDisk = numberOfCDInDiskData.get(numberOfThisDisk) == null 1538 ? 0 : numberOfCDInDiskData.get(numberOfThisDisk); 1539 if (numOfEntriesOnThisDisk >= ZIP64_MAGIC_SHORT) { 1540 throw new Zip64RequiredException(Zip64RequiredException 1541 .TOO_MANY_ENTRIES_ON_THIS_DISK_MESSAGE); 1542 } 1543 1544 // number of entries 1545 if (entries.size() >= ZIP64_MAGIC_SHORT) { 1546 throw new Zip64RequiredException(Zip64RequiredException 1547 .TOO_MANY_ENTRIES_MESSAGE); 1548 } 1549 1550 if (cdLength >= ZIP64_MAGIC) { 1551 throw new Zip64RequiredException(Zip64RequiredException 1552 .SIZE_OF_CENTRAL_DIRECTORY_TOO_BIG_MESSAGE); 1553 } 1554 1555 if (cdOffset >= ZIP64_MAGIC) { 1556 throw new Zip64RequiredException(Zip64RequiredException 1557 .ARCHIVE_TOO_BIG_MESSAGE); 1558 } 1559 } 1560 1561 /** 1562 * Writes the "ZIP64 End of central dir record" and 1563 * "ZIP64 End of central dir locator". 1564 * @throws IOException on error 1565 * @since 1.3 1566 */ 1567 protected void writeZip64CentralDirectory() throws IOException { 1568 if (zip64Mode == Zip64Mode.Never) { 1569 return; 1570 } 1571 1572 if (!hasUsedZip64 && shouldUseZip64EOCD()) { 1573 // actually "will use" 1574 hasUsedZip64 = true; 1575 } 1576 1577 if (!hasUsedZip64) { 1578 return; 1579 } 1580 1581 long offset = streamCompressor.getTotalBytesWritten(); 1582 long diskNumberStart = 0L; 1583 if(isSplitZip) { 1584 // when creating a split zip, the offset of should be 1585 // the offset to the corresponding segment disk 1586 ZipSplitOutputStream zipSplitOutputStream = (ZipSplitOutputStream)this.out; 1587 offset = zipSplitOutputStream.getCurrentSplitSegmentBytesWritten(); 1588 diskNumberStart = zipSplitOutputStream.getCurrentSplitSegmentIndex(); 1589 } 1590 1591 1592 writeOut(ZIP64_EOCD_SIG); 1593 // size of zip64 end of central directory, we don't have any variable length 1594 // as we don't support the extensible data sector, yet 1595 writeOut(ZipEightByteInteger 1596 .getBytes(SHORT /* version made by */ 1597 + SHORT /* version needed to extract */ 1598 + WORD /* disk number */ 1599 + WORD /* disk with central directory */ 1600 + DWORD /* number of entries in CD on this disk */ 1601 + DWORD /* total number of entries */ 1602 + DWORD /* size of CD */ 1603 + (long) DWORD /* offset of CD */ 1604 )); 1605 1606 // version made by and version needed to extract 1607 writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); 1608 writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); 1609 1610 // number of this disk 1611 int numberOfThisDisk = 0; 1612 if (isSplitZip) { 1613 numberOfThisDisk = ((ZipSplitOutputStream)this.out).getCurrentSplitSegmentIndex(); 1614 } 1615 writeOut(ZipLong.getBytes(numberOfThisDisk)); 1616 1617 // disk number of the start of central directory 1618 writeOut(ZipLong.getBytes(cdDiskNumberStart)); 1619 1620 // total number of entries in the central directory on this disk 1621 int numOfEntriesOnThisDisk = isSplitZip 1622 ? (numberOfCDInDiskData.get(numberOfThisDisk) == null ? 0 : numberOfCDInDiskData.get(numberOfThisDisk)) 1623 : entries.size(); 1624 final byte[] numOfEntriesOnThisDiskData = ZipEightByteInteger.getBytes(numOfEntriesOnThisDisk); 1625 writeOut(numOfEntriesOnThisDiskData); 1626 1627 // number of entries 1628 final byte[] num = ZipEightByteInteger.getBytes(entries.size()); 1629 writeOut(num); 1630 1631 // length and location of CD 1632 writeOut(ZipEightByteInteger.getBytes(cdLength)); 1633 writeOut(ZipEightByteInteger.getBytes(cdOffset)); 1634 1635 // no "zip64 extensible data sector" for now 1636 1637 if(isSplitZip) { 1638 // based on the zip specification, the End Of Central Directory record and 1639 // the Zip64 End Of Central Directory locator record must be on the same segment 1640 final int zip64EOCDLOCLength = WORD /* length of ZIP64_EOCD_LOC_SIG */ 1641 + WORD /* disk number of ZIP64_EOCD_SIG */ 1642 + DWORD /* offset of ZIP64_EOCD_SIG */ 1643 + WORD /* total number of disks */; 1644 1645 final long unsplittableContentSize = zip64EOCDLOCLength + eocdLength; 1646 ((ZipSplitOutputStream)this.out).prepareToWriteUnsplittableContent(unsplittableContentSize); 1647 } 1648 1649 // and now the "ZIP64 end of central directory locator" 1650 writeOut(ZIP64_EOCD_LOC_SIG); 1651 1652 // disk number holding the ZIP64 EOCD record 1653 writeOut(ZipLong.getBytes(diskNumberStart)); 1654 // relative offset of ZIP64 EOCD record 1655 writeOut(ZipEightByteInteger.getBytes(offset)); 1656 // total number of disks 1657 if(isSplitZip) { 1658 // the Zip64 End Of Central Directory Locator and the End Of Central Directory must be 1659 // in the same split disk, it means they must be located in the last disk 1660 final int totalNumberOfDisks = ((ZipSplitOutputStream)this.out).getCurrentSplitSegmentIndex() + 1; 1661 writeOut(ZipLong.getBytes(totalNumberOfDisks)); 1662 } else { 1663 writeOut(ONE); 1664 } 1665 } 1666 1667 /** 1668 * 4.4.1.4 If one of the fields in the end of central directory 1669 * record is too small to hold required data, the field SHOULD be 1670 * set to -1 (0xFFFF or 0xFFFFFFFF) and the ZIP64 format record 1671 * SHOULD be created. 1672 * @return true if zip64 End Of Central Directory is needed 1673 */ 1674 private boolean shouldUseZip64EOCD() { 1675 int numberOfThisDisk = 0; 1676 if(isSplitZip) { 1677 numberOfThisDisk = ((ZipSplitOutputStream)this.out).getCurrentSplitSegmentIndex(); 1678 } 1679 int numOfEntriesOnThisDisk = numberOfCDInDiskData.get(numberOfThisDisk) == null ? 0 : numberOfCDInDiskData.get(numberOfThisDisk); 1680 return numberOfThisDisk >= ZIP64_MAGIC_SHORT /* number of this disk */ 1681 || cdDiskNumberStart >= ZIP64_MAGIC_SHORT /* number of the disk with the start of the central directory */ 1682 || numOfEntriesOnThisDisk >= ZIP64_MAGIC_SHORT /* total number of entries in the central directory on this disk */ 1683 || entries.size() >= ZIP64_MAGIC_SHORT /* total number of entries in the central directory */ 1684 || cdLength >= ZIP64_MAGIC /* size of the central directory */ 1685 || cdOffset >= ZIP64_MAGIC; /* offset of start of central directory with respect to 1686 the starting disk number */ 1687 } 1688 1689 /** 1690 * Write bytes to output or random access file. 1691 * @param data the byte array to write 1692 * @throws IOException on error 1693 */ 1694 protected final void writeOut(final byte[] data) throws IOException { 1695 streamCompressor.writeOut(data, 0, data.length); 1696 } 1697 1698 1699 /** 1700 * Write bytes to output or random access file. 1701 * @param data the byte array to write 1702 * @param offset the start position to write from 1703 * @param length the number of bytes to write 1704 * @throws IOException on error 1705 */ 1706 protected final void writeOut(final byte[] data, final int offset, final int length) 1707 throws IOException { 1708 streamCompressor.writeOut(data, offset, length); 1709 } 1710 1711 1712 private GeneralPurposeBit getGeneralPurposeBits(final boolean utfFallback, boolean usesDataDescriptor) { 1713 final GeneralPurposeBit b = new GeneralPurposeBit(); 1714 b.useUTF8ForNames(useUTF8Flag || utfFallback); 1715 if (usesDataDescriptor) { 1716 b.useDataDescriptor(true); 1717 } 1718 return b; 1719 } 1720 1721 private int versionNeededToExtract(final int zipMethod, final boolean zip64, final boolean usedDataDescriptor) { 1722 if (zip64) { 1723 return ZIP64_MIN_VERSION; 1724 } 1725 if (usedDataDescriptor) { 1726 return DATA_DESCRIPTOR_MIN_VERSION; 1727 } 1728 return versionNeededToExtractMethod(zipMethod); 1729 } 1730 1731 private boolean usesDataDescriptor(final int zipMethod, boolean phased) { 1732 return !phased && zipMethod == DEFLATED && channel == null; 1733 } 1734 1735 private int versionNeededToExtractMethod(int zipMethod) { 1736 return zipMethod == DEFLATED ? DEFLATE_MIN_VERSION : INITIAL_VERSION; 1737 } 1738 1739 /** 1740 * Creates a new zip entry taking some information from the given 1741 * file and using the provided name. 1742 * 1743 * <p>The name will be adjusted to end with a forward slash "/" if 1744 * the file is a directory. If the file is not a directory a 1745 * potential trailing forward slash will be stripped from the 1746 * entry name.</p> 1747 * 1748 * <p>Must not be used if the stream has already been closed.</p> 1749 */ 1750 @Override 1751 public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName) 1752 throws IOException { 1753 if (finished) { 1754 throw new IOException("Stream has already been finished"); 1755 } 1756 return new ZipArchiveEntry(inputFile, entryName); 1757 } 1758 1759 /** 1760 * Get the existing ZIP64 extended information extra field or 1761 * create a new one and add it to the entry. 1762 * 1763 * @since 1.3 1764 */ 1765 private Zip64ExtendedInformationExtraField 1766 getZip64Extra(final ZipArchiveEntry ze) { 1767 if (entry != null) { 1768 entry.causedUseOfZip64 = !hasUsedZip64; 1769 } 1770 hasUsedZip64 = true; 1771 Zip64ExtendedInformationExtraField z64 = 1772 (Zip64ExtendedInformationExtraField) 1773 ze.getExtraField(Zip64ExtendedInformationExtraField 1774 .HEADER_ID); 1775 if (z64 == null) { 1776 /* 1777 System.err.println("Adding z64 for " + ze.getName() 1778 + ", method: " + ze.getMethod() 1779 + " (" + (ze.getMethod() == STORED) + ")" 1780 + ", channel: " + (channel != null)); 1781 */ 1782 z64 = new Zip64ExtendedInformationExtraField(); 1783 } 1784 1785 // even if the field is there already, make sure it is the first one 1786 ze.addAsFirstExtraField(z64); 1787 1788 return z64; 1789 } 1790 1791 /** 1792 * Is there a ZIP64 extended information extra field for the 1793 * entry? 1794 * 1795 * @since 1.3 1796 */ 1797 private boolean hasZip64Extra(final ZipArchiveEntry ze) { 1798 return ze.getExtraField(Zip64ExtendedInformationExtraField 1799 .HEADER_ID) 1800 != null; 1801 } 1802 1803 /** 1804 * If the mode is AsNeeded and the entry is a compressed entry of 1805 * unknown size that gets written to a non-seekable stream then 1806 * change the default to Never. 1807 * 1808 * @since 1.3 1809 */ 1810 private Zip64Mode getEffectiveZip64Mode(final ZipArchiveEntry ze) { 1811 if (zip64Mode != Zip64Mode.AsNeeded 1812 || channel != null 1813 || ze.getMethod() != DEFLATED 1814 || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) { 1815 return zip64Mode; 1816 } 1817 return Zip64Mode.Never; 1818 } 1819 1820 private ZipEncoding getEntryEncoding(final ZipArchiveEntry ze) { 1821 final boolean encodable = zipEncoding.canEncode(ze.getName()); 1822 return !encodable && fallbackToUTF8 1823 ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; 1824 } 1825 1826 private ByteBuffer getName(final ZipArchiveEntry ze) throws IOException { 1827 return getEntryEncoding(ze).encode(ze.getName()); 1828 } 1829 1830 /** 1831 * Closes the underlying stream/file without finishing the 1832 * archive, the result will likely be a corrupt archive. 1833 * 1834 * <p>This method only exists to support tests that generate 1835 * corrupt archives so they can clean up any temporary files.</p> 1836 */ 1837 void destroy() throws IOException { 1838 try { 1839 if (channel != null) { 1840 channel.close(); 1841 } 1842 } finally { 1843 if (out != null) { 1844 out.close(); 1845 } 1846 } 1847 } 1848 1849 /** 1850 * enum that represents the possible policies for creating Unicode 1851 * extra fields. 1852 */ 1853 public static final class UnicodeExtraFieldPolicy { 1854 /** 1855 * Always create Unicode extra fields. 1856 */ 1857 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always"); 1858 /** 1859 * Never create Unicode extra fields. 1860 */ 1861 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never"); 1862 /** 1863 * Create Unicode extra fields for file names that cannot be 1864 * encoded using the specified encoding. 1865 */ 1866 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = 1867 new UnicodeExtraFieldPolicy("not encodeable"); 1868 1869 private final String name; 1870 private UnicodeExtraFieldPolicy(final String n) { 1871 name = n; 1872 } 1873 @Override 1874 public String toString() { 1875 return name; 1876 } 1877 } 1878 1879 /** 1880 * Structure collecting information for the entry that is 1881 * currently being written. 1882 */ 1883 private static final class CurrentEntry { 1884 private CurrentEntry(final ZipArchiveEntry entry) { 1885 this.entry = entry; 1886 } 1887 /** 1888 * Current ZIP entry. 1889 */ 1890 private final ZipArchiveEntry entry; 1891 /** 1892 * Offset for CRC entry in the local file header data for the 1893 * current entry starts here. 1894 */ 1895 private long localDataStart = 0; 1896 /** 1897 * Data for local header data 1898 */ 1899 private long dataStart = 0; 1900 /** 1901 * Number of bytes read for the current entry (can't rely on 1902 * Deflater#getBytesRead) when using DEFLATED. 1903 */ 1904 private long bytesRead = 0; 1905 /** 1906 * Whether current entry was the first one using ZIP64 features. 1907 */ 1908 private boolean causedUseOfZip64 = false; 1909 /** 1910 * Whether write() has been called at all. 1911 * 1912 * <p>In order to create a valid archive {@link 1913 * #closeArchiveEntry closeArchiveEntry} will write an empty 1914 * array to get the CRC right if nothing has been written to 1915 * the stream at all.</p> 1916 */ 1917 private boolean hasWritten; 1918 } 1919 1920 private static final class EntryMetaData { 1921 private final long offset; 1922 private final boolean usesDataDescriptor; 1923 private EntryMetaData(long offset, boolean usesDataDescriptor) { 1924 this.offset = offset; 1925 this.usesDataDescriptor = usesDataDescriptor; 1926 } 1927 } 1928}