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