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