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