View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.archivers.zip;
20  
21  import java.nio.file.attribute.FileTime;
22  import java.util.Date;
23  import java.util.Objects;
24  import java.util.zip.ZipException;
25  
26  import org.apache.commons.io.file.attribute.FileTimes;
27  
28  /**
29   * NTFS extra field that was thought to store various attributes but in reality only stores timestamps.
30   *
31   * <pre>
32   *    4.5.5 -NTFS Extra Field (0x000a):
33   *
34   *       The following is the layout of the NTFS attributes
35   *       "extra" block. (Note: At this time the Mtime, Atime
36   *       and Ctime values MAY be used on any WIN32 system.)
37   *
38   *       Note: all fields stored in Intel low-byte/high-byte order.
39   *
40   *         Value      Size       Description
41   *         -----      ----       -----------
42   * (NTFS)  0x000a     2 bytes    Tag for this "extra" block type
43   *         TSize      2 bytes    Size of the total "extra" block
44   *         Reserved   4 bytes    Reserved for future use
45   *         Tag1       2 bytes    NTFS attribute tag value #1
46   *         Size1      2 bytes    Size of attribute #1, in bytes
47   *         (var)      Size1      Attribute #1 data
48   *          .
49   *          .
50   *          .
51   *          TagN       2 bytes    NTFS attribute tag value #N
52   *          SizeN      2 bytes    Size of attribute #N, in bytes
53   *          (var)      SizeN      Attribute #N data
54   *
55   *        For NTFS, values for Tag1 through TagN are as follows:
56   *        (currently only one set of attributes is defined for NTFS)
57   *
58   *          Tag        Size       Description
59   *          -----      ----       -----------
60   *          0x0001     2 bytes    Tag for attribute #1
61   *          Size1      2 bytes    Size of attribute #1, in bytes
62   *          Mtime      8 bytes    File last modification time
63   *          Atime      8 bytes    File last access time
64   *          Ctime      8 bytes    File creation time
65   * </pre>
66   *
67   * @since 1.11
68   * @NotThreadSafe
69   */
70  public class X000A_NTFS implements ZipExtraField {
71  
72      /**
73       * The header ID for this extra field.
74       *
75       * @since 1.23
76       */
77      public static final ZipShort HEADER_ID = new ZipShort(0x000a);
78  
79      private static final ZipShort TIME_ATTR_TAG = new ZipShort(0x0001);
80      private static final ZipShort TIME_ATTR_SIZE = new ZipShort(3 * 8);
81  
82      private static ZipEightByteInteger dateToZip(final Date d) {
83          if (d == null) {
84              return null;
85          }
86          return new ZipEightByteInteger(FileTimes.toNtfsTime(d));
87      }
88  
89      private static ZipEightByteInteger fileTimeToZip(final FileTime time) {
90          if (time == null) {
91              return null;
92          }
93          return new ZipEightByteInteger(FileTimes.toNtfsTime(time));
94      }
95  
96      private static Date zipToDate(final ZipEightByteInteger z) {
97          if (z == null || ZipEightByteInteger.ZERO.equals(z)) {
98              return null;
99          }
100         return FileTimes.ntfsTimeToDate(z.getLongValue());
101     }
102 
103     private static FileTime zipToFileTime(final ZipEightByteInteger z) {
104         if (z == null || ZipEightByteInteger.ZERO.equals(z)) {
105             return null;
106         }
107         return FileTimes.ntfsTimeToFileTime(z.getLongValue());
108     }
109 
110     private ZipEightByteInteger modifyTime = ZipEightByteInteger.ZERO;
111 
112     private ZipEightByteInteger accessTime = ZipEightByteInteger.ZERO;
113 
114     private ZipEightByteInteger createTime = ZipEightByteInteger.ZERO;
115 
116     @Override
117     public boolean equals(final Object o) {
118         if (o instanceof X000A_NTFS) {
119             final X000A_NTFS xf = (X000A_NTFS) o;
120 
121             return Objects.equals(modifyTime, xf.modifyTime) && Objects.equals(accessTime, xf.accessTime) && Objects.equals(createTime, xf.createTime);
122         }
123         return false;
124     }
125 
126     /**
127      * Gets the access time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
128      *
129      * @return access time as a {@link FileTime} or null.
130      * @since 1.23
131      */
132     public FileTime getAccessFileTime() {
133         return zipToFileTime(accessTime);
134     }
135 
136     /**
137      * Gets the access time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
138      *
139      * @return access time as java.util.Date or null.
140      */
141     public Date getAccessJavaTime() {
142         return zipToDate(accessTime);
143     }
144 
145     /**
146      * Gets the "File last access time" of this ZIP entry as a ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} if no such timestamp exists in
147      * the ZIP entry.
148      *
149      * @return File last access time
150      */
151     public ZipEightByteInteger getAccessTime() {
152         return accessTime;
153     }
154 
155     /**
156      * Gets the actual data to put into central directory data - without Header-ID or length specifier.
157      *
158      * @return the central directory data
159      */
160     @Override
161     public byte[] getCentralDirectoryData() {
162         return getLocalFileDataData();
163     }
164 
165     /**
166      * Gets the length of the extra field in the local file data - without Header-ID or length specifier.
167      *
168      * <p>
169      * For X5455 the central length is often smaller than the local length, because central cannot contain access or create timestamps.
170      * </p>
171      *
172      * @return a {@code ZipShort} for the length of the data of this extra field
173      */
174     @Override
175     public ZipShort getCentralDirectoryLength() {
176         return getLocalFileDataLength();
177     }
178 
179     /**
180      * Gets the create time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
181      *
182      * @return create time as a {@link FileTime} or null.
183      * @since 1.23
184      */
185     public FileTime getCreateFileTime() {
186         return zipToFileTime(createTime);
187     }
188 
189     /**
190      * Gets the create time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
191      *
192      * @return create time as java.util.Date or null.
193      */
194     public Date getCreateJavaTime() {
195         return zipToDate(createTime);
196     }
197 
198     /**
199      * Gets the "File creation time" of this ZIP entry as a ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} if no such timestamp exists in the
200      * ZIP entry.
201      *
202      * @return File creation time
203      */
204     public ZipEightByteInteger getCreateTime() {
205         return createTime;
206     }
207 
208     /**
209      * Gets the Header-ID.
210      *
211      * @return the value for the header id for this extrafield
212      */
213     @Override
214     public ZipShort getHeaderId() {
215         return HEADER_ID;
216     }
217 
218     /**
219      * Gets the actual data to put into local file data - without Header-ID or length specifier.
220      *
221      * @return get the data
222      */
223     @Override
224     public byte[] getLocalFileDataData() {
225         final byte[] data = new byte[getLocalFileDataLength().getValue()];
226         int pos = 4;
227         System.arraycopy(TIME_ATTR_TAG.getBytes(), 0, data, pos, 2);
228         pos += 2;
229         System.arraycopy(TIME_ATTR_SIZE.getBytes(), 0, data, pos, 2);
230         pos += 2;
231         System.arraycopy(modifyTime.getBytes(), 0, data, pos, 8);
232         pos += 8;
233         System.arraycopy(accessTime.getBytes(), 0, data, pos, 8);
234         pos += 8;
235         System.arraycopy(createTime.getBytes(), 0, data, pos, 8);
236         return data;
237     }
238 
239     /**
240      * Gets the length of the extra field in the local file data - without Header-ID or length specifier.
241      *
242      * @return a {@code ZipShort} for the length of the data of this extra field
243      */
244     @Override
245     public ZipShort getLocalFileDataLength() {
246         return new ZipShort(4 /* reserved */
247                 + 2 /* Tag#1 */
248                 + 2 /* Size#1 */
249                 + 3 * 8 /* time values */);
250     }
251 
252     /**
253      * Gets the modify time as a {@link FileTime} of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
254      *
255      * @return modify time as a {@link FileTime} or null.
256      * @since 1.23
257      */
258     public FileTime getModifyFileTime() {
259         return zipToFileTime(modifyTime);
260     }
261 
262     /**
263      * Gets the modify time as a java.util.Date of this ZIP entry, or null if no such timestamp exists in the ZIP entry.
264      *
265      * @return modify time as java.util.Date or null.
266      */
267     public Date getModifyJavaTime() {
268         return zipToDate(modifyTime);
269     }
270 
271     /**
272      * Gets the "File last modification time" of this ZIP entry as a ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} if no such timestamp exists
273      * in the ZIP entry.
274      *
275      * @return File last modification time
276      */
277     public ZipEightByteInteger getModifyTime() {
278         return modifyTime;
279     }
280 
281     @Override
282     public int hashCode() {
283         int hc = -123;
284         if (modifyTime != null) {
285             hc ^= modifyTime.hashCode();
286         }
287         if (accessTime != null) {
288             // Since accessTime is often same as modifyTime,
289             // this prevents them from XOR negating each other.
290             hc ^= Integer.rotateLeft(accessTime.hashCode(), 11);
291         }
292         if (createTime != null) {
293             hc ^= Integer.rotateLeft(createTime.hashCode(), 22);
294         }
295         return hc;
296     }
297 
298     /**
299      * Doesn't do anything special since this class always uses the same parsing logic for both central directory and local file data.
300      */
301     @Override
302     public void parseFromCentralDirectoryData(final byte[] buffer, final int offset, final int length) throws ZipException {
303         reset();
304         parseFromLocalFileData(buffer, offset, length);
305     }
306 
307     /**
308      * Populate data from this array as if it was in local file data.
309      *
310      * @param data   an array of bytes
311      * @param offset the start offset
312      * @param length the number of bytes in the array from offset
313      * @throws java.util.zip.ZipException on error
314      */
315     @Override
316     public void parseFromLocalFileData(final byte[] data, int offset, final int length) throws ZipException {
317         final int len = offset + length;
318 
319         // skip reserved
320         offset += 4;
321 
322         while (offset + 4 <= len) {
323             final ZipShort tag = new ZipShort(data, offset);
324             offset += 2;
325             if (tag.equals(TIME_ATTR_TAG)) {
326                 readTimeAttr(data, offset, len - offset);
327                 break;
328             }
329             final ZipShort size = new ZipShort(data, offset);
330             offset += 2 + size.getValue();
331         }
332     }
333 
334     private void readTimeAttr(final byte[] data, int offset, final int length) {
335         if (length >= 2 + 3 * 8) {
336             final ZipShort tagValueLength = new ZipShort(data, offset);
337             if (TIME_ATTR_SIZE.equals(tagValueLength)) {
338                 offset += 2;
339                 modifyTime = new ZipEightByteInteger(data, offset);
340                 offset += 8;
341                 accessTime = new ZipEightByteInteger(data, offset);
342                 offset += 8;
343                 createTime = new ZipEightByteInteger(data, offset);
344             }
345         }
346     }
347 
348     /**
349      * Reset state back to newly constructed state. Helps us make sure parse() calls always generate clean results.
350      */
351     private void reset() {
352         this.modifyTime = ZipEightByteInteger.ZERO;
353         this.accessTime = ZipEightByteInteger.ZERO;
354         this.createTime = ZipEightByteInteger.ZERO;
355     }
356 
357     /**
358      * Sets the access time.
359      *
360      * @param time access time as a {@link FileTime}
361      * @since 1.23
362      */
363     public void setAccessFileTime(final FileTime time) {
364         setAccessTime(fileTimeToZip(time));
365     }
366 
367     /**
368      * Sets the access time as a java.util.Date of this ZIP entry.
369      *
370      * @param d access time as java.util.Date
371      */
372     public void setAccessJavaTime(final Date d) {
373         setAccessTime(dateToZip(d));
374     }
375 
376     /**
377      * Sets the File last access time of this ZIP entry using a ZipEightByteInteger object.
378      *
379      * @param t ZipEightByteInteger of the access time
380      */
381     public void setAccessTime(final ZipEightByteInteger t) {
382         accessTime = t == null ? ZipEightByteInteger.ZERO : t;
383     }
384 
385     /**
386      * Sets the create time.
387      *
388      * @param time create time as a {@link FileTime}
389      * @since 1.23
390      */
391     public void setCreateFileTime(final FileTime time) {
392         setCreateTime(fileTimeToZip(time));
393     }
394 
395     /**
396      * <p>
397      * Sets the create time as a java.util.Date of this ZIP entry. Supplied value is truncated to per-second precision (milliseconds zeroed-out).
398      * </p>
399      * <p>
400      * 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
401      * flags is also set.
402      * </p>
403      *
404      * @param d create time as java.util.Date
405      */
406     public void setCreateJavaTime(final Date d) {
407         setCreateTime(dateToZip(d));
408     }
409 
410     /**
411      * Sets the File creation time of this ZIP entry using a ZipEightByteInteger object.
412      *
413      * @param t ZipEightByteInteger of the create time
414      */
415     public void setCreateTime(final ZipEightByteInteger t) {
416         createTime = t == null ? ZipEightByteInteger.ZERO : t;
417     }
418 
419     /**
420      * Sets the modify time.
421      *
422      * @param time modify time as a {@link FileTime}
423      * @since 1.23
424      */
425     public void setModifyFileTime(final FileTime time) {
426         setModifyTime(fileTimeToZip(time));
427     }
428 
429     /**
430      * Sets the modify time as a java.util.Date of this ZIP entry.
431      *
432      * @param d modify time as java.util.Date
433      */
434     public void setModifyJavaTime(final Date d) {
435         setModifyTime(dateToZip(d));
436     }
437 
438     /**
439      * Sets the File last modification time of this ZIP entry using a ZipEightByteInteger object.
440      *
441      * @param t ZipEightByteInteger of the modify time
442      */
443     public void setModifyTime(final ZipEightByteInteger t) {
444         modifyTime = t == null ? ZipEightByteInteger.ZERO : t;
445     }
446 
447     /**
448      * Returns a String representation of this class useful for debugging purposes.
449      *
450      * @return A String representation of this class useful for debugging purposes.
451      */
452     @Override
453     public String toString() {
454         // @formatter:off
455         return new StringBuilder()
456             .append("0x000A Zip Extra Field:")
457             .append(" Modify:[")
458             .append(getModifyFileTime())
459             .append("] ")
460             .append(" Access:[")
461             .append(getAccessFileTime())
462             .append("] ")
463             .append(" Create:[")
464             .append(getCreateFileTime())
465             .append("] ")
466             .toString();
467         // @formatter:on
468     }
469 }