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 }