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 java.io.File; 021import java.io.IOException; 022import java.nio.file.Files; 023import java.nio.file.LinkOption; 024import java.nio.file.Path; 025import java.nio.file.attribute.FileTime; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.Date; 029import java.util.List; 030import java.util.Objects; 031import java.util.zip.ZipException; 032 033import org.apache.commons.compress.archivers.ArchiveEntry; 034import org.apache.commons.compress.archivers.EntryStreamOffsets; 035import org.apache.commons.compress.utils.ByteUtils; 036 037/** 038 * Extension that adds better handling of extra fields and provides 039 * access to the internal and external file attributes. 040 * 041 * <p>The extra data is expected to follow the recommendation of 042 * <a href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">APPNOTE.TXT</a>:</p> 043 * <ul> 044 * <li>the extra byte array consists of a sequence of extra fields</li> 045 * <li>each extra fields starts by a two byte header id followed by 046 * a two byte sequence holding the length of the remainder of 047 * data.</li> 048 * </ul> 049 * 050 * <p>Any extra data that cannot be parsed by the rules above will be 051 * consumed as "unparseable" extra data and treated differently by the 052 * methods of this class. Versions prior to Apache Commons Compress 053 * 1.1 would have thrown an exception if any attempt was made to read 054 * or write extra data not conforming to the recommendation.</p> 055 * 056 * @NotThreadSafe 057 */ 058public class ZipArchiveEntry extends java.util.zip.ZipEntry 059 implements ArchiveEntry, EntryStreamOffsets 060{ 061 062 public static final int PLATFORM_UNIX = 3; 063 public static final int PLATFORM_FAT = 0; 064 public static final int CRC_UNKNOWN = -1; 065 private static final int SHORT_MASK = 0xFFFF; 066 private static final int SHORT_SHIFT = 16; 067 /** 068 * Indicates how the name of this entry has been determined. 069 * @since 1.16 070 */ 071 public enum NameSource { 072 /** 073 * The name has been read from the archive using the encoding 074 * of the archive specified when creating the {@link 075 * ZipArchiveInputStream} or {@link ZipFile} (defaults to the 076 * platform's default encoding). 077 */ 078 NAME, 079 /** 080 * The name has been read from the archive and the archive 081 * specified the EFS flag which indicates the name has been 082 * encoded as UTF-8. 083 */ 084 NAME_WITH_EFS_FLAG, 085 /** 086 * The name has been read from an {@link UnicodePathExtraField 087 * Unicode Extra Field}. 088 */ 089 UNICODE_EXTRA_FIELD 090 } 091 092 /** 093 * Indicates how the comment of this entry has been determined. 094 * @since 1.16 095 */ 096 public enum CommentSource { 097 /** 098 * The comment has been read from the archive using the encoding 099 * of the archive specified when creating the {@link 100 * ZipArchiveInputStream} or {@link ZipFile} (defaults to the 101 * platform's default encoding). 102 */ 103 COMMENT, 104 /** 105 * The comment has been read from an {@link UnicodeCommentExtraField 106 * Unicode Extra Field}. 107 */ 108 UNICODE_EXTRA_FIELD 109 } 110 111 /** 112 * The {@link java.util.zip.ZipEntry} base class only supports 113 * the compression methods STORED and DEFLATED. We override the 114 * field so that any compression methods can be used. 115 * <p> 116 * The default value -1 means that the method has not been specified. 117 * 118 * @see <a href="https://issues.apache.org/jira/browse/COMPRESS-93" 119 * >COMPRESS-93</a> 120 */ 121 private int method = ZipMethod.UNKNOWN_CODE; 122 123 /** 124 * The {@link java.util.zip.ZipEntry#setSize} method in the base 125 * class throws an IllegalArgumentException if the size is bigger 126 * than 2GB for Java versions < 7 and even in Java 7+ if the 127 * implementation in java.util.zip doesn't support Zip64 itself 128 * (it is an optional feature). 129 * 130 * <p>We need to keep our own size information for Zip64 support.</p> 131 */ 132 private long size = SIZE_UNKNOWN; 133 134 private int internalAttributes; 135 private int versionRequired; 136 private int versionMadeBy; 137 private int platform = PLATFORM_FAT; 138 private int rawFlag; 139 private long externalAttributes; 140 private int alignment; 141 private ZipExtraField[] extraFields; 142 private UnparseableExtraFieldData unparseableExtra; 143 private String name; 144 private byte[] rawName; 145 private GeneralPurposeBit gpb = new GeneralPurposeBit(); 146 private long localHeaderOffset = OFFSET_UNKNOWN; 147 private long dataOffset = OFFSET_UNKNOWN; 148 private boolean isStreamContiguous; 149 private NameSource nameSource = NameSource.NAME; 150 private CommentSource commentSource = CommentSource.COMMENT; 151 private long diskNumberStart; 152 static final ZipArchiveEntry[] EMPTY_ZIP_ARCHIVE_ENTRY_ARRAY = new ZipArchiveEntry[0]; 153 154 /** 155 * Creates a new zip entry with the specified name. 156 * 157 * <p>Assumes the entry represents a directory if and only if the 158 * name ends with a forward slash "/".</p> 159 * 160 * @param name the name of the entry 161 */ 162 public ZipArchiveEntry(final String name) { 163 super(name); 164 setName(name); 165 } 166 167 /** 168 * Creates a new zip entry with fields taken from the specified zip entry. 169 * 170 * <p>Assumes the entry represents a directory if and only if the 171 * name ends with a forward slash "/".</p> 172 * 173 * @param entry the entry to get fields from 174 * @throws ZipException on error 175 */ 176 public ZipArchiveEntry(final java.util.zip.ZipEntry entry) throws ZipException { 177 super(entry); 178 setName(entry.getName()); 179 final byte[] extra = entry.getExtra(); 180 if (extra != null) { 181 setExtraFields(ExtraFieldUtils.parse(extra, true, ExtraFieldParsingMode.BEST_EFFORT)); } else { 182 // initializes extra data to an empty byte array 183 setExtra(); 184 } 185 setMethod(entry.getMethod()); 186 this.size = entry.getSize(); 187 } 188 189 /** 190 * Creates a new zip entry with fields taken from the specified zip entry. 191 * 192 * <p>Assumes the entry represents a directory if and only if the 193 * name ends with a forward slash "/".</p> 194 * 195 * @param entry the entry to get fields from 196 * @throws ZipException on error 197 */ 198 public ZipArchiveEntry(final ZipArchiveEntry entry) throws ZipException { 199 this((java.util.zip.ZipEntry) entry); 200 setInternalAttributes(entry.getInternalAttributes()); 201 setExternalAttributes(entry.getExternalAttributes()); 202 setExtraFields(getAllExtraFieldsNoCopy()); 203 setPlatform(entry.getPlatform()); 204 final GeneralPurposeBit other = entry.getGeneralPurposeBit(); 205 setGeneralPurposeBit(other == null ? null : 206 (GeneralPurposeBit) other.clone()); 207 } 208 209 /** 210 */ 211 protected ZipArchiveEntry() { 212 this(""); 213 } 214 215 /** 216 * Creates a new zip entry taking some information from the given 217 * file and using the provided name. 218 * 219 * <p>The name will be adjusted to end with a forward slash "/" if 220 * the file is a directory. If the file is not a directory a 221 * potential trailing forward slash will be stripped from the 222 * entry name.</p> 223 * @param inputFile file to create the entry from 224 * @param entryName name of the entry 225 */ 226 public ZipArchiveEntry(final File inputFile, final String entryName) { 227 this(inputFile.isDirectory() && !entryName.endsWith("/") ? 228 entryName + "/" : entryName); 229 if (inputFile.isFile()){ 230 setSize(inputFile.length()); 231 } 232 setTime(inputFile.lastModified()); 233 // TODO are there any other fields we can set here? 234 } 235 236 /** 237 * Creates a new zip entry taking some information from the given 238 * path and using the provided name. 239 * 240 * <p>The name will be adjusted to end with a forward slash "/" if 241 * the file is a directory. If the file is not a directory a 242 * potential trailing forward slash will be stripped from the 243 * entry name.</p> 244 * @param inputPath path to create the entry from. 245 * @param entryName name of the entry. 246 * @param options options indicating how symbolic links are handled. 247 * @throws IOException if an I/O error occurs. 248 * @since 1.21 249 */ 250 public ZipArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException { 251 this(Files.isDirectory(inputPath, options) && !entryName.endsWith("/") ? 252 entryName + "/" : entryName); 253 if (Files.isRegularFile(inputPath, options)){ 254 setSize(Files.size(inputPath)); 255 } 256 setTime(Files.getLastModifiedTime(inputPath, options)); 257 // TODO are there any other fields we can set here? 258 } 259 260 /** 261 * Sets the modification time of the entry. 262 * @param fileTime the entry modification time. 263 * @since 1.21 264 */ 265 public void setTime(final FileTime fileTime) { 266 setTime(fileTime.toMillis()); 267 } 268 269 /** 270 * Overwrite clone. 271 * @return a cloned copy of this ZipArchiveEntry 272 */ 273 @Override 274 public Object clone() { 275 final ZipArchiveEntry e = (ZipArchiveEntry) super.clone(); 276 277 e.setInternalAttributes(getInternalAttributes()); 278 e.setExternalAttributes(getExternalAttributes()); 279 e.setExtraFields(getAllExtraFieldsNoCopy()); 280 return e; 281 } 282 283 /** 284 * Returns the compression method of this entry, or -1 if the 285 * compression method has not been specified. 286 * 287 * @return compression method 288 * 289 * @since 1.1 290 */ 291 @Override 292 public int getMethod() { 293 return method; 294 } 295 296 /** 297 * Sets the compression method of this entry. 298 * 299 * @param method compression method 300 * 301 * @since 1.1 302 */ 303 @Override 304 public void setMethod(final int method) { 305 if (method < 0) { 306 throw new IllegalArgumentException( 307 "ZIP compression method can not be negative: " + method); 308 } 309 this.method = method; 310 } 311 312 /** 313 * Retrieves the internal file attributes. 314 * 315 * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill 316 * this field, you must use {@link ZipFile} if you want to read 317 * entries using this attribute.</p> 318 * 319 * @return the internal file attributes 320 */ 321 public int getInternalAttributes() { 322 return internalAttributes; 323 } 324 325 /** 326 * Sets the internal file attributes. 327 * @param value an <code>int</code> value 328 */ 329 public void setInternalAttributes(final int value) { 330 internalAttributes = value; 331 } 332 333 /** 334 * Retrieves the external file attributes. 335 * 336 * <p><b>Note</b>: {@link ZipArchiveInputStream} is unable to fill 337 * this field, you must use {@link ZipFile} if you want to read 338 * entries using this attribute.</p> 339 * 340 * @return the external file attributes 341 */ 342 public long getExternalAttributes() { 343 return externalAttributes; 344 } 345 346 /** 347 * Sets the external file attributes. 348 * @param value an <code>long</code> value 349 */ 350 public void setExternalAttributes(final long value) { 351 externalAttributes = value; 352 } 353 354 /** 355 * Sets Unix permissions in a way that is understood by Info-Zip's 356 * unzip command. 357 * @param mode an <code>int</code> value 358 */ 359 public void setUnixMode(final int mode) { 360 // CheckStyle:MagicNumberCheck OFF - no point 361 setExternalAttributes((mode << SHORT_SHIFT) 362 // MS-DOS read-only attribute 363 | ((mode & 0200) == 0 ? 1 : 0) 364 // MS-DOS directory flag 365 | (isDirectory() ? 0x10 : 0)); 366 // CheckStyle:MagicNumberCheck ON 367 platform = PLATFORM_UNIX; 368 } 369 370 /** 371 * Unix permission. 372 * @return the unix permissions 373 */ 374 public int getUnixMode() { 375 return platform != PLATFORM_UNIX ? 0 : 376 (int) ((getExternalAttributes() >> SHORT_SHIFT) & SHORT_MASK); 377 } 378 379 /** 380 * Returns true if this entry represents a unix symlink, 381 * in which case the entry's content contains the target path 382 * for the symlink. 383 * 384 * @since 1.5 385 * @return true if the entry represents a unix symlink, false otherwise. 386 */ 387 public boolean isUnixSymlink() { 388 return (getUnixMode() & UnixStat.FILE_TYPE_FLAG) == UnixStat.LINK_FLAG; 389 } 390 391 /** 392 * Platform specification to put into the "version made 393 * by" part of the central file header. 394 * 395 * @return PLATFORM_FAT unless {@link #setUnixMode setUnixMode} 396 * has been called, in which case PLATFORM_UNIX will be returned. 397 */ 398 public int getPlatform() { 399 return platform; 400 } 401 402 /** 403 * Set the platform (UNIX or FAT). 404 * @param platform an <code>int</code> value - 0 is FAT, 3 is UNIX 405 */ 406 protected void setPlatform(final int platform) { 407 this.platform = platform; 408 } 409 410 /** 411 * Gets currently configured alignment. 412 * 413 * @return 414 * alignment for this entry. 415 * @since 1.14 416 */ 417 protected int getAlignment() { 418 return this.alignment; 419 } 420 421 /** 422 * Sets alignment for this entry. 423 * 424 * @param alignment 425 * requested alignment, 0 for default. 426 * @since 1.14 427 */ 428 public void setAlignment(final int alignment) { 429 if ((alignment & (alignment - 1)) != 0 || alignment > 0xffff) { 430 throw new IllegalArgumentException("Invalid value for alignment, must be power of two and no bigger than " 431 + 0xffff + " but is " + alignment); 432 } 433 this.alignment = alignment; 434 } 435 436 /** 437 * Replaces all currently attached extra fields with the new array. 438 * @param fields an array of extra fields 439 */ 440 public void setExtraFields(final ZipExtraField[] fields) { 441 unparseableExtra = null; 442 final List<ZipExtraField> newFields = new ArrayList<>(); 443 if (fields != null) { 444 for (final ZipExtraField field : fields) { 445 if (field instanceof UnparseableExtraFieldData) { 446 unparseableExtra = (UnparseableExtraFieldData) field; 447 } else { 448 newFields.add(field); 449 } 450 } 451 } 452 extraFields = newFields.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY); 453 setExtra(); 454 } 455 456 /** 457 * Retrieves all extra fields that have been parsed successfully. 458 * 459 * <p><b>Note</b>: The set of extra fields may be incomplete when 460 * {@link ZipArchiveInputStream} has been used as some extra 461 * fields use the central directory to store additional 462 * information.</p> 463 * 464 * @return an array of the extra fields 465 */ 466 public ZipExtraField[] getExtraFields() { 467 return getParseableExtraFields(); 468 } 469 470 /** 471 * Retrieves extra fields. 472 * @param includeUnparseable whether to also return unparseable 473 * extra fields as {@link UnparseableExtraFieldData} if such data 474 * exists. 475 * @return an array of the extra fields 476 * 477 * @since 1.1 478 */ 479 public ZipExtraField[] getExtraFields(final boolean includeUnparseable) { 480 return includeUnparseable ? 481 getAllExtraFields() : 482 getParseableExtraFields(); 483 } 484 485 /** 486 * Retrieves extra fields. 487 * @param parsingBehavior controls parsing of extra fields. 488 * @return an array of the extra fields 489 * 490 * @throws ZipException if parsing fails, can not happen if {@code 491 * parsingBehavior} is {@link ExtraFieldParsingMode#BEST_EFFORT}. 492 * 493 * @since 1.19 494 */ 495 public ZipExtraField[] getExtraFields(final ExtraFieldParsingBehavior parsingBehavior) 496 throws ZipException { 497 if (parsingBehavior == ExtraFieldParsingMode.BEST_EFFORT) { 498 return getExtraFields(true); 499 } 500 if (parsingBehavior == ExtraFieldParsingMode.ONLY_PARSEABLE_LENIENT) { 501 return getExtraFields(false); 502 } 503 final byte[] local = getExtra(); 504 final List<ZipExtraField> localFields = new ArrayList<>(Arrays.asList(ExtraFieldUtils.parse(local, true, 505 parsingBehavior))); 506 final byte[] central = getCentralDirectoryExtra(); 507 final List<ZipExtraField> centralFields = new ArrayList<>(Arrays.asList(ExtraFieldUtils.parse(central, false, 508 parsingBehavior))); 509 final List<ZipExtraField> merged = new ArrayList<>(); 510 for (final ZipExtraField l : localFields) { 511 ZipExtraField c = null; 512 if (l instanceof UnparseableExtraFieldData) { 513 c = findUnparseable(centralFields); 514 } else { 515 c = findMatching(l.getHeaderId(), centralFields); 516 } 517 if (c != null) { 518 final byte[] cd = c.getCentralDirectoryData(); 519 if (cd != null && cd.length > 0) { 520 l.parseFromCentralDirectoryData(cd, 0, cd.length); 521 } 522 centralFields.remove(c); 523 } 524 merged.add(l); 525 } 526 merged.addAll(centralFields); 527 return merged.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY); 528 } 529 530 private ZipExtraField[] getParseableExtraFieldsNoCopy() { 531 if (extraFields == null) { 532 return ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY; 533 } 534 return extraFields; 535 } 536 537 private ZipExtraField[] getParseableExtraFields() { 538 final ZipExtraField[] parseableExtraFields = getParseableExtraFieldsNoCopy(); 539 return (parseableExtraFields == extraFields) ? copyOf(parseableExtraFields, parseableExtraFields.length) 540 : parseableExtraFields; 541 } 542 543 /** 544 * Get all extra fields, including unparseable ones. 545 * @return An array of all extra fields. Not necessarily a copy of internal data structures, hence private method 546 */ 547 private ZipExtraField[] getAllExtraFieldsNoCopy() { 548 if (extraFields == null) { 549 return getUnparseableOnly(); 550 } 551 return unparseableExtra != null ? getMergedFields() : extraFields; 552 } 553 554 private ZipExtraField[] getMergedFields() { 555 final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); 556 zipExtraFields[extraFields.length] = unparseableExtra; 557 return zipExtraFields; 558 } 559 560 private ZipExtraField[] getUnparseableOnly() { 561 return unparseableExtra == null ? ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY : new ZipExtraField[] { unparseableExtra }; 562 } 563 564 private ZipExtraField[] getAllExtraFields() { 565 final ZipExtraField[] allExtraFieldsNoCopy = getAllExtraFieldsNoCopy(); 566 return (allExtraFieldsNoCopy == extraFields) ? copyOf(allExtraFieldsNoCopy, allExtraFieldsNoCopy.length) 567 : allExtraFieldsNoCopy; 568 } 569 570 private ZipExtraField findUnparseable(final List<ZipExtraField> fs) { 571 for (final ZipExtraField f : fs) { 572 if (f instanceof UnparseableExtraFieldData) { 573 return f; 574 } 575 } 576 return null; 577 } 578 579 private ZipExtraField findMatching(final ZipShort headerId, final List<ZipExtraField> fs) { 580 for (final ZipExtraField f : fs) { 581 if (headerId.equals(f.getHeaderId())) { 582 return f; 583 } 584 } 585 return null; 586 } 587 588 /** 589 * Adds an extra field - replacing an already present extra field 590 * of the same type. 591 * 592 * <p>If no extra field of the same type exists, the field will be 593 * added as last field.</p> 594 * @param ze an extra field 595 */ 596 public void addExtraField(final ZipExtraField ze) { 597 if (ze instanceof UnparseableExtraFieldData) { 598 unparseableExtra = (UnparseableExtraFieldData) ze; 599 } else { 600 if (extraFields == null) { 601 extraFields = new ZipExtraField[]{ ze }; 602 } else { 603 if (getExtraField(ze.getHeaderId()) != null) { 604 removeExtraField(ze.getHeaderId()); 605 } 606 final ZipExtraField[] zipExtraFields = copyOf(extraFields, extraFields.length + 1); 607 zipExtraFields[zipExtraFields.length - 1] = ze; 608 extraFields = zipExtraFields; 609 } 610 } 611 setExtra(); 612 } 613 614 /** 615 * Adds an extra field - replacing an already present extra field 616 * of the same type. 617 * 618 * <p>The new extra field will be the first one.</p> 619 * @param ze an extra field 620 */ 621 public void addAsFirstExtraField(final ZipExtraField ze) { 622 if (ze instanceof UnparseableExtraFieldData) { 623 unparseableExtra = (UnparseableExtraFieldData) ze; 624 } else { 625 if (getExtraField(ze.getHeaderId()) != null) { 626 removeExtraField(ze.getHeaderId()); 627 } 628 final ZipExtraField[] copy = extraFields; 629 final int newLen = extraFields != null ? extraFields.length + 1 : 1; 630 extraFields = new ZipExtraField[newLen]; 631 extraFields[0] = ze; 632 if (copy != null){ 633 System.arraycopy(copy, 0, extraFields, 1, extraFields.length - 1); 634 } 635 } 636 setExtra(); 637 } 638 639 /** 640 * Remove an extra field. 641 * @param type the type of extra field to remove 642 */ 643 public void removeExtraField(final ZipShort type) { 644 if (extraFields == null) { 645 throw new java.util.NoSuchElementException(); 646 } 647 648 final List<ZipExtraField> newResult = new ArrayList<>(); 649 for (final ZipExtraField extraField : extraFields) { 650 if (!type.equals(extraField.getHeaderId())) { 651 newResult.add(extraField); 652 } 653 } 654 if (extraFields.length == newResult.size()) { 655 throw new java.util.NoSuchElementException(); 656 } 657 extraFields = newResult.toArray(ExtraFieldUtils.EMPTY_ZIP_EXTRA_FIELD_ARRAY); 658 setExtra(); 659 } 660 661 /** 662 * Removes unparseable extra field data. 663 * 664 * @since 1.1 665 */ 666 public void removeUnparseableExtraFieldData() { 667 if (unparseableExtra == null) { 668 throw new java.util.NoSuchElementException(); 669 } 670 unparseableExtra = null; 671 setExtra(); 672 } 673 674 /** 675 * Looks up an extra field by its header id. 676 * 677 * @param type the header id 678 * @return null if no such field exists. 679 */ 680 public ZipExtraField getExtraField(final ZipShort type) { 681 if (extraFields != null) { 682 for (final ZipExtraField extraField : extraFields) { 683 if (type.equals(extraField.getHeaderId())) { 684 return extraField; 685 } 686 } 687 } 688 return null; 689 } 690 691 /** 692 * Looks up extra field data that couldn't be parsed correctly. 693 * 694 * @return null if no such field exists. 695 * 696 * @since 1.1 697 */ 698 public UnparseableExtraFieldData getUnparseableExtraFieldData() { 699 return unparseableExtra; 700 } 701 702 /** 703 * Parses the given bytes as extra field data and consumes any 704 * unparseable data as an {@link UnparseableExtraFieldData} 705 * instance. 706 * @param extra an array of bytes to be parsed into extra fields 707 * @throws RuntimeException if the bytes cannot be parsed 708 * @throws RuntimeException on error 709 */ 710 @Override 711 public void setExtra(final byte[] extra) throws RuntimeException { 712 try { 713 final ZipExtraField[] local = ExtraFieldUtils.parse(extra, true, ExtraFieldParsingMode.BEST_EFFORT); 714 mergeExtraFields(local, true); 715 } catch (final ZipException e) { 716 // actually this is not possible as of Commons Compress 1.1 717 throw new RuntimeException("Error parsing extra fields for entry: " //NOSONAR 718 + getName() + " - " + e.getMessage(), e); 719 } 720 } 721 722 /** 723 * Unfortunately {@link java.util.zip.ZipOutputStream 724 * java.util.zip.ZipOutputStream} seems to access the extra data 725 * directly, so overriding getExtra doesn't help - we need to 726 * modify super's data directly. 727 */ 728 protected void setExtra() { 729 super.setExtra(ExtraFieldUtils.mergeLocalFileDataData(getAllExtraFieldsNoCopy())); 730 } 731 732 /** 733 * Sets the central directory part of extra fields. 734 * @param b an array of bytes to be parsed into extra fields 735 */ 736 public void setCentralDirectoryExtra(final byte[] b) { 737 try { 738 final ZipExtraField[] central = ExtraFieldUtils.parse(b, false, ExtraFieldParsingMode.BEST_EFFORT); 739 mergeExtraFields(central, false); 740 } catch (final ZipException e) { 741 // actually this is not possible as of Commons Compress 1.19 742 throw new RuntimeException(e.getMessage(), e); //NOSONAR 743 } 744 } 745 746 /** 747 * Retrieves the extra data for the local file data. 748 * @return the extra data for local file 749 */ 750 public byte[] getLocalFileDataExtra() { 751 final byte[] extra = getExtra(); 752 return extra != null ? extra : ByteUtils.EMPTY_BYTE_ARRAY; 753 } 754 755 /** 756 * Retrieves the extra data for the central directory. 757 * @return the central directory extra data 758 */ 759 public byte[] getCentralDirectoryExtra() { 760 return ExtraFieldUtils.mergeCentralDirectoryData(getAllExtraFieldsNoCopy()); 761 } 762 763 /** 764 * Get the name of the entry. 765 * 766 * <p>This method returns the raw name as it is stored inside of the archive.</p> 767 * 768 * @return the entry name 769 */ 770 @Override 771 public String getName() { 772 return name == null ? super.getName() : name; 773 } 774 775 /** 776 * Is this entry a directory? 777 * @return true if the entry is a directory 778 */ 779 @Override 780 public boolean isDirectory() { 781 final String n = getName(); 782 return n != null && n.endsWith("/"); 783 } 784 785 /** 786 * Set the name of the entry. 787 * @param name the name to use 788 */ 789 protected void setName(String name) { 790 if (name != null && getPlatform() == PLATFORM_FAT 791 && !name.contains("/")) { 792 name = name.replace('\\', '/'); 793 } 794 this.name = name; 795 } 796 797 /** 798 * Gets the uncompressed size of the entry data. 799 * 800 * <p><b>Note</b>: {@link ZipArchiveInputStream} may create 801 * entries that return {@link #SIZE_UNKNOWN SIZE_UNKNOWN} as long 802 * as the entry hasn't been read completely.</p> 803 * 804 * @return the entry size 805 */ 806 @Override 807 public long getSize() { 808 return size; 809 } 810 811 /** 812 * Sets the uncompressed size of the entry data. 813 * @param size the uncompressed size in bytes 814 * @throws IllegalArgumentException if the specified size is less 815 * than 0 816 */ 817 @Override 818 public void setSize(final long size) { 819 if (size < 0) { 820 throw new IllegalArgumentException("Invalid entry size"); 821 } 822 this.size = size; 823 } 824 825 /** 826 * Sets the name using the raw bytes and the string created from 827 * it by guessing or using the configured encoding. 828 * @param name the name to use created from the raw bytes using 829 * the guessed or configured encoding 830 * @param rawName the bytes originally read as name from the 831 * archive 832 * @since 1.2 833 */ 834 protected void setName(final String name, final byte[] rawName) { 835 setName(name); 836 this.rawName = rawName; 837 } 838 839 /** 840 * Returns the raw bytes that made up the name before it has been 841 * converted using the configured or guessed encoding. 842 * 843 * <p>This method will return null if this instance has not been 844 * read from an archive.</p> 845 * 846 * @return the raw name bytes 847 * @since 1.2 848 */ 849 public byte[] getRawName() { 850 if (rawName != null) { 851 return Arrays.copyOf(rawName, rawName.length); 852 } 853 return null; 854 } 855 856 protected long getLocalHeaderOffset() { 857 return this.localHeaderOffset; 858 } 859 860 protected void setLocalHeaderOffset(final long localHeaderOffset) { 861 this.localHeaderOffset = localHeaderOffset; 862 } 863 864 @Override 865 public long getDataOffset() { 866 return dataOffset; 867 } 868 869 /** 870 * Sets the data offset. 871 * 872 * @param dataOffset 873 * new value of data offset. 874 */ 875 protected void setDataOffset(final long dataOffset) { 876 this.dataOffset = dataOffset; 877 } 878 879 @Override 880 public boolean isStreamContiguous() { 881 return isStreamContiguous; 882 } 883 884 protected void setStreamContiguous(final boolean isStreamContiguous) { 885 this.isStreamContiguous = isStreamContiguous; 886 } 887 888 /** 889 * Get the hashCode of the entry. 890 * This uses the name as the hashcode. 891 * @return a hashcode. 892 */ 893 @Override 894 public int hashCode() { 895 // this method has severe consequences on performance. We cannot rely 896 // on the super.hashCode() method since super.getName() always return 897 // the empty string in the current implementation (there's no setter) 898 // so it is basically draining the performance of a hashmap lookup 899 final String n = getName(); 900 return (n == null ? "" : n).hashCode(); 901 } 902 903 /** 904 * The "general purpose bit" field. 905 * @return the general purpose bit 906 * @since 1.1 907 */ 908 public GeneralPurposeBit getGeneralPurposeBit() { 909 return gpb; 910 } 911 912 /** 913 * The "general purpose bit" field. 914 * @param b the general purpose bit 915 * @since 1.1 916 */ 917 public void setGeneralPurposeBit(final GeneralPurposeBit b) { 918 gpb = b; 919 } 920 921 /** 922 * If there are no extra fields, use the given fields as new extra 923 * data - otherwise merge the fields assuming the existing fields 924 * and the new fields stem from different locations inside the 925 * archive. 926 * @param f the extra fields to merge 927 * @param local whether the new fields originate from local data 928 */ 929 private void mergeExtraFields(final ZipExtraField[] f, final boolean local) { 930 if (extraFields == null) { 931 setExtraFields(f); 932 } else { 933 for (final ZipExtraField element : f) { 934 final ZipExtraField existing; 935 if (element instanceof UnparseableExtraFieldData) { 936 existing = unparseableExtra; 937 } else { 938 existing = getExtraField(element.getHeaderId()); 939 } 940 if (existing == null) { 941 addExtraField(element); 942 } else { 943 final byte[] b = local ? element.getLocalFileDataData() 944 : element.getCentralDirectoryData(); 945 try { 946 if (local) { 947 existing.parseFromLocalFileData(b, 0, b.length); 948 } else { 949 existing.parseFromCentralDirectoryData(b, 0, b.length); 950 } 951 } catch (final ZipException ex) { 952 // emulate ExtraFieldParsingMode.fillAndMakeUnrecognizedOnError 953 final UnrecognizedExtraField u = new UnrecognizedExtraField(); 954 u.setHeaderId(existing.getHeaderId()); 955 if (local) { 956 u.setLocalFileDataData(b); 957 u.setCentralDirectoryData(existing.getCentralDirectoryData()); 958 } else { 959 u.setLocalFileDataData(existing.getLocalFileDataData()); 960 u.setCentralDirectoryData(b); 961 } 962 removeExtraField(existing.getHeaderId()); 963 addExtraField(u); 964 } 965 } 966 } 967 setExtra(); 968 } 969 } 970 971 /** 972 * Wraps {@link java.util.zip.ZipEntry#getTime} with a {@link Date} as the 973 * entry's last modified date. 974 * 975 * <p>Changes to the implementation of {@link java.util.zip.ZipEntry#getTime} 976 * leak through and the returned value may depend on your local 977 * time zone as well as your version of Java.</p> 978 */ 979 @Override 980 public Date getLastModifiedDate() { 981 return new Date(getTime()); 982 } 983 984 /* (non-Javadoc) 985 * @see java.lang.Object#equals(java.lang.Object) 986 */ 987 @Override 988 public boolean equals(final Object obj) { 989 if (this == obj) { 990 return true; 991 } 992 if (obj == null || getClass() != obj.getClass()) { 993 return false; 994 } 995 final ZipArchiveEntry other = (ZipArchiveEntry) obj; 996 final String myName = getName(); 997 final String otherName = other.getName(); 998 if (!Objects.equals(myName, otherName)) { 999 return false; 1000 } 1001 String myComment = getComment(); 1002 String otherComment = other.getComment(); 1003 if (myComment == null) { 1004 myComment = ""; 1005 } 1006 if (otherComment == null) { 1007 otherComment = ""; 1008 } 1009 return getTime() == other.getTime() 1010 && myComment.equals(otherComment) 1011 && getInternalAttributes() == other.getInternalAttributes() 1012 && getPlatform() == other.getPlatform() 1013 && getExternalAttributes() == other.getExternalAttributes() 1014 && getMethod() == other.getMethod() 1015 && getSize() == other.getSize() 1016 && getCrc() == other.getCrc() 1017 && getCompressedSize() == other.getCompressedSize() 1018 && Arrays.equals(getCentralDirectoryExtra(), 1019 other.getCentralDirectoryExtra()) 1020 && Arrays.equals(getLocalFileDataExtra(), 1021 other.getLocalFileDataExtra()) 1022 && localHeaderOffset == other.localHeaderOffset 1023 && dataOffset == other.dataOffset 1024 && gpb.equals(other.gpb); 1025 } 1026 1027 /** 1028 * Sets the "version made by" field. 1029 * @param versionMadeBy "version made by" field 1030 * @since 1.11 1031 */ 1032 public void setVersionMadeBy(final int versionMadeBy) { 1033 this.versionMadeBy = versionMadeBy; 1034 } 1035 1036 /** 1037 * Sets the "version required to expand" field. 1038 * @param versionRequired "version required to expand" field 1039 * @since 1.11 1040 */ 1041 public void setVersionRequired(final int versionRequired) { 1042 this.versionRequired = versionRequired; 1043 } 1044 1045 /** 1046 * The "version required to expand" field. 1047 * @return "version required to expand" field 1048 * @since 1.11 1049 */ 1050 public int getVersionRequired() { 1051 return versionRequired; 1052 } 1053 1054 /** 1055 * The "version made by" field. 1056 * @return "version made by" field 1057 * @since 1.11 1058 */ 1059 public int getVersionMadeBy() { 1060 return versionMadeBy; 1061 } 1062 1063 /** 1064 * The content of the flags field. 1065 * @return content of the flags field 1066 * @since 1.11 1067 */ 1068 public int getRawFlag() { 1069 return rawFlag; 1070 } 1071 1072 /** 1073 * Sets the content of the flags field. 1074 * @param rawFlag content of the flags field 1075 * @since 1.11 1076 */ 1077 public void setRawFlag(final int rawFlag) { 1078 this.rawFlag = rawFlag; 1079 } 1080 1081 /** 1082 * The source of the name field value. 1083 * @return source of the name field value 1084 * @since 1.16 1085 */ 1086 public NameSource getNameSource() { 1087 return nameSource; 1088 } 1089 1090 /** 1091 * Sets the source of the name field value. 1092 * @param nameSource source of the name field value 1093 * @since 1.16 1094 */ 1095 public void setNameSource(final NameSource nameSource) { 1096 this.nameSource = nameSource; 1097 } 1098 1099 /** 1100 * The source of the comment field value. 1101 * @return source of the comment field value 1102 * @since 1.16 1103 */ 1104 public CommentSource getCommentSource() { 1105 return commentSource; 1106 } 1107 1108 /** 1109 * Sets the source of the comment field value. 1110 * @param commentSource source of the comment field value 1111 * @since 1.16 1112 */ 1113 public void setCommentSource(final CommentSource commentSource) { 1114 this.commentSource = commentSource; 1115 } 1116 1117 /** 1118 * The number of the split segment this entry starts at. 1119 * 1120 * @return the number of the split segment this entry starts at. 1121 * @since 1.20 1122 */ 1123 public long getDiskNumberStart() { 1124 return diskNumberStart; 1125 } 1126 1127 /** 1128 * The number of the split segment this entry starts at. 1129 * 1130 * @param diskNumberStart the number of the split segment this entry starts at. 1131 * @since 1.20 1132 */ 1133 public void setDiskNumberStart(final long diskNumberStart) { 1134 this.diskNumberStart = diskNumberStart; 1135 } 1136 1137 private ZipExtraField[] copyOf(final ZipExtraField[] src, final int length) { 1138 final ZipExtraField[] cpy = new ZipExtraField[length]; 1139 System.arraycopy(src, 0, cpy, 0, Math.min(src.length, length)); 1140 return cpy; 1141 } 1142 1143 /** 1144 * How to try to parse the extra fields. 1145 * 1146 * <p>Configures the behavior for:</p> 1147 * <ul> 1148 * <li>What shall happen if the extra field content doesn't 1149 * follow the recommended pattern of two-byte id followed by a 1150 * two-byte length?</li> 1151 * <li>What shall happen if an extra field is generally supported 1152 * by Commons Compress but its content cannot be parsed 1153 * correctly? This may for example happen if the archive is 1154 * corrupt, it triggers a bug in Commons Compress or the extra 1155 * field uses a version not (yet) supported by Commons 1156 * Compress.</li> 1157 * </ul> 1158 * 1159 * @since 1.19 1160 */ 1161 public enum ExtraFieldParsingMode implements ExtraFieldParsingBehavior { 1162 /** 1163 * Try to parse as many extra fields as possible and wrap 1164 * unknown extra fields as well as supported extra fields that 1165 * cannot be parsed in {@link UnrecognizedExtraField}. 1166 * 1167 * <p>Wrap extra data that doesn't follow the recommended 1168 * pattern in an {@link UnparseableExtraFieldData} 1169 * instance.</p> 1170 * 1171 * <p>This is the default behavior starting with Commons Compress 1.19.</p> 1172 */ 1173 BEST_EFFORT(ExtraFieldUtils.UnparseableExtraField.READ) { 1174 @Override 1175 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) { 1176 return fillAndMakeUnrecognizedOnError(field, data, off, len, local); 1177 } 1178 }, 1179 /** 1180 * Try to parse as many extra fields as possible and wrap 1181 * unknown extra fields in {@link UnrecognizedExtraField}. 1182 * 1183 * <p>Wrap extra data that doesn't follow the recommended 1184 * pattern in an {@link UnparseableExtraFieldData} 1185 * instance.</p> 1186 * 1187 * <p>Throw an exception if an extra field that is generally 1188 * supported cannot be parsed.</p> 1189 * 1190 * <p>This used to be the default behavior prior to Commons 1191 * Compress 1.19.</p> 1192 */ 1193 STRICT_FOR_KNOW_EXTRA_FIELDS(ExtraFieldUtils.UnparseableExtraField.READ), 1194 /** 1195 * Try to parse as many extra fields as possible and wrap 1196 * unknown extra fields as well as supported extra fields that 1197 * cannot be parsed in {@link UnrecognizedExtraField}. 1198 * 1199 * <p>Ignore extra data that doesn't follow the recommended 1200 * pattern.</p> 1201 */ 1202 ONLY_PARSEABLE_LENIENT(ExtraFieldUtils.UnparseableExtraField.SKIP) { 1203 @Override 1204 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) { 1205 return fillAndMakeUnrecognizedOnError(field, data, off, len, local); 1206 } 1207 }, 1208 /** 1209 * Try to parse as many extra fields as possible and wrap 1210 * unknown extra fields in {@link UnrecognizedExtraField}. 1211 * 1212 * <p>Ignore extra data that doesn't follow the recommended 1213 * pattern.</p> 1214 * 1215 * <p>Throw an exception if an extra field that is generally 1216 * supported cannot be parsed.</p> 1217 */ 1218 ONLY_PARSEABLE_STRICT(ExtraFieldUtils.UnparseableExtraField.SKIP), 1219 /** 1220 * Throw an exception if any of the recognized extra fields 1221 * cannot be parsed or any extra field violates the 1222 * recommended pattern. 1223 */ 1224 DRACONIC(ExtraFieldUtils.UnparseableExtraField.THROW); 1225 1226 private final ExtraFieldUtils.UnparseableExtraField onUnparseableData; 1227 1228 ExtraFieldParsingMode(final ExtraFieldUtils.UnparseableExtraField onUnparseableData) { 1229 this.onUnparseableData = onUnparseableData; 1230 } 1231 1232 @Override 1233 public ZipExtraField onUnparseableExtraField(final byte[] data, final int off, final int len, final boolean local, 1234 final int claimedLength) throws ZipException { 1235 return onUnparseableData.onUnparseableExtraField(data, off, len, local, claimedLength); 1236 } 1237 1238 @Override 1239 public ZipExtraField createExtraField(final ZipShort headerId) 1240 throws ZipException, InstantiationException, IllegalAccessException { 1241 return ExtraFieldUtils.createExtraField(headerId); 1242 } 1243 1244 @Override 1245 public ZipExtraField fill(final ZipExtraField field, final byte[] data, final int off, final int len, final boolean local) 1246 throws ZipException { 1247 return ExtraFieldUtils.fillExtraField(field, data, off, len, local); 1248 } 1249 1250 private static ZipExtraField fillAndMakeUnrecognizedOnError(final ZipExtraField field, final byte[] data, final int off, 1251 final int len, final boolean local) { 1252 try { 1253 return ExtraFieldUtils.fillExtraField(field, data, off, len, local); 1254 } catch (final ZipException ex) { 1255 final UnrecognizedExtraField u = new UnrecognizedExtraField(); 1256 u.setHeaderId(field.getHeaderId()); 1257 if (local) { 1258 u.setLocalFileDataData(Arrays.copyOfRange(data, off, off + len)); 1259 } else { 1260 u.setCentralDirectoryData(Arrays.copyOfRange(data, off, off + len)); 1261 } 1262 return u; 1263 } 1264 } 1265 } 1266}