    001/*
    002 *  Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 *
    017 */
    018package org.apache.commons.compress.archivers.zip;
    019
    020import java.nio.file.attribute.FileTime;
    021import java.util.Date;
    022import java.util.Objects;
    023import java.util.zip.ZipException;
    024
    025import org.apache.commons.compress.utils.TimeUtils;
    026
    027/**
    028 * NTFS extra field that was thought to store various attributes but
    029 * in reality only stores timestamps.
    030 *
    031 * <pre>
    032 *    4.5.5 -NTFS Extra Field (0x000a):
    033 *
    034 *       The following is the layout of the NTFS attributes
    035 *       "extra" block. (Note: At this time the Mtime, Atime
    036 *       and Ctime values MAY be used on any WIN32 system.)
    037 *
    038 *       Note: all fields stored in Intel low-byte/high-byte order.
    039 *
    040 *         Value      Size       Description
    041 *         -----      ----       -----------
    042 * (NTFS)  0x000a     2 bytes    Tag for this "extra" block type
    043 *         TSize      2 bytes    Size of the total "extra" block
    044 *         Reserved   4 bytes    Reserved for future use
    045 *         Tag1       2 bytes    NTFS attribute tag value #1
    046 *         Size1      2 bytes    Size of attribute #1, in bytes
    047 *         (var)      Size1      Attribute #1 data
    048 *          .
    049 *          .
    050 *          .
    051 *          TagN       2 bytes    NTFS attribute tag value #N
    052 *          SizeN      2 bytes    Size of attribute #N, in bytes
    053 *          (var)      SizeN      Attribute #N data
    054 *
    055 *        For NTFS, values for Tag1 through TagN are as follows:
    056 *        (currently only one set of attributes is defined for NTFS)
    057 *
    058 *          Tag        Size       Description
    059 *          -----      ----       -----------
    060 *          0x0001     2 bytes    Tag for attribute #1
    061 *          Size1      2 bytes    Size of attribute #1, in bytes
    062 *          Mtime      8 bytes    File last modification time
    063 *          Atime      8 bytes    File last access time
    064 *          Ctime      8 bytes    File creation time
    065 * </pre>
    066 *
    067 * @since 1.11
    068 * @NotThreadSafe
    069 */
    070public class X000A_NTFS implements ZipExtraField {
    071
    072    /**
    073     * The header ID for this extra field.
    074     *
    075     * @since 1.23
    076     */
    077    public static final ZipShort HEADER_ID = new ZipShort(0x000a);
    078
    079    private static final ZipShort TIME_ATTR_TAG = new ZipShort(0x0001);
    080    private static final ZipShort TIME_ATTR_SIZE = new ZipShort(3 * 8);
    081
    082    private static ZipEightByteInteger dateToZip(final Date d) {
    083        if (d == null) {
    084            return null;
    085        }
    086        return new ZipEightByteInteger(TimeUtils.toNtfsTime(d));
    087    }
    088    private static ZipEightByteInteger fileTimeToZip(final FileTime time) {
    089        if (time == null) {
    090            return null;
    091        }
    092        return new ZipEightByteInteger(TimeUtils.toNtfsTime(time));
    093    }
    094    private static Date zipToDate(final ZipEightByteInteger z) {
    095        if (z == null || ZipEightByteInteger.ZERO.equals(z)) {
    096            return null;
    097        }
    098        return TimeUtils.ntfsTimeToDate(z.getLongValue());
    099    }
    100
    101    private static FileTime zipToFileTime(final ZipEightByteInteger z) {
    102        if (z == null || ZipEightByteInteger.ZERO.equals(z)) {
    103            return null;
    104        }
    105        return TimeUtils.ntfsTimeToFileTime(z.getLongValue());
    106    }
    107
    108    private ZipEightByteInteger modifyTime = ZipEightByteInteger.ZERO;
    109
    110    private ZipEightByteInteger accessTime = ZipEightByteInteger.ZERO;
    111
    112    private ZipEightByteInteger createTime = ZipEightByteInteger.ZERO;
    113
    114    @Override
    115    public boolean equals(final Object o) {
    116        if (o instanceof X000A_NTFS) {
    117            final X000A_NTFS xf = (X000A_NTFS) o;
    118
    119            return Objects.equals(modifyTime, xf.modifyTime) &&
    120                   Objects.equals(accessTime, xf.accessTime) &&
    121                   Objects.equals(createTime, xf.createTime);
    122        }
    123        return false;
    124    }
    125
    126    /**
    127     * Gets the access time as a {@link FileTime}
    128     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    129     *
    130     * @return access time as a {@link FileTime} or null.
    131     * @since 1.23
    132     */
    133    public FileTime getAccessFileTime() {
    134        return zipToFileTime(accessTime);
    135    }
    136
    137    /**
    138     * Gets the access time as a java.util.Date
    139     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    140     *
    141     * @return access time as java.util.Date or null.
    142     */
    143    public Date getAccessJavaTime() {
    144        return zipToDate(accessTime);
    145    }
    146
    147    /**
    148     * Gets the "File last access time" of this ZIP entry as a
    149     * ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO}
    150     * if no such timestamp exists in the ZIP entry.
    151     *
    152     * @return File last access time
    153     */
    154    public ZipEightByteInteger getAccessTime() { return accessTime; }
    155
    156    /**
    157     * Gets the actual data to put into central directory data - without Header-ID
    158     * or length specifier.
    159     *
    160     * @return the central directory data
    161     */
    162    @Override
    163    public byte[] getCentralDirectoryData() {
    164        return getLocalFileDataData();
    165    }
    166
    167    /**
    168     * Gets the length of the extra field in the local file data - without
    169     * Header-ID or length specifier.
    170     *
    171     * <p>For X5455 the central length is often smaller than the
    172     * local length, because central cannot contain access or create
    173     * timestamps.</p>
    174     *
    175     * @return a {@code ZipShort} for the length of the data of this extra field
    176     */
    177    @Override
    178    public ZipShort getCentralDirectoryLength() {
    179        return getLocalFileDataLength();
    180    }
    181
    182    /**
    183     * Gets the create time as a {@link FileTime}
    184     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    185     *
    186     * @return create time as a {@link FileTime} or null.
    187     * @since 1.23
    188     */
    189    public FileTime getCreateFileTime() {
    190        return zipToFileTime(createTime);
    191    }
    192
    193    /**
    194     * Gets the create time as a java.util.Date of this ZIP
    195     * entry, or null if no such timestamp exists in the ZIP entry.
    196     *
    197     * @return create time as java.util.Date or null.
    198     */
    199    public Date getCreateJavaTime() {
    200        return zipToDate(createTime);
    201    }
    202
    203    /**
    204     * Gets the "File creation time" of this ZIP entry as a
    205     * ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO}
    206     * if no such timestamp exists in the ZIP entry.
    207     *
    208     * @return File creation time
    209     */
    210    public ZipEightByteInteger getCreateTime() { return createTime; }
    211
    212    /**
    213     * Gets the Header-ID.
    214     *
    215     * @return the value for the header id for this extrafield
    216     */
    217    @Override
    218    public ZipShort getHeaderId() {
    219        return HEADER_ID;
    220    }
    221
    222    /**
    223     * Gets the actual data to put into local file data - without Header-ID
    224     * or length specifier.
    225     *
    226     * @return get the data
    227     */
    228    @Override
    229    public byte[] getLocalFileDataData() {
    230        final byte[] data = new byte[getLocalFileDataLength().getValue()];
    231        int pos = 4;
    232        System.arraycopy(TIME_ATTR_TAG.getBytes(), 0, data, pos, 2);
    233        pos += 2;
    234        System.arraycopy(TIME_ATTR_SIZE.getBytes(), 0, data, pos, 2);
    235        pos += 2;
    236        System.arraycopy(modifyTime.getBytes(), 0, data, pos, 8);
    237        pos += 8;
    238        System.arraycopy(accessTime.getBytes(), 0, data, pos, 8);
    239        pos += 8;
    240        System.arraycopy(createTime.getBytes(), 0, data, pos, 8);
    241        return data;
    242    }
    243
    244    /**
    245     * Gets the length of the extra field in the local file data - without
    246     * Header-ID or length specifier.
    247     *
    248     * @return a {@code ZipShort} for the length of the data of this extra field
    249     */
    250    @Override
    251    public ZipShort getLocalFileDataLength() {
    252        return new ZipShort(4 /* reserved */
    253                            + 2 /* Tag#1 */
    254                            + 2 /* Size#1 */
    255                            + 3 * 8 /* time values */);
    256    }
    257
    258    /**
    259     * Gets the modify time as a {@link FileTime}
    260     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    261     *
    262     * @return modify time as a {@link FileTime} or null.
    263     * @since 1.23
    264     */
    265    public FileTime getModifyFileTime() {
    266        return zipToFileTime(modifyTime);
    267    }
    268
    269    /**
    270     * Gets the modify time as a java.util.Date
    271     * of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
    272     *
    273     * @return modify time as java.util.Date or null.
    274     */
    275    public Date getModifyJavaTime() {
    276        return zipToDate(modifyTime);
    277    }
    278
    279    /**
    280     * Gets the "File last modification time" of this ZIP entry as
    281     * a ZipEightByteInteger object, or {@link
    282     * ZipEightByteInteger#ZERO} if no such timestamp exists in the
    283     * ZIP entry.
    284     *
    285     * @return File last modification time
    286     */
    287    public ZipEightByteInteger getModifyTime() { return modifyTime; }
    288
    289    @Override
    290    public int hashCode() {
    291        int hc = -123;
    292        if (modifyTime != null) {
    293            hc ^= modifyTime.hashCode();
    294        }
    295        if (accessTime != null) {
    296            // Since accessTime is often same as modifyTime,
    297            // this prevents them from XOR negating each other.
    298            hc ^= Integer.rotateLeft(accessTime.hashCode(), 11);
    299        }
    300        if (createTime != null) {
    301            hc ^= Integer.rotateLeft(createTime.hashCode(), 22);
    302        }
    303        return hc;
    304    }
    305
    306    /**
    307     * Doesn't do anything special since this class always uses the
    308     * same parsing logic for both central directory and local file data.
    309     */
    310    @Override
    311    public void parseFromCentralDirectoryData(
    312            final byte[] buffer, final int offset, final int length
    313    ) throws ZipException {
    314        reset();
    315        parseFromLocalFileData(buffer, offset, length);
    316    }
    317
    318    /**
    319     * Populate data from this array as if it was in local file data.
    320     *
    321     * @param data   an array of bytes
    322     * @param offset the start offset
    323     * @param length the number of bytes in the array from offset
    324     * @throws java.util.zip.ZipException on error
    325     */
    326    @Override
    327    public void parseFromLocalFileData(
    328            final byte[] data, int offset, final int length
    329    ) throws ZipException {
    330        final int len = offset + length;
    331
    332        // skip reserved
    333        offset += 4;
    334
    335        while (offset + 4 <= len) {
    336            final ZipShort tag = new ZipShort(data, offset);
    337            offset += 2;
    338            if (tag.equals(TIME_ATTR_TAG)) {
    339                readTimeAttr(data, offset, len - offset);
    340                break;
    341            }
    342            final ZipShort size = new ZipShort(data, offset);
    343            offset += 2 + size.getValue();
    344        }
    345    }
    346
    347    private void readTimeAttr(final byte[] data, int offset, final int length) {
    348        if (length >= 2 + 3 * 8) {
    349            final ZipShort tagValueLength = new ZipShort(data, offset);
    350            if (TIME_ATTR_SIZE.equals(tagValueLength)) {
    351                offset += 2;
    352                modifyTime = new ZipEightByteInteger(data, offset);
    353                offset += 8;
    354                accessTime = new ZipEightByteInteger(data, offset);
    355                offset += 8;
    356                createTime = new ZipEightByteInteger(data, offset);
    357            }
    358        }
    359    }
    360
    361    /**
    362     * Reset state back to newly constructed state.  Helps us make sure
    363     * parse() calls always generate clean results.
    364     */
    365    private void reset() {
    366        this.modifyTime = ZipEightByteInteger.ZERO;
    367        this.accessTime = ZipEightByteInteger.ZERO;
    368        this.createTime = ZipEightByteInteger.ZERO;
    369    }
    370
    371    /**
    372     * Sets the access time.
    373     *
    374     * @param time access time as a {@link FileTime}
    375     * @since 1.23
    376     */
    377    public void setAccessFileTime(final FileTime time) {
    378        setAccessTime(fileTimeToZip(time));
    379    }
    380
    381    /**
    382     * Sets the access time as a java.util.Date
    383     * of this ZIP entry.
    384     *
    385     * @param d access time as java.util.Date
    386     */
    387    public void setAccessJavaTime(final Date d) { setAccessTime(dateToZip(d)); }
    388
    389    /**
    390     * Sets the File last access time of this ZIP entry using a
    391     * ZipEightByteInteger object.
    392     *
    393     * @param t ZipEightByteInteger of the access time
    394     */
    395    public void setAccessTime(final ZipEightByteInteger t) {
    396        accessTime = t == null ? ZipEightByteInteger.ZERO : t;
    397    }
    398
    399    /**
    400     * Sets the create time.
    401     *
    402     * @param time create time as a {@link FileTime}
    403     * @since 1.23
    404     */
    405    public void setCreateFileTime(final FileTime time) {
    406        setCreateTime(fileTimeToZip(time));
    407    }
    408
    409    /**
    410     * <p>
    411     * Sets the create time as a java.util.Date
    412     * of this ZIP entry.  Supplied value is truncated to per-second
    413     * precision (milliseconds zeroed-out).
    414     * </p><p>
    415     * Note: the setters for flags and timestamps are decoupled.
    416     * Even if the timestamp is not-null, it will only be written
    417     * out if the corresponding bit in the flags is also set.
    418     * </p>
    419     *
    420     * @param d create time as java.util.Date
    421     */
    422    public void setCreateJavaTime(final Date d) { setCreateTime(dateToZip(d)); }
    423
    424    /**
    425     * Sets the File creation time of this ZIP entry using a
    426     * ZipEightByteInteger object.
    427     *
    428     * @param t ZipEightByteInteger of the create time
    429     */
    430    public void setCreateTime(final ZipEightByteInteger t) {
    431        createTime = t == null ? ZipEightByteInteger.ZERO : t;
    432    }
    433
    434    /**
    435     * Sets the modify time.
    436     *
    437     * @param time modify time as a {@link FileTime}
    438     * @since 1.23
    439     */
    440    public void setModifyFileTime(final FileTime time) {
    441        setModifyTime(fileTimeToZip(time));
    442    }
    443
    444    /**
    445     * Sets the modify time as a java.util.Date of this ZIP entry.
    446     *
    447     * @param d modify time as java.util.Date
    448     */
    449    public void setModifyJavaTime(final Date d) { setModifyTime(dateToZip(d)); }
    450
    451    /**
    452     * Sets the File last modification time of this ZIP entry using a
    453     * ZipEightByteInteger object.
    454     *
    455     * @param t ZipEightByteInteger of the modify time
    456     */
    457    public void setModifyTime(final ZipEightByteInteger t) {
    458        modifyTime = t == null ? ZipEightByteInteger.ZERO : t;
    459    }
    460
    461    /**
    462     * Returns a String representation of this class useful for
    463     * debugging purposes.
    464     *
    465     * @return A String representation of this class useful for
    466     *         debugging purposes.
    467     */
    468    @Override
    469    public String toString() {
    470        final StringBuilder buf = new StringBuilder();
    471        buf.append("0x000A Zip Extra Field:")
    472            .append(" Modify:[").append(getModifyFileTime()).append("] ")
    473            .append(" Access:[").append(getAccessFileTime()).append("] ")
    474            .append(" Create:[").append(getCreateFileTime()).append("] ");
    475        return buf.toString();
    476    }
    477}