001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.commons.compress.archivers.zip; 020 021import java.io.IOException; 022import java.math.BigInteger; 023import java.time.Instant; 024import java.time.LocalDateTime; 025import java.time.ZoneId; 026import java.util.Arrays; 027import java.util.Calendar; 028import java.util.Date; 029import java.util.zip.CRC32; 030import java.util.zip.ZipEntry; 031import java.util.zip.ZipException; 032 033/** 034 * Utility class for handling DOS and Java time conversions. 035 * 036 * @Immutable 037 */ 038public abstract class ZipUtil { 039 040 /** 041 * DOS time constant for representing timestamps before 1980. Smallest date/time ZIP can handle. 042 * <p> 043 * MS-DOS records file dates and times as packed 16-bit values. An MS-DOS date has the following format. 044 * </p> 045 * <p> 046 * Bits Contents 047 * </p> 048 * <ul> 049 * <li>0-4: Day of the month (1-31).</li> 050 * <li>5-8: Month (1 = January, 2 = February, and so on).</li> 051 * <li>9-15: Year offset from 1980 (add 1980 to get the actual year).</li> 052 * </ul> 053 * 054 * An MS-DOS time has the following format. 055 * <p> 056 * Bits Contents 057 * </p> 058 * <ul> 059 * <li>0-4: Second divided by 2.</li> 060 * <li>5-10: Minute (0-59).</li> 061 * <li>11-15: Hour (0-23 on a 24-hour clock).</li> 062 * </ul> 063 * 064 * This constant expresses the minimum DOS date of January 1st 1980 at 00:00:00 or, bit-by-bit: 065 * <ul> 066 * <li>Year: 0000000</li> 067 * <li>Month: 0001</li> 068 * <li>Day: 00001</li> 069 * <li>Hour: 00000</li> 070 * <li>Minute: 000000</li> 071 * <li>Seconds: 00000</li> 072 * </ul> 073 * 074 * <p> 075 * This was copied from {@link ZipEntry}. 076 * </p> 077 * 078 * @since 1.23 079 */ 080 private static final long DOSTIME_BEFORE_1980 = 1 << 21 | 1 << 16; // 0x210000 081 082 /** Java time representation of the smallest date/time ZIP can handle */ 083 private static final long DOSTIME_BEFORE_1980_AS_JAVA_TIME = dosToJavaTime(DOSTIME_BEFORE_1980); 084 085 /** 086 * Approximately 128 years, in milliseconds (ignoring leap years, etc.). 087 * 088 * <p> 089 * This establish an approximate high-bound value for DOS times in milliseconds since epoch, used to enable an efficient but sufficient bounds check to 090 * avoid generating extended last modified time entries. 091 * </p> 092 * <p> 093 * Calculating the exact number is locale dependent, would require loading TimeZone data eagerly, and would make little practical sense. Since DOS times 094 * theoretically go to 2107 - with compatibility not guaranteed after 2099 - setting this to a time that is before but near 2099 should be sufficient. 095 * </p> 096 * 097 * <p> 098 * This was copied from {@link ZipEntry}. 099 * </p> 100 * 101 * @since 1.23 102 */ 103 private static final long UPPER_DOSTIME_BOUND = 128L * 365 * 24 * 60 * 60 * 1000; 104 105 /** 106 * Assumes a negative integer really is a positive integer that has wrapped around and re-creates the original value. 107 * 108 * @param i the value to treat as unsigned int. 109 * @return the unsigned int as a long. 110 */ 111 public static long adjustToLong(final int i) { 112 if (i < 0) { 113 return 2 * (long) Integer.MAX_VALUE + 2 + i; 114 } 115 return i; 116 } 117 118 /** 119 * Tests if this library is able to read or write the given entry. 120 */ 121 static boolean canHandleEntryData(final ZipArchiveEntry entry) { 122 return supportsEncryptionOf(entry) && supportsMethodOf(entry); 123 } 124 125 /** 126 * Checks whether the entry requires features not (yet) supported by the library and throws an exception if it does. 127 */ 128 static void checkRequestedFeatures(final ZipArchiveEntry ze) throws UnsupportedZipFeatureException { 129 if (!supportsEncryptionOf(ze)) { 130 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.ENCRYPTION, ze); 131 } 132 if (!supportsMethodOf(ze)) { 133 final ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod()); 134 if (m == null) { 135 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.METHOD, ze); 136 } 137 throw new UnsupportedZipFeatureException(m, ze); 138 } 139 } 140 141 /** 142 * Creates a copy of the given array - or return null if the argument is null. 143 */ 144 static byte[] copy(final byte[] from) { 145 if (from != null) { 146 return Arrays.copyOf(from, from.length); 147 } 148 return null; 149 } 150 151 static void copy(final byte[] from, final byte[] to, final int offset) { 152 if (from != null) { 153 System.arraycopy(from, 0, to, offset, from.length); 154 } 155 } 156 157 private static Date dosToJavaDate(final long dosTime) { 158 final Calendar cal = Calendar.getInstance(); 159 // CheckStyle:MagicNumberCheck OFF - no point 160 cal.set(Calendar.YEAR, (int) (dosTime >> 25 & 0x7f) + 1980); 161 cal.set(Calendar.MONTH, (int) (dosTime >> 21 & 0x0f) - 1); 162 cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); 163 cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); 164 cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); 165 cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); 166 cal.set(Calendar.MILLISECOND, 0); 167 // CheckStyle:MagicNumberCheck ON 168 return cal.getTime(); 169 } 170 171 /** 172 * Converts DOS time to Java time (number of milliseconds since epoch). 173 * 174 * @param dosTime time to convert 175 * @return converted time 176 */ 177 public static long dosToJavaTime(final long dosTime) { 178 return dosToJavaDate(dosTime).getTime(); 179 } 180 181 /** 182 * Converts a DOS date/time field to a Date object. 183 * 184 * @param zipDosTime contains the stored DOS time. 185 * @return a Date instance corresponding to the given time. 186 */ 187 public static Date fromDosTime(final ZipLong zipDosTime) { 188 final long dosTime = zipDosTime.getValue(); 189 return dosToJavaDate(dosTime); 190 } 191 192 /** 193 * If the stored CRC matches the one of the given name, return the Unicode name of the given field. 194 * 195 * <p> 196 * If the field is null or the CRCs don't match, return null instead. 197 * </p> 198 */ 199 private static String getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField field, final byte[] originalNameBytes) { 200 if (field != null) { 201 final CRC32 crc32 = new CRC32(); 202 crc32.update(originalNameBytes); 203 final long origCRC32 = crc32.getValue(); 204 205 if (origCRC32 == field.getNameCRC32()) { 206 try { 207 return ZipEncodingHelper.ZIP_ENCODING_UTF_8.decode(field.getUnicodeName()); 208 } catch (final IOException ignored) { 209 // UTF-8 unsupported? should be impossible the 210 // Unicode*ExtraField must contain some bad bytes 211 } 212 } 213 } 214 // TODO log this anywhere? 215 return null; 216 } 217 218 /** 219 * Tests whether a given time (in milliseconds since Epoch) can be safely represented as DOS time 220 * 221 * @param time time in milliseconds since epoch 222 * @return true if the time can be safely represented as DOS time, false otherwise 223 * @since 1.23 224 */ 225 public static boolean isDosTime(final long time) { 226 return time <= UPPER_DOSTIME_BOUND && 227 (time == DOSTIME_BEFORE_1980_AS_JAVA_TIME || javaToDosTime(time) != DOSTIME_BEFORE_1980); 228 } 229 230 private static LocalDateTime javaEpochToLocalDateTime(final long time) { 231 final Instant instant = Instant.ofEpochMilli(time); 232 return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); 233 } 234 235 // version with integer overflow fixed - see https://bugs.openjdk.org/browse/JDK-8130914 236 private static long javaToDosTime(final long t) { 237 final LocalDateTime ldt = javaEpochToLocalDateTime(t); 238 if (ldt.getYear() < 1980) { 239 return DOSTIME_BEFORE_1980; 240 } 241 return (ldt.getYear() - 1980 << 25 | ldt.getMonthValue() << 21 | ldt.getDayOfMonth() << 16 | ldt.getHour() << 11 | ldt.getMinute() << 5 242 | ldt.getSecond() >> 1) & 0xffffffffL; 243 } 244 245 /** 246 * <p> 247 * Converts a long into a BigInteger. Negative numbers between -1 and -2^31 are treated as unsigned 32 bit (for example, positive) integers. Negative 248 * numbers below -2^31 cause an IllegalArgumentException to be thrown. 249 * </p> 250 * 251 * @param l long to convert to BigInteger. 252 * @return BigInteger representation of the provided long. 253 */ 254 static BigInteger longToBig(long l) { 255 if (l < Integer.MIN_VALUE) { 256 throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]"); 257 } 258 if (l < 0 && l >= Integer.MIN_VALUE) { 259 // If someone passes in a -2, they probably mean 4294967294 260 // (For example, Unix UID/GID's are 32 bit unsigned.) 261 l = adjustToLong((int) l); 262 } 263 return BigInteger.valueOf(l); 264 } 265 266 /** 267 * Constructs a new ZipException. 268 * 269 * @param message the detail message. 270 * @param cause throwable The cause of this Throwable. 271 * @return a new ZipException. 272 */ 273 static ZipException newZipException(final String message, final Throwable cause) { 274 return (ZipException) new ZipException(message).initCause(cause); 275 } 276 277 /** 278 * Reverses a byte[] array. Reverses in-place (thus provided array is mutated), but also returns same for convenience. 279 * 280 * @param array to reverse (mutated in-place, but also returned for convenience). 281 * @return the reversed array (mutated in-place, but also returned for convenience). 282 * @since 1.5 283 */ 284 public static byte[] reverse(final byte[] array) { 285 final int z = array.length - 1; // position of last element 286 for (int i = 0; i < array.length / 2; i++) { 287 final byte x = array[i]; 288 array[i] = array[z - i]; 289 array[z - i] = x; 290 } 291 return array; 292 } 293 294 /** 295 * If the entry has Unicode*ExtraFields and the CRCs of the names/comments match those of the extra fields, transfer the known Unicode values from the extra 296 * field. 297 */ 298 static void setNameAndCommentFromExtraFields(final ZipArchiveEntry ze, final byte[] originalNameBytes, final byte[] commentBytes) { 299 final ZipExtraField nameCandidate = ze.getExtraField(UnicodePathExtraField.UPATH_ID); 300 final UnicodePathExtraField name = nameCandidate instanceof UnicodePathExtraField ? (UnicodePathExtraField) nameCandidate : null; 301 final String newName = getUnicodeStringIfOriginalMatches(name, originalNameBytes); 302 if (newName != null) { 303 ze.setName(newName); 304 ze.setNameSource(ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD); 305 } 306 307 if (commentBytes != null && commentBytes.length > 0) { 308 final ZipExtraField cmtCandidate = ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); 309 final UnicodeCommentExtraField cmt = cmtCandidate instanceof UnicodeCommentExtraField ? (UnicodeCommentExtraField) cmtCandidate : null; 310 final String newComment = getUnicodeStringIfOriginalMatches(cmt, commentBytes); 311 if (newComment != null) { 312 ze.setComment(newComment); 313 ze.setCommentSource(ZipArchiveEntry.CommentSource.UNICODE_EXTRA_FIELD); 314 } 315 } 316 } 317 318 /** 319 * Converts a signed byte into an unsigned integer representation (for example, -1 becomes 255). 320 * 321 * @param b byte to convert to int 322 * @return int representation of the provided byte 323 * @since 1.5 324 * @deprecated Use {@link Byte#toUnsignedInt(byte)}. 325 */ 326 @Deprecated 327 public static int signedByteToUnsignedInt(final byte b) { 328 return Byte.toUnsignedInt(b); 329 } 330 331 /** 332 * Tests if this library supports the encryption used by the given entry. 333 * 334 * @return true if the entry isn't encrypted at all 335 */ 336 private static boolean supportsEncryptionOf(final ZipArchiveEntry entry) { 337 return !entry.getGeneralPurposeBit().usesEncryption(); 338 } 339 340 /** 341 * Tests if this library supports the compression method used by the given entry. 342 * 343 * @return true if the compression method is supported 344 */ 345 private static boolean supportsMethodOf(final ZipArchiveEntry entry) { 346 final int method = entry.getMethod(); 347 return method == ZipEntry.STORED || method == ZipMethod.UNSHRINKING.getCode() 348 || method == ZipMethod.IMPLODING.getCode() || method == ZipEntry.DEFLATED 349 || method == ZipMethod.ENHANCED_DEFLATED.getCode() || method == ZipMethod.BZIP2.getCode() 350 || ZipMethod.isZstd(method) 351 || method == ZipMethod.XZ.getCode(); 352 } 353 354 /** 355 * Converts a Date object to a DOS date/time field. 356 * 357 * @param time the {@code Date} to convert 358 * @return the date as a {@code ZipLong} 359 */ 360 public static ZipLong toDosTime(final Date time) { 361 return new ZipLong(toDosTime(time.getTime())); 362 } 363 364 /** 365 * Converts a Date object to a DOS date/time field. 366 * 367 * <p> 368 * Stolen from InfoZip's {@code fileio.c} 369 * </p> 370 * 371 * @param t number of milliseconds since the epoch 372 * @return the date as a byte array 373 */ 374 public static byte[] toDosTime(final long t) { 375 final byte[] result = new byte[4]; 376 toDosTime(t, result, 0); 377 return result; 378 } 379 380 /** 381 * Converts a Date object to a DOS date/time field. 382 * 383 * <p> 384 * Stolen from InfoZip's {@code fileio.c} 385 * </p> 386 * 387 * @param t number of milliseconds since the epoch 388 * @param buf the output buffer 389 * @param offset The offset within the output buffer of the first byte to be written. must be non-negative and no larger than {@code buf.length-4} 390 */ 391 public static void toDosTime(final long t, final byte[] buf, final int offset) { 392 ZipLong.putLong(javaToDosTime(t), buf, offset); 393 } 394 395 /** 396 * Converts a BigInteger to a long, and throws a NumberFormatException if the BigInteger is too big. 397 * 398 * @param big BigInteger to convert. 399 * @return {@code BigInteger} converted to a {@code long}. 400 */ 401 static long toLong(final BigInteger big) { 402 try { 403 return big.longValueExact(); 404 } catch (final ArithmeticException e) { 405 throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]"); 406 } 407 } 408 409 /** 410 * Converts an unsigned integer to a signed byte (for example, 255 becomes -1). 411 * 412 * @param i integer to convert to byte 413 * @return byte representation of the provided int 414 * @throws IllegalArgumentException if the provided integer is not inside the range [0,255]. 415 * @since 1.5 416 */ 417 public static byte unsignedIntToSignedByte(final int i) { 418 if (i > 255 || i < 0) { 419 throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]"); 420 } 421 if (i < 128) { 422 return (byte) i; 423 } 424 return (byte) (i - 256); 425 } 426}