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