1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 package org.apache.commons.compress.archivers.zip; 19 20 import org.apache.commons.compress.archivers.ArchiveEntry; 21 import org.apache.commons.compress.archivers.EntryStreamOffsets; 22 23 import java.io.File; 24 import java.util.ArrayList; 25 import java.util.Arrays; 26 import java.util.Date; 27 import java.util.List; 28 import java.util.zip.ZipException; 29 30 /** 31 * Extension that adds better handling of extra fields and provides 32 * access to the internal and external file attributes. 33 * 34 * <p>The extra data is expected to follow the recommendation of 35 * <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>:</p> 36 * <ul> 37 * <li>the extra byte array consists of a sequence of extra fields</li> 38 * <li>each extra fields starts by a two byte header id followed by 39 * a two byte sequence holding the length of the remainder of 40 * data.</li> 41 * </ul> 42 * 43 * <p>Any extra data that cannot be parsed by the rules above will be 44 * consumed as "unparseable" extra data and treated differently by the 45 * methods of this class. Versions prior to Apache Commons Compress 46 * 1.1 would have thrown an exception if any attempt was made to read 47 * or write extra data not conforming to the recommendation.</p> 48 * 49 * @NotThreadSafe 50 */ 51 public class ZipArchiveEntry extends java.util.zip.ZipEntry 52 implements ArchiveEntry, EntryStreamOffsets 53 { 54 55 public static final int PLATFORM_UNIX = 3; 56 public static final int PLATFORM_FAT = 0; 57 public static final int CRC_UNKNOWN = -1; 58 private static final int SHORT_MASK = 0xFFFF; 59 private static final int SHORT_SHIFT = 16; 60 private static final byte[] EMPTY = new byte[0]; 61 62 /** 63 * Indicates how the name of this entry has been determined. 64 * @since 1.16 65 */ 66 public enum NameSource { 67 /** 68 * The name has been read from the archive using the encoding 69 * of the archive specified when creating the {@link 70 * ZipArchiveInputStream} or {@link ZipFile} (defaults to the 71 * platform's default encoding). 72 */ 73 NAME, 74 /** 75 * The name has been read from the archive and the archive 76 * specified the EFS flag which indicates the name has been 77 * encoded as UTF-8. 78 */ 79 NAME_WITH_EFS_FLAG, 80 /** 81 * The name has been read from an {@link UnicodePathExtraField 82 * Unicode Extra Field}. 83 */ 84 UNICODE_EXTRA_FIELD 85 } 86 87 /** 88 * Indicates how the comment of this entry has been determined. 89 * @since 1.16 90 */ 91 public enum CommentSource { 92 /** 93 * The comment has been read from the archive using the encoding 94 * of the archive specified when creating the {@link 95 * ZipArchiveInputStream} or {@link ZipFile} (defaults to the 96 * platform's default encoding). 97 */ 98 COMMENT, 99 /** 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 */ ZipArchiveEntry(final String name)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 */ ZipArchiveEntry(final java.util.zip.ZipEntry entry)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 */ ZipArchiveEntry(final ZipArchiveEntry entry)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 */ ZipArchiveEntry()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 */ ZipArchiveEntry(final File inputFile, final String entryName)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 clone()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 getMethod()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 setMethod(final int method)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 */ getInternalAttributes()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 */ setInternalAttributes(final int value)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 */ getExternalAttributes()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 */ setExternalAttributes(final long value)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 */ setUnixMode(final int mode)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 */ getUnixMode()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 */ isUnixSymlink()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 */ getPlatform()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 */ setPlatform(final int platform)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 */ getAlignment()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 */ setAlignment(int alignment)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 */ setExtraFields(final ZipExtraField[] fields)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 */ getExtraFields()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 */ getExtraFields(final boolean includeUnparseable)441 public ZipExtraField[] getExtraFields(final boolean includeUnparseable) { 442 return includeUnparseable ? 443 getAllExtraFields() : 444 getParseableExtraFields(); 445 } 446 getParseableExtraFieldsNoCopy()447 private ZipExtraField[] getParseableExtraFieldsNoCopy() { 448 if (extraFields == null) { 449 return noExtraFields; 450 } 451 return extraFields; 452 } 453 getParseableExtraFields()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 */ getAllExtraFieldsNoCopy()463 private ZipExtraField[] getAllExtraFieldsNoCopy() { 464 if (extraFields == null) { 465 return getUnparseableOnly(); 466 } 467 return unparseableExtra != null ? getMergedFields() : extraFields; 468 } 469 copyOf(final ZipExtraField[] src)470 private ZipExtraField[] copyOf(final ZipExtraField[] src){ 471 return copyOf(src, src.length); 472 } 473 copyOf(final ZipExtraField[] src, final int length)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 getMergedFields()480 private ZipExtraField[] getMergedFields() { 481 final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); 482 zipExtraFields[extraFields.length] = unparseableExtra; 483 return zipExtraFields; 484 } 485 getUnparseableOnly()486 private ZipExtraField[] getUnparseableOnly() { 487 return unparseableExtra == null ? noExtraFields : new ZipExtraField[] { unparseableExtra }; 488 } 489 getAllExtraFields()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 */ addExtraField(final ZipExtraField ze)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 */ addAsFirstExtraField(final ZipExtraField ze)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 */ removeExtraField(final ZipShort type)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 */ removeUnparseableExtraFieldData()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 */ getExtraField(final ZipShort type)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 */ getUnparseableExtraFieldData()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 setExtra(final byte[] extra)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 */ setExtra()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 */ setCentralDirectoryExtra(final byte[] b)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 */ getLocalFileDataExtra()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 */ getCentralDirectoryExtra()668 public byte[] getCentralDirectoryExtra() { 669 return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy()); 670 } 671 672 /** 673 * Get the name of the entry. 674 * 675 * <p>This method returns the raw name as it is stored inside of the archive.</p> 676 * 677 * @return the entry name 678 */ 679 @Override getName()680 public String getName() { 681 return name == null ? super.getName() : name; 682 } 683 684 /** 685 * Is this entry a directory? 686 * @return true if the entry is a directory 687 */ 688 @Override isDirectory()689 public boolean isDirectory() { 690 return getName().endsWith("/"); 691 } 692 693 /** 694 * Set the name of the entry. 695 * @param name the name to use 696 */ setName(String name)697 protected void setName(String name) { 698 if (name != null && getPlatform() == PLATFORM_FAT 699 && !name.contains("/")) { 700 name = name.replace('\\', '/'); 701 } 702 this.name = name; 703 } 704 705 /** 706 * Gets the uncompressed size of the entry data. 707 * 708 * <p><b>Note</b>: {@link ZipArchiveInputStream} may create 709 * entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long 710 * as the entry hasn't been read completely.</p> 711 * 712 * @return the entry size 713 */ 714 @Override getSize()715 public long getSize() { 716 return size; 717 } 718 719 /** 720 * Sets the uncompressed size of the entry data. 721 * @param size the uncompressed size in bytes 722 * @throws IllegalArgumentException if the specified size is less 723 * than 0 724 */ 725 @Override setSize(final long size)726 public void setSize(final long size) { 727 if (size < 0) { 728 throw new IllegalArgumentException("invalid entry size"); 729 } 730 this.size = size; 731 } 732 733 /** 734 * Sets the name using the raw bytes and the string created from 735 * it by guessing or using the configured encoding. 736 * @param name the name to use created from the raw bytes using 737 * the guessed or configured encoding 738 * @param rawName the bytes originally read as name from the 739 * archive 740 * @since 1.2 741 */ setName(final String name, final byte[] rawName)742 protected void setName(final String name, final byte[] rawName) { 743 setName(name); 744 this.rawName = rawName; 745 } 746 747 /** 748 * Returns the raw bytes that made up the name before it has been 749 * converted using the configured or guessed encoding. 750 * 751 * <p>This method will return null if this instance has not been 752 * read from an archive.</p> 753 * 754 * @return the raw name bytes 755 * @since 1.2 756 */ getRawName()757 public byte[] getRawName() { 758 if (rawName != null) { 759 final byte[] b = new byte[rawName.length]; 760 System.arraycopy(rawName, 0, b, 0, rawName.length); 761 return b; 762 } 763 return null; 764 } 765 getLocalHeaderOffset()766 protected long getLocalHeaderOffset() { 767 return this.localHeaderOffset; 768 } 769 setLocalHeaderOffset(long localHeaderOffset)770 protected void setLocalHeaderOffset(long localHeaderOffset) { 771 this.localHeaderOffset = localHeaderOffset; 772 } 773 774 @Override getDataOffset()775 public long getDataOffset() { 776 return dataOffset; 777 } 778 779 /** 780 * Sets the data offset. 781 * 782 * @param dataOffset 783 * new value of data offset. 784 */ setDataOffset(long dataOffset)785 protected void setDataOffset(long dataOffset) { 786 this.dataOffset = dataOffset; 787 } 788 789 @Override isStreamContiguous()790 public boolean isStreamContiguous() { 791 return isStreamContiguous; 792 } 793 setStreamContiguous(boolean isStreamContiguous)794 protected void setStreamContiguous(boolean isStreamContiguous) { 795 this.isStreamContiguous = isStreamContiguous; 796 } 797 798 /** 799 * Get the hashCode of the entry. 800 * This uses the name as the hashcode. 801 * @return a hashcode. 802 */ 803 @Override hashCode()804 public int hashCode() { 805 // this method has severe consequences on performance. We cannot rely 806 // on the super.hashCode() method since super.getName() always return 807 // the empty string in the current implemention (there's no setter) 808 // so it is basically draining the performance of a hashmap lookup 809 return getName().hashCode(); 810 } 811 812 /** 813 * The "general purpose bit" field. 814 * @return the general purpose bit 815 * @since 1.1 816 */ getGeneralPurposeBit()817 public GeneralPurposeBit getGeneralPurposeBit() { 818 return gpb; 819 } 820 821 /** 822 * The "general purpose bit" field. 823 * @param b the general purpose bit 824 * @since 1.1 825 */ setGeneralPurposeBit(final GeneralPurposeBit b)826 public void setGeneralPurposeBit(final GeneralPurposeBit b) { 827 gpb = b; 828 } 829 830 /** 831 * If there are no extra fields, use the given fields as new extra 832 * data - otherwise merge the fields assuming the existing fields 833 * and the new fields stem from different locations inside the 834 * archive. 835 * @param f the extra fields to merge 836 * @param local whether the new fields originate from local data 837 */ mergeExtraFields(final ZipExtraField[] f, final boolean local)838 private void mergeExtraFields(final ZipExtraField[] f, final boolean local) 839 throws ZipException { 840 if (extraFields == null) { 841 setExtraFields(f); 842 } else { 843 for (final ZipExtraField element : f) { 844 ZipExtraField existing; 845 if (element instanceof UnparseableExtraFieldData) { 846 existing = unparseableExtra; 847 } else { 848 existing = getExtraField(element.getHeaderId()); 849 } 850 if (existing == null) { 851 addExtraField(element); 852 } else { 853 if (local) { 854 final byte[] b = element.getLocalFileDataData(); 855 existing.parseFromLocalFileData(b, 0, b.length); 856 } else { 857 final byte[] b = element.getCentralDirectoryData(); 858 existing.parseFromCentralDirectoryData(b, 0, b.length); 859 } 860 } 861 } 862 setExtra(); 863 } 864 } 865 866 /** 867 * Wraps {@link java.util.zip.ZipEntry#getTime} with a {@link Date} as the 868 * entry's last modified date. 869 * 870 * <p>Changes to the implementation of {@link java.util.zip.ZipEntry#getTime} 871 * leak through and the returned value may depend on your local 872 * time zone as well as your version of Java.</p> 873 */ 874 @Override getLastModifiedDate()875 public Date getLastModifiedDate() { 876 return new Date(getTime()); 877 } 878 879 /* (non-Javadoc) 880 * @see java.lang.Object#equals(java.lang.Object) 881 */ 882 @Override equals(final Object obj)883 public boolean equals(final Object obj) { 884 if (this == obj) { 885 return true; 886 } 887 if (obj == null || getClass() != obj.getClass()) { 888 return false; 889 } 890 final ZipArchiveEntry other = (ZipArchiveEntry) obj; 891 final String myName = getName(); 892 final String otherName = other.getName(); 893 if (myName == null) { 894 if (otherName != null) { 895 return false; 896 } 897 } else if (!myName.equals(otherName)) { 898 return false; 899 } 900 String myComment = getComment(); 901 String otherComment = other.getComment(); 902 if (myComment == null) { 903 myComment = ""; 904 } 905 if (otherComment == null) { 906 otherComment = ""; 907 } 908 return getTime() == other.getTime() 909 && myComment.equals(otherComment) 910 && getInternalAttributes() == other.getInternalAttributes() 911 && getPlatform() == other.getPlatform() 912 && getExternalAttributes() == other.getExternalAttributes() 913 && getMethod() == other.getMethod() 914 && getSize() == other.getSize() 915 && getCrc() == other.getCrc() 916 && getCompressedSize() == other.getCompressedSize() 917 && Arrays.equals(getCentralDirectoryExtra(), 918 other.getCentralDirectoryExtra()) 919 && Arrays.equals(getLocalFileDataExtra(), 920 other.getLocalFileDataExtra()) 921 && localHeaderOffset == other.localHeaderOffset 922 && dataOffset == other.dataOffset 923 && gpb.equals(other.gpb); 924 } 925 926 /** 927 * Sets the "version made by" field. 928 * @param versionMadeBy "version made by" field 929 * @since 1.11 930 */ setVersionMadeBy(final int versionMadeBy)931 public void setVersionMadeBy(final int versionMadeBy) { 932 this.versionMadeBy = versionMadeBy; 933 } 934 935 /** 936 * Sets the "version required to expand" field. 937 * @param versionRequired "version required to expand" field 938 * @since 1.11 939 */ setVersionRequired(final int versionRequired)940 public void setVersionRequired(final int versionRequired) { 941 this.versionRequired = versionRequired; 942 } 943 944 /** 945 * The "version required to expand" field. 946 * @return "version required to expand" field 947 * @since 1.11 948 */ getVersionRequired()949 public int getVersionRequired() { 950 return versionRequired; 951 } 952 953 /** 954 * The "version made by" field. 955 * @return "version made by" field 956 * @since 1.11 957 */ getVersionMadeBy()958 public int getVersionMadeBy() { 959 return versionMadeBy; 960 } 961 962 /** 963 * The content of the flags field. 964 * @return content of the flags field 965 * @since 1.11 966 */ getRawFlag()967 public int getRawFlag() { 968 return rawFlag; 969 } 970 971 /** 972 * Sets the content of the flags field. 973 * @param rawFlag content of the flags field 974 * @since 1.11 975 */ setRawFlag(final int rawFlag)976 public void setRawFlag(final int rawFlag) { 977 this.rawFlag = rawFlag; 978 } 979 980 /** 981 * The source of the name field value. 982 * @return source of the name field value 983 * @since 1.16 984 */ getNameSource()985 public NameSource getNameSource() { 986 return nameSource; 987 } 988 989 /** 990 * Sets the source of the name field value. 991 * @param nameSource source of the name field value 992 * @since 1.16 993 */ setNameSource(NameSource nameSource)994 public void setNameSource(NameSource nameSource) { 995 this.nameSource = nameSource; 996 } 997 998 /** 999 * The source of the comment field value. 1000 * @return source of the comment field value 1001 * @since 1.16 1002 */ getCommentSource()1003 public CommentSource getCommentSource() { 1004 return commentSource; 1005 } 1006 1007 /** 1008 * Sets the source of the comment field value. 1009 * @param commentSource source of the comment field value 1010 * @since 1.16 1011 */ setCommentSource(CommentSource commentSource)1012 public void setCommentSource(CommentSource commentSource) { 1013 this.commentSource = commentSource; 1014 } 1015 1016 } 1017