X000A_NTFS.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.archivers.zip;

  18. import java.nio.file.attribute.FileTime;
  19. import java.util.Date;
  20. import java.util.Objects;
  21. import java.util.zip.ZipException;

  22. import org.apache.commons.io.file.attribute.FileTimes;

  23. /**
  24.  * NTFS extra field that was thought to store various attributes but in reality only stores timestamps.
  25.  *
  26.  * <pre>
  27.  *    4.5.5 -NTFS Extra Field (0x000a):
  28.  *
  29.  *       The following is the layout of the NTFS attributes
  30.  *       "extra" block. (Note: At this time the Mtime, Atime
  31.  *       and Ctime values MAY be used on any WIN32 system.)
  32.  *
  33.  *       Note: all fields stored in Intel low-byte/high-byte order.
  34.  *
  35.  *         Value      Size       Description
  36.  *         -----      ----       -----------
  37.  * (NTFS)  0x000a     2 bytes    Tag for this "extra" block type
  38.  *         TSize      2 bytes    Size of the total "extra" block
  39.  *         Reserved   4 bytes    Reserved for future use
  40.  *         Tag1       2 bytes    NTFS attribute tag value #1
  41.  *         Size1      2 bytes    Size of attribute #1, in bytes
  42.  *         (var)      Size1      Attribute #1 data
  43.  *          .
  44.  *          .
  45.  *          .
  46.  *          TagN       2 bytes    NTFS attribute tag value #N
  47.  *          SizeN      2 bytes    Size of attribute #N, in bytes
  48.  *          (var)      SizeN      Attribute #N data
  49.  *
  50.  *        For NTFS, values for Tag1 through TagN are as follows:
  51.  *        (currently only one set of attributes is defined for NTFS)
  52.  *
  53.  *          Tag        Size       Description
  54.  *          -----      ----       -----------
  55.  *          0x0001     2 bytes    Tag for attribute #1
  56.  *          Size1      2 bytes    Size of attribute #1, in bytes
  57.  *          Mtime      8 bytes    File last modification time
  58.  *          Atime      8 bytes    File last access time
  59.  *          Ctime      8 bytes    File creation time
  60.  * </pre>
  61.  *
  62.  * @since 1.11
  63.  * @NotThreadSafe
  64.  */
  65. public class X000A_NTFS implements ZipExtraField {

  66.     /**
  67.      * The header ID for this extra field.
  68.      *
  69.      * @since 1.23
  70.      */
  71.     public static final ZipShort HEADER_ID = new ZipShort(0x000a);

  72.     private static final ZipShort TIME_ATTR_TAG = new ZipShort(0x0001);
  73.     private static final ZipShort TIME_ATTR_SIZE = new ZipShort(3 * 8);

  74.     private static ZipEightByteInteger dateToZip(final Date d) {
  75.         if (d == null) {
  76.             return null;
  77.         }
  78.         return new ZipEightByteInteger(FileTimes.toNtfsTime(d));
  79.     }

  80.     private static ZipEightByteInteger fileTimeToZip(final FileTime time) {
  81.         if (time == null) {
  82.             return null;
  83.         }
  84.         return new ZipEightByteInteger(FileTimes.toNtfsTime(time));
  85.     }

  86.     private static Date zipToDate(final ZipEightByteInteger z) {
  87.         if (z == null || ZipEightByteInteger.ZERO.equals(z)) {
  88.             return null;
  89.         }
  90.         return FileTimes.ntfsTimeToDate(z.getLongValue());
  91.     }

  92.     private static FileTime zipToFileTime(final ZipEightByteInteger z) {
  93.         if (z == null || ZipEightByteInteger.ZERO.equals(z)) {
  94.             return null;
  95.         }
  96.         return FileTimes.ntfsTimeToFileTime(z.getLongValue());
  97.     }

  98.     private ZipEightByteInteger modifyTime = ZipEightByteInteger.ZERO;

  99.     private ZipEightByteInteger accessTime = ZipEightByteInteger.ZERO;

  100.     private ZipEightByteInteger createTime = ZipEightByteInteger.ZERO;

  101.     @Override
  102.     public boolean equals(final Object o) {
  103.         if (o instanceof X000A_NTFS) {
  104.             final X000A_NTFS xf = (X000A_NTFS) o;

  105.             return Objects.equals(modifyTime, xf.modifyTime) && Objects.equals(accessTime, xf.accessTime) && Objects.equals(createTime, xf.createTime);
  106.         }
  107.         return false;
  108.     }

  109.     /**
  110.      * Gets the access time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
  111.      *
  112.      * @return access time as a {@link FileTime} or null.
  113.      * @since 1.23
  114.      */
  115.     public FileTime getAccessFileTime() {
  116.         return zipToFileTime(accessTime);
  117.     }

  118.     /**
  119.      * Gets the access time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
  120.      *
  121.      * @return access time as java.util.Date or null.
  122.      */
  123.     public Date getAccessJavaTime() {
  124.         return zipToDate(accessTime);
  125.     }

  126.     /**
  127.      * Gets the "File last access time" of this ZIP entry as a ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} if no such timestamp exists in
  128.      * the ZIP entry.
  129.      *
  130.      * @return File last access time
  131.      */
  132.     public ZipEightByteInteger getAccessTime() {
  133.         return accessTime;
  134.     }

  135.     /**
  136.      * Gets the actual data to put into central directory data - without Header-ID or length specifier.
  137.      *
  138.      * @return the central directory data
  139.      */
  140.     @Override
  141.     public byte[] getCentralDirectoryData() {
  142.         return getLocalFileDataData();
  143.     }

  144.     /**
  145.      * Gets the length of the extra field in the local file data - without Header-ID or length specifier.
  146.      *
  147.      * <p>
  148.      * For X5455 the central length is often smaller than the local length, because central cannot contain access or create timestamps.
  149.      * </p>
  150.      *
  151.      * @return a {@code ZipShort} for the length of the data of this extra field
  152.      */
  153.     @Override
  154.     public ZipShort getCentralDirectoryLength() {
  155.         return getLocalFileDataLength();
  156.     }

  157.     /**
  158.      * Gets the create time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
  159.      *
  160.      * @return create time as a {@link FileTime} or null.
  161.      * @since 1.23
  162.      */
  163.     public FileTime getCreateFileTime() {
  164.         return zipToFileTime(createTime);
  165.     }

  166.     /**
  167.      * Gets the create time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
  168.      *
  169.      * @return create time as java.util.Date or null.
  170.      */
  171.     public Date getCreateJavaTime() {
  172.         return zipToDate(createTime);
  173.     }

  174.     /**
  175.      * Gets the "File creation time" of this ZIP entry as a ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} if no such timestamp exists in the
  176.      * ZIP entry.
  177.      *
  178.      * @return File creation time
  179.      */
  180.     public ZipEightByteInteger getCreateTime() {
  181.         return createTime;
  182.     }

  183.     /**
  184.      * Gets the Header-ID.
  185.      *
  186.      * @return the value for the header id for this extrafield
  187.      */
  188.     @Override
  189.     public ZipShort getHeaderId() {
  190.         return HEADER_ID;
  191.     }

  192.     /**
  193.      * Gets the actual data to put into local file data - without Header-ID or length specifier.
  194.      *
  195.      * @return get the data
  196.      */
  197.     @Override
  198.     public byte[] getLocalFileDataData() {
  199.         final byte[] data = new byte[getLocalFileDataLength().getValue()];
  200.         int pos = 4;
  201.         System.arraycopy(TIME_ATTR_TAG.getBytes(), 0, data, pos, 2);
  202.         pos += 2;
  203.         System.arraycopy(TIME_ATTR_SIZE.getBytes(), 0, data, pos, 2);
  204.         pos += 2;
  205.         System.arraycopy(modifyTime.getBytes(), 0, data, pos, 8);
  206.         pos += 8;
  207.         System.arraycopy(accessTime.getBytes(), 0, data, pos, 8);
  208.         pos += 8;
  209.         System.arraycopy(createTime.getBytes(), 0, data, pos, 8);
  210.         return data;
  211.     }

  212.     /**
  213.      * Gets the length of the extra field in the local file data - without Header-ID or length specifier.
  214.      *
  215.      * @return a {@code ZipShort} for the length of the data of this extra field
  216.      */
  217.     @Override
  218.     public ZipShort getLocalFileDataLength() {
  219.         return new ZipShort(4 /* reserved */
  220.                 + 2 /* Tag#1 */
  221.                 + 2 /* Size#1 */
  222.                 + 3 * 8 /* time values */);
  223.     }

  224.     /**
  225.      * Gets the modify time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
  226.      *
  227.      * @return modify time as a {@link FileTime} or null.
  228.      * @since 1.23
  229.      */
  230.     public FileTime getModifyFileTime() {
  231.         return zipToFileTime(modifyTime);
  232.     }

  233.     /**
  234.      * Gets the modify time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
  235.      *
  236.      * @return modify time as java.util.Date or null.
  237.      */
  238.     public Date getModifyJavaTime() {
  239.         return zipToDate(modifyTime);
  240.     }

  241.     /**
  242.      * Gets the "File last modification time" of this ZIP entry as a ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} if no such timestamp exists
  243.      * in the ZIP entry.
  244.      *
  245.      * @return File last modification time
  246.      */
  247.     public ZipEightByteInteger getModifyTime() {
  248.         return modifyTime;
  249.     }

  250.     @Override
  251.     public int hashCode() {
  252.         int hc = -123;
  253.         if (modifyTime != null) {
  254.             hc ^= modifyTime.hashCode();
  255.         }
  256.         if (accessTime != null) {
  257.             // Since accessTime is often same as modifyTime,
  258.             // this prevents them from XOR negating each other.
  259.             hc ^= Integer.rotateLeft(accessTime.hashCode(), 11);
  260.         }
  261.         if (createTime != null) {
  262.             hc ^= Integer.rotateLeft(createTime.hashCode(), 22);
  263.         }
  264.         return hc;
  265.     }

  266.     /**
  267.      * Doesn't do anything special since this class always uses the same parsing logic for both central directory and local file data.
  268.      */
  269.     @Override
  270.     public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
  271.         reset();
  272.         parseFromLocalFileData(buffer, offset, length);
  273.     }

  274.     /**
  275.      * Populate data from this array as if it was in local file data.
  276.      *
  277.      * @param data   an array of bytes
  278.      * @param offset the start offset
  279.      * @param length the number of bytes in the array from offset
  280.      * @throws java.util.zip.ZipException on error
  281.      */
  282.     @Override
  283.     public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
  284.         final int len = offset + length;

  285.         // skip reserved
  286.         offset += 4;

  287.         while (offset + 4 <= len) {
  288.             final ZipShort tag = new ZipShort(data, offset);
  289.             offset += 2;
  290.             if (tag.equals(TIME_ATTR_TAG)) {
  291.                 readTimeAttr(data, offset, len - offset);
  292.                 break;
  293.             }
  294.             final ZipShort size = new ZipShort(data, offset);
  295.             offset += 2 + size.getValue();
  296.         }
  297.     }

  298.     private void readTimeAttr(final byte[] data, int offset, final int length) {
  299.         if (length >= 2 + 3 * 8) {
  300.             final ZipShort tagValueLength = new ZipShort(data, offset);
  301.             if (TIME_ATTR_SIZE.equals(tagValueLength)) {
  302.                 offset += 2;
  303.                 modifyTime = new ZipEightByteInteger(data, offset);
  304.                 offset += 8;
  305.                 accessTime = new ZipEightByteInteger(data, offset);
  306.                 offset += 8;
  307.                 createTime = new ZipEightByteInteger(data, offset);
  308.             }
  309.         }
  310.     }

  311.     /**
  312.      * Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
  313.      */
  314.     private void reset() {
  315.         this.modifyTime = ZipEightByteInteger.ZERO;
  316.         this.accessTime = ZipEightByteInteger.ZERO;
  317.         this.createTime = ZipEightByteInteger.ZERO;
  318.     }

  319.     /**
  320.      * Sets the access time.
  321.      *
  322.      * @param time access time as a {@link FileTime}
  323.      * @since 1.23
  324.      */
  325.     public void setAccessFileTime(final FileTime time) {
  326.         setAccessTime(fileTimeToZip(time));
  327.     }

  328.     /**
  329.      * Sets the access time as a java.util.Date of this ZIP entry.
  330.      *
  331.      * @param d access time as java.util.Date
  332.      */
  333.     public void setAccessJavaTime(final Date d) {
  334.         setAccessTime(dateToZip(d));
  335.     }

  336.     /**
  337.      * Sets the File last access time of this ZIP entry using a ZipEightByteInteger object.
  338.      *
  339.      * @param t ZipEightByteInteger of the access time
  340.      */
  341.     public void setAccessTime(final ZipEightByteInteger t) {
  342.         accessTime = t == null ? ZipEightByteInteger.ZERO : t;
  343.     }

  344.     /**
  345.      * Sets the create time.
  346.      *
  347.      * @param time create time as a {@link FileTime}
  348.      * @since 1.23
  349.      */
  350.     public void setCreateFileTime(final FileTime time) {
  351.         setCreateTime(fileTimeToZip(time));
  352.     }

  353.     /**
  354.      * <p>
  355.      * Sets the create time as a java.util.Date of this ZIP entry. Supplied value is truncated to per-second precision (milliseconds zeroed-out).
  356.      * </p>
  357.      * <p>
  358.      * Note: the setters for flags and timestamps are decoupled. Even if the timestamp is not-null, it will only be written out if the corresponding bit in the
  359.      * flags is also set.
  360.      * </p>
  361.      *
  362.      * @param d create time as java.util.Date
  363.      */
  364.     public void setCreateJavaTime(final Date d) {
  365.         setCreateTime(dateToZip(d));
  366.     }

  367.     /**
  368.      * Sets the File creation time of this ZIP entry using a ZipEightByteInteger object.
  369.      *
  370.      * @param t ZipEightByteInteger of the create time
  371.      */
  372.     public void setCreateTime(final ZipEightByteInteger t) {
  373.         createTime = t == null ? ZipEightByteInteger.ZERO : t;
  374.     }

  375.     /**
  376.      * Sets the modify time.
  377.      *
  378.      * @param time modify time as a {@link FileTime}
  379.      * @since 1.23
  380.      */
  381.     public void setModifyFileTime(final FileTime time) {
  382.         setModifyTime(fileTimeToZip(time));
  383.     }

  384.     /**
  385.      * Sets the modify time as a java.util.Date of this ZIP entry.
  386.      *
  387.      * @param d modify time as java.util.Date
  388.      */
  389.     public void setModifyJavaTime(final Date d) {
  390.         setModifyTime(dateToZip(d));
  391.     }

  392.     /**
  393.      * Sets the File last modification time of this ZIP entry using a ZipEightByteInteger object.
  394.      *
  395.      * @param t ZipEightByteInteger of the modify time
  396.      */
  397.     public void setModifyTime(final ZipEightByteInteger t) {
  398.         modifyTime = t == null ? ZipEightByteInteger.ZERO : t;
  399.     }

  400.     /**
  401.      * Returns a String representation of this class useful for debugging purposes.
  402.      *
  403.      * @return A String representation of this class useful for debugging purposes.
  404.      */
  405.     @Override
  406.     public String toString() {
  407.         // @formatter:off
  408.         return new StringBuilder()
  409.             .append("0x000A Zip Extra Field:")
  410.             .append(" Modify:[")
  411.             .append(getModifyFileTime())
  412.             .append("] ")
  413.             .append(" Access:[")
  414.             .append(getAccessFileTime())
  415.             .append("] ")
  416.             .append(" Create:[")
  417.             .append(getCreateFileTime())
  418.             .append("] ")
  419.             .toString();
  420.         // @formatter:on
  421.     }
  422. }