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