• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
18 package org.apache.commons.compress.archivers.zip;
19 
20 import java.util.Date;
21 import java.util.zip.ZipException;
22 
23 /**
24  * NTFS extra field that was thought to store various attributes but
25  * in reality only stores timestamps.
26  *
27  * <pre>
28  *    4.5.5 -NTFS Extra Field (0x000a):
29  *
30  *       The following is the layout of the NTFS attributes
31  *       "extra" block. (Note: At this time the Mtime, Atime
32  *       and Ctime values MAY be used on any WIN32 system.)
33  *
34  *       Note: all fields stored in Intel low-byte/high-byte order.
35  *
36  *         Value      Size       Description
37  *         -----      ----       -----------
38  * (NTFS)  0x000a     2 bytes    Tag for this "extra" block type
39  *         TSize      2 bytes    Size of the total "extra" block
40  *         Reserved   4 bytes    Reserved for future use
41  *         Tag1       2 bytes    NTFS attribute tag value #1
42  *         Size1      2 bytes    Size of attribute #1, in bytes
43  *         (var)      Size1      Attribute #1 data
44  *          .
45  *          .
46  *          .
47  *          TagN       2 bytes    NTFS attribute tag value #N
48  *          SizeN      2 bytes    Size of attribute #N, in bytes
49  *          (var)      SizeN      Attribute #N data
50  *
51  *        For NTFS, values for Tag1 through TagN are as follows:
52  *        (currently only one set of attributes is defined for NTFS)
53  *
54  *          Tag        Size       Description
55  *          -----      ----       -----------
56  *          0x0001     2 bytes    Tag for attribute #1
57  *          Size1      2 bytes    Size of attribute #1, in bytes
58  *          Mtime      8 bytes    File last modification time
59  *          Atime      8 bytes    File last access time
60  *          Ctime      8 bytes    File creation time
61  * </pre>
62  *
63  * @since 1.11
64  * @NotThreadSafe
65  */
66 public class X000A_NTFS implements ZipExtraField {
67     private static final ZipShort HEADER_ID = new ZipShort(0x000a);
68     private static final ZipShort TIME_ATTR_TAG = new ZipShort(0x0001);
69     private static final ZipShort TIME_ATTR_SIZE = new ZipShort(3 * 8);
70 
71     private ZipEightByteInteger modifyTime = ZipEightByteInteger.ZERO;
72     private ZipEightByteInteger accessTime = ZipEightByteInteger.ZERO;
73     private ZipEightByteInteger createTime = ZipEightByteInteger.ZERO;
74 
75     /**
76      * The Header-ID.
77      *
78      * @return the value for the header id for this extrafield
79      */
80     @Override
getHeaderId()81     public ZipShort getHeaderId() {
82         return HEADER_ID;
83     }
84 
85     /**
86      * Length of the extra field in the local file data - without
87      * Header-ID or length specifier.
88      *
89      * @return a <code>ZipShort</code> for the length of the data of this extra field
90      */
91     @Override
getLocalFileDataLength()92     public ZipShort getLocalFileDataLength() {
93         return new ZipShort(4 /* reserved */
94                             + 2 /* Tag#1 */
95                             + 2 /* Size#1 */
96                             + 3 * 8 /* time values */);
97     }
98 
99     /**
100      * Length of the extra field in the local file data - without
101      * Header-ID or length specifier.
102      *
103      * <p>For X5455 the central length is often smaller than the
104      * local length, because central cannot contain access or create
105      * timestamps.</p>
106      *
107      * @return a <code>ZipShort</code> for the length of the data of this extra field
108      */
109     @Override
getCentralDirectoryLength()110     public ZipShort getCentralDirectoryLength() {
111         return getLocalFileDataLength();
112     }
113 
114     /**
115      * The actual data to put into local file data - without Header-ID
116      * or length specifier.
117      *
118      * @return get the data
119      */
120     @Override
getLocalFileDataData()121     public byte[] getLocalFileDataData() {
122         final byte[] data = new byte[getLocalFileDataLength().getValue()];
123         int pos = 4;
124         System.arraycopy(TIME_ATTR_TAG.getBytes(), 0, data, pos, 2);
125         pos += 2;
126         System.arraycopy(TIME_ATTR_SIZE.getBytes(), 0, data, pos, 2);
127         pos += 2;
128         System.arraycopy(modifyTime.getBytes(), 0, data, pos, 8);
129         pos += 8;
130         System.arraycopy(accessTime.getBytes(), 0, data, pos, 8);
131         pos += 8;
132         System.arraycopy(createTime.getBytes(), 0, data, pos, 8);
133         return data;
134     }
135 
136     /**
137      * The actual data to put into central directory data - without Header-ID
138      * or length specifier.
139      *
140      * @return the central directory data
141      */
142     @Override
getCentralDirectoryData()143     public byte[] getCentralDirectoryData() {
144         return getLocalFileDataData();
145     }
146 
147     /**
148      * Populate data from this array as if it was in local file data.
149      *
150      * @param data   an array of bytes
151      * @param offset the start offset
152      * @param length the number of bytes in the array from offset
153      * @throws java.util.zip.ZipException on error
154      */
155     @Override
parseFromLocalFileData( final byte[] data, int offset, final int length )156     public void parseFromLocalFileData(
157             final byte[] data, int offset, final int length
158     ) throws ZipException {
159         final int len = offset + length;
160 
161         // skip reserved
162         offset += 4;
163 
164         while (offset + 4 <= len) {
165             final ZipShort tag = new ZipShort(data, offset);
166             offset += 2;
167             if (tag.equals(TIME_ATTR_TAG)) {
168                 readTimeAttr(data, offset, len - offset);
169                 break;
170             }
171             final ZipShort size = new ZipShort(data, offset);
172             offset += 2 + size.getValue();
173         }
174     }
175 
176     /**
177      * Doesn't do anything special since this class always uses the
178      * same parsing logic for both central directory and local file data.
179      */
180     @Override
parseFromCentralDirectoryData( final byte[] buffer, final int offset, final int length )181     public void parseFromCentralDirectoryData(
182             final byte[] buffer, final int offset, final int length
183     ) throws ZipException {
184         reset();
185         parseFromLocalFileData(buffer, offset, length);
186     }
187 
188     /**
189      * Returns the "File last modification time" of this zip entry as
190      * a ZipEightByteInteger object, or {@link
191      * ZipEightByteInteger#ZERO} if no such timestamp exists in the
192      * zip entry.
193      *
194      * @return File last modification time
195      */
getModifyTime()196     public ZipEightByteInteger getModifyTime() { return modifyTime; }
197 
198     /**
199      * Returns the "File last access time" of this zip entry as a
200      * ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO}
201      * if no such timestamp exists in the zip entry.
202      *
203      * @return File last access time
204      */
getAccessTime()205     public ZipEightByteInteger getAccessTime() { return accessTime; }
206 
207     /**
208      * Returns the "File creation time" of this zip entry as a
209      * ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO}
210      * if no such timestamp exists in the zip entry.
211      *
212      * @return File creation time
213      */
getCreateTime()214     public ZipEightByteInteger getCreateTime() { return createTime; }
215 
216     /**
217      * Returns the modify time as a java.util.Date
218      * of this zip entry, or null if no such timestamp exists in the zip entry.
219      *
220      * @return modify time as java.util.Date or null.
221      */
getModifyJavaTime()222     public Date getModifyJavaTime() {
223         return zipToDate(modifyTime);
224     }
225 
226     /**
227      * Returns the access time as a java.util.Date
228      * of this zip entry, or null if no such timestamp exists in the zip entry.
229      *
230      * @return access time as java.util.Date or null.
231      */
getAccessJavaTime()232     public Date getAccessJavaTime() {
233         return zipToDate(accessTime);
234     }
235 
236     /**
237      * Returns the create time as a a java.util.Date of this zip
238      * entry, or null if no such timestamp exists in the zip entry.
239      *
240      * @return create time as java.util.Date or null.
241      */
getCreateJavaTime()242     public Date getCreateJavaTime() {
243         return zipToDate(createTime);
244     }
245 
246     /**
247      * Sets the File last modification time of this zip entry using a
248      * ZipEightByteInteger object.
249      *
250      * @param t ZipEightByteInteger of the modify time
251      */
setModifyTime(final ZipEightByteInteger t)252     public void setModifyTime(final ZipEightByteInteger t) {
253         modifyTime = t == null ? ZipEightByteInteger.ZERO : t;
254     }
255 
256     /**
257      * Sets the File last access time of this zip entry using a
258      * ZipEightByteInteger object.
259      *
260      * @param t ZipEightByteInteger of the access time
261      */
setAccessTime(final ZipEightByteInteger t)262     public void setAccessTime(final ZipEightByteInteger t) {
263         accessTime = t == null ? ZipEightByteInteger.ZERO : t;
264     }
265 
266     /**
267      * Sets the File creation time of this zip entry using a
268      * ZipEightByteInteger object.
269      *
270      * @param t ZipEightByteInteger of the create time
271      */
setCreateTime(final ZipEightByteInteger t)272     public void setCreateTime(final ZipEightByteInteger t) {
273         createTime = t == null ? ZipEightByteInteger.ZERO : t;
274     }
275 
276     /**
277      * Sets the modify time as a java.util.Date of this zip entry.
278      *
279      * @param d modify time as java.util.Date
280      */
setModifyJavaTime(final Date d)281     public void setModifyJavaTime(final Date d) { setModifyTime(dateToZip(d)); }
282 
283     /**
284      * Sets the access time as a java.util.Date
285      * of this zip entry.
286      *
287      * @param d access time as java.util.Date
288      */
setAccessJavaTime(final Date d)289     public void setAccessJavaTime(final Date d) { setAccessTime(dateToZip(d)); }
290 
291     /**
292      * <p>
293      * Sets the create time as a java.util.Date
294      * of this zip entry.  Supplied value is truncated to per-second
295      * precision (milliseconds zeroed-out).
296      * </p><p>
297      * Note: the setters for flags and timestamps are decoupled.
298      * Even if the timestamp is not-null, it will only be written
299      * out if the corresponding bit in the flags is also set.
300      * </p>
301      *
302      * @param d create time as java.util.Date
303      */
setCreateJavaTime(final Date d)304     public void setCreateJavaTime(final Date d) { setCreateTime(dateToZip(d)); }
305 
306     /**
307      * Returns a String representation of this class useful for
308      * debugging purposes.
309      *
310      * @return A String representation of this class useful for
311      *         debugging purposes.
312      */
313     @Override
toString()314     public String toString() {
315         final StringBuilder buf = new StringBuilder();
316         buf.append("0x000A Zip Extra Field:")
317             .append(" Modify:[").append(getModifyJavaTime()).append("] ")
318             .append(" Access:[").append(getAccessJavaTime()).append("] ")
319             .append(" Create:[").append(getCreateJavaTime()).append("] ");
320         return buf.toString();
321     }
322 
323     @Override
equals(final Object o)324     public boolean equals(final Object o) {
325         if (o instanceof X000A_NTFS) {
326             final X000A_NTFS xf = (X000A_NTFS) o;
327 
328             return (modifyTime == xf.modifyTime || (modifyTime != null && modifyTime.equals(xf.modifyTime))) &&
329                     (accessTime == xf.accessTime || (accessTime != null && accessTime.equals(xf.accessTime))) &&
330                     (createTime == xf.createTime || (createTime != null && createTime.equals(xf.createTime)));
331         }
332         return false;
333     }
334 
335     @Override
hashCode()336     public int hashCode() {
337         int hc = -123;
338         if (modifyTime != null) {
339             hc ^= modifyTime.hashCode();
340         }
341         if (accessTime != null) {
342             // Since accessTime is often same as modifyTime,
343             // this prevents them from XOR negating each other.
344             hc ^= Integer.rotateLeft(accessTime.hashCode(), 11);
345         }
346         if (createTime != null) {
347             hc ^= Integer.rotateLeft(createTime.hashCode(), 22);
348         }
349         return hc;
350     }
351 
352     /**
353      * Reset state back to newly constructed state.  Helps us make sure
354      * parse() calls always generate clean results.
355      */
reset()356     private void reset() {
357         this.modifyTime = ZipEightByteInteger.ZERO;
358         this.accessTime = ZipEightByteInteger.ZERO;
359         this.createTime = ZipEightByteInteger.ZERO;
360     }
361 
readTimeAttr(final byte[] data, int offset, final int length)362     private void readTimeAttr(final byte[] data, int offset, final int length) {
363         if (length >= 2 + 3 * 8) {
364             final ZipShort tagValueLength = new ZipShort(data, offset);
365             if (TIME_ATTR_SIZE.equals(tagValueLength)) {
366                 offset += 2;
367                 modifyTime = new ZipEightByteInteger(data, offset);
368                 offset += 8;
369                 accessTime = new ZipEightByteInteger(data, offset);
370                 offset += 8;
371                 createTime = new ZipEightByteInteger(data, offset);
372             }
373         }
374     }
375 
376     // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290%28v=vs.85%29.aspx
377     // A file time is a 64-bit value that represents the number of
378     // 100-nanosecond intervals that have elapsed since 12:00
379     // A.M. January 1, 1601 Coordinated Universal Time (UTC).
380     // this is the offset of Windows time 0 to Unix epoch in 100-nanosecond intervals
381     private static final long EPOCH_OFFSET = -116444736000000000L;
382 
dateToZip(final Date d)383     private static ZipEightByteInteger dateToZip(final Date d) {
384         if (d == null) { return null; }
385         return new ZipEightByteInteger((d.getTime() * 10000L) - EPOCH_OFFSET);
386     }
387 
zipToDate(final ZipEightByteInteger z)388     private static Date zipToDate(final ZipEightByteInteger z) {
389         if (z == null || ZipEightByteInteger.ZERO.equals(z)) { return null; }
390         final long l = (z.getLongValue() + EPOCH_OFFSET) / 10000L;
391         return new Date(l);
392     }
393 
394 }
395