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