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