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