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 byte[] localHeader = createLocalFileHeader(ze, name, encodable, phased); 1030 final long localHeaderStart = streamCompressor.getTotalBytesWritten(); 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) { 1040 final byte[] extra = ze.getLocalFileDataExtra(); 1041 final int nameLen = name.limit() - name.position(); 1042 final int len= LFH_FILENAME_OFFSET + nameLen + extra.length; 1043 final byte[] buf = new byte[len]; 1044 1045 System.arraycopy(LFH_SIG, 0, buf, LFH_SIG_OFFSET, WORD); 1046 1047 //store method in local variable to prevent multiple method calls 1048 final int zipMethod = ze.getMethod(); 1049 1050 if (phased && !isZip64Required(entry.entry, zip64Mode)){ 1051 putShort(INITIAL_VERSION, buf, LFH_VERSION_NEEDED_OFFSET); 1052 } else { 1053 putShort(versionNeededToExtract(zipMethod, hasZip64Extra(ze)), buf, LFH_VERSION_NEEDED_OFFSET); 1054 } 1055 1056 final GeneralPurposeBit generalPurposeBit = getGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8); 1057 generalPurposeBit.encode(buf, LFH_GPB_OFFSET); 1058 1059 // compression method 1060 putShort(zipMethod, buf, LFH_METHOD_OFFSET); 1061 1062 ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, LFH_TIME_OFFSET); 1063 1064 // CRC 1065 if (phased){ 1066 putLong(ze.getCrc(), buf, LFH_CRC_OFFSET); 1067 } else if (zipMethod == DEFLATED || channel != null) { 1068 System.arraycopy(LZERO, 0, buf, LFH_CRC_OFFSET, WORD); 1069 } else { 1070 putLong(ze.getCrc(), buf, LFH_CRC_OFFSET); 1071 } 1072 1073 // compressed length 1074 // uncompressed length 1075 if (hasZip64Extra(entry.entry)){ 1076 // point to ZIP64 extended information extra field for 1077 // sizes, may get rewritten once sizes are known if 1078 // stream is seekable 1079 ZipLong.ZIP64_MAGIC.putLong(buf, LFH_COMPRESSED_SIZE_OFFSET); 1080 ZipLong.ZIP64_MAGIC.putLong(buf, LFH_ORIGINAL_SIZE_OFFSET); 1081 } else if (phased) { 1082 putLong(ze.getCompressedSize(), buf, LFH_COMPRESSED_SIZE_OFFSET); 1083 putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET); 1084 } else if (zipMethod == DEFLATED || channel != null) { 1085 System.arraycopy(LZERO, 0, buf, LFH_COMPRESSED_SIZE_OFFSET, WORD); 1086 System.arraycopy(LZERO, 0, buf, LFH_ORIGINAL_SIZE_OFFSET, WORD); 1087 } else { // Stored 1088 putLong(ze.getSize(), buf, LFH_COMPRESSED_SIZE_OFFSET); 1089 putLong(ze.getSize(), buf, LFH_ORIGINAL_SIZE_OFFSET); 1090 } 1091 // file name length 1092 putShort(nameLen, buf, LFH_FILENAME_LENGTH_OFFSET); 1093 1094 // extra field length 1095 putShort(extra.length, buf, LFH_EXTRA_LENGTH_OFFSET); 1096 1097 // file name 1098 System.arraycopy( name.array(), name.arrayOffset(), buf, LFH_FILENAME_OFFSET, nameLen); 1099 1100 System.arraycopy(extra, 0, buf, LFH_FILENAME_OFFSET + nameLen, extra.length); 1101 return buf; 1102 } 1103 1104 1105 /** 1106 * Adds UnicodeExtra fields for name and file comment if mode is 1107 * ALWAYS or the data cannot be encoded using the configured 1108 * encoding. 1109 */ 1110 private void addUnicodeExtraFields(final ZipArchiveEntry ze, final boolean encodable, 1111 final ByteBuffer name) 1112 throws IOException { 1113 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 1114 || !encodable) { 1115 ze.addExtraField(new UnicodePathExtraField(ze.getName(), 1116 name.array(), 1117 name.arrayOffset(), 1118 name.limit() 1119 - name.position())); 1120 } 1121 1122 final String comm = ze.getComment(); 1123 if (comm != null && !"".equals(comm)) { 1124 1125 final boolean commentEncodable = zipEncoding.canEncode(comm); 1126 1127 if (createUnicodeExtraFields == UnicodeExtraFieldPolicy.ALWAYS 1128 || !commentEncodable) { 1129 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm); 1130 ze.addExtraField(new UnicodeCommentExtraField(comm, 1131 commentB.array(), 1132 commentB.arrayOffset(), 1133 commentB.limit() 1134 - commentB.position()) 1135 ); 1136 } 1137 } 1138 } 1139 1140 /** 1141 * Writes the data descriptor entry. 1142 * @param ze the entry to write 1143 * @throws IOException on error 1144 */ 1145 protected void writeDataDescriptor(final ZipArchiveEntry ze) throws IOException { 1146 if (ze.getMethod() != DEFLATED || channel != null) { 1147 return; 1148 } 1149 writeCounted(DD_SIG); 1150 writeCounted(ZipLong.getBytes(ze.getCrc())); 1151 if (!hasZip64Extra(ze)) { 1152 writeCounted(ZipLong.getBytes(ze.getCompressedSize())); 1153 writeCounted(ZipLong.getBytes(ze.getSize())); 1154 } else { 1155 writeCounted(ZipEightByteInteger.getBytes(ze.getCompressedSize())); 1156 writeCounted(ZipEightByteInteger.getBytes(ze.getSize())); 1157 } 1158 } 1159 1160 /** 1161 * Writes the central file header entry. 1162 * @param ze the entry to write 1163 * @throws IOException on error 1164 * @throws Zip64RequiredException if the archive's size exceeds 4 1165 * GByte and {@link Zip64Mode #setUseZip64} is {@link 1166 * Zip64Mode#Never}. 1167 */ 1168 protected void writeCentralFileHeader(final ZipArchiveEntry ze) throws IOException { 1169 final byte[] centralFileHeader = createCentralFileHeader(ze); 1170 writeCounted(centralFileHeader); 1171 } 1172 1173 private byte[] createCentralFileHeader(final ZipArchiveEntry ze) throws IOException { 1174 1175 final long lfhOffset = offsets.get(ze); 1176 final boolean needsZip64Extra = hasZip64Extra(ze) 1177 || ze.getCompressedSize() >= ZIP64_MAGIC 1178 || ze.getSize() >= ZIP64_MAGIC 1179 || lfhOffset >= ZIP64_MAGIC 1180 || zip64Mode == Zip64Mode.Always; 1181 1182 if (needsZip64Extra && zip64Mode == Zip64Mode.Never) { 1183 // must be the offset that is too big, otherwise an 1184 // exception would have been throw in putArchiveEntry or 1185 // closeArchiveEntry 1186 throw new Zip64RequiredException(Zip64RequiredException 1187 .ARCHIVE_TOO_BIG_MESSAGE); 1188 } 1189 1190 1191 handleZip64Extra(ze, lfhOffset, needsZip64Extra); 1192 1193 return createCentralFileHeader(ze, getName(ze), lfhOffset, needsZip64Extra); 1194 } 1195 1196 /** 1197 * Writes the central file header entry. 1198 * @param ze the entry to write 1199 * @param name The encoded name 1200 * @param lfhOffset Local file header offset for this file 1201 * @throws IOException on error 1202 */ 1203 private byte[] createCentralFileHeader(final ZipArchiveEntry ze, final ByteBuffer name, final long lfhOffset, 1204 final boolean needsZip64Extra) throws IOException { 1205 final byte[] extra = ze.getCentralDirectoryExtra(); 1206 1207 // file comment length 1208 String comm = ze.getComment(); 1209 if (comm == null) { 1210 comm = ""; 1211 } 1212 1213 final ByteBuffer commentB = getEntryEncoding(ze).encode(comm); 1214 final int nameLen = name.limit() - name.position(); 1215 final int commentLen = commentB.limit() - commentB.position(); 1216 final int len= CFH_FILENAME_OFFSET + nameLen + extra.length + commentLen; 1217 final byte[] buf = new byte[len]; 1218 1219 System.arraycopy(CFH_SIG, 0, buf, CFH_SIG_OFFSET, WORD); 1220 1221 // version made by 1222 // CheckStyle:MagicNumber OFF 1223 putShort((ze.getPlatform() << 8) | (!hasUsedZip64 ? DATA_DESCRIPTOR_MIN_VERSION : ZIP64_MIN_VERSION), 1224 buf, CFH_VERSION_MADE_BY_OFFSET); 1225 1226 final int zipMethod = ze.getMethod(); 1227 final boolean encodable = zipEncoding.canEncode(ze.getName()); 1228 putShort(versionNeededToExtract(zipMethod, needsZip64Extra), buf, CFH_VERSION_NEEDED_OFFSET); 1229 getGeneralPurposeBits(zipMethod, !encodable && fallbackToUTF8).encode(buf, CFH_GPB_OFFSET); 1230 1231 // compression method 1232 putShort(zipMethod, buf, CFH_METHOD_OFFSET); 1233 1234 1235 // last mod. time and date 1236 ZipUtil.toDosTime(calendarInstance, ze.getTime(), buf, CFH_TIME_OFFSET); 1237 1238 // CRC 1239 // compressed length 1240 // uncompressed length 1241 putLong(ze.getCrc(), buf, CFH_CRC_OFFSET); 1242 if (ze.getCompressedSize() >= ZIP64_MAGIC 1243 || ze.getSize() >= ZIP64_MAGIC 1244 || zip64Mode == Zip64Mode.Always) { 1245 ZipLong.ZIP64_MAGIC.putLong(buf, CFH_COMPRESSED_SIZE_OFFSET); 1246 ZipLong.ZIP64_MAGIC.putLong(buf, CFH_ORIGINAL_SIZE_OFFSET); 1247 } else { 1248 putLong(ze.getCompressedSize(), buf, CFH_COMPRESSED_SIZE_OFFSET); 1249 putLong(ze.getSize(), buf, CFH_ORIGINAL_SIZE_OFFSET); 1250 } 1251 1252 putShort(nameLen, buf, CFH_FILENAME_LENGTH_OFFSET); 1253 1254 // extra field length 1255 putShort(extra.length, buf, CFH_EXTRA_LENGTH_OFFSET); 1256 1257 putShort(commentLen, buf, CFH_COMMENT_LENGTH_OFFSET); 1258 1259 // disk number start 1260 System.arraycopy(ZERO, 0, buf, CFH_DISK_NUMBER_OFFSET, SHORT); 1261 1262 // internal file attributes 1263 putShort(ze.getInternalAttributes(), buf, CFH_INTERNAL_ATTRIBUTES_OFFSET); 1264 1265 // external file attributes 1266 putLong(ze.getExternalAttributes(), buf, CFH_EXTERNAL_ATTRIBUTES_OFFSET); 1267 1268 // relative offset of LFH 1269 if (lfhOffset >= ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) { 1270 putLong(ZIP64_MAGIC, buf, CFH_LFH_OFFSET); 1271 } else { 1272 putLong(Math.min(lfhOffset, ZIP64_MAGIC), buf, CFH_LFH_OFFSET); 1273 } 1274 1275 // file name 1276 System.arraycopy(name.array(), name.arrayOffset(), buf, CFH_FILENAME_OFFSET, nameLen); 1277 1278 final int extraStart = CFH_FILENAME_OFFSET + nameLen; 1279 System.arraycopy(extra, 0, buf, extraStart, extra.length); 1280 1281 final int commentStart = extraStart + extra.length; 1282 1283 // file comment 1284 System.arraycopy(commentB.array(), commentB.arrayOffset(), buf, commentStart, commentLen); 1285 return buf; 1286 } 1287 1288 /** 1289 * If the entry needs Zip64 extra information inside the central 1290 * directory then configure its data. 1291 */ 1292 private void handleZip64Extra(final ZipArchiveEntry ze, final long lfhOffset, 1293 final boolean needsZip64Extra) { 1294 if (needsZip64Extra) { 1295 final Zip64ExtendedInformationExtraField z64 = getZip64Extra(ze); 1296 if (ze.getCompressedSize() >= ZIP64_MAGIC 1297 || ze.getSize() >= ZIP64_MAGIC 1298 || zip64Mode == Zip64Mode.Always) { 1299 z64.setCompressedSize(new ZipEightByteInteger(ze.getCompressedSize())); 1300 z64.setSize(new ZipEightByteInteger(ze.getSize())); 1301 } else { 1302 // reset value that may have been set for LFH 1303 z64.setCompressedSize(null); 1304 z64.setSize(null); 1305 } 1306 if (lfhOffset >= ZIP64_MAGIC || zip64Mode == Zip64Mode.Always) { 1307 z64.setRelativeHeaderOffset(new ZipEightByteInteger(lfhOffset)); 1308 } 1309 ze.setExtra(); 1310 } 1311 } 1312 1313 /** 1314 * Writes the "End of central dir record". 1315 * @throws IOException on error 1316 * @throws Zip64RequiredException if the archive's size exceeds 4 1317 * GByte or there are more than 65535 entries inside the archive 1318 * and {@link Zip64Mode #setUseZip64} is {@link Zip64Mode#Never}. 1319 */ 1320 protected void writeCentralDirectoryEnd() throws IOException { 1321 writeCounted(EOCD_SIG); 1322 1323 // disk numbers 1324 writeCounted(ZERO); 1325 writeCounted(ZERO); 1326 1327 // number of entries 1328 final int numberOfEntries = entries.size(); 1329 if (numberOfEntries > ZIP64_MAGIC_SHORT 1330 && zip64Mode == Zip64Mode.Never) { 1331 throw new Zip64RequiredException(Zip64RequiredException 1332 .TOO_MANY_ENTRIES_MESSAGE); 1333 } 1334 if (cdOffset > ZIP64_MAGIC && zip64Mode == Zip64Mode.Never) { 1335 throw new Zip64RequiredException(Zip64RequiredException 1336 .ARCHIVE_TOO_BIG_MESSAGE); 1337 } 1338 1339 final byte[] num = ZipShort.getBytes(Math.min(numberOfEntries, 1340 ZIP64_MAGIC_SHORT)); 1341 writeCounted(num); 1342 writeCounted(num); 1343 1344 // length and location of CD 1345 writeCounted(ZipLong.getBytes(Math.min(cdLength, ZIP64_MAGIC))); 1346 writeCounted(ZipLong.getBytes(Math.min(cdOffset, ZIP64_MAGIC))); 1347 1348 // ZIP file comment 1349 final ByteBuffer data = this.zipEncoding.encode(comment); 1350 final int dataLen = data.limit() - data.position(); 1351 writeCounted(ZipShort.getBytes(dataLen)); 1352 streamCompressor.writeCounted(data.array(), data.arrayOffset(), dataLen); 1353 } 1354 1355 /** 1356 * Writes the "ZIP64 End of central dir record" and 1357 * "ZIP64 End of central dir locator". 1358 * @throws IOException on error 1359 * @since 1.3 1360 */ 1361 protected void writeZip64CentralDirectory() throws IOException { 1362 if (zip64Mode == Zip64Mode.Never) { 1363 return; 1364 } 1365 1366 if (!hasUsedZip64 1367 && (cdOffset >= ZIP64_MAGIC || cdLength >= ZIP64_MAGIC 1368 || entries.size() >= ZIP64_MAGIC_SHORT)) { 1369 // actually "will use" 1370 hasUsedZip64 = true; 1371 } 1372 1373 if (!hasUsedZip64) { 1374 return; 1375 } 1376 1377 final long offset = streamCompressor.getTotalBytesWritten(); 1378 1379 writeOut(ZIP64_EOCD_SIG); 1380 // size, we don't have any variable length as we don't support 1381 // the extensible data sector, yet 1382 writeOut(ZipEightByteInteger 1383 .getBytes(SHORT /* version made by */ 1384 + SHORT /* version needed to extract */ 1385 + WORD /* disk number */ 1386 + WORD /* disk with central directory */ 1387 + DWORD /* number of entries in CD on this disk */ 1388 + DWORD /* total number of entries */ 1389 + DWORD /* size of CD */ 1390 + (long) DWORD /* offset of CD */ 1391 )); 1392 1393 // version made by and version needed to extract 1394 writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); 1395 writeOut(ZipShort.getBytes(ZIP64_MIN_VERSION)); 1396 1397 // disk numbers - four bytes this time 1398 writeOut(LZERO); 1399 writeOut(LZERO); 1400 1401 // number of entries 1402 final byte[] num = ZipEightByteInteger.getBytes(entries.size()); 1403 writeOut(num); 1404 writeOut(num); 1405 1406 // length and location of CD 1407 writeOut(ZipEightByteInteger.getBytes(cdLength)); 1408 writeOut(ZipEightByteInteger.getBytes(cdOffset)); 1409 1410 // no "zip64 extensible data sector" for now 1411 1412 // and now the "ZIP64 end of central directory locator" 1413 writeOut(ZIP64_EOCD_LOC_SIG); 1414 1415 // disk number holding the ZIP64 EOCD record 1416 writeOut(LZERO); 1417 // relative offset of ZIP64 EOCD record 1418 writeOut(ZipEightByteInteger.getBytes(offset)); 1419 // total number of disks 1420 writeOut(ONE); 1421 } 1422 1423 /** 1424 * Write bytes to output or random access file. 1425 * @param data the byte array to write 1426 * @throws IOException on error 1427 */ 1428 protected final void writeOut(final byte[] data) throws IOException { 1429 streamCompressor.writeOut(data, 0, data.length); 1430 } 1431 1432 1433 /** 1434 * Write bytes to output or random access file. 1435 * @param data the byte array to write 1436 * @param offset the start position to write from 1437 * @param length the number of bytes to write 1438 * @throws IOException on error 1439 */ 1440 protected final void writeOut(final byte[] data, final int offset, final int length) 1441 throws IOException { 1442 streamCompressor.writeOut(data, offset, length); 1443 } 1444 1445 1446 private GeneralPurposeBit getGeneralPurposeBits(final int zipMethod, final boolean utfFallback) { 1447 final GeneralPurposeBit b = new GeneralPurposeBit(); 1448 b.useUTF8ForNames(useUTF8Flag || utfFallback); 1449 if (isDeflatedToOutputStream(zipMethod)) { 1450 b.useDataDescriptor(true); 1451 } 1452 return b; 1453 } 1454 1455 private int versionNeededToExtract(final int zipMethod, final boolean zip64) { 1456 if (zip64) { 1457 return ZIP64_MIN_VERSION; 1458 } 1459 // requires version 2 as we are going to store length info 1460 // in the data descriptor 1461 return (isDeflatedToOutputStream(zipMethod)) ? 1462 DATA_DESCRIPTOR_MIN_VERSION : 1463 INITIAL_VERSION; 1464 } 1465 1466 private boolean isDeflatedToOutputStream(final int zipMethod) { 1467 return zipMethod == DEFLATED && channel == null; 1468 } 1469 1470 1471 /** 1472 * Creates a new zip entry taking some information from the given 1473 * file and using the provided name. 1474 * 1475 * <p>The name will be adjusted to end with a forward slash "/" if 1476 * the file is a directory. If the file is not a directory a 1477 * potential trailing forward slash will be stripped from the 1478 * entry name.</p> 1479 * 1480 * <p>Must not be used if the stream has already been closed.</p> 1481 */ 1482 @Override 1483 public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName) 1484 throws IOException { 1485 if (finished) { 1486 throw new IOException("Stream has already been finished"); 1487 } 1488 return new ZipArchiveEntry(inputFile, entryName); 1489 } 1490 1491 /** 1492 * Get the existing ZIP64 extended information extra field or 1493 * create a new one and add it to the entry. 1494 * 1495 * @since 1.3 1496 */ 1497 private Zip64ExtendedInformationExtraField 1498 getZip64Extra(final ZipArchiveEntry ze) { 1499 if (entry != null) { 1500 entry.causedUseOfZip64 = !hasUsedZip64; 1501 } 1502 hasUsedZip64 = true; 1503 Zip64ExtendedInformationExtraField z64 = 1504 (Zip64ExtendedInformationExtraField) 1505 ze.getExtraField(Zip64ExtendedInformationExtraField 1506 .HEADER_ID); 1507 if (z64 == null) { 1508 /* 1509 System.err.println("Adding z64 for " + ze.getName() 1510 + ", method: " + ze.getMethod() 1511 + " (" + (ze.getMethod() == STORED) + ")" 1512 + ", channel: " + (channel != null)); 1513 */ 1514 z64 = new Zip64ExtendedInformationExtraField(); 1515 } 1516 1517 // even if the field is there already, make sure it is the first one 1518 ze.addAsFirstExtraField(z64); 1519 1520 return z64; 1521 } 1522 1523 /** 1524 * Is there a ZIP64 extended information extra field for the 1525 * entry? 1526 * 1527 * @since 1.3 1528 */ 1529 private boolean hasZip64Extra(final ZipArchiveEntry ze) { 1530 return ze.getExtraField(Zip64ExtendedInformationExtraField 1531 .HEADER_ID) 1532 != null; 1533 } 1534 1535 /** 1536 * If the mode is AsNeeded and the entry is a compressed entry of 1537 * unknown size that gets written to a non-seekable stream the 1538 * change the default to Never. 1539 * 1540 * @since 1.3 1541 */ 1542 private Zip64Mode getEffectiveZip64Mode(final ZipArchiveEntry ze) { 1543 if (zip64Mode != Zip64Mode.AsNeeded 1544 || channel != null 1545 || ze.getMethod() != DEFLATED 1546 || ze.getSize() != ArchiveEntry.SIZE_UNKNOWN) { 1547 return zip64Mode; 1548 } 1549 return Zip64Mode.Never; 1550 } 1551 1552 private ZipEncoding getEntryEncoding(final ZipArchiveEntry ze) { 1553 final boolean encodable = zipEncoding.canEncode(ze.getName()); 1554 return !encodable && fallbackToUTF8 1555 ? ZipEncodingHelper.UTF8_ZIP_ENCODING : zipEncoding; 1556 } 1557 1558 private ByteBuffer getName(final ZipArchiveEntry ze) throws IOException { 1559 return getEntryEncoding(ze).encode(ze.getName()); 1560 } 1561 1562 /** 1563 * Closes the underlying stream/file without finishing the 1564 * archive, the result will likely be a corrupt archive. 1565 * 1566 * <p>This method only exists to support tests that generate 1567 * corrupt archives so they can clean up any temporary files.</p> 1568 */ 1569 void destroy() throws IOException { 1570 if (channel != null) { 1571 channel.close(); 1572 } 1573 if (out != null) { 1574 out.close(); 1575 } 1576 } 1577 1578 /** 1579 * enum that represents the possible policies for creating Unicode 1580 * extra fields. 1581 */ 1582 public static final class UnicodeExtraFieldPolicy { 1583 /** 1584 * Always create Unicode extra fields. 1585 */ 1586 public static final UnicodeExtraFieldPolicy ALWAYS = new UnicodeExtraFieldPolicy("always"); 1587 /** 1588 * Never create Unicode extra fields. 1589 */ 1590 public static final UnicodeExtraFieldPolicy NEVER = new UnicodeExtraFieldPolicy("never"); 1591 /** 1592 * Create Unicode extra fields for filenames that cannot be 1593 * encoded using the specified encoding. 1594 */ 1595 public static final UnicodeExtraFieldPolicy NOT_ENCODEABLE = 1596 new UnicodeExtraFieldPolicy("not encodeable"); 1597 1598 private final String name; 1599 private UnicodeExtraFieldPolicy(final String n) { 1600 name = n; 1601 } 1602 @Override 1603 public String toString() { 1604 return name; 1605 } 1606 } 1607 1608 /** 1609 * Structure collecting information for the entry that is 1610 * currently being written. 1611 */ 1612 private static final class CurrentEntry { 1613 private CurrentEntry(final ZipArchiveEntry entry) { 1614 this.entry = entry; 1615 } 1616 /** 1617 * Current ZIP entry. 1618 */ 1619 private final ZipArchiveEntry entry; 1620 /** 1621 * Offset for CRC entry in the local file header data for the 1622 * current entry starts here. 1623 */ 1624 private long localDataStart = 0; 1625 /** 1626 * Data for local header data 1627 */ 1628 private long dataStart = 0; 1629 /** 1630 * Number of bytes read for the current entry (can't rely on 1631 * Deflater#getBytesRead) when using DEFLATED. 1632 */ 1633 private long bytesRead = 0; 1634 /** 1635 * Whether current entry was the first one using ZIP64 features. 1636 */ 1637 private boolean causedUseOfZip64 = false; 1638 /** 1639 * Whether write() has been called at all. 1640 * 1641 * <p>In order to create a valid archive {@link 1642 * #closeArchiveEntry closeArchiveEntry} will write an empty 1643 * array to get the CRC right if nothing has been written to 1644 * the stream at all.</p> 1645 */ 1646 private boolean hasWritten; 1647 } 1648 1649}