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