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