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