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