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