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