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