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.File; 022import java.io.IOException; 023import java.nio.file.Files; 024import java.nio.file.LinkOption; 025import java.nio.file.Path; 026import java.nio.file.attribute.BasicFileAttributes; 027import java.nio.file.attribute.FileTime; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Date; 031import java.util.LinkedList; 032import java.util.List; 033import java.util.NoSuchElementException; 034import java.util.Objects; 035import java.util.function.Function; 036import java.util.zip.ZipEntry; 037import java.util.zip.ZipException; 038 039import org.apache.commons.compress.archivers.ArchiveEntry; 040import org.apache.commons.compress.archivers.EntryStreamOffsets; 041import org.apache.commons.compress.utils.ByteUtils; 042import org.apache.commons.io.file.attribute.FileTimes; 043import org.apache.commons.lang3.ArrayUtils; 044 045/** 046 * Extension that adds better handling of extra fields and provides access to the internal and external file attributes. 047 * 048 * <p> 049 * The extra data is expected to follow the recommendation of <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>: 050 * </p> 051 * <ul> 052 * <li>the extra byte array consists of a sequence of extra fields</li> 053 * <li>each extra fields starts by a two byte header id followed by a two byte sequence holding the length of the remainder of data.</li> 054 * </ul> 055 * 056 * <p> 057 * Any extra data that cannot be parsed by the rules above will be consumed as "unparseable" extra data and treated differently by the methods of this class. 058 * Versions prior to Apache Commons Compress 1.1 would have thrown an exception if any attempt was made to read or write extra data not conforming to the 059 * recommendation. 060 * </p> 061 * 062 * @NotThreadSafe 063 */ 064public class ZipArchiveEntry extends ZipEntry implements ArchiveEntry, EntryStreamOffsets { 065 066 /** 067 * Enumerates how the comment of this entry has been determined. 068 * 069 * @since 1.16 070 */ 071 public enum CommentSource { 072 073 /** 074 * The comment has been read from the archive using the encoding of the archive specified when creating the {@link ZipArchiveInputStream} or 075 * {@link ZipFile} (defaults to the platform's default encoding). 076 */ 077 COMMENT, 078 079 /** 080 * The comment has been read from an {@link UnicodeCommentExtraField Unicode Extra Field}. 081 */ 082 UNICODE_EXTRA_FIELD 083 } 084 085 /** 086 * Enumerates how to try to parse the extra fields. 087 * 088 * <p> 089 * Configures the behavior for: 090 * </p> 091 * <ul> 092 * <li>What shall happen if the extra field content doesn't follow the recommended pattern of two-byte id followed by a two-byte length?</li> 093 * <li>What shall happen if an extra field is generally supported by Commons Compress but its content cannot be parsed correctly? This may for example 094 * happen if the archive is corrupt, it triggers a bug in Commons Compress or the extra field uses a version not (yet) supported by Commons Compress.</li> 095 * </ul> 096 * 097 * @since 1.19 098 */ 099 public enum ExtraFieldParsingMode implements ExtraFieldParsingBehavior { 100 101 /** 102 * Try to parse as many extra fields as possible and wrap unknown extra fields as well as supported extra fields that cannot be parsed in 103 * {@link UnrecognizedExtraField}. 104 * 105 * <p> 106 * Wrap extra data that doesn't follow the recommended pattern in an {@link UnparseableExtraFieldData} instance. 107 * </p> 108 * 109 * <p> 110 * This is the default behavior starting with Commons Compress 1.19. 111 * </p> 112 */ 113 BEST_EFFORT(ExtraFieldUtils.UnparseableExtraField.READ) { 114 @Override 115 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) { 116 return fillAndMakeUnrecognizedOnError(field, data, off, len, local); 117 } 118 }, 119 120 /** 121 * Try to parse as many extra fields as possible and wrap unknown extra fields in {@link UnrecognizedExtraField}. 122 * 123 * <p> 124 * Wrap extra data that doesn't follow the recommended pattern in an {@link UnparseableExtraFieldData} instance. 125 * </p> 126 * 127 * <p> 128 * Throw an exception if an extra field that is generally supported cannot be parsed. 129 * </p> 130 * 131 * <p> 132 * This used to be the default behavior prior to Commons Compress 1.19. 133 * </p> 134 */ 135 STRICT_FOR_KNOW_EXTRA_FIELDS(ExtraFieldUtils.UnparseableExtraField.READ), 136 137 /** 138 * Try to parse as many extra fields as possible and wrap unknown extra fields as well as supported extra fields that cannot be parsed in 139 * {@link UnrecognizedExtraField}. 140 * 141 * <p> 142 * Ignore extra data that doesn't follow the recommended pattern. 143 * </p> 144 */ 145 ONLY_PARSEABLE_LENIENT(ExtraFieldUtils.UnparseableExtraField.SKIP) { 146 @Override 147 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) { 148 return fillAndMakeUnrecognizedOnError(field, data, off, len, local); 149 } 150 }, 151 152 /** 153 * Try to parse as many extra fields as possible and wrap unknown extra fields in {@link UnrecognizedExtraField}. 154 * 155 * <p> 156 * Ignore extra data that doesn't follow the recommended pattern. 157 * </p> 158 * 159 * <p> 160 * Throw an exception if an extra field that is generally supported cannot be parsed. 161 * </p> 162 */ 163 ONLY_PARSEABLE_STRICT(ExtraFieldUtils.UnparseableExtraField.SKIP), 164 165 /** 166 * Throw an exception if any of the recognized extra fields cannot be parsed or any extra field violates the recommended pattern. 167 */ 168 DRACONIC(ExtraFieldUtils.UnparseableExtraField.THROW); 169 170 private static ZipExtraField fillAndMakeUnrecognizedOnError(final ZipExtraField field, final byte[] data, final int off, final int len, 171 final boolean local) { 172 try { 173 return ExtraFieldUtils.fillExtraField(field, data, off, len, local); 174 } catch (final ZipException ex) { 175 final UnrecognizedExtraField u = new UnrecognizedExtraField(); 176 u.setHeaderId(field.getHeaderId()); 177 if (local) { 178 u.setLocalFileDataData(Arrays.copyOfRange(data, off, off + len)); 179 } else { 180 u.setCentralDirectoryData(Arrays.copyOfRange(data, off, off + len)); 181 } 182 return u; 183 } 184 } 185 186 private final ExtraFieldUtils.UnparseableExtraField onUnparseableData; 187 188 ExtraFieldParsingMode(final ExtraFieldUtils.UnparseableExtraField onUnparseableData) { 189 this.onUnparseableData = onUnparseableData; 190 } 191 192 @Override 193 public ZipExtraField createExtraField(final ZipShort headerId) { 194 return ExtraFieldUtils.createExtraField(headerId); 195 } 196 197 @Override 198 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) throws ZipException { 199 return ExtraFieldUtils.fillExtraField(field, data, off, len, local); 200 } 201 202 @Override 203 public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, final int claimedLength) 204 throws ZipException { 205 return onUnparseableData.onUnparseableExtraField(data, off, len, local, claimedLength); 206 } 207 } 208 209 /** 210 * Indicates how the name of this entry has been determined. 211 * 212 * @since 1.16 213 */ 214 public enum NameSource { 215 216 /** 217 * The name has been read from the archive using the encoding of the archive specified when creating the {@link ZipArchiveInputStream} or 218 * {@link ZipFile} (defaults to the platform's default encoding). 219 */ 220 NAME, 221 222 /** 223 * The name has been read from the archive and the archive specified the EFS flag which indicates the name has been encoded as UTF-8. 224 */ 225 NAME_WITH_EFS_FLAG, 226 227 /** 228 * The name has been read from an {@link UnicodePathExtraField Unicode Extra Field}. 229 */ 230 UNICODE_EXTRA_FIELD 231 } 232 233 private static final String ZIP_DIR_SEP = "/"; 234 235 static final ZipArchiveEntry[] EMPTY_ARRAY = {}; 236 static LinkedList<ZipArchiveEntry> EMPTY_LINKED_LIST = new LinkedList<>(); 237 238 /** 239 * Platform is UNIX. 240 */ 241 public static final int PLATFORM_UNIX = 3; 242 243 /** 244 * Platform is FAT. 245 */ 246 public static final int PLATFORM_FAT = 0; 247 248 /** 249 * Platform is unknown. 250 */ 251 public static final int CRC_UNKNOWN = -1; 252 253 private static final int SHORT_MASK = 0xFFFF; 254 255 private static final int SHORT_SHIFT = 16; 256 257 private static boolean canConvertToInfoZipExtendedTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) { 258 return FileTimes.isUnixTime(lastModifiedTime) && FileTimes.isUnixTime(lastAccessTime) && FileTimes.isUnixTime(creationTime); 259 } 260 261 private static boolean isDirectoryEntryName(final String entryName) { 262 return entryName.endsWith(ZIP_DIR_SEP); 263 } 264 265 private static String toDirectoryEntryName(final String entryName) { 266 return isDirectoryEntryName(entryName) ? entryName : entryName + ZIP_DIR_SEP; 267 } 268 269 private static String toEntryName(final File inputFile, final String entryName) { 270 return inputFile.isDirectory() ? toDirectoryEntryName(entryName) : entryName; 271 } 272 273 private static String toEntryName(final Path inputPath, final String entryName, final LinkOption... options) { 274 return Files.isDirectory(inputPath, options) ? toDirectoryEntryName(entryName) : entryName; 275 } 276 277 /** 278 * The {@link ZipEntry} base class only supports the compression methods STORED and DEFLATED. We override the field so that any compression 279 * methods can be used. 280 * <p> 281 * The default value -1 means that the method has not been specified. 282 * </p> 283 * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93">COMPRESS-93</a> 284 */ 285 private int method = ZipMethod.UNKNOWN_CODE; 286 287 /** 288 * The {@link ZipEntry#setSize} method in the base class throws an IllegalArgumentException if the size is bigger than 2GB for Java versions 289 * < 7 and even in Java 7+ if the implementation in java.util.zip doesn't support Zip64 itself (it is an optional feature). 290 * <p> 291 * We need to keep our own size information for Zip64 support. 292 * </p> 293 */ 294 private long size = SIZE_UNKNOWN; 295 private int internalAttributes; 296 private int versionRequired; 297 private int versionMadeBy; 298 private int platform = PLATFORM_FAT; 299 private int rawFlag; 300 private long externalAttributes; 301 private int alignment; 302 private ZipExtraField[] extraFields; 303 private UnparseableExtraFieldData unparseableExtra; 304 private String name; 305 private byte[] rawName; 306 private GeneralPurposeBit generalPurposeBit = new GeneralPurposeBit(); 307 private long localHeaderOffset = OFFSET_UNKNOWN; 308 private long dataOffset = OFFSET_UNKNOWN; 309 private boolean isStreamContiguous; 310 311 private NameSource nameSource = NameSource.NAME; 312 313 private final Function<ZipShort, ZipExtraField> extraFieldFactory; 314 315 private CommentSource commentSource = CommentSource.COMMENT; 316 317 private long diskNumberStart; 318 319 private boolean lastModifiedDateSet; 320 321 private long time = -1; 322 323 /** 324 */ 325 protected ZipArchiveEntry() { 326 this(""); 327 } 328 329 /** 330 * Creates a new ZIP entry taking some information from the given file and using the provided name. 331 * 332 * <p> 333 * 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 334 * will be stripped from the entry name. 335 * </p> 336 * 337 * @param inputFile file to create the entry from 338 * @param entryName name of the entry 339 */ 340 public ZipArchiveEntry(final File inputFile, final String entryName) { 341 this(null, inputFile, entryName); 342 } 343 344 /** 345 * Creates a new ZIP entry taking some information from the given file and using the provided name. 346 * 347 * <p> 348 * 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 349 * will be stripped from the entry name. 350 * </p> 351 * 352 * @param extraFieldFactory custom lookup factory for extra fields or null 353 * @param inputFile file to create the entry from 354 * @param entryName name of the entry 355 */ 356 private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final File inputFile, final String entryName) { 357 this(extraFieldFactory, toEntryName(inputFile, entryName)); 358 try { 359 setAttributes(inputFile.toPath()); 360 } catch (final IOException e) { // NOSONAR 361 if (inputFile.isFile()) { 362 setSize(inputFile.length()); 363 } 364 setTime(inputFile.lastModified()); 365 } 366 } 367 368 /** 369 * Creates a new ZIP entry taking some information from the given path and using the provided name. 370 * 371 * <p> 372 * 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 373 * will be stripped from the entry name. 374 * </p> 375 * 376 * @param extraFieldFactory custom lookup factory for extra fields or null 377 * @param inputPath path to create the entry from. 378 * @param entryName name of the entry. 379 * @param options options indicating how symbolic links are handled. 380 * @throws IOException if an I/O error occurs. 381 */ 382 private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final Path inputPath, final String entryName, 383 final LinkOption... options) throws IOException { 384 this(extraFieldFactory, toEntryName(inputPath, entryName, options)); 385 setAttributes(inputPath, options); 386 } 387 388 /** 389 * Creates a new ZIP entry with the specified name. 390 * 391 * <p> 392 * Assumes the entry represents a directory if and only if the name ends with a forward slash "/". 393 * </p> 394 * 395 * @param extraFieldFactory custom lookup factory for extra fields or null 396 * @param name the name of the entry 397 */ 398 private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final String name) { 399 super(name); 400 this.extraFieldFactory = extraFieldFactory; 401 setName(name); 402 } 403 404 /** 405 * Creates a new ZIP entry with fields taken from the specified ZIP entry. 406 * 407 * <p> 408 * Assumes the entry represents a directory if and only if the name ends with a forward slash "/". 409 * </p> 410 * 411 * @param extraFieldFactory the extra field lookup factory. 412 * @param entry the entry to get fields from 413 * @throws ZipException on error 414 */ 415 private ZipArchiveEntry(final Function<ZipShort, ZipExtraField> extraFieldFactory, final ZipEntry entry) throws ZipException { 416 super(entry); 417 this.extraFieldFactory = extraFieldFactory; 418 setName(entry.getName()); 419 final byte[] extra = entry.getExtra(); 420 if (extra != null) { 421 setExtraFields(parseExtraFields(extra, true, ExtraFieldParsingMode.BEST_EFFORT)); 422 } else { 423 // initializes extra data to an empty byte array 424 setExtra(); 425 } 426 setMethod(entry.getMethod()); 427 this.size = entry.getSize(); 428 } 429 430 /** 431 * Creates a new ZIP entry taking some information from the given path and using the provided name. 432 * 433 * <p> 434 * 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 435 * will be stripped from the entry name. 436 * </p> 437 * 438 * @param inputPath path to create the entry from. 439 * @param entryName name of the entry. 440 * @param options options indicating how symbolic links are handled. 441 * @throws IOException if an I/O error occurs. 442 * @since 1.21 443 */ 444 public ZipArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException { 445 this(null, inputPath, entryName, options); 446 } 447 448 /** 449 * Creates a new ZIP entry with the specified name. 450 * 451 * <p> 452 * Assumes the entry represents a directory if and only if the name ends with a forward slash "/". 453 * </p> 454 * 455 * @param name the name of the entry 456 * @since 1.26.0 457 */ 458 public ZipArchiveEntry(final String name) { 459 this((Function<ZipShort, ZipExtraField>) null, name); 460 } 461 462 /** 463 * Creates a new ZIP entry with fields taken from the specified ZIP entry. 464 * 465 * <p> 466 * Assumes the entry represents a directory if and only if the name ends with a forward slash "/". 467 * </p> 468 * 469 * @param entry the entry to get fields from 470 * @throws ZipException on error 471 */ 472 public ZipArchiveEntry(final ZipArchiveEntry entry) throws ZipException { 473 this((ZipEntry) entry); 474 setInternalAttributes(entry.getInternalAttributes()); 475 setExternalAttributes(entry.getExternalAttributes()); 476 setExtraFields(entry.getAllExtraFieldsNoCopy()); 477 setPlatform(entry.getPlatform()); 478 final GeneralPurposeBit other = entry.getGeneralPurposeBit(); 479 setGeneralPurposeBit(other == null ? null : (GeneralPurposeBit) other.clone()); 480 } 481 482 /** 483 * Creates a new ZIP entry with fields taken from the specified ZIP entry. 484 * 485 * <p> 486 * Assumes the entry represents a directory if and only if the name ends with a forward slash "/". 487 * </p> 488 * 489 * @param entry the entry to get fields from 490 * @throws ZipException on error 491 */ 492 public ZipArchiveEntry(final ZipEntry entry) throws ZipException { 493 this(null, entry); 494 } 495 496 /** 497 * Adds an extra field - replacing an already present extra field of the same type. 498 * 499 * <p> 500 * The new extra field will be the first one. 501 * </p> 502 * 503 * @param ze an extra field 504 */ 505 public void addAsFirstExtraField(final ZipExtraField ze) { 506 if (ze instanceof UnparseableExtraFieldData) { 507 unparseableExtra = (UnparseableExtraFieldData) ze; 508 } else { 509 if (getExtraField(ze.getHeaderId()) != null) { 510 internalRemoveExtraField(ze.getHeaderId()); 511 } 512 final ZipExtraField[] copy = extraFields; 513 final int newLen = ArrayUtils.getLength(extraFields) + 1; 514 extraFields = new ZipExtraField[newLen]; 515 extraFields[0] = ze; 516 if (copy != null) { 517 System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1); 518 } 519 } 520 setExtra(); 521 } 522 523 /** 524 * Adds an extra field - replacing an already present extra field of the same type. 525 * 526 * <p> 527 * If no extra field of the same type exists, the field will be added as last field. 528 * </p> 529 * 530 * @param ze an extra field 531 */ 532 public void addExtraField(final ZipExtraField ze) { 533 internalAddExtraField(ze); 534 setExtra(); 535 } 536 537 private void addInfoZipExtendedTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) { 538 final X5455_ExtendedTimestamp infoZipTimestamp = new X5455_ExtendedTimestamp(); 539 if (lastModifiedTime != null) { 540 infoZipTimestamp.setModifyFileTime(lastModifiedTime); 541 } 542 if (lastAccessTime != null) { 543 infoZipTimestamp.setAccessFileTime(lastAccessTime); 544 } 545 if (creationTime != null) { 546 infoZipTimestamp.setCreateFileTime(creationTime); 547 } 548 internalAddExtraField(infoZipTimestamp); 549 } 550 551 private void addNTFSTimestamp(final FileTime lastModifiedTime, final FileTime lastAccessTime, final FileTime creationTime) { 552 final X000A_NTFS ntfsTimestamp = new X000A_NTFS(); 553 if (lastModifiedTime != null) { 554 ntfsTimestamp.setModifyFileTime(lastModifiedTime); 555 } 556 if (lastAccessTime != null) { 557 ntfsTimestamp.setAccessFileTime(lastAccessTime); 558 } 559 if (creationTime != null) { 560 ntfsTimestamp.setCreateFileTime(creationTime); 561 } 562 internalAddExtraField(ntfsTimestamp); 563 } 564 565 /** 566 * Overwrite clone. 567 * 568 * @return a cloned copy of this ZipArchiveEntry 569 */ 570 @Override 571 public Object clone() { 572 final ZipArchiveEntry e = (ZipArchiveEntry) super.clone(); 573 574 e.setInternalAttributes(getInternalAttributes()); 575 e.setExternalAttributes(getExternalAttributes()); 576 e.setExtraFields(getAllExtraFieldsNoCopy()); 577 return e; 578 } 579 580 private ZipExtraField[] copyOf(final ZipExtraField[] src, final int length) { 581 return Arrays.copyOf(src, length); 582 } 583 584 @Override 585 public boolean equals(final Object obj) { 586 if (this == obj) { 587 return true; 588 } 589 if (obj == null || getClass() != obj.getClass()) { 590 return false; 591 } 592 final ZipArchiveEntry other = (ZipArchiveEntry) obj; 593 final String myName = getName(); 594 final String otherName = other.getName(); 595 if (!Objects.equals(myName, otherName)) { 596 return false; 597 } 598 String myComment = getComment(); 599 String otherComment = other.getComment(); 600 if (myComment == null) { 601 myComment = ""; 602 } 603 if (otherComment == null) { 604 otherComment = ""; 605 } 606 return Objects.equals(getLastModifiedTime(), other.getLastModifiedTime()) && Objects.equals(getLastAccessTime(), other.getLastAccessTime()) 607 && Objects.equals(getCreationTime(), other.getCreationTime()) && myComment.equals(otherComment) 608 && getInternalAttributes() == other.getInternalAttributes() && getPlatform() == other.getPlatform() 609 && getExternalAttributes() == other.getExternalAttributes() && getMethod() == other.getMethod() && getSize() == other.getSize() 610 && getCrc() == other.getCrc() && getCompressedSize() == other.getCompressedSize() 611 && Arrays.equals(getCentralDirectoryExtra(), other.getCentralDirectoryExtra()) 612 && Arrays.equals(getLocalFileDataExtra(), other.getLocalFileDataExtra()) && localHeaderOffset == other.localHeaderOffset 613 && dataOffset == other.dataOffset && generalPurposeBit.equals(other.generalPurposeBit); 614 } 615 616 private ZipExtraField findMatching(final ZipShort headerId, final List<ZipExtraField> fs) { 617 return fs.stream().filter(f -> headerId.equals(f.getHeaderId())).findFirst().orElse(null); 618 } 619 620 private ZipExtraField findUnparseable(final List<ZipExtraField> fs) { 621 return fs.stream().filter(UnparseableExtraFieldData.class::isInstance).findFirst().orElse(null); 622 } 623 624 /** 625 * Gets currently configured alignment. 626 * 627 * @return alignment for this entry. 628 * @since 1.14 629 */ 630 protected int getAlignment() { 631 return this.alignment; 632 } 633 634 private ZipExtraField[] getAllExtraFields() { 635 final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy(); 636 return allExtraFieldsNoCopy == extraFields ? copyOf(allExtraFieldsNoCopy, allExtraFieldsNoCopy.length) : allExtraFieldsNoCopy; 637 } 638 639 /** 640 * Gets all extra fields, including unparseable ones. 641 * 642 * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method 643 */ 644 private ZipExtraField[] getAllExtraFieldsNoCopy() { 645 if (extraFields == null) { 646 return getUnparseableOnly(); 647 } 648 return unparseableExtra != null ? getMergedFields() : extraFields; 649 } 650 651 /** 652 * Gets the extra data for the central directory. 653 * 654 * @return the central directory extra data 655 */ 656 public byte[] getCentralDirectoryExtra() { 657 return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy()); 658 } 659 660 /** 661 * * Gets the source of the comment field value. 662 * 663 * @return source of the comment field value 664 * @since 1.16 665 */ 666 public CommentSource getCommentSource() { 667 return commentSource; 668 } 669 670 @Override 671 public long getDataOffset() { 672 return dataOffset; 673 } 674 675 /** 676 * Gets the number of the split segment this entry starts at. 677 * 678 * @return the number of the split segment this entry starts at. 679 * @since 1.20 680 */ 681 public long getDiskNumberStart() { 682 return diskNumberStart; 683 } 684 685 /** 686 * Gets the external file attributes. 687 * 688 * <p> 689 * <strong>Note</strong>: {@link ZipArchiveInputStream} is unable to fill this field, you must use {@link ZipFile} if you want to read entries using this 690 * attribute. 691 * </p> 692 * 693 * @return the external file attributes 694 */ 695 public long getExternalAttributes() { 696 return externalAttributes; 697 } 698 699 /** 700 * Gets an extra field by its header id. 701 * 702 * @param type the header id 703 * @return null if no such field exists. 704 */ 705 public ZipExtraField getExtraField(final ZipShort type) { 706 if (extraFields != null) { 707 for (final ZipExtraField extraField : extraFields) { 708 if (type.equals(extraField.getHeaderId())) { 709 return extraField; 710 } 711 } 712 } 713 return null; 714 } 715 716 /** 717 * Gets all extra fields that have been parsed successfully. 718 * 719 * <p> 720 * <strong>Note</strong>: The set of extra fields may be incomplete when {@link ZipArchiveInputStream} has been used as some extra fields use the central 721 * directory to store additional information. 722 * </p> 723 * 724 * @return an array of the extra fields 725 */ 726 public ZipExtraField[] getExtraFields() { 727 return getParseableExtraFields(); 728 } 729 730 /** 731 * Gets extra fields. 732 * 733 * @param includeUnparseable whether to also return unparseable extra fields as {@link UnparseableExtraFieldData} if such data exists. 734 * @return an array of the extra fields 735 * @since 1.1 736 */ 737 public ZipExtraField[] getExtraFields(final boolean includeUnparseable) { 738 return includeUnparseable ? getAllExtraFields() : getParseableExtraFields(); 739 } 740 741 /** 742 * Gets extra fields. 743 * 744 * @param parsingBehavior controls parsing of extra fields. 745 * @return an array of the extra fields 746 * @throws ZipException if parsing fails, cannot happen if {@code 747 * parsingBehavior} is {@link ExtraFieldParsingMode#BEST_EFFORT}. 748 * @since 1.19 749 */ 750 public ZipExtraField[] getExtraFields(final ExtraFieldParsingBehavior parsingBehavior) throws ZipException { 751 if (parsingBehavior == ExtraFieldParsingMode.BEST_EFFORT) { 752 return getExtraFields(true); 753 } 754 if (parsingBehavior == ExtraFieldParsingMode.ONLY_PARSEABLE_LENIENT) { 755 return getExtraFields(false); 756 } 757 final byte[] local = getExtra(); 758 final List<ZipExtraField> localFields = new ArrayList<>(Arrays.asList(parseExtraFields(local, true, parsingBehavior))); 759 final byte[] central = getCentralDirectoryExtra(); 760 final List<ZipExtraField> centralFields = new ArrayList<>(Arrays.asList(parseExtraFields(central, false, parsingBehavior))); 761 final List<ZipExtraField> merged = new ArrayList<>(); 762 for (final ZipExtraField l : localFields) { 763 final ZipExtraField c; 764 if (l instanceof UnparseableExtraFieldData) { 765 c = findUnparseable(centralFields); 766 } else { 767 c = findMatching(l.getHeaderId(), centralFields); 768 } 769 if (c != null) { 770 final byte[] cd = c.getCentralDirectoryData(); 771 if (!ArrayUtils.isEmpty(cd)) { 772 l.parseFromCentralDirectoryData(cd, 0, cd.length); 773 } 774 centralFields.remove(c); 775 } 776 merged.add(l); 777 } 778 merged.addAll(centralFields); 779 return merged.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY); 780 } 781 782 /** 783 * The "general purpose bit" field. 784 * 785 * @return the general purpose bit 786 * @since 1.1 787 */ 788 public GeneralPurposeBit getGeneralPurposeBit() { 789 return generalPurposeBit; 790 } 791 792 /** 793 * Gets the internal file attributes. 794 * 795 * <p> 796 * <strong>Note</strong>: {@link ZipArchiveInputStream} is unable to fill this field, you must use {@link ZipFile} if you want to read entries using this 797 * attribute. 798 * </p> 799 * 800 * @return the internal file attributes 801 */ 802 public int getInternalAttributes() { 803 return internalAttributes; 804 } 805 806 /** 807 * Wraps {@link ZipEntry#getTime} with a {@link Date} as the entry's last modified date. 808 * 809 * <p> 810 * Changes to the implementation of {@link ZipEntry#getTime()} leak through and the returned value may depend on your local time zone as well as 811 * your version of Java. 812 * </p> 813 */ 814 @Override 815 public Date getLastModifiedDate() { 816 return new Date(getTime()); 817 } 818 819 /** 820 * Gets the extra data for the local file data. 821 * 822 * @return the extra data for local file 823 */ 824 public byte[] getLocalFileDataExtra() { 825 final byte[] extra = getExtra(); 826 return extra != null ? extra : ByteUtils.EMPTY_BYTE_ARRAY; 827 } 828 829 /** 830 * Gets the local header offset. 831 * 832 * @return the local header offset. 833 * @since 1.24.0 834 */ 835 public long getLocalHeaderOffset() { 836 return this.localHeaderOffset; 837 } 838 839 private ZipExtraField[] getMergedFields() { 840 final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); 841 zipExtraFields[extraFields.length] = unparseableExtra; 842 return zipExtraFields; 843 } 844 845 /** 846 * Gets the compression method of this entry, or -1 if the compression method has not been specified. 847 * 848 * @return compression method 849 * @since 1.1 850 */ 851 @Override 852 public int getMethod() { 853 return method; 854 } 855 856 /** 857 * Gets the name of the entry. 858 * 859 * <p> 860 * This method returns the raw name as it is stored inside of the archive. 861 * </p> 862 * 863 * @return the entry name 864 */ 865 @Override 866 public String getName() { 867 return name == null ? super.getName() : name; 868 } 869 870 /** 871 * The source of the name field value. 872 * 873 * @return source of the name field value 874 * @since 1.16 875 */ 876 public NameSource getNameSource() { 877 return nameSource; 878 } 879 880 private ZipExtraField[] getParseableExtraFields() { 881 final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy(); 882 return parseableExtraFields == extraFields ? copyOf(parseableExtraFields, parseableExtraFields.length) : parseableExtraFields; 883 } 884 885 private ZipExtraField[] getParseableExtraFieldsNoCopy() { 886 if (extraFields == null) { 887 return ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY; 888 } 889 return extraFields; 890 } 891 892 /** 893 * Platform specification to put into the "version made by" part of the central file header. 894 * 895 * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} has been called, in which case PLATFORM_UNIX will be returned. 896 */ 897 public int getPlatform() { 898 return platform; 899 } 900 901 /** 902 * The content of the flags field. 903 * 904 * @return content of the flags field 905 * @since 1.11 906 */ 907 public int getRawFlag() { 908 return rawFlag; 909 } 910 911 /** 912 * Returns the raw bytes that made up the name before it has been converted using the configured or guessed encoding. 913 * 914 * <p> 915 * This method will return null if this instance has not been read from an archive. 916 * </p> 917 * 918 * @return the raw name bytes 919 * @since 1.2 920 */ 921 public byte[] getRawName() { 922 if (rawName != null) { 923 return Arrays.copyOf(rawName, rawName.length); 924 } 925 return null; 926 } 927 928 /** 929 * Gets the uncompressed size of the entry data. 930 * 931 * <p> 932 * <strong>Note</strong>: {@link ZipArchiveInputStream} may create entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long as the entry hasn't been 933 * read completely. 934 * </p> 935 * 936 * @return the entry size 937 */ 938 @Override 939 public long getSize() { 940 return size; 941 } 942 943 /** 944 * {@inheritDoc} 945 * 946 * <p> 947 * Override to work around bug <a href="https://bugs.openjdk.org/browse/JDK-8130914">JDK-8130914</a> 948 * </p> 949 * 950 * @return The last modification time of the entry in milliseconds since the epoch, or -1 if not specified 951 * @see #setTime(long) 952 * @see #setLastModifiedTime(FileTime) 953 */ 954 @Override 955 public long getTime() { 956 if (lastModifiedDateSet) { 957 return getLastModifiedTime().toMillis(); 958 } 959 return time != -1 ? time : super.getTime(); 960 } 961 962 /** 963 * Gets the Unix permission. 964 * 965 * @return the Unix permissions. 966 */ 967 public int getUnixMode() { 968 return platform != PLATFORM_UNIX ? 0 : (int) (getExternalAttributes() >> SHORT_SHIFT & SHORT_MASK); 969 } 970 971 /** 972 * Gets up extra field data that couldn't be parsed correctly. 973 * 974 * @return null if no such field exists. 975 * @since 1.1 976 */ 977 public UnparseableExtraFieldData getUnparseableExtraFieldData() { 978 return unparseableExtra; 979 } 980 981 private ZipExtraField[] getUnparseableOnly() { 982 return unparseableExtra == null ? ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY : new ZipExtraField[] { unparseableExtra }; 983 } 984 985 /** 986 * Gets the "version made by" field. 987 * 988 * @return "version made by" field 989 * @since 1.11 990 */ 991 public int getVersionMadeBy() { 992 return versionMadeBy; 993 } 994 995 /** 996 * Gets the "version required to expand" field. 997 * 998 * @return "version required to expand" field 999 * @since 1.11 1000 */ 1001 public int getVersionRequired() { 1002 return versionRequired; 1003 } 1004 1005 /** 1006 * Gets the hash code of the entry. This uses the name as the hash code. 1007 * 1008 * @return a hash code. 1009 */ 1010 @Override 1011 public int hashCode() { 1012 // this method has severe consequences on performance. We cannot rely 1013 // on the super.hashCode() method since super.getName() always return 1014 // the empty string in the current implementation (there's no setter) 1015 // so it is basically draining the performance of a hashmap lookup 1016 return getName().hashCode(); 1017 } 1018 1019 private void internalAddExtraField(final ZipExtraField ze) { 1020 if (ze instanceof UnparseableExtraFieldData) { 1021 unparseableExtra = (UnparseableExtraFieldData) ze; 1022 } else if (extraFields == null) { 1023 extraFields = new ZipExtraField[] { ze }; 1024 } else { 1025 if (getExtraField(ze.getHeaderId()) != null) { 1026 internalRemoveExtraField(ze.getHeaderId()); 1027 } 1028 final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); 1029 zipExtraFields[zipExtraFields.length - 1] = ze; 1030 extraFields = zipExtraFields; 1031 } 1032 } 1033 1034 private void internalRemoveExtraField(final ZipShort type) { 1035 if (extraFields == null) { 1036 return; 1037 } 1038 final List<ZipExtraField> newResult = new ArrayList<>(); 1039 for (final ZipExtraField extraField : extraFields) { 1040 if (!type.equals(extraField.getHeaderId())) { 1041 newResult.add(extraField); 1042 } 1043 } 1044 if (extraFields.length == newResult.size()) { 1045 return; 1046 } 1047 extraFields = newResult.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY); 1048 } 1049 1050 private void internalSetLastModifiedTime(final FileTime time) { 1051 super.setLastModifiedTime(time); 1052 this.time = time.toMillis(); 1053 lastModifiedDateSet = true; 1054 } 1055 1056 /** 1057 * Tests whether this entry is a directory. 1058 * 1059 * @return true if the entry is a directory. 1060 */ 1061 @Override 1062 public boolean isDirectory() { 1063 return isDirectoryEntryName(getName()); 1064 } 1065 1066 @Override 1067 public boolean isStreamContiguous() { 1068 return isStreamContiguous; 1069 } 1070 1071 /** 1072 * Tests whether this entry represents a Unix symlink, in which case the entry's content contains the target path for the symlink. 1073 * 1074 * @return true if the entry represents a Unix symlink, false otherwise. 1075 * @since 1.5 1076 */ 1077 public boolean isUnixSymlink() { 1078 return (getUnixMode() & UnixStat.FILE_TYPE_FLAG) == UnixStat.LINK_FLAG; 1079 } 1080 1081 /** 1082 * If there are no extra fields, use the given fields as new extra data - otherwise merge the fields assuming the existing fields and the new fields stem 1083 * from different locations inside the archive. 1084 * 1085 * @param f the extra fields to merge 1086 * @param local whether the new fields originate from local data 1087 */ 1088 private void mergeExtraFields(final ZipExtraField[] f, final boolean local) { 1089 if (extraFields == null) { 1090 setExtraFields(f); 1091 } else { 1092 for (final ZipExtraField element : f) { 1093 final ZipExtraField existing; 1094 if (element instanceof UnparseableExtraFieldData) { 1095 existing = unparseableExtra; 1096 } else { 1097 existing = getExtraField(element.getHeaderId()); 1098 } 1099 if (existing == null) { 1100 internalAddExtraField(element); 1101 } else { 1102 final byte[] b = local ? element.getLocalFileDataData() : element.getCentralDirectoryData(); 1103 try { 1104 if (local) { 1105 existing.parseFromLocalFileData(b, 0, b.length); 1106 } else { 1107 existing.parseFromCentralDirectoryData(b, 0, b.length); 1108 } 1109 } catch (final ZipException ex) { 1110 // emulate ExtraFieldParsingMode.fillAndMakeUnrecognizedOnError 1111 final UnrecognizedExtraField u = new UnrecognizedExtraField(); 1112 u.setHeaderId(existing.getHeaderId()); 1113 if (local) { 1114 u.setLocalFileDataData(b); 1115 u.setCentralDirectoryData(existing.getCentralDirectoryData()); 1116 } else { 1117 u.setLocalFileDataData(existing.getLocalFileDataData()); 1118 u.setCentralDirectoryData(b); 1119 } 1120 internalRemoveExtraField(existing.getHeaderId()); 1121 internalAddExtraField(u); 1122 } 1123 } 1124 } 1125 setExtra(); 1126 } 1127 } 1128 1129 private ZipExtraField[] parseExtraFields(final byte[] data, final boolean local, final ExtraFieldParsingBehavior parsingBehavior) throws ZipException { 1130 if (extraFieldFactory != null) { 1131 return ExtraFieldUtils.parse(data, local, new ExtraFieldParsingBehavior() { 1132 @Override 1133 public ZipExtraField createExtraField(final ZipShort headerId) throws ZipException, InstantiationException, IllegalAccessException { 1134 final ZipExtraField field = extraFieldFactory.apply(headerId); 1135 return field == null ? parsingBehavior.createExtraField(headerId) : field; 1136 } 1137 1138 @Override 1139 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) throws ZipException { 1140 return parsingBehavior.fill(field, data, off, len, local); 1141 } 1142 1143 @Override 1144 public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, final int claimedLength) 1145 throws ZipException { 1146 return parsingBehavior.onUnparseableExtraField(data, off, len, local, claimedLength); 1147 } 1148 }); 1149 } 1150 return ExtraFieldUtils.parse(data, local, parsingBehavior); 1151 } 1152 1153 /** 1154 * Remove an extra field. 1155 * 1156 * @param type the type of extra field to remove 1157 */ 1158 public void removeExtraField(final ZipShort type) { 1159 if (getExtraField(type) == null) { 1160 throw new NoSuchElementException(); 1161 } 1162 internalRemoveExtraField(type); 1163 setExtra(); 1164 } 1165 1166 /** 1167 * Removes unparseable extra field data. 1168 * 1169 * @since 1.1 1170 */ 1171 public void removeUnparseableExtraFieldData() { 1172 if (unparseableExtra == null) { 1173 throw new NoSuchElementException(); 1174 } 1175 unparseableExtra = null; 1176 setExtra(); 1177 } 1178 1179 private boolean requiresExtraTimeFields() { 1180 if (getLastAccessTime() != null || getCreationTime() != null) { 1181 return true; 1182 } 1183 return lastModifiedDateSet; 1184 } 1185 1186 /** 1187 * Sets alignment for this entry. 1188 * 1189 * @param alignment requested alignment, 0 for default. 1190 * @since 1.14 1191 */ 1192 public void setAlignment(final int alignment) { 1193 if ((alignment & alignment - 1) != 0 || alignment > 0xffff) { 1194 throw new IllegalArgumentException("Invalid value for alignment, must be power of two and no bigger than " + 0xffff + " but is " + alignment); 1195 } 1196 this.alignment = alignment; 1197 } 1198 1199 private void setAttributes(final Path inputPath, final LinkOption... options) throws IOException { 1200 final BasicFileAttributes attributes = Files.readAttributes(inputPath, BasicFileAttributes.class, options); 1201 if (attributes.isRegularFile()) { 1202 setSize(attributes.size()); 1203 } 1204 super.setLastModifiedTime(attributes.lastModifiedTime()); 1205 super.setCreationTime(attributes.creationTime()); 1206 super.setLastAccessTime(attributes.lastAccessTime()); 1207 setExtraTimeFields(); 1208 } 1209 1210 /** 1211 * Sets the central directory part of extra fields. 1212 * 1213 * @param b an array of bytes to be parsed into extra fields 1214 */ 1215 public void setCentralDirectoryExtra(final byte[] b) { 1216 try { 1217 mergeExtraFields(parseExtraFields(b, false, ExtraFieldParsingMode.BEST_EFFORT), false); 1218 } catch (final ZipException e) { 1219 // actually this is not possible as of Commons Compress 1.19 1220 throw new IllegalArgumentException(e.getMessage(), e); // NOSONAR 1221 } 1222 } 1223 1224 /** 1225 * Sets the source of the comment field value. 1226 * 1227 * @param commentSource source of the comment field value 1228 * @since 1.16 1229 */ 1230 public void setCommentSource(final CommentSource commentSource) { 1231 this.commentSource = commentSource; 1232 } 1233 1234 @Override 1235 public ZipEntry setCreationTime(final FileTime time) { 1236 super.setCreationTime(time); 1237 setExtraTimeFields(); 1238 return this; 1239 } 1240 /* 1241 * (non-Javadoc) 1242 * 1243 * @see Object#equals(Object) 1244 */ 1245 1246 /** 1247 * Sets the data offset. 1248 * 1249 * @param dataOffset new value of data offset. 1250 */ 1251 protected void setDataOffset(final long dataOffset) { 1252 this.dataOffset = dataOffset; 1253 } 1254 1255 /** 1256 * The number of the split segment this entry starts at. 1257 * 1258 * @param diskNumberStart the number of the split segment this entry starts at. 1259 * @since 1.20 1260 */ 1261 public void setDiskNumberStart(final long diskNumberStart) { 1262 this.diskNumberStart = diskNumberStart; 1263 } 1264 1265 /** 1266 * Sets the external file attributes. 1267 * 1268 * @param value an {@code long} value 1269 */ 1270 public void setExternalAttributes(final long value) { 1271 externalAttributes = value; 1272 } 1273 1274 /** 1275 * Unfortunately {@link java.util.zip.ZipOutputStream} seems to access the extra data directly, so overriding getExtra doesn't help - we need to modify 1276 * super's data directly and on every update. 1277 */ 1278 protected void setExtra() { 1279 // ZipEntry will update the time fields here, so we need to reprocess them afterwards 1280 super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getAllExtraFieldsNoCopy())); 1281 // Reprocess and overwrite the modifications made by ZipEntry#setExtra(byte[]) 1282 updateTimeFieldsFromExtraFields(); 1283 } 1284 1285 /** 1286 * Parses the given bytes as extra field data and consumes any unparseable data as an {@link UnparseableExtraFieldData} instance. 1287 * 1288 * @param extra an array of bytes to be parsed into extra fields 1289 * @throws RuntimeException if the bytes cannot be parsed 1290 * @throws RuntimeException on error 1291 */ 1292 @Override 1293 public void setExtra(final byte[] extra) throws RuntimeException { 1294 try { 1295 mergeExtraFields(parseExtraFields(extra, true, ExtraFieldParsingMode.BEST_EFFORT), true); 1296 } catch (final ZipException e) { 1297 // actually this is not possible as of Commons Compress 1.1 1298 throw new IllegalArgumentException("Error parsing extra fields for entry: " // NOSONAR 1299 + getName() + " - " + e.getMessage(), e); 1300 } 1301 } 1302 1303 /** 1304 * Replaces all currently attached extra fields with the new array. 1305 * 1306 * @param fields an array of extra fields 1307 */ 1308 public void setExtraFields(final ZipExtraField[] fields) { 1309 unparseableExtra = null; 1310 final List<ZipExtraField> newFields = new ArrayList<>(); 1311 if (fields != null) { 1312 for (final ZipExtraField field : fields) { 1313 if (field instanceof UnparseableExtraFieldData) { 1314 unparseableExtra = (UnparseableExtraFieldData) field; 1315 } else { 1316 newFields.add(field); 1317 } 1318 } 1319 } 1320 extraFields = newFields.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY); 1321 setExtra(); 1322 } 1323 1324 private void setExtraTimeFields() { 1325 if (getExtraField(X5455_ExtendedTimestamp.HEADER_ID) != null) { 1326 internalRemoveExtraField(X5455_ExtendedTimestamp.HEADER_ID); 1327 } 1328 if (getExtraField(X000A_NTFS.HEADER_ID) != null) { 1329 internalRemoveExtraField(X000A_NTFS.HEADER_ID); 1330 } 1331 if (requiresExtraTimeFields()) { 1332 final FileTime lastModifiedTime = getLastModifiedTime(); 1333 final FileTime lastAccessTime = getLastAccessTime(); 1334 final FileTime creationTime = getCreationTime(); 1335 if (canConvertToInfoZipExtendedTimestamp(lastModifiedTime, lastAccessTime, creationTime)) { 1336 addInfoZipExtendedTimestamp(lastModifiedTime, lastAccessTime, creationTime); 1337 } 1338 addNTFSTimestamp(lastModifiedTime, lastAccessTime, creationTime); 1339 } 1340 setExtra(); 1341 } 1342 1343 /** 1344 * Sets the "general purpose bit" field. 1345 * 1346 * @param generalPurposeBit the general purpose bit 1347 * @since 1.1 1348 */ 1349 public void setGeneralPurposeBit(final GeneralPurposeBit generalPurposeBit) { 1350 this.generalPurposeBit = generalPurposeBit; 1351 } 1352 1353 /** 1354 * Sets the internal file attributes. 1355 * 1356 * @param internalAttributes an {@code int} value 1357 */ 1358 public void setInternalAttributes(final int internalAttributes) { 1359 this.internalAttributes = internalAttributes; 1360 } 1361 1362 @Override 1363 public ZipEntry setLastAccessTime(final FileTime fileTime) { 1364 super.setLastAccessTime(fileTime); 1365 setExtraTimeFields(); 1366 return this; 1367 } 1368 1369 @Override 1370 public ZipEntry setLastModifiedTime(final FileTime fileTime) { 1371 internalSetLastModifiedTime(fileTime); 1372 setExtraTimeFields(); 1373 return this; 1374 } 1375 1376 /** 1377 * Sets the local header offset. 1378 * 1379 * @param localHeaderOffset the local header offset. 1380 */ 1381 protected void setLocalHeaderOffset(final long localHeaderOffset) { 1382 this.localHeaderOffset = localHeaderOffset; 1383 } 1384 1385 /** 1386 * Sets the compression method of this entry. 1387 * 1388 * @param method compression method 1389 * @since 1.1 1390 */ 1391 @Override 1392 public void setMethod(final int method) { 1393 if (method < 0) { 1394 throw new IllegalArgumentException("ZIP compression method cannot be negative: " + method); 1395 } 1396 this.method = method; 1397 } 1398 1399 /** 1400 * Sets the name of the entry. 1401 * 1402 * @param name the name to use 1403 */ 1404 protected void setName(String name) { 1405 if (name != null && getPlatform() == PLATFORM_FAT && !name.contains(ZIP_DIR_SEP)) { 1406 name = name.replace('\\', '/'); 1407 } 1408 this.name = name; 1409 } 1410 1411 /** 1412 * Sets the name using the raw bytes and the string created from it by guessing or using the configured encoding. 1413 * 1414 * @param name the name to use created from the raw bytes using the guessed or configured encoding 1415 * @param rawName the bytes originally read as name from the archive 1416 * @since 1.2 1417 */ 1418 protected void setName(final String name, final byte[] rawName) { 1419 setName(name); 1420 this.rawName = rawName; 1421 } 1422 1423 /** 1424 * Sets the source of the name field value. 1425 * 1426 * @param nameSource source of the name field value 1427 * @since 1.16 1428 */ 1429 public void setNameSource(final NameSource nameSource) { 1430 this.nameSource = nameSource; 1431 } 1432 1433 /** 1434 * Sets the platform (Unix or FAT). 1435 * 1436 * @param platform an {@code int} value - 0 is FAT, 3 is Unix 1437 */ 1438 protected void setPlatform(final int platform) { 1439 this.platform = platform; 1440 } 1441 1442 /** 1443 * Sets the content of the flags field. 1444 * 1445 * @param rawFlag content of the flags field 1446 * @since 1.11 1447 */ 1448 public void setRawFlag(final int rawFlag) { 1449 this.rawFlag = rawFlag; 1450 } 1451 1452 /** 1453 * Sets the uncompressed size of the entry data. 1454 * 1455 * @param size the uncompressed size in bytes 1456 * @throws IllegalArgumentException if the specified size is less than 0 1457 */ 1458 @Override 1459 public void setSize(final long size) { 1460 if (size < 0) { 1461 throw new IllegalArgumentException("Invalid entry size"); 1462 } 1463 this.size = size; 1464 } 1465 1466 /** 1467 * Sets whether the stream is contiguous, that is, not split among several archive parts, interspersed with control blocks, and so on. 1468 * 1469 * @param isStreamContiguous whether the stream is contiguous 1470 */ 1471 protected void setStreamContiguous(final boolean isStreamContiguous) { 1472 this.isStreamContiguous = isStreamContiguous; 1473 } 1474 1475 /** 1476 * Sets the modification time of the entry. 1477 * 1478 * @param fileTime the entry modification time. 1479 * @since 1.21 1480 */ 1481 public void setTime(final FileTime fileTime) { 1482 setTime(fileTime.toMillis()); 1483 } 1484 1485 /** 1486 * 1487 * {@inheritDoc} 1488 * 1489 * <p> 1490 * Override to work around bug <a href="https://bugs.openjdk.org/browse/JDK-8130914">JDK-8130914</a> 1491 * </p> 1492 * 1493 * @param timeEpochMillis The last modification time of the entry in milliseconds since the epoch. 1494 * @see #getTime() 1495 * @see #getLastModifiedTime() 1496 */ 1497 @Override 1498 public void setTime(final long timeEpochMillis) { 1499 if (ZipUtil.isDosTime(timeEpochMillis)) { 1500 super.setTime(timeEpochMillis); 1501 this.time = timeEpochMillis; 1502 lastModifiedDateSet = false; 1503 setExtraTimeFields(); 1504 } else { 1505 setLastModifiedTime(FileTime.fromMillis(timeEpochMillis)); 1506 } 1507 } 1508 1509 /** 1510 * Sets Unix permissions in a way that is understood by Info-Zip's unzip command. 1511 * 1512 * @param mode an {@code int} value 1513 */ 1514 public void setUnixMode(final int mode) { 1515 // CheckStyle:MagicNumberCheck OFF - no point 1516 setExternalAttributes(mode << SHORT_SHIFT 1517 // MS-DOS read-only attribute 1518 | ((mode & 0200) == 0 ? 1 : 0) 1519 // MS-DOS directory flag 1520 | (isDirectory() ? 0x10 : 0)); 1521 // CheckStyle:MagicNumberCheck ON 1522 platform = PLATFORM_UNIX; 1523 } 1524 1525 /** 1526 * Sets the "version made by" field. 1527 * 1528 * @param versionMadeBy "version made by" field 1529 * @since 1.11 1530 */ 1531 public void setVersionMadeBy(final int versionMadeBy) { 1532 this.versionMadeBy = versionMadeBy; 1533 } 1534 1535 /** 1536 * Sets the "version required to expand" field. 1537 * 1538 * @param versionRequired "version required to expand" field 1539 * @since 1.11 1540 */ 1541 public void setVersionRequired(final int versionRequired) { 1542 this.versionRequired = versionRequired; 1543 } 1544 1545 private void updateTimeFieldsFromExtraFields() { 1546 // Update times from X5455_ExtendedTimestamp field 1547 updateTimeFromExtendedTimestampField(); 1548 // Update times from X000A_NTFS field, overriding X5455_ExtendedTimestamp if both are present 1549 updateTimeFromNtfsField(); 1550 } 1551 1552 /** 1553 * Workaround for the fact that, as of Java 17, {@link ZipEntry} does not properly modify the entry's {@code xdostime} field, only setting 1554 * {@code mtime}. While this is not strictly necessary, it's better to maintain the same behavior between this and the NTFS field. 1555 */ 1556 private void updateTimeFromExtendedTimestampField() { 1557 final ZipExtraField extraField = getExtraField(X5455_ExtendedTimestamp.HEADER_ID); 1558 if (extraField instanceof X5455_ExtendedTimestamp) { 1559 final X5455_ExtendedTimestamp extendedTimestamp = (X5455_ExtendedTimestamp) extraField; 1560 if (extendedTimestamp.isBit0_modifyTimePresent()) { 1561 final FileTime modifyTime = extendedTimestamp.getModifyFileTime(); 1562 if (modifyTime != null) { 1563 internalSetLastModifiedTime(modifyTime); 1564 } 1565 } 1566 if (extendedTimestamp.isBit1_accessTimePresent()) { 1567 final FileTime accessTime = extendedTimestamp.getAccessFileTime(); 1568 if (accessTime != null) { 1569 super.setLastAccessTime(accessTime); 1570 } 1571 } 1572 if (extendedTimestamp.isBit2_createTimePresent()) { 1573 final FileTime creationTime = extendedTimestamp.getCreateFileTime(); 1574 if (creationTime != null) { 1575 super.setCreationTime(creationTime); 1576 } 1577 } 1578 } 1579 } 1580 1581 /** 1582 * Workaround for the fact that, as of Java 17, {@link ZipEntry} parses NTFS timestamps with a maximum precision of microseconds, which is 1583 * lower than the 100ns precision provided by this extra field. 1584 */ 1585 private void updateTimeFromNtfsField() { 1586 final ZipExtraField extraField = getExtraField(X000A_NTFS.HEADER_ID); 1587 if (extraField instanceof X000A_NTFS) { 1588 final X000A_NTFS ntfsTimestamp = (X000A_NTFS) extraField; 1589 final FileTime modifyTime = ntfsTimestamp.getModifyFileTime(); 1590 if (modifyTime != null) { 1591 internalSetLastModifiedTime(modifyTime); 1592 } 1593 final FileTime accessTime = ntfsTimestamp.getAccessFileTime(); 1594 if (accessTime != null) { 1595 super.setLastAccessTime(accessTime); 1596 } 1597 final FileTime creationTime = ntfsTimestamp.getCreateFileTime(); 1598 if (creationTime != null) { 1599 super.setCreationTime(creationTime); 1600 } 1601 } 1602 } 1603}