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