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