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