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