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.util.Date; 021import java.util.Objects; 022import java.util.zip.ZipException; 023 024/** 025 * NTFS extra field that was thought to store various attributes but 026 * in reality only stores timestamps. 027 * 028 * <pre> 029 * 4.5.5 -NTFS Extra Field (0x000a): 030 * 031 * The following is the layout of the NTFS attributes 032 * "extra" block. (Note: At this time the Mtime, Atime 033 * and Ctime values MAY be used on any WIN32 system.) 034 * 035 * Note: all fields stored in Intel low-byte/high-byte order. 036 * 037 * Value Size Description 038 * ----- ---- ----------- 039 * (NTFS) 0x000a 2 bytes Tag for this "extra" block type 040 * TSize 2 bytes Size of the total "extra" block 041 * Reserved 4 bytes Reserved for future use 042 * Tag1 2 bytes NTFS attribute tag value #1 043 * Size1 2 bytes Size of attribute #1, in bytes 044 * (var) Size1 Attribute #1 data 045 * . 046 * . 047 * . 048 * TagN 2 bytes NTFS attribute tag value #N 049 * SizeN 2 bytes Size of attribute #N, in bytes 050 * (var) SizeN Attribute #N data 051 * 052 * For NTFS, values for Tag1 through TagN are as follows: 053 * (currently only one set of attributes is defined for NTFS) 054 * 055 * Tag Size Description 056 * ----- ---- ----------- 057 * 0x0001 2 bytes Tag for attribute #1 058 * Size1 2 bytes Size of attribute #1, in bytes 059 * Mtime 8 bytes File last modification time 060 * Atime 8 bytes File last access time 061 * Ctime 8 bytes File creation time 062 * </pre> 063 * 064 * @since 1.11 065 * @NotThreadSafe 066 */ 067public class X000A_NTFS implements ZipExtraField { 068 private static final ZipShort HEADER_ID = new ZipShort(0x000a); 069 private static final ZipShort TIME_ATTR_TAG = new ZipShort(0x0001); 070 private static final ZipShort TIME_ATTR_SIZE = new ZipShort(3 * 8); 071 072 private ZipEightByteInteger modifyTime = ZipEightByteInteger.ZERO; 073 private ZipEightByteInteger accessTime = ZipEightByteInteger.ZERO; 074 private ZipEightByteInteger createTime = ZipEightByteInteger.ZERO; 075 076 /** 077 * The Header-ID. 078 * 079 * @return the value for the header id for this extrafield 080 */ 081 @Override 082 public ZipShort getHeaderId() { 083 return HEADER_ID; 084 } 085 086 /** 087 * Length of the extra field in the local file data - without 088 * Header-ID or length specifier. 089 * 090 * @return a {@code ZipShort} for the length of the data of this extra field 091 */ 092 @Override 093 public ZipShort getLocalFileDataLength() { 094 return new ZipShort(4 /* reserved */ 095 + 2 /* Tag#1 */ 096 + 2 /* Size#1 */ 097 + 3 * 8 /* time values */); 098 } 099 100 /** 101 * Length of the extra field in the local file data - without 102 * Header-ID or length specifier. 103 * 104 * <p>For X5455 the central length is often smaller than the 105 * local length, because central cannot contain access or create 106 * timestamps.</p> 107 * 108 * @return a {@code ZipShort} for the length of the data of this extra field 109 */ 110 @Override 111 public ZipShort getCentralDirectoryLength() { 112 return getLocalFileDataLength(); 113 } 114 115 /** 116 * The actual data to put into local file data - without Header-ID 117 * or length specifier. 118 * 119 * @return get the data 120 */ 121 @Override 122 public byte[] getLocalFileDataData() { 123 final byte[] data = new byte[getLocalFileDataLength().getValue()]; 124 int pos = 4; 125 System.arraycopy(TIME_ATTR_TAG.getBytes(), 0, data, pos, 2); 126 pos += 2; 127 System.arraycopy(TIME_ATTR_SIZE.getBytes(), 0, data, pos, 2); 128 pos += 2; 129 System.arraycopy(modifyTime.getBytes(), 0, data, pos, 8); 130 pos += 8; 131 System.arraycopy(accessTime.getBytes(), 0, data, pos, 8); 132 pos += 8; 133 System.arraycopy(createTime.getBytes(), 0, data, pos, 8); 134 return data; 135 } 136 137 /** 138 * The actual data to put into central directory data - without Header-ID 139 * or length specifier. 140 * 141 * @return the central directory data 142 */ 143 @Override 144 public byte[] getCentralDirectoryData() { 145 return getLocalFileDataData(); 146 } 147 148 /** 149 * Populate data from this array as if it was in local file data. 150 * 151 * @param data an array of bytes 152 * @param offset the start offset 153 * @param length the number of bytes in the array from offset 154 * @throws java.util.zip.ZipException on error 155 */ 156 @Override 157 public void parseFromLocalFileData( 158 final byte[] data, int offset, final int length 159 ) throws ZipException { 160 final int len = offset + length; 161 162 // skip reserved 163 offset += 4; 164 165 while (offset + 4 <= len) { 166 final ZipShort tag = new ZipShort(data, offset); 167 offset += 2; 168 if (tag.equals(TIME_ATTR_TAG)) { 169 readTimeAttr(data, offset, len - offset); 170 break; 171 } 172 final ZipShort size = new ZipShort(data, offset); 173 offset += 2 + size.getValue(); 174 } 175 } 176 177 /** 178 * Doesn't do anything special since this class always uses the 179 * same parsing logic for both central directory and local file data. 180 */ 181 @Override 182 public void parseFromCentralDirectoryData( 183 final byte[] buffer, final int offset, final int length 184 ) throws ZipException { 185 reset(); 186 parseFromLocalFileData(buffer, offset, length); 187 } 188 189 /** 190 * Returns the "File last modification time" of this zip entry as 191 * a ZipEightByteInteger object, or {@link 192 * ZipEightByteInteger#ZERO} if no such timestamp exists in the 193 * zip entry. 194 * 195 * @return File last modification time 196 */ 197 public ZipEightByteInteger getModifyTime() { return modifyTime; } 198 199 /** 200 * Returns the "File last access time" of this zip entry as a 201 * ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} 202 * if no such timestamp exists in the zip entry. 203 * 204 * @return File last access time 205 */ 206 public ZipEightByteInteger getAccessTime() { return accessTime; } 207 208 /** 209 * Returns the "File creation time" of this zip entry as a 210 * ZipEightByteInteger object, or {@link ZipEightByteInteger#ZERO} 211 * if no such timestamp exists in the zip entry. 212 * 213 * @return File creation time 214 */ 215 public ZipEightByteInteger getCreateTime() { return createTime; } 216 217 /** 218 * Returns the modify time as a java.util.Date 219 * of this zip entry, or null if no such timestamp exists in the zip entry. 220 * 221 * @return modify time as java.util.Date or null. 222 */ 223 public Date getModifyJavaTime() { 224 return zipToDate(modifyTime); 225 } 226 227 /** 228 * Returns the access time as a java.util.Date 229 * of this zip entry, or null if no such timestamp exists in the zip entry. 230 * 231 * @return access time as java.util.Date or null. 232 */ 233 public Date getAccessJavaTime() { 234 return zipToDate(accessTime); 235 } 236 237 /** 238 * Returns the create time as a a java.util.Date of this zip 239 * entry, or null if no such timestamp exists in the zip entry. 240 * 241 * @return create time as java.util.Date or null. 242 */ 243 public Date getCreateJavaTime() { 244 return zipToDate(createTime); 245 } 246 247 /** 248 * Sets the File last modification time of this zip entry using a 249 * ZipEightByteInteger object. 250 * 251 * @param t ZipEightByteInteger of the modify time 252 */ 253 public void setModifyTime(final ZipEightByteInteger t) { 254 modifyTime = t == null ? ZipEightByteInteger.ZERO : t; 255 } 256 257 /** 258 * Sets the File last access time of this zip entry using a 259 * ZipEightByteInteger object. 260 * 261 * @param t ZipEightByteInteger of the access time 262 */ 263 public void setAccessTime(final ZipEightByteInteger t) { 264 accessTime = t == null ? ZipEightByteInteger.ZERO : t; 265 } 266 267 /** 268 * Sets the File creation time of this zip entry using a 269 * ZipEightByteInteger object. 270 * 271 * @param t ZipEightByteInteger of the create time 272 */ 273 public void setCreateTime(final ZipEightByteInteger t) { 274 createTime = t == null ? ZipEightByteInteger.ZERO : t; 275 } 276 277 /** 278 * Sets the modify time as a java.util.Date of this zip entry. 279 * 280 * @param d modify time as java.util.Date 281 */ 282 public void setModifyJavaTime(final Date d) { setModifyTime(dateToZip(d)); } 283 284 /** 285 * Sets the access time as a java.util.Date 286 * of this zip entry. 287 * 288 * @param d access time as java.util.Date 289 */ 290 public void setAccessJavaTime(final Date d) { setAccessTime(dateToZip(d)); } 291 292 /** 293 * <p> 294 * Sets the create time as a java.util.Date 295 * of this zip entry. Supplied value is truncated to per-second 296 * precision (milliseconds zeroed-out). 297 * </p><p> 298 * Note: the setters for flags and timestamps are decoupled. 299 * Even if the timestamp is not-null, it will only be written 300 * out if the corresponding bit in the flags is also set. 301 * </p> 302 * 303 * @param d create time as java.util.Date 304 */ 305 public void setCreateJavaTime(final Date d) { setCreateTime(dateToZip(d)); } 306 307 /** 308 * Returns a String representation of this class useful for 309 * debugging purposes. 310 * 311 * @return A String representation of this class useful for 312 * debugging purposes. 313 */ 314 @Override 315 public String toString() { 316 final StringBuilder buf = new StringBuilder(); 317 buf.append("0x000A Zip Extra Field:") 318 .append(" Modify:[").append(getModifyJavaTime()).append("] ") 319 .append(" Access:[").append(getAccessJavaTime()).append("] ") 320 .append(" Create:[").append(getCreateJavaTime()).append("] "); 321 return buf.toString(); 322 } 323 324 @Override 325 public boolean equals(final Object o) { 326 if (o instanceof X000A_NTFS) { 327 final X000A_NTFS xf = (X000A_NTFS) o; 328 329 return Objects.equals(modifyTime, xf.modifyTime) && 330 Objects.equals(accessTime, xf.accessTime) && 331 Objects.equals(createTime, xf.createTime); 332 } 333 return false; 334 } 335 336 @Override 337 public int hashCode() { 338 int hc = -123; 339 if (modifyTime != null) { 340 hc ^= modifyTime.hashCode(); 341 } 342 if (accessTime != null) { 343 // Since accessTime is often same as modifyTime, 344 // this prevents them from XOR negating each other. 345 hc ^= Integer.rotateLeft(accessTime.hashCode(), 11); 346 } 347 if (createTime != null) { 348 hc ^= Integer.rotateLeft(createTime.hashCode(), 22); 349 } 350 return hc; 351 } 352 353 /** 354 * Reset state back to newly constructed state. Helps us make sure 355 * parse() calls always generate clean results. 356 */ 357 private void reset() { 358 this.modifyTime = ZipEightByteInteger.ZERO; 359 this.accessTime = ZipEightByteInteger.ZERO; 360 this.createTime = ZipEightByteInteger.ZERO; 361 } 362 363 private void readTimeAttr(final byte[] data, int offset, final int length) { 364 if (length >= 2 + 3 * 8) { 365 final ZipShort tagValueLength = new ZipShort(data, offset); 366 if (TIME_ATTR_SIZE.equals(tagValueLength)) { 367 offset += 2; 368 modifyTime = new ZipEightByteInteger(data, offset); 369 offset += 8; 370 accessTime = new ZipEightByteInteger(data, offset); 371 offset += 8; 372 createTime = new ZipEightByteInteger(data, offset); 373 } 374 } 375 } 376 377 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290%28v=vs.85%29.aspx 378 // A file time is a 64-bit value that represents the number of 379 // 100-nanosecond intervals that have elapsed since 12:00 380 // A.M. January 1, 1601 Coordinated Universal Time (UTC). 381 // this is the offset of Windows time 0 to Unix epoch in 100-nanosecond intervals 382 private static final long EPOCH_OFFSET = -116444736000000000L; 383 384 private static ZipEightByteInteger dateToZip(final Date d) { 385 if (d == null) { return null; } 386 return new ZipEightByteInteger((d.getTime() * 10000L) - EPOCH_OFFSET); 387 } 388 389 private static Date zipToDate(final ZipEightByteInteger z) { 390 if (z == null || ZipEightByteInteger.ZERO.equals(z)) { return null; } 391 final long l = (z.getLongValue() + EPOCH_OFFSET) / 10000L; 392 return new Date(l); 393 } 394 395}