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