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