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