001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.compress.archivers.zip; 018 019import java.nio.file.attribute.FileTime; 020import java.util.Date; 021import java.util.Objects; 022import java.util.zip.ZipException; 023 024import org.apache.commons.io.file.attribute.FileTimes; 025 026/** 027 * NTFS extra field that was thought to store various attributes but in reality only stores timestamps. 028 * 029 * <pre> 030 * 4.5.5 -NTFS Extra Field (0x000a): 031 * 032 * The following is the layout of the NTFS attributes 033 * "extra" block. (Note: At this time the Mtime, Atime 034 * and Ctime values MAY be used on any WIN32 system.) 035 * 036 * Note: all fields stored in Intel low-byte/high-byte order. 037 * 038 * Value Size Description 039 * ----- ---- ----------- 040 * (NTFS) 0x000a 2 bytes Tag for this "extra" block type 041 * TSize 2 bytes Size of the total "extra" block 042 * Reserved 4 bytes Reserved for future use 043 * Tag1 2 bytes NTFS attribute tag value #1 044 * Size1 2 bytes Size of attribute #1, in bytes 045 * (var) Size1 Attribute #1 data 046 * . 047 * . 048 * . 049 * TagN 2 bytes NTFS attribute tag value #N 050 * SizeN 2 bytes Size of attribute #N, in bytes 051 * (var) SizeN Attribute #N data 052 * 053 * For NTFS, values for Tag1 through TagN are as follows: 054 * (currently only one set of attributes is defined for NTFS) 055 * 056 * Tag Size Description 057 * ----- ---- ----------- 058 * 0x0001 2 bytes Tag for attribute #1 059 * Size1 2 bytes Size of attribute #1, in bytes 060 * Mtime 8 bytes File last modification time 061 * Atime 8 bytes File last access time 062 * Ctime 8 bytes File creation time 063 * </pre> 064 * 065 * @since 1.11 066 * @NotThreadSafe 067 */ 068public class X000A_NTFS implements ZipExtraField { 069 070 /** 071 * The header ID for this extra field. 072 * 073 * @since 1.23 074 */ 075 public static final ZipShort HEADER_ID = new ZipShort(0x000a); 076 077 private static final ZipShort TIME_ATTR_TAG = new ZipShort(0x0001); 078 private static final ZipShort TIME_ATTR_SIZE = new ZipShort(3 * 8); 079 080 private static ZipEightByteInteger dateToZip(final Date d) { 081 if (d == null) { 082 return null; 083 } 084 return new ZipEightByteInteger(FileTimes.toNtfsTime(d)); 085 } 086 087 private static ZipEightByteInteger fileTimeToZip(final FileTime time) { 088 if (time == null) { 089 return null; 090 } 091 return new ZipEightByteInteger(FileTimes.toNtfsTime(time)); 092 } 093 094 private static Date zipToDate(final ZipEightByteInteger z) { 095 if (z == null || ZipEightByteInteger.ZERO.equals(z)) { 096 return null; 097 } 098 return FileTimes.ntfsTimeToDate(z.getLongValue()); 099 } 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}