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 * 017 */ 018package org.apache.commons.compress.archivers.zip; 019 020import java.nio.file.attribute.FileTime; 021import java.util.Date; 022import java.util.Objects; 023import java.util.zip.ZipException; 024 025import org.apache.commons.compress.utils.TimeUtils; 026 027/** 028 * NTFS extra field that was thought to store various attributes but 029 * in reality only stores timestamps. 030 * 031 * <pre> 032 * 4.5.5 -NTFS Extra Field (0x000a): 033 * 034 * The following is the layout of the NTFS attributes 035 * "extra" block. (Note: At this time the Mtime, Atime 036 * and Ctime values MAY be used on any WIN32 system.) 037 * 038 * Note: all fields stored in Intel low-byte/high-byte order. 039 * 040 * Value Size Description 041 * ----- ---- ----------- 042 * (NTFS) 0x000a 2 bytes Tag for this "extra" block type 043 * TSize 2 bytes Size of the total "extra" block 044 * Reserved 4 bytes Reserved for future use 045 * Tag1 2 bytes NTFS attribute tag value #1 046 * Size1 2 bytes Size of attribute #1, in bytes 047 * (var) Size1 Attribute #1 data 048 * . 049 * . 050 * . 051 * TagN 2 bytes NTFS attribute tag value #N 052 * SizeN 2 bytes Size of attribute #N, in bytes 053 * (var) SizeN Attribute #N data 054 * 055 * For NTFS, values for Tag1 through TagN are as follows: 056 * (currently only one set of attributes is defined for NTFS) 057 * 058 * Tag Size Description 059 * ----- ---- ----------- 060 * 0x0001 2 bytes Tag for attribute #1 061 * Size1 2 bytes Size of attribute #1, in bytes 062 * Mtime 8 bytes File last modification time 063 * Atime 8 bytes File last access time 064 * Ctime 8 bytes File creation time 065 * </pre> 066 * 067 * @since 1.11 068 * @NotThreadSafe 069 */ 070public class X000A_NTFS implements ZipExtraField { 071 072 /** 073 * The header ID for this extra field. 074 * 075 * @since 1.23 076 */ 077 public static final ZipShort HEADER_ID = new ZipShort(0x000a); 078 079 private static final ZipShort TIME_ATTR_TAG = new ZipShort(0x0001); 080 private static final ZipShort TIME_ATTR_SIZE = new ZipShort(3 * 8); 081 082 private static ZipEightByteInteger dateToZip(final Date d) { 083 if (d == null) { 084 return null; 085 } 086 return new ZipEightByteInteger(TimeUtils.toNtfsTime(d)); 087 } 088 private static ZipEightByteInteger fileTimeToZip(final FileTime time) { 089 if (time == null) { 090 return null; 091 } 092 return new ZipEightByteInteger(TimeUtils.toNtfsTime(time)); 093 } 094 private static Date zipToDate(final ZipEightByteInteger z) { 095 if (z == null || ZipEightByteInteger.ZERO.equals(z)) { 096 return null; 097 } 098 return TimeUtils.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 TimeUtils.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) && 120 Objects.equals(accessTime, xf.accessTime) && 121 Objects.equals(createTime, xf.createTime); 122 } 123 return false; 124 } 125 126 /** 127 * Gets the access time as a {@link FileTime} 128 * of this ZIP entry, or null if no such timestamp exists in the ZIP entry. 129 * 130 * @return access time as a {@link FileTime} or null. 131 * @since 1.23 132 */ 133 public FileTime getAccessFileTime() { 134 return zipToFileTime(accessTime); 135 } 136 137 /** 138 * Gets the access time as a java.util.Date 139 * of this ZIP entry, or null if no such timestamp exists in the ZIP entry. 140 * 141 * @return access time as java.util.Date or null. 142 */ 143 public Date getAccessJavaTime() { 144 return zipToDate(accessTime); 145 } 146 147 /** 148 * Gets the "File last access time" of this ZIP entry as a 149 * ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} 150 * if no such timestamp exists in the ZIP entry. 151 * 152 * @return File last access time 153 */ 154 public ZipEightByteInteger getAccessTime() { return accessTime; } 155 156 /** 157 * Gets the actual data to put into central directory data - without Header-ID 158 * or length specifier. 159 * 160 * @return the central directory data 161 */ 162 @Override 163 public byte[] getCentralDirectoryData() { 164 return getLocalFileDataData(); 165 } 166 167 /** 168 * Gets the length of the extra field in the local file data - without 169 * Header-ID or length specifier. 170 * 171 * <p>For X5455 the central length is often smaller than the 172 * local length, because central cannot contain access or create 173 * timestamps.</p> 174 * 175 * @return a {@code ZipShort} for the length of the data of this extra field 176 */ 177 @Override 178 public ZipShort getCentralDirectoryLength() { 179 return getLocalFileDataLength(); 180 } 181 182 /** 183 * Gets the create time as a {@link FileTime} 184 * of this ZIP entry, or null if no such timestamp exists in the ZIP entry. 185 * 186 * @return create time as a {@link FileTime} or null. 187 * @since 1.23 188 */ 189 public FileTime getCreateFileTime() { 190 return zipToFileTime(createTime); 191 } 192 193 /** 194 * Gets the create time as a java.util.Date of this ZIP 195 * entry, or null if no such timestamp exists in the ZIP entry. 196 * 197 * @return create time as java.util.Date or null. 198 */ 199 public Date getCreateJavaTime() { 200 return zipToDate(createTime); 201 } 202 203 /** 204 * Gets the "File creation time" of this ZIP entry as a 205 * ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} 206 * if no such timestamp exists in the ZIP entry. 207 * 208 * @return File creation time 209 */ 210 public ZipEightByteInteger getCreateTime() { return createTime; } 211 212 /** 213 * Gets the Header-ID. 214 * 215 * @return the value for the header id for this extrafield 216 */ 217 @Override 218 public ZipShort getHeaderId() { 219 return HEADER_ID; 220 } 221 222 /** 223 * Gets the actual data to put into local file data - without Header-ID 224 * or length specifier. 225 * 226 * @return get the data 227 */ 228 @Override 229 public byte[] getLocalFileDataData() { 230 final byte[] data = new byte[getLocalFileDataLength().getValue()]; 231 int pos = 4; 232 System.arraycopy(TIME_ATTR_TAG.getBytes(), 0, data, pos, 2); 233 pos += 2; 234 System.arraycopy(TIME_ATTR_SIZE.getBytes(), 0, data, pos, 2); 235 pos += 2; 236 System.arraycopy(modifyTime.getBytes(), 0, data, pos, 8); 237 pos += 8; 238 System.arraycopy(accessTime.getBytes(), 0, data, pos, 8); 239 pos += 8; 240 System.arraycopy(createTime.getBytes(), 0, data, pos, 8); 241 return data; 242 } 243 244 /** 245 * Gets the length of the extra field in the local file data - without 246 * Header-ID or length specifier. 247 * 248 * @return a {@code ZipShort} for the length of the data of this extra field 249 */ 250 @Override 251 public ZipShort getLocalFileDataLength() { 252 return new ZipShort(4 /* reserved */ 253 + 2 /* Tag#1 */ 254 + 2 /* Size#1 */ 255 + 3 * 8 /* time values */); 256 } 257 258 /** 259 * Gets the modify time as a {@link FileTime} 260 * of this ZIP entry, or null if no such timestamp exists in the ZIP entry. 261 * 262 * @return modify time as a {@link FileTime} or null. 263 * @since 1.23 264 */ 265 public FileTime getModifyFileTime() { 266 return zipToFileTime(modifyTime); 267 } 268 269 /** 270 * Gets the modify time as a java.util.Date 271 * of this ZIP entry, or null if no such timestamp exists in the ZIP entry. 272 * 273 * @return modify time as java.util.Date or null. 274 */ 275 public Date getModifyJavaTime() { 276 return zipToDate(modifyTime); 277 } 278 279 /** 280 * Gets the "File last modification time" of this ZIP entry as 281 * a ZipEightByteInteger object, or {@link 282 * ZipEightByteInteger#ZERO} if no such timestamp exists in the 283 * ZIP entry. 284 * 285 * @return File last modification time 286 */ 287 public ZipEightByteInteger getModifyTime() { return modifyTime; } 288 289 @Override 290 public int hashCode() { 291 int hc = -123; 292 if (modifyTime != null) { 293 hc ^= modifyTime.hashCode(); 294 } 295 if (accessTime != null) { 296 // Since accessTime is often same as modifyTime, 297 // this prevents them from XOR negating each other. 298 hc ^= Integer.rotateLeft(accessTime.hashCode(), 11); 299 } 300 if (createTime != null) { 301 hc ^= Integer.rotateLeft(createTime.hashCode(), 22); 302 } 303 return hc; 304 } 305 306 /** 307 * Doesn't do anything special since this class always uses the 308 * same parsing logic for both central directory and local file data. 309 */ 310 @Override 311 public void parseFromCentralDirectoryData( 312 final byte[] buffer, final int offset, final int length 313 ) throws ZipException { 314 reset(); 315 parseFromLocalFileData(buffer, offset, length); 316 } 317 318 /** 319 * Populate data from this array as if it was in local file data. 320 * 321 * @param data an array of bytes 322 * @param offset the start offset 323 * @param length the number of bytes in the array from offset 324 * @throws java.util.zip.ZipException on error 325 */ 326 @Override 327 public void parseFromLocalFileData( 328 final byte[] data, int offset, final int length 329 ) throws ZipException { 330 final int len = offset + length; 331 332 // skip reserved 333 offset += 4; 334 335 while (offset + 4 <= len) { 336 final ZipShort tag = new ZipShort(data, offset); 337 offset += 2; 338 if (tag.equals(TIME_ATTR_TAG)) { 339 readTimeAttr(data, offset, len - offset); 340 break; 341 } 342 final ZipShort size = new ZipShort(data, offset); 343 offset += 2 + size.getValue(); 344 } 345 } 346 347 private void readTimeAttr(final byte[] data, int offset, final int length) { 348 if (length >= 2 + 3 * 8) { 349 final ZipShort tagValueLength = new ZipShort(data, offset); 350 if (TIME_ATTR_SIZE.equals(tagValueLength)) { 351 offset += 2; 352 modifyTime = new ZipEightByteInteger(data, offset); 353 offset += 8; 354 accessTime = new ZipEightByteInteger(data, offset); 355 offset += 8; 356 createTime = new ZipEightByteInteger(data, offset); 357 } 358 } 359 } 360 361 /** 362 * Reset state back to newly constructed state. Helps us make sure 363 * parse() calls always generate clean results. 364 */ 365 private void reset() { 366 this.modifyTime = ZipEightByteInteger.ZERO; 367 this.accessTime = ZipEightByteInteger.ZERO; 368 this.createTime = ZipEightByteInteger.ZERO; 369 } 370 371 /** 372 * Sets the access time. 373 * 374 * @param time access time as a {@link FileTime} 375 * @since 1.23 376 */ 377 public void setAccessFileTime(final FileTime time) { 378 setAccessTime(fileTimeToZip(time)); 379 } 380 381 /** 382 * Sets the access time as a java.util.Date 383 * of this ZIP entry. 384 * 385 * @param d access time as java.util.Date 386 */ 387 public void setAccessJavaTime(final Date d) { setAccessTime(dateToZip(d)); } 388 389 /** 390 * Sets the File last access time of this ZIP entry using a 391 * ZipEightByteInteger object. 392 * 393 * @param t ZipEightByteInteger of the access time 394 */ 395 public void setAccessTime(final ZipEightByteInteger t) { 396 accessTime = t == null ? ZipEightByteInteger.ZERO : t; 397 } 398 399 /** 400 * Sets the create time. 401 * 402 * @param time create time as a {@link FileTime} 403 * @since 1.23 404 */ 405 public void setCreateFileTime(final FileTime time) { 406 setCreateTime(fileTimeToZip(time)); 407 } 408 409 /** 410 * <p> 411 * Sets the create time as a java.util.Date 412 * of this ZIP entry. Supplied value is truncated to per-second 413 * precision (milliseconds zeroed-out). 414 * </p><p> 415 * Note: the setters for flags and timestamps are decoupled. 416 * Even if the timestamp is not-null, it will only be written 417 * out if the corresponding bit in the flags is also set. 418 * </p> 419 * 420 * @param d create time as java.util.Date 421 */ 422 public void setCreateJavaTime(final Date d) { setCreateTime(dateToZip(d)); } 423 424 /** 425 * Sets the File creation time of this ZIP entry using a 426 * ZipEightByteInteger object. 427 * 428 * @param t ZipEightByteInteger of the create time 429 */ 430 public void setCreateTime(final ZipEightByteInteger t) { 431 createTime = t == null ? ZipEightByteInteger.ZERO : t; 432 } 433 434 /** 435 * Sets the modify time. 436 * 437 * @param time modify time as a {@link FileTime} 438 * @since 1.23 439 */ 440 public void setModifyFileTime(final FileTime time) { 441 setModifyTime(fileTimeToZip(time)); 442 } 443 444 /** 445 * Sets the modify time as a java.util.Date of this ZIP entry. 446 * 447 * @param d modify time as java.util.Date 448 */ 449 public void setModifyJavaTime(final Date d) { setModifyTime(dateToZip(d)); } 450 451 /** 452 * Sets the File last modification time of this ZIP entry using a 453 * ZipEightByteInteger object. 454 * 455 * @param t ZipEightByteInteger of the modify time 456 */ 457 public void setModifyTime(final ZipEightByteInteger t) { 458 modifyTime = t == null ? ZipEightByteInteger.ZERO : t; 459 } 460 461 /** 462 * Returns a String representation of this class useful for 463 * debugging purposes. 464 * 465 * @return A String representation of this class useful for 466 * debugging purposes. 467 */ 468 @Override 469 public String toString() { 470 final StringBuilder buf = new StringBuilder(); 471 buf.append("0x000A Zip Extra Field:") 472 .append(" Modify:[").append(getModifyFileTime()).append("] ") 473 .append(" Access:[").append(getAccessFileTime()).append("] ") 474 .append(" Create:[").append(getCreateFileTime()).append("] "); 475 return buf.toString(); 476 } 477}