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