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