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