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