1 /* 2 * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.zip; 27 28 import static java.util.zip.ZipUtils.*; 29 import java.nio.file.attribute.FileTime; 30 import java.util.Objects; 31 import java.util.concurrent.TimeUnit; 32 import java.time.LocalDateTime; 33 import java.time.ZonedDateTime; 34 import java.time.ZoneId; 35 36 import static java.util.zip.ZipConstants64.*; 37 38 /** 39 * This class is used to represent a ZIP file entry. 40 * 41 * @author David Connelly 42 */ 43 public 44 class ZipEntry implements ZipConstants, Cloneable { 45 46 String name; // entry name 47 long xdostime = -1; // last modification time (in extended DOS time, 48 // where milliseconds lost in conversion might 49 // be encoded into the upper half) 50 FileTime mtime; // last modification time, from extra field data 51 FileTime atime; // last access time, from extra field data 52 FileTime ctime; // creation time, from extra field data 53 long crc = -1; // crc-32 of entry data 54 long size = -1; // uncompressed size of entry data 55 long csize = -1; // compressed size of entry data 56 int method = -1; // compression method 57 int flag = 0; // general purpose flag 58 byte[] extra; // optional extra field data for entry 59 String comment; // optional comment string for entry 60 ZipCryption zipCryption = null; // encryption / decryption engine 61 byte[] encryptionHeader; // ZIP encryption header 62 boolean passphraseValidated = false; // passphrase validation is not yet. 63 64 /** 65 * Compression method for uncompressed entries. 66 */ 67 public static final int STORED = 0; 68 69 /** 70 * Compression method for compressed (deflated) entries. 71 */ 72 public static final int DEFLATED = 8; 73 74 /** 75 * DOS time constant for representing timestamps before 1980. 76 */ 77 static final long DOSTIME_BEFORE_1980 = (1 << 21) | (1 << 16); 78 79 /** 80 * Approximately 128 years, in milliseconds (ignoring leap years etc). 81 * 82 * This establish an approximate high-bound value for DOS times in 83 * milliseconds since epoch, used to enable an efficient but 84 * sufficient bounds check to avoid generating extended last modified 85 * time entries. 86 * 87 * Calculating the exact number is locale dependent, would require loading 88 * TimeZone data eagerly, and would make little practical sense. Since DOS 89 * times theoretically go to 2107 - with compatibility not guaranteed 90 * after 2099 - setting this to a time that is before but near 2099 91 * should be sufficient. 92 */ 93 private static final long UPPER_DOSTIME_BOUND = 94 128L * 365 * 24 * 60 * 60 * 1000; 95 96 /** 97 * Creates a new zip entry with the specified name. 98 * 99 * @param name 100 * The entry name 101 * 102 * @throws NullPointerException if the entry name is null 103 * @throws IllegalArgumentException if the entry name is longer than 104 * 0xFFFF bytes 105 */ 106 public ZipEntry(String name) { 107 Objects.requireNonNull(name, "name"); 108 if (name.length() > 0xFFFF) { 109 throw new IllegalArgumentException("entry name too long"); 110 } 111 this.name = name; 112 } 113 114 /** 115 * Creates a new zip entry with fields taken from the specified 116 * zip entry. 117 * 118 * @param e 119 * A zip Entry object 120 * 121 * @throws NullPointerException if the entry object is null 122 */ 123 public ZipEntry(ZipEntry e) { 124 Objects.requireNonNull(e, "entry"); 125 name = e.name; 126 xdostime = e.xdostime; 127 mtime = e.mtime; 128 atime = e.atime; 129 ctime = e.ctime; 130 crc = e.crc; 131 size = e.size; 132 csize = e.csize; 133 method = e.method; 134 flag = e.flag; 135 extra = e.extra; 136 comment = e.comment; 137 zipCryption = e.zipCryption; 138 encryptionHeader = e.encryptionHeader; 139 } 140 141 /** 142 * Creates a new un-initialized zip entry 143 */ 144 ZipEntry() {} 145 146 /** 147 * Returns the name of the entry. 148 * @return the name of the entry 149 */ 150 public String getName() { 151 return name; 152 } 153 154 /** 155 * Sets the last modification time of the entry. 156 * 157 * <p> If the entry is output to a ZIP file or ZIP file formatted 158 * output stream the last modification time set by this method will 159 * be stored into the {@code date and time fields} of the zip file 160 * entry and encoded in standard {@code MS-DOS date and time format}. 161 * The {@link java.util.TimeZone#getDefault() default TimeZone} is 162 * used to convert the epoch time to the MS-DOS data and time. 163 * 164 * @param time 165 * The last modification time of the entry in milliseconds 166 * since the epoch 167 * 168 * @see #getTime() 169 * @see #getLastModifiedTime() 170 */ 171 public void setTime(long time) { 172 this.xdostime = javaToExtendedDosTime(time); 173 // Avoid setting the mtime field if time is in the valid 174 // range for a DOS time 175 if (xdostime != DOSTIME_BEFORE_1980 && time <= UPPER_DOSTIME_BOUND) { 176 this.mtime = null; 177 } else { 178 this.mtime = FileTime.from(time, TimeUnit.MILLISECONDS); 179 } 180 } 181 182 /** 183 * Returns the last modification time of the entry. 184 * 185 * <p> If the entry is read from a ZIP file or ZIP file formatted 186 * input stream, this is the last modification time from the {@code 187 * date and time fields} of the zip file entry. The 188 * {@link java.util.TimeZone#getDefault() default TimeZone} is used 189 * to convert the standard MS-DOS formatted date and time to the 190 * epoch time. 191 * 192 * @return The last modification time of the entry in milliseconds 193 * since the epoch, or -1 if not specified 194 * 195 * @see #setTime(long) 196 * @see #setLastModifiedTime(FileTime) 197 */ 198 public long getTime() { 199 if (mtime != null) { 200 return mtime.toMillis(); 201 } 202 return (xdostime != -1) ? extendedDosToJavaTime(xdostime) : -1; 203 } 204 205 /** 206 * Sets the last modification time of the entry in local date-time. 207 * 208 * <p> If the entry is output to a ZIP file or ZIP file formatted 209 * output stream the last modification time set by this method will 210 * be stored into the {@code date and time fields} of the zip file 211 * entry and encoded in standard {@code MS-DOS date and time format}. 212 * If the date-time set is out of the range of the standard {@code 213 * MS-DOS date and time format}, the time will also be stored into 214 * zip file entry's extended timestamp fields in {@code optional 215 * extra data} in UTC time. The {@link java.time.ZoneId#systemDefault() 216 * system default TimeZone} is used to convert the local date-time 217 * to UTC time. 218 * 219 * <p> {@code LocalDateTime} uses a precision of nanoseconds, whereas 220 * this class uses a precision of milliseconds. The conversion will 221 * truncate any excess precision information as though the amount in 222 * nanoseconds was subject to integer division by one million. 223 * 224 * @param time 225 * The last modification time of the entry in local date-time 226 * 227 * @see #getTimeLocal() 228 * @since 9 229 */ 230 public void setTimeLocal(LocalDateTime time) { 231 int year = time.getYear() - 1980; 232 if (year < 0) { 233 this.xdostime = DOSTIME_BEFORE_1980; 234 } else { 235 this.xdostime = (year << 25 | 236 time.getMonthValue() << 21 | 237 time.getDayOfMonth() << 16 | 238 time.getHour() << 11 | 239 time.getMinute() << 5 | 240 time.getSecond() >> 1) 241 + ((long)(((time.getSecond() & 0x1) * 1000) + 242 time.getNano() / 1000_000) << 32); 243 } 244 if (xdostime != DOSTIME_BEFORE_1980 && year <= 0x7f) { 245 this.mtime = null; 246 } else { 247 this.mtime = FileTime.from( 248 ZonedDateTime.of(time, ZoneId.systemDefault()).toInstant()); 249 } 250 } 251 252 /** 253 * Returns the last modification time of the entry in local date-time. 254 * 255 * <p> If the entry is read from a ZIP file or ZIP file formatted 256 * input stream, this is the last modification time from the zip 257 * file entry's {@code optional extra data} if the extended timestamp 258 * fields are present. Otherwise, the last modification time is read 259 * from entry's standard MS-DOS formatted {@code date and time fields}. 260 * 261 * <p> The {@link java.time.ZoneId#systemDefault() system default TimeZone} 262 * is used to convert the UTC time to local date-time. 263 * 264 * @return The last modification time of the entry in local date-time 265 * 266 * @see #setTimeLocal(LocalDateTime) 267 * @since 9 268 */ 269 public LocalDateTime getTimeLocal() { 270 if (mtime != null) { 271 return LocalDateTime.ofInstant(mtime.toInstant(), ZoneId.systemDefault()); 272 } 273 int ms = (int)(xdostime >> 32); 274 return LocalDateTime.of((int)(((xdostime >> 25) & 0x7f) + 1980), 275 (int)((xdostime >> 21) & 0x0f), 276 (int)((xdostime >> 16) & 0x1f), 277 (int)((xdostime >> 11) & 0x1f), 278 (int)((xdostime >> 5) & 0x3f), 279 (int)((xdostime << 1) & 0x3e) + ms / 1000, 280 (ms % 1000) * 1000_000); 281 } 282 283 284 /** 285 * Sets the last modification time of the entry. 286 * 287 * <p> When output to a ZIP file or ZIP file formatted output stream 288 * the last modification time set by this method will be stored into 289 * zip file entry's {@code date and time fields} in {@code standard 290 * MS-DOS date and time format}), and the extended timestamp fields 291 * in {@code optional extra data} in UTC time. 292 * 293 * @param time 294 * The last modification time of the entry 295 * @return This zip entry 296 * 297 * @throws NullPointerException if the {@code time} is null 298 * 299 * @see #getLastModifiedTime() 300 * @since 1.8 301 */ 302 public ZipEntry setLastModifiedTime(FileTime time) { 303 this.mtime = Objects.requireNonNull(time, "lastModifiedTime"); 304 this.xdostime = javaToExtendedDosTime(time.to(TimeUnit.MILLISECONDS)); 305 return this; 306 } 307 308 /** 309 * Returns the last modification time of the entry. 310 * 311 * <p> If the entry is read from a ZIP file or ZIP file formatted 312 * input stream, this is the last modification time from the zip 313 * file entry's {@code optional extra data} if the extended timestamp 314 * fields are present. Otherwise the last modification time is read 315 * from the entry's {@code date and time fields}, the {@link 316 * java.util.TimeZone#getDefault() default TimeZone} is used to convert 317 * the standard MS-DOS formatted date and time to the epoch time. 318 * 319 * @return The last modification time of the entry, null if not specified 320 * 321 * @see #setLastModifiedTime(FileTime) 322 * @since 1.8 323 */ 324 public FileTime getLastModifiedTime() { 325 if (mtime != null) 326 return mtime; 327 if (xdostime == -1) 328 return null; 329 return FileTime.from(getTime(), TimeUnit.MILLISECONDS); 330 } 331 332 /** 333 * Sets the last access time of the entry. 334 * 335 * <p> If set, the last access time will be stored into the extended 336 * timestamp fields of entry's {@code optional extra data}, when output 337 * to a ZIP file or ZIP file formatted stream. 338 * 339 * @param time 340 * The last access time of the entry 341 * @return This zip entry 342 * 343 * @throws NullPointerException if the {@code time} is null 344 * 345 * @see #getLastAccessTime() 346 * @since 1.8 347 */ 348 public ZipEntry setLastAccessTime(FileTime time) { 349 this.atime = Objects.requireNonNull(time, "lastAccessTime"); 350 return this; 351 } 352 353 /** 354 * Returns the last access time of the entry. 355 * 356 * <p> The last access time is from the extended timestamp fields 357 * of entry's {@code optional extra data} when read from a ZIP file 358 * or ZIP file formatted stream. 359 * 360 * @return The last access time of the entry, null if not specified 361 362 * @see #setLastAccessTime(FileTime) 363 * @since 1.8 364 */ 365 public FileTime getLastAccessTime() { 366 return atime; 367 } 368 369 /** 370 * Sets the creation time of the entry. 371 * 372 * <p> If set, the creation time will be stored into the extended 373 * timestamp fields of entry's {@code optional extra data}, when 374 * output to a ZIP file or ZIP file formatted stream. 375 * 376 * @param time 377 * The creation time of the entry 378 * @return This zip entry 379 * 380 * @throws NullPointerException if the {@code time} is null 381 * 382 * @see #getCreationTime() 383 * @since 1.8 384 */ 385 public ZipEntry setCreationTime(FileTime time) { 386 this.ctime = Objects.requireNonNull(time, "creationTime"); 387 return this; 388 } 389 390 /** 391 * Returns the creation time of the entry. 392 * 393 * <p> The creation time is from the extended timestamp fields of 394 * entry's {@code optional extra data} when read from a ZIP file 395 * or ZIP file formatted stream. 396 * 397 * @return the creation time of the entry, null if not specified 398 * @see #setCreationTime(FileTime) 399 * @since 1.8 400 */ 401 public FileTime getCreationTime() { 402 return ctime; 403 } 404 405 /** 406 * Sets the uncompressed size of the entry data. 407 * 408 * @param size the uncompressed size in bytes 409 * 410 * @throws IllegalArgumentException if the specified size is less 411 * than 0, is greater than 0xFFFFFFFF when 412 * <a href="package-summary.html#zip64">ZIP64 format</a> is not supported, 413 * or is less than 0 when ZIP64 is supported 414 * @see #getSize() 415 */ 416 public void setSize(long size) { 417 if (size < 0) { 418 throw new IllegalArgumentException("invalid entry size"); 419 } 420 this.size = size; 421 } 422 423 /** 424 * Returns the uncompressed size of the entry data. 425 * 426 * @return the uncompressed size of the entry data, or -1 if not known 427 * @see #setSize(long) 428 */ 429 public long getSize() { 430 return size; 431 } 432 433 /** 434 * Returns the size of the compressed entry data. 435 * 436 * <p> In the case of a stored entry, the compressed size will be the same 437 * as the uncompressed size of the entry. 438 * 439 * @return the size of the compressed entry data, or -1 if not known 440 * @see #setCompressedSize(long) 441 */ 442 public long getCompressedSize() { 443 return csize; 444 } 445 446 /** 447 * Sets the size of the compressed entry data. 448 * 449 * @param csize the compressed size to set to 450 * 451 * @see #getCompressedSize() 452 */ 453 public void setCompressedSize(long csize) { 454 this.csize = csize; 455 } 456 457 /** 458 * Sets the CRC-32 checksum of the uncompressed entry data. 459 * 460 * @param crc the CRC-32 value 461 * 462 * @throws IllegalArgumentException if the specified CRC-32 value is 463 * less than 0 or greater than 0xFFFFFFFF 464 * @see #getCrc() 465 */ 466 public void setCrc(long crc) { 467 if (crc < 0 || crc > 0xFFFFFFFFL) { 468 throw new IllegalArgumentException("invalid entry crc-32"); 469 } 470 this.crc = crc; 471 } 472 473 /** 474 * Returns the CRC-32 checksum of the uncompressed entry data. 475 * 476 * @return the CRC-32 checksum of the uncompressed entry data, or -1 if 477 * not known 478 * 479 * @see #setCrc(long) 480 */ 481 public long getCrc() { 482 return crc; 483 } 484 485 /** 486 * Sets the compression method for the entry. 487 * 488 * @param method the compression method, either STORED or DEFLATED 489 * 490 * @throws IllegalArgumentException if the specified compression 491 * method is invalid 492 * @see #getMethod() 493 */ 494 public void setMethod(int method) { 495 if (method != STORED && method != DEFLATED) { 496 throw new IllegalArgumentException("invalid compression method"); 497 } 498 this.method = method; 499 } 500 501 /** 502 * Returns the compression method of the entry. 503 * 504 * @return the compression method of the entry, or -1 if not specified 505 * @see #setMethod(int) 506 */ 507 public int getMethod() { 508 return method; 509 } 510 511 /** 512 * Sets the optional extra field data for the entry. 513 * 514 * <p> Invoking this method may change this entry's last modification 515 * time, last access time and creation time, if the {@code extra} field 516 * data includes the extensible timestamp fields, such as {@code NTFS tag 517 * 0x0001} or {@code Info-ZIP Extended Timestamp}, as specified in 518 * <a href="http://www.info-zip.org/doc/appnote-19970311-iz.zip">Info-ZIP 519 * Application Note 970311</a>. 520 * 521 * @param extra 522 * The extra field data bytes 523 * 524 * @throws IllegalArgumentException if the length of the specified 525 * extra field data is greater than 0xFFFF bytes 526 * 527 * @see #getExtra() 528 */ 529 public void setExtra(byte[] extra) { 530 setExtra0(extra, false); 531 } 532 533 /** 534 * Sets the optional extra field data for the entry. 535 * 536 * @param extra 537 * the extra field data bytes 538 * @param doZIP64 539 * if true, set size and csize from ZIP64 fields if present 540 */ 541 void setExtra0(byte[] extra, boolean doZIP64) { 542 if (extra != null) { 543 if (extra.length > 0xFFFF) { 544 throw new IllegalArgumentException("invalid extra field length"); 545 } 546 // extra fields are in "HeaderID(2)DataSize(2)Data... format 547 int off = 0; 548 int len = extra.length; 549 while (off + 4 < len) { 550 int tag = get16(extra, off); 551 int sz = get16(extra, off + 2); 552 off += 4; 553 if (off + sz > len) // invalid data 554 break; 555 switch (tag) { 556 case EXTID_ZIP64: 557 if (doZIP64) { 558 // LOC extra zip64 entry MUST include BOTH original 559 // and compressed file size fields. 560 // If invalid zip64 extra fields, simply skip. Even 561 // it's rare, it's possible the entry size happens to 562 // be the magic value and it "accidently" has some 563 // bytes in extra match the id. 564 if (sz >= 16) { 565 size = get64(extra, off); 566 csize = get64(extra, off + 8); 567 } 568 } 569 break; 570 case EXTID_NTFS: 571 if (sz < 32) // reserved 4 bytes + tag 2 bytes + size 2 bytes 572 break; // m[a|c]time 24 bytes 573 int pos = off + 4; // reserved 4 bytes 574 if (get16(extra, pos) != 0x0001 || get16(extra, pos + 2) != 24) 575 break; 576 mtime = winTimeToFileTime(get64(extra, pos + 4)); 577 atime = winTimeToFileTime(get64(extra, pos + 12)); 578 ctime = winTimeToFileTime(get64(extra, pos + 20)); 579 break; 580 case EXTID_EXTT: 581 int flag = Byte.toUnsignedInt(extra[off]); 582 int sz0 = 1; 583 // The CEN-header extra field contains the modification 584 // time only, or no timestamp at all. 'sz' is used to 585 // flag its presence or absence. But if mtime is present 586 // in LOC it must be present in CEN as well. 587 if ((flag & 0x1) != 0 && (sz0 + 4) <= sz) { 588 mtime = unixTimeToFileTime(get32S(extra, off + sz0)); 589 sz0 += 4; 590 } 591 if ((flag & 0x2) != 0 && (sz0 + 4) <= sz) { 592 atime = unixTimeToFileTime(get32S(extra, off + sz0)); 593 sz0 += 4; 594 } 595 if ((flag & 0x4) != 0 && (sz0 + 4) <= sz) { 596 ctime = unixTimeToFileTime(get32S(extra, off + sz0)); 597 sz0 += 4; 598 } 599 break; 600 default: 601 } 602 off += sz; 603 } 604 } 605 this.extra = extra; 606 } 607 608 /** 609 * Returns the extra field data for the entry. 610 * 611 * @return the extra field data for the entry, or null if none 612 * 613 * @see #setExtra(byte[]) 614 */ 615 public byte[] getExtra() { 616 return extra; 617 } 618 619 /** 620 * Sets the optional comment string for the entry. 621 * 622 * <p>ZIP entry comments have maximum length of 0xffff. If the length of the 623 * specified comment string is greater than 0xFFFF bytes after encoding, only 624 * the first 0xFFFF bytes are output to the ZIP file entry. 625 * 626 * @param comment the comment string 627 * 628 * @see #getComment() 629 */ 630 public void setComment(String comment) { 631 this.comment = comment; 632 } 633 634 /** 635 * Returns the comment string for the entry. 636 * 637 * @return the comment string for the entry, or null if none 638 * 639 * @see #setComment(String) 640 */ 641 public String getComment() { 642 return comment; 643 } 644 645 /** 646 * Returns true if this is a directory entry. A directory entry is 647 * defined to be one whose name ends with a '/'. 648 * @return true if this is a directory entry 649 */ 650 public boolean isDirectory() { 651 return name.endsWith("/"); 652 } 653 654 /** 655 * Returns a string representation of the ZIP entry. 656 */ 657 public String toString() { 658 return getName(); 659 } 660 661 /** 662 * Returns the hash code value for this entry. 663 */ 664 public int hashCode() { 665 return name.hashCode(); 666 } 667 668 /** 669 * Returns a copy of this entry. 670 */ 671 public Object clone() { 672 try { 673 ZipEntry e = (ZipEntry)super.clone(); 674 e.extra = (extra == null) ? null : extra.clone(); 675 return e; 676 } catch (CloneNotSupportedException e) { 677 // This should never happen, since we are Cloneable 678 throw new InternalError(e); 679 } 680 } 681 682 /** 683 * Set passphrase for ZIP encryption and decryption. 684 * @param passphrase Passphrase for encryption and decryption. 685 */ 686 public void setPassphrase(String passphrase) { 687 zipCryption = new TraditionalZipCryption(passphrase); 688 } 689 690 ZipCryption getZipCryption() { 691 return zipCryption; 692 } 693 694 /** 695 * Get the state whether encrypted entry or not. 696 * @return true if passphrase is required. 697 */ 698 public boolean isPassphraseRequired() { 699 return (flag & 1) == 1; 700 } 701 702 /** 703 * Validate passphrase for this entry. 704 * @return true if passphrase is valid, or this entry is not encrypted. 705 * @throws ZipException Passphrase is not set or Encryption header is not set. 706 */ 707 public boolean isValidPassphrase() throws ZipException{ 708 if (!isPassphraseRequired()) { 709 return true; 710 } 711 712 if (zipCryption == null) { 713 throw new ZipException("Passphrase is not set."); 714 } 715 if (passphraseValidated) { 716 return true; 717 } 718 if (encryptionHeader == null) { 719 throw new ZipException("Encryption header is not set."); 720 } 721 722 zipCryption.reset(); 723 byte[] tmp = encryptionHeader.clone(); 724 zipCryption.decryptBytes(tmp); 725 passphraseValidated = zipCryption.isValid(this, tmp); 726 return passphraseValidated; 727 } 728 }