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