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