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 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, 177 ExtraFieldUtils 178 .UnparseableExtraField.READ)); 179 } else { 180 // initializes extra data to an empty byte array 181 setExtra(); 182 } 183 setMethod(entry.getMethod()); 184 this.size = entry.getSize(); 185 } 186 187 /** 188 * Creates a new zip entry with fields taken from the specified zip entry. 189 * 190 * <p>Assumes the entry represents a directory if and only if the 191 * name ends with a forward slash "/".</p> 192 * 193 * @param entry the entry to get fields from 194 * @throws ZipException on error 195 */ 196 public ZipArchiveEntry(final ZipArchiveEntry entry) throws ZipException { 197 this((java.util.zip.ZipEntry) entry); 198 setInternalAttributes(entry.getInternalAttributes()); 199 setExternalAttributes(entry.getExternalAttributes()); 200 setExtraFields(getAllExtraFieldsNoCopy()); 201 setPlatform(entry.getPlatform()); 202 final GeneralPurposeBit other = entry.getGeneralPurposeBit(); 203 setGeneralPurposeBit(other == null ? null : 204 (GeneralPurposeBit) other.clone()); 205 } 206 207 /** 208 */ 209 protected ZipArchiveEntry() { 210 this(""); 211 } 212 213 /** 214 * Creates a new zip entry taking some information from the given 215 * file and using the provided name. 216 * 217 * <p>The name will be adjusted to end with a forward slash "/" if 218 * the file is a directory. If the file is not a directory a 219 * potential trailing forward slash will be stripped from the 220 * entry name.</p> 221 * @param inputFile file to create the entry from 222 * @param entryName name of the entry 223 */ 224 public ZipArchiveEntry(final File inputFile, final String entryName) { 225 this(inputFile.isDirectory() && !entryName.endsWith("/") ? 226 entryName + "/" : entryName); 227 if (inputFile.isFile()){ 228 setSize(inputFile.length()); 229 } 230 setTime(inputFile.lastModified()); 231 // TODO are there any other fields we can set here? 232 } 233 234 /** 235 * Overwrite clone. 236 * @return a cloned copy of this ZipArchiveEntry 237 */ 238 @Override 239 public Object clone() { 240 final ZipArchiveEntry e = (ZipArchiveEntry) super.clone(); 241 242 e.setInternalAttributes(getInternalAttributes()); 243 e.setExternalAttributes(getExternalAttributes()); 244 e.setExtraFields(getAllExtraFieldsNoCopy()); 245 return e; 246 } 247 248 /** 249 * Returns the compression method of this entry, or -1 if the 250 * compression method has not been specified. 251 * 252 * @return compression method 253 * 254 * @since 1.1 255 */ 256 @Override 257 public int getMethod() { 258 return method; 259 } 260 261 /** 262 * Sets the compression method of this entry. 263 * 264 * @param method compression method 265 * 266 * @since 1.1 267 */ 268 @Override 269 public void setMethod(final int method) { 270 if (method < 0) { 271 throw new IllegalArgumentException( 272 "ZIP compression method can not be negative: " + method); 273 } 274 this.method = method; 275 } 276 277 /** 278 * Retrieves the internal file attributes. 279 * 280 * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill 281 * this field, you must use {@link ZipFile} if you want to read 282 * entries using this attribute.</p> 283 * 284 * @return the internal file attributes 285 */ 286 public int getInternalAttributes() { 287 return internalAttributes; 288 } 289 290 /** 291 * Sets the internal file attributes. 292 * @param value an <code>int</code> value 293 */ 294 public void setInternalAttributes(final int value) { 295 internalAttributes = value; 296 } 297 298 /** 299 * Retrieves the external file attributes. 300 * 301 * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill 302 * this field, you must use {@link ZipFile} if you want to read 303 * entries using this attribute.</p> 304 * 305 * @return the external file attributes 306 */ 307 public long getExternalAttributes() { 308 return externalAttributes; 309 } 310 311 /** 312 * Sets the external file attributes. 313 * @param value an <code>long</code> value 314 */ 315 public void setExternalAttributes(final long value) { 316 externalAttributes = value; 317 } 318 319 /** 320 * Sets Unix permissions in a way that is understood by Info-Zip's 321 * unzip command. 322 * @param mode an <code>int</code> value 323 */ 324 public void setUnixMode(final int mode) { 325 // CheckStyle:MagicNumberCheck OFF - no point 326 setExternalAttributes((mode << SHORT_SHIFT) 327 // MS-DOS read-only attribute 328 | ((mode & 0200) == 0 ? 1 : 0) 329 // MS-DOS directory flag 330 | (isDirectory() ? 0x10 : 0)); 331 // CheckStyle:MagicNumberCheck ON 332 platform = PLATFORM_UNIX; 333 } 334 335 /** 336 * Unix permission. 337 * @return the unix permissions 338 */ 339 public int getUnixMode() { 340 return platform != PLATFORM_UNIX ? 0 : 341 (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK); 342 } 343 344 /** 345 * Returns true if this entry represents a unix symlink, 346 * in which case the entry's content contains the target path 347 * for the symlink. 348 * 349 * @since 1.5 350 * @return true if the entry represents a unix symlink, false otherwise. 351 */ 352 public boolean isUnixSymlink() { 353 return (getUnixMode() & UnixStat.FILE_TYPE_FLAG) == UnixStat.LINK_FLAG; 354 } 355 356 /** 357 * Platform specification to put into the "version made 358 * by" part of the central file header. 359 * 360 * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} 361 * has been called, in which case PLATFORM_UNIX will be returned. 362 */ 363 public int getPlatform() { 364 return platform; 365 } 366 367 /** 368 * Set the platform (UNIX or FAT). 369 * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX 370 */ 371 protected void setPlatform(final int platform) { 372 this.platform = platform; 373 } 374 375 /** 376 * Gets currently configured alignment. 377 * 378 * @return 379 * alignment for this entry. 380 * @since 1.14 381 */ 382 protected int getAlignment() { 383 return this.alignment; 384 } 385 386 /** 387 * Sets alignment for this entry. 388 * 389 * @param alignment 390 * requested alignment, 0 for default. 391 * @since 1.14 392 */ 393 public void setAlignment(int alignment) { 394 if ((alignment & (alignment - 1)) != 0 || alignment > 0xffff) { 395 throw new IllegalArgumentException("Invalid value for alignment, must be power of two and no bigger than " 396 + 0xffff + " but is " + alignment); 397 } 398 this.alignment = alignment; 399 } 400 401 /** 402 * Replaces all currently attached extra fields with the new array. 403 * @param fields an array of extra fields 404 */ 405 public void setExtraFields(final ZipExtraField[] fields) { 406 final List<ZipExtraField> newFields = new ArrayList<>(); 407 for (final ZipExtraField field : fields) { 408 if (field instanceof UnparseableExtraFieldData) { 409 unparseableExtra = (UnparseableExtraFieldData) field; 410 } else { 411 newFields.add( field); 412 } 413 } 414 extraFields = newFields.toArray(new ZipExtraField[newFields.size()]); 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 private ZipExtraField[] getParseableExtraFieldsNoCopy() { 448 if (extraFields == null) { 449 return noExtraFields; 450 } 451 return extraFields; 452 } 453 454 private ZipExtraField[] getParseableExtraFields() { 455 final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy(); 456 return (parseableExtraFields == extraFields) ? copyOf(parseableExtraFields) : parseableExtraFields; 457 } 458 459 /** 460 * Get all extra fields, including unparseable ones. 461 * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method 462 */ 463 private ZipExtraField[] getAllExtraFieldsNoCopy() { 464 if (extraFields == null) { 465 return getUnparseableOnly(); 466 } 467 return unparseableExtra != null ? getMergedFields() : extraFields; 468 } 469 470 private ZipExtraField[] copyOf(final ZipExtraField[] src){ 471 return copyOf(src, src.length); 472 } 473 474 private ZipExtraField[] copyOf(final ZipExtraField[] src, final int length) { 475 final ZipExtraField[] cpy = new ZipExtraField[length]; 476 System.arraycopy(src, 0, cpy, 0, Math.min(src.length, length)); 477 return cpy; 478 } 479 480 private ZipExtraField[] getMergedFields() { 481 final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); 482 zipExtraFields[extraFields.length] = unparseableExtra; 483 return zipExtraFields; 484 } 485 486 private ZipExtraField[] getUnparseableOnly() { 487 return unparseableExtra == null ? noExtraFields : new ZipExtraField[] { unparseableExtra }; 488 } 489 490 private ZipExtraField[] getAllExtraFields() { 491 final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy(); 492 return (allExtraFieldsNoCopy == extraFields) ? copyOf( allExtraFieldsNoCopy) : allExtraFieldsNoCopy; 493 } 494 /** 495 * Adds an extra field - replacing an already present extra field 496 * of the same type. 497 * 498 * <p>If no extra field of the same type exists, the field will be 499 * added as last field.</p> 500 * @param ze an extra field 501 */ 502 public void addExtraField(final ZipExtraField ze) { 503 if (ze instanceof UnparseableExtraFieldData) { 504 unparseableExtra = (UnparseableExtraFieldData) ze; 505 } else { 506 if (extraFields == null) { 507 extraFields = new ZipExtraField[]{ ze}; 508 } else { 509 if (getExtraField(ze.getHeaderId())!= null){ 510 removeExtraField(ze.getHeaderId()); 511 } 512 final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); 513 zipExtraFields[zipExtraFields.length -1] = ze; 514 extraFields = zipExtraFields; 515 } 516 } 517 setExtra(); 518 } 519 520 /** 521 * Adds an extra field - replacing an already present extra field 522 * of the same type. 523 * 524 * <p>The new extra field will be the first one.</p> 525 * @param ze an extra field 526 */ 527 public void addAsFirstExtraField(final ZipExtraField ze) { 528 if (ze instanceof UnparseableExtraFieldData) { 529 unparseableExtra = (UnparseableExtraFieldData) ze; 530 } else { 531 if (getExtraField(ze.getHeaderId()) != null){ 532 removeExtraField(ze.getHeaderId()); 533 } 534 final ZipExtraField[] copy = extraFields; 535 final int newLen = extraFields != null ? extraFields.length + 1: 1; 536 extraFields = new ZipExtraField[newLen]; 537 extraFields[0] = ze; 538 if (copy != null){ 539 System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1); 540 } 541 } 542 setExtra(); 543 } 544 545 /** 546 * Remove an extra field. 547 * @param type the type of extra field to remove 548 */ 549 public void removeExtraField(final ZipShort type) { 550 if (extraFields == null) { 551 throw new java.util.NoSuchElementException(); 552 } 553 554 final List<ZipExtraField> newResult = new ArrayList<>(); 555 for (final ZipExtraField extraField : extraFields) { 556 if (!type.equals(extraField.getHeaderId())){ 557 newResult.add( extraField); 558 } 559 } 560 if (extraFields.length == newResult.size()) { 561 throw new java.util.NoSuchElementException(); 562 } 563 extraFields = newResult.toArray(new ZipExtraField[newResult.size()]); 564 setExtra(); 565 } 566 567 /** 568 * Removes unparseable extra field data. 569 * 570 * @since 1.1 571 */ 572 public void removeUnparseableExtraFieldData() { 573 if (unparseableExtra == null) { 574 throw new java.util.NoSuchElementException(); 575 } 576 unparseableExtra = null; 577 setExtra(); 578 } 579 580 /** 581 * Looks up an extra field by its header id. 582 * 583 * @param type the header id 584 * @return null if no such field exists. 585 */ 586 public ZipExtraField getExtraField(final ZipShort type) { 587 if (extraFields != null) { 588 for (final ZipExtraField extraField : extraFields) { 589 if (type.equals(extraField.getHeaderId())) { 590 return extraField; 591 } 592 } 593 } 594 return null; 595 } 596 597 /** 598 * Looks up extra field data that couldn't be parsed correctly. 599 * 600 * @return null if no such field exists. 601 * 602 * @since 1.1 603 */ 604 public UnparseableExtraFieldData getUnparseableExtraFieldData() { 605 return unparseableExtra; 606 } 607 608 /** 609 * Parses the given bytes as extra field data and consumes any 610 * unparseable data as an {@link UnparseableExtraFieldData} 611 * instance. 612 * @param extra an array of bytes to be parsed into extra fields 613 * @throws RuntimeException if the bytes cannot be parsed 614 * @throws RuntimeException on error 615 */ 616 @Override 617 public void setExtra(final byte[] extra) throws RuntimeException { 618 try { 619 final ZipExtraField[] local = 620 ExtraFieldUtils.parse(extra, true, 621 ExtraFieldUtils.UnparseableExtraField.READ); 622 mergeExtraFields(local, true); 623 } catch (final ZipException e) { 624 // actually this is not possible as of Commons Compress 1.1 625 throw new RuntimeException("Error parsing extra fields for entry: " //NOSONAR 626 + getName() + " - " + e.getMessage(), e); 627 } 628 } 629 630 /** 631 * Unfortunately {@link java.util.zip.ZipOutputStream 632 * java.util.zip.ZipOutputStream} seems to access the extra data 633 * directly, so overriding getExtra doesn't help - we need to 634 * modify super's data directly. 635 */ 636 protected void setExtra() { 637 super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getAllExtraFieldsNoCopy())); 638 } 639 640 /** 641 * Sets the central directory part of extra fields. 642 * @param b an array of bytes to be parsed into extra fields 643 */ 644 public void setCentralDirectoryExtra(final byte[] b) { 645 try { 646 final ZipExtraField[] central = 647 ExtraFieldUtils.parse(b, false, 648 ExtraFieldUtils.UnparseableExtraField.READ); 649 mergeExtraFields(central, false); 650 } catch (final ZipException e) { 651 throw new RuntimeException(e.getMessage(), e); //NOSONAR 652 } 653 } 654 655 /** 656 * Retrieves the extra data for the local file data. 657 * @return the extra data for local file 658 */ 659 public byte[] getLocalFileDataExtra() { 660 final byte[] extra = getExtra(); 661 return extra != null ? extra : EMPTY; 662 } 663 664 /** 665 * Retrieves the extra data for the central directory. 666 * @return the central directory extra data 667 */ 668 public byte[] getCentralDirectoryExtra() { 669 return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy()); 670 } 671 672 /** 673 * Get the name of the entry. 674 * @return the entry name 675 */ 676 @Override 677 public String getName() { 678 return name == null ? super.getName() : name; 679 } 680 681 /** 682 * Is this entry a directory? 683 * @return true if the entry is a directory 684 */ 685 @Override 686 public boolean isDirectory() { 687 return getName().endsWith("/"); 688 } 689 690 /** 691 * Set the name of the entry. 692 * @param name the name to use 693 */ 694 protected void setName(String name) { 695 if (name != null && getPlatform() == PLATFORM_FAT 696 && !name.contains("/")) { 697 name = name.replace('\\', '/'); 698 } 699 this.name = name; 700 } 701 702 /** 703 * Gets the uncompressed size of the entry data. 704 * 705 * <p><b>Note</b>: {@link ZipArchiveInputStream} may create 706 * entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long 707 * as the entry hasn't been read completely.</p> 708 * 709 * @return the entry size 710 */ 711 @Override 712 public long getSize() { 713 return size; 714 } 715 716 /** 717 * Sets the uncompressed size of the entry data. 718 * @param size the uncompressed size in bytes 719 * @throws IllegalArgumentException if the specified size is less 720 * than 0 721 */ 722 @Override 723 public void setSize(final long size) { 724 if (size < 0) { 725 throw new IllegalArgumentException("invalid entry size"); 726 } 727 this.size = size; 728 } 729 730 /** 731 * Sets the name using the raw bytes and the string created from 732 * it by guessing or using the configured encoding. 733 * @param name the name to use created from the raw bytes using 734 * the guessed or configured encoding 735 * @param rawName the bytes originally read as name from the 736 * archive 737 * @since 1.2 738 */ 739 protected void setName(final String name, final byte[] rawName) { 740 setName(name); 741 this.rawName = rawName; 742 } 743 744 /** 745 * Returns the raw bytes that made up the name before it has been 746 * converted using the configured or guessed encoding. 747 * 748 * <p>This method will return null if this instance has not been 749 * read from an archive.</p> 750 * 751 * @return the raw name bytes 752 * @since 1.2 753 */ 754 public byte[] getRawName() { 755 if (rawName != null) { 756 final byte[] b = new byte[rawName.length]; 757 System.arraycopy(rawName, 0, b, 0, rawName.length); 758 return b; 759 } 760 return null; 761 } 762 763 protected long getLocalHeaderOffset() { 764 return this.localHeaderOffset; 765 } 766 767 protected void setLocalHeaderOffset(long localHeaderOffset) { 768 this.localHeaderOffset = localHeaderOffset; 769 } 770 771 @Override 772 public long getDataOffset() { 773 return dataOffset; 774 } 775 776 /** 777 * Sets the data offset. 778 * 779 * @param dataOffset 780 * new value of data offset. 781 */ 782 protected void setDataOffset(long dataOffset) { 783 this.dataOffset = dataOffset; 784 } 785 786 @Override 787 public boolean isStreamContiguous() { 788 return isStreamContiguous; 789 } 790 791 protected void setStreamContiguous(boolean isStreamContiguous) { 792 this.isStreamContiguous = isStreamContiguous; 793 } 794 795 /** 796 * Get the hashCode of the entry. 797 * This uses the name as the hashcode. 798 * @return a hashcode. 799 */ 800 @Override 801 public int hashCode() { 802 // this method has severe consequences on performance. We cannot rely 803 // on the super.hashCode() method since super.getName() always return 804 // the empty string in the current implemention (there's no setter) 805 // so it is basically draining the performance of a hashmap lookup 806 return getName().hashCode(); 807 } 808 809 /** 810 * The "general purpose bit" field. 811 * @return the general purpose bit 812 * @since 1.1 813 */ 814 public GeneralPurposeBit getGeneralPurposeBit() { 815 return gpb; 816 } 817 818 /** 819 * The "general purpose bit" field. 820 * @param b the general purpose bit 821 * @since 1.1 822 */ 823 public void setGeneralPurposeBit(final GeneralPurposeBit b) { 824 gpb = b; 825 } 826 827 /** 828 * If there are no extra fields, use the given fields as new extra 829 * data - otherwise merge the fields assuming the existing fields 830 * and the new fields stem from different locations inside the 831 * archive. 832 * @param f the extra fields to merge 833 * @param local whether the new fields originate from local data 834 */ 835 private void mergeExtraFields(final ZipExtraField[] f, final boolean local) 836 throws ZipException { 837 if (extraFields == null) { 838 setExtraFields(f); 839 } else { 840 for (final ZipExtraField element : f) { 841 ZipExtraField existing; 842 if (element instanceof UnparseableExtraFieldData) { 843 existing = unparseableExtra; 844 } else { 845 existing = getExtraField(element.getHeaderId()); 846 } 847 if (existing == null) { 848 addExtraField(element); 849 } else { 850 if (local) { 851 final byte[] b = element.getLocalFileDataData(); 852 existing.parseFromLocalFileData(b, 0, b.length); 853 } else { 854 final byte[] b = element.getCentralDirectoryData(); 855 existing.parseFromCentralDirectoryData(b, 0, b.length); 856 } 857 } 858 } 859 setExtra(); 860 } 861 } 862 863 /** 864 * Wraps {@link java.util.zip.ZipEntry#getTime} with a {@link Date} as the 865 * entry's last modified date. 866 * 867 * <p>Changes to the implementation of {@link java.util.zip.ZipEntry#getTime} 868 * leak through and the returned value may depend on your local 869 * time zone as well as your version of Java.</p> 870 */ 871 @Override 872 public Date getLastModifiedDate() { 873 return new Date(getTime()); 874 } 875 876 /* (non-Javadoc) 877 * @see java.lang.Object#equals(java.lang.Object) 878 */ 879 @Override 880 public boolean equals(final Object obj) { 881 if (this == obj) { 882 return true; 883 } 884 if (obj == null || getClass() != obj.getClass()) { 885 return false; 886 } 887 final ZipArchiveEntry other = (ZipArchiveEntry) obj; 888 final String myName = getName(); 889 final String otherName = other.getName(); 890 if (myName == null) { 891 if (otherName != null) { 892 return false; 893 } 894 } else if (!myName.equals(otherName)) { 895 return false; 896 } 897 String myComment = getComment(); 898 String otherComment = other.getComment(); 899 if (myComment == null) { 900 myComment = ""; 901 } 902 if (otherComment == null) { 903 otherComment = ""; 904 } 905 return getTime() == other.getTime() 906 && myComment.equals(otherComment) 907 && getInternalAttributes() == other.getInternalAttributes() 908 && getPlatform() == other.getPlatform() 909 && getExternalAttributes() == other.getExternalAttributes() 910 && getMethod() == other.getMethod() 911 && getSize() == other.getSize() 912 && getCrc() == other.getCrc() 913 && getCompressedSize() == other.getCompressedSize() 914 && Arrays.equals(getCentralDirectoryExtra(), 915 other.getCentralDirectoryExtra()) 916 && Arrays.equals(getLocalFileDataExtra(), 917 other.getLocalFileDataExtra()) 918 && localHeaderOffset == other.localHeaderOffset 919 && dataOffset == other.dataOffset 920 && gpb.equals(other.gpb); 921 } 922 923 /** 924 * Sets the "version made by" field. 925 * @param versionMadeBy "version made by" field 926 * @since 1.11 927 */ 928 public void setVersionMadeBy(final int versionMadeBy) { 929 this.versionMadeBy = versionMadeBy; 930 } 931 932 /** 933 * Sets the "version required to expand" field. 934 * @param versionRequired "version required to expand" field 935 * @since 1.11 936 */ 937 public void setVersionRequired(final int versionRequired) { 938 this.versionRequired = versionRequired; 939 } 940 941 /** 942 * The "version required to expand" field. 943 * @return "version required to expand" field 944 * @since 1.11 945 */ 946 public int getVersionRequired() { 947 return versionRequired; 948 } 949 950 /** 951 * The "version made by" field. 952 * @return "version made by" field 953 * @since 1.11 954 */ 955 public int getVersionMadeBy() { 956 return versionMadeBy; 957 } 958 959 /** 960 * The content of the flags field. 961 * @return content of the flags field 962 * @since 1.11 963 */ 964 public int getRawFlag() { 965 return rawFlag; 966 } 967 968 /** 969 * Sets the content of the flags field. 970 * @param rawFlag content of the flags field 971 * @since 1.11 972 */ 973 public void setRawFlag(final int rawFlag) { 974 this.rawFlag = rawFlag; 975 } 976 977 /** 978 * The source of the name field value. 979 * @return source of the name field value 980 * @since 1.16 981 */ 982 public NameSource getNameSource() { 983 return nameSource; 984 } 985 986 /** 987 * Sets the source of the name field value. 988 * @param nameSource source of the name field value 989 * @since 1.16 990 */ 991 public void setNameSource(NameSource nameSource) { 992 this.nameSource = nameSource; 993 } 994 995 /** 996 * The source of the comment field value. 997 * @return source of the comment field value 998 * @since 1.16 999 */ 1000 public CommentSource getCommentSource() { 1001 return commentSource; 1002 } 1003 1004 /** 1005 * Sets the source of the comment field value. 1006 * @param commentSource source of the comment field value 1007 * @since 1.16 1008 */ 1009 public void setCommentSource(CommentSource commentSource) { 1010 this.commentSource = commentSource; 1011 } 1012 1013}