1 /* 2 * Copyright (c) 1995, 2018, 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.ZipConstants64.*; 29 import static java.util.zip.ZipUtils.*; 30 31 import java.nio.file.attribute.FileTime; 32 import java.nio.file.attribute.PosixFilePermission; 33 import java.nio.file.attribute.PosixFilePermissions; 34 import java.time.LocalDateTime; 35 import java.time.ZoneId; 36 import java.time.ZonedDateTime; 37 import java.util.Objects; 38 import java.util.Optional; 39 import java.util.Set; 40 import java.util.concurrent.TimeUnit; 41 42 /** 43 * This class is used to represent a ZIP file entry. 44 * 45 * @author David Connelly 46 * @since 1.1 47 */ 48 public 49 class ZipEntry implements ZipConstants, Cloneable { 50 51 String name; // entry name 52 long xdostime = -1; // last modification time (in extended DOS time, 53 // where milliseconds lost in conversion might 54 // be encoded into the upper half) 55 FileTime mtime; // last modification time, from extra field data 56 FileTime atime; // last access time, from extra field data 57 FileTime ctime; // creation time, from extra field data 58 long crc = -1; // crc-32 of entry data 59 long size = -1; // uncompressed size of entry data 60 long csize = -1; // compressed size of entry data 61 int method = -1; // compression method 62 int flag = 0; // general purpose flag 63 int posixPerms = -1; // posix permissions 64 byte[] extra; // optional extra field data for entry 65 String comment; // optional comment string for entry 66 67 /** 68 * Compression method for uncompressed entries. 69 */ 70 public static final int STORED = 0; 71 72 /** 73 * Compression method for compressed (deflated) entries. 74 */ 75 public static final int DEFLATED = 8; 76 77 /** 78 * DOS time constant for representing timestamps before 1980. 79 */ 80 static final long DOSTIME_BEFORE_1980 = (1 << 21) | (1 << 16); 81 82 /** 83 * Approximately 128 years, in milliseconds (ignoring leap years etc). 84 * 85 * This establish an approximate high-bound value for DOS times in 86 * milliseconds since epoch, used to enable an efficient but 87 * sufficient bounds check to avoid generating extended last modified 88 * time entries. 89 * 90 * Calculating the exact number is locale dependent, would require loading 91 * TimeZone data eagerly, and would make little practical sense. Since DOS 92 * times theoretically go to 2107 - with compatibility not guaranteed 93 * after 2099 - setting this to a time that is before but near 2099 94 * should be sufficient. 95 */ 96 private static final long UPPER_DOSTIME_BOUND = 97 128L * 365 * 24 * 60 * 60 * 1000; 98 99 /** 100 * Creates a new zip entry with the specified name. 101 * 102 * @param name 103 * The entry name 104 * 105 * @throws NullPointerException if the entry name is null 106 * @throws IllegalArgumentException if the entry name is longer than 107 * 0xFFFF bytes 108 */ 109 public ZipEntry(String name) { 110 Objects.requireNonNull(name, "name"); 111 if (name.length() > 0xFFFF) { 112 throw new IllegalArgumentException("entry name too long"); 113 } 114 this.name = name; 115 } 116 117 /** 118 * Creates a new zip entry with fields taken from the specified 119 * zip entry. 120 * 121 * @param e 122 * A zip Entry object 123 * 124 * @throws NullPointerException if the entry object is null 125 */ 126 public ZipEntry(ZipEntry e) { 127 Objects.requireNonNull(e, "entry"); 128 name = e.name; 129 xdostime = e.xdostime; 130 mtime = e.mtime; 131 atime = e.atime; 132 ctime = e.ctime; 133 crc = e.crc; 134 size = e.size; 135 csize = e.csize; 136 method = e.method; 137 flag = e.flag; 138 extra = e.extra; 139 comment = e.comment; 140 } 141 142 /** 143 * Creates a new un-initialized zip entry 144 */ 145 ZipEntry() {} 146 147 /** 148 * Returns the name of the entry. 149 * @return the name of the entry 150 */ 151 public String getName() { 152 return name; 153 } 154 155 /** 156 * Sets the last modification time of the entry. 157 * 158 * <p> If the entry is output to a ZIP file or ZIP file formatted 159 * output stream the last modification time set by this method will 160 * be stored into the {@code date and time fields} of the zip file 161 * entry and encoded in standard {@code MS-DOS date and time format}. 162 * The {@link java.util.TimeZone#getDefault() default TimeZone} is 163 * used to convert the epoch time to the MS-DOS data and time. 164 * 165 * @param time 166 * The last modification time of the entry in milliseconds 167 * since the epoch 168 * 169 * @see #getTime() 170 * @see #getLastModifiedTime() 171 */ 172 public void setTime(long time) { 173 this.xdostime = javaToExtendedDosTime(time); 174 // Avoid setting the mtime field if time is in the valid 175 // range for a DOS time 176 if (xdostime != DOSTIME_BEFORE_1980 && time <= UPPER_DOSTIME_BOUND) { 177 this.mtime = null; 178 } else { 179 this.mtime = FileTime.from(time, TimeUnit.MILLISECONDS); 180 } 181 } 182 183 /** 184 * Returns the last modification time of the entry. 185 * 186 * <p> If the entry is read from a ZIP file or ZIP file formatted 187 * input stream, this is the last modification time from the {@code 188 * date and time fields} of the zip file entry. The 189 * {@link java.util.TimeZone#getDefault() default TimeZone} is used 190 * to convert the standard MS-DOS formatted date and time to the 191 * epoch time. 192 * 193 * @return The last modification time of the entry in milliseconds 194 * since the epoch, or -1 if not specified 195 * 196 * @see #setTime(long) 197 * @see #setLastModifiedTime(FileTime) 198 */ 199 public long getTime() { 200 if (mtime != null) { 201 return mtime.toMillis(); 202 } 203 return (xdostime != -1) ? extendedDosToJavaTime(xdostime) : -1; 204 } 205 206 /** 207 * Sets the last modification time of the entry in local date-time. 208 * 209 * <p> If the entry is output to a ZIP file or ZIP file formatted 210 * output stream the last modification time set by this method will 211 * be stored into the {@code date and time fields} of the zip file 212 * entry and encoded in standard {@code MS-DOS date and time format}. 213 * If the date-time set is out of the range of the standard {@code 214 * MS-DOS date and time format}, the time will also be stored into 215 * zip file entry's extended timestamp fields in {@code optional 216 * extra data} in UTC time. The {@link java.time.ZoneId#systemDefault() 217 * system default TimeZone} is used to convert the local date-time 218 * to UTC time. 219 * 220 * <p> {@code LocalDateTime} uses a precision of nanoseconds, whereas 221 * this class uses a precision of milliseconds. The conversion will 222 * truncate any excess precision information as though the amount in 223 * nanoseconds was subject to integer division by one million. 224 * 225 * @param time 226 * The last modification time of the entry in local date-time 227 * 228 * @see #getTimeLocal() 229 * @since 9 230 */ 231 public void setTimeLocal(LocalDateTime time) { 232 int year = time.getYear() - 1980; 233 if (year < 0) { 234 this.xdostime = DOSTIME_BEFORE_1980; 235 } else { 236 this.xdostime = ((year << 25 | 237 time.getMonthValue() << 21 | 238 time.getDayOfMonth() << 16 | 239 time.getHour() << 11 | 240 time.getMinute() << 5 | 241 time.getSecond() >> 1) & 0xffffffffL) 242 + ((long)(((time.getSecond() & 0x1) * 1000) + 243 time.getNano() / 1000_000) << 32); 244 } 245 if (xdostime != DOSTIME_BEFORE_1980 && year <= 0x7f) { 246 this.mtime = null; 247 } else { 248 this.mtime = FileTime.from( 249 ZonedDateTime.of(time, ZoneId.systemDefault()).toInstant()); 250 } 251 } 252 253 /** 254 * Returns the last modification time of the entry in local date-time. 255 * 256 * <p> If the entry is read from a ZIP file or ZIP file formatted 257 * input stream, this is the last modification time from the zip 258 * file entry's {@code optional extra data} if the extended timestamp 259 * fields are present. Otherwise, the last modification time is read 260 * from entry's standard MS-DOS formatted {@code date and time fields}. 261 * 262 * <p> The {@link java.time.ZoneId#systemDefault() system default TimeZone} 263 * is used to convert the UTC time to local date-time. 264 * 265 * @return The last modification time of the entry in local date-time 266 * 267 * @see #setTimeLocal(LocalDateTime) 268 * @since 9 269 */ 270 public LocalDateTime getTimeLocal() { 271 if (mtime != null) { 272 return LocalDateTime.ofInstant(mtime.toInstant(), ZoneId.systemDefault()); 273 } 274 int ms = (int)(xdostime >> 32); 275 return LocalDateTime.of((int)(((xdostime >> 25) & 0x7f) + 1980), 276 (int)((xdostime >> 21) & 0x0f), 277 (int)((xdostime >> 16) & 0x1f), 278 (int)((xdostime >> 11) & 0x1f), 279 (int)((xdostime >> 5) & 0x3f), 280 (int)((xdostime << 1) & 0x3e) + ms / 1000, 281 (ms % 1000) * 1000_000); 282 } 283 284 285 /** 286 * Sets the last modification time of the entry. 287 * 288 * <p> When output to a ZIP file or ZIP file formatted output stream 289 * the last modification time set by this method will be stored into 290 * zip file entry's {@code date and time fields} in {@code standard 291 * MS-DOS date and time format}), and the extended timestamp fields 292 * in {@code optional extra data} in UTC time. 293 * 294 * @param time 295 * The last modification time of the entry 296 * @return This zip entry 297 * 298 * @throws NullPointerException if the {@code time} is null 299 * 300 * @see #getLastModifiedTime() 301 * @since 1.8 302 */ 303 public ZipEntry setLastModifiedTime(FileTime time) { 304 this.mtime = Objects.requireNonNull(time, "lastModifiedTime"); 305 this.xdostime = javaToExtendedDosTime(time.to(TimeUnit.MILLISECONDS)); 306 return this; 307 } 308 309 /** 310 * Returns the last modification time of the entry. 311 * 312 * <p> If the entry is read from a ZIP file or ZIP file formatted 313 * input stream, this is the last modification time from the zip 314 * file entry's {@code optional extra data} if the extended timestamp 315 * fields are present. Otherwise the last modification time is read 316 * from the entry's {@code date and time fields}, the {@link 317 * java.util.TimeZone#getDefault() default TimeZone} is used to convert 318 * the standard MS-DOS formatted date and time to the epoch time. 319 * 320 * @return The last modification time of the entry, null if not specified 321 * 322 * @see #setLastModifiedTime(FileTime) 323 * @since 1.8 324 */ 325 public FileTime getLastModifiedTime() { 326 if (mtime != null) 327 return mtime; 328 if (xdostime == -1) 329 return null; 330 return FileTime.from(getTime(), TimeUnit.MILLISECONDS); 331 } 332 333 /** 334 * Sets the last access time of the entry. 335 * 336 * <p> If set, the last access time will be stored into the extended 337 * timestamp fields of entry's {@code optional extra data}, when output 338 * to a ZIP file or ZIP file formatted stream. 339 * 340 * @param time 341 * The last access time of the entry 342 * @return This zip entry 343 * 344 * @throws NullPointerException if the {@code time} is null 345 * 346 * @see #getLastAccessTime() 347 * @since 1.8 348 */ 349 public ZipEntry setLastAccessTime(FileTime time) { 350 this.atime = Objects.requireNonNull(time, "lastAccessTime"); 351 return this; 352 } 353 354 /** 355 * Returns the last access time of the entry. 356 * 357 * <p> The last access time is from the extended timestamp fields 358 * of entry's {@code optional extra data} when read from a ZIP file 359 * or ZIP file formatted stream. 360 * 361 * @return The last access time of the entry, null if not specified 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 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 long wtime = get64(extra, pos + 4); 577 if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { 578 mtime = winTimeToFileTime(wtime); 579 } 580 wtime = get64(extra, pos + 12); 581 if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { 582 atime = winTimeToFileTime(wtime); 583 } 584 wtime = get64(extra, pos + 20); 585 if (wtime != WINDOWS_TIME_NOT_AVAILABLE) { 586 ctime = winTimeToFileTime(wtime); 587 } 588 break; 589 case EXTID_EXTT: 590 int flag = Byte.toUnsignedInt(extra[off]); 591 int sz0 = 1; 592 // The CEN-header extra field contains the modification 593 // time only, or no timestamp at all. 'sz' is used to 594 // flag its presence or absence. But if mtime is present 595 // in LOC it must be present in CEN as well. 596 if ((flag & 0x1) != 0 && (sz0 + 4) <= sz) { 597 mtime = unixTimeToFileTime(get32S(extra, off + sz0)); 598 sz0 += 4; 599 } 600 if ((flag & 0x2) != 0 && (sz0 + 4) <= sz) { 601 atime = unixTimeToFileTime(get32S(extra, off + sz0)); 602 sz0 += 4; 603 } 604 if ((flag & 0x4) != 0 && (sz0 + 4) <= sz) { 605 ctime = unixTimeToFileTime(get32S(extra, off + sz0)); 606 sz0 += 4; 607 } 608 break; 609 default: 610 } 611 off += sz; 612 } 613 } 614 this.extra = extra; 615 } 616 617 /** 618 * Returns the extra field data for the entry. 619 * 620 * @return the extra field data for the entry, or null if none 621 * 622 * @see #setExtra(byte[]) 623 */ 624 public byte[] getExtra() { 625 return extra; 626 } 627 628 /** 629 * Sets the optional comment string for the entry. 630 * 631 * <p>ZIP entry comments have maximum length of 0xffff. If the length of the 632 * specified comment string is greater than 0xFFFF bytes after encoding, only 633 * the first 0xFFFF bytes are output to the ZIP file entry. 634 * 635 * @param comment the comment string 636 * 637 * @see #getComment() 638 */ 639 public void setComment(String comment) { 640 this.comment = comment; 641 } 642 643 /** 644 * Returns the comment string for the entry. 645 * 646 * @return the comment string for the entry, or null if none 647 * 648 * @see #setComment(String) 649 */ 650 public String getComment() { 651 return comment; 652 } 653 654 /** 655 * Returns true if this is a directory entry. A directory entry is 656 * defined to be one whose name ends with a '/'. 657 * @return true if this is a directory entry 658 */ 659 public boolean isDirectory() { 660 return name.endsWith("/"); 661 } 662 663 /** 664 * Returns the set of Posix file permissions that are associated with 665 * this ZipEntry. This information might not be present all times, 666 * e.g. it is platform dependend. Also, when reading a zip file via 667 * {@link ZipInputStream}, the posix permissions will be null 668 * because data is stored in the CEN of the zip file which is not 669 * evaluated then. If you rely on Posix permissions, you need to access 670 * the zip file via {@link ZipFile}. 671 * 672 * @return The set of Posix File permissions, in case they are associated. 673 * 674 * @since 12 675 */ 676 public Optional<Set<PosixFilePermission>> getPosixPermissions() { 677 if (posixPerms == -1) { 678 return Optional.empty(); 679 } 680 return Optional.of(PosixFilePermissions.fromFlags(posixPerms)); 681 } 682 683 /** 684 * Sets the set of Posix file permissions for this ZipEntry. 685 * 686 * @param permissions A set of PosixFilePermissions. If the value is null, 687 * no permission information will be stored in the zip 688 * file. 689 * 690 * @since 12 691 */ 692 public void setPosixPermissions(Set<PosixFilePermission> permissions) { 693 if (permissions == null) { 694 posixPerms = -1; 695 return; 696 } 697 posixPerms = PosixFilePermissions.toFlags(permissions); 698 } 699 700 /** 701 * Returns a string representation of the ZIP entry. 702 */ 703 public String toString() { 704 return getName(); 705 } 706 707 /** 708 * Returns the hash code value for this entry. 709 */ 710 public int hashCode() { 711 return name.hashCode(); 712 } 713 714 /** 715 * Returns a copy of this entry. 716 */ 717 public Object clone() { 718 try { 719 ZipEntry e = (ZipEntry)super.clone(); 720 e.extra = (extra == null) ? null : extra.clone(); 721 return e; 722 } catch (CloneNotSupportedException e) { 723 // This should never happen, since we are Cloneable 724 throw new InternalError(e); 725 } 726 } 727 }