1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 package org.apache.commons.compress.archivers.zip; 19 20 import java.io.IOException; 21 import java.math.BigInteger; 22 import java.util.Calendar; 23 import java.util.Date; 24 import java.util.zip.CRC32; 25 import java.util.zip.ZipEntry; 26 27 /** 28 * Utility class for handling DOS and Java time conversions. 29 * @Immutable 30 */ 31 public abstract class ZipUtil { 32 /** 33 * Smallest date/time ZIP can handle. 34 */ 35 private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L); 36 37 /** 38 * Convert a Date object to a DOS date/time field. 39 * @param time the <code>Date</code> to convert 40 * @return the date as a <code>ZipLong</code> 41 */ toDosTime(final Date time)42 public static ZipLong toDosTime(final Date time) { 43 return new ZipLong(toDosTime(time.getTime())); 44 } 45 46 /** 47 * Convert a Date object to a DOS date/time field. 48 * 49 * <p>Stolen from InfoZip's <code>fileio.c</code></p> 50 * @param t number of milliseconds since the epoch 51 * @return the date as a byte array 52 */ toDosTime(final long t)53 public static byte[] toDosTime(final long t) { 54 final byte[] result = new byte[4]; 55 toDosTime(t, result, 0); 56 return result; 57 } 58 59 /** 60 * Convert a Date object to a DOS date/time field. 61 * 62 * <p>Stolen from InfoZip's <code>fileio.c</code></p> 63 * @param t number of milliseconds since the epoch 64 * @param buf the output buffer 65 * @param offset 66 * The offset within the output buffer of the first byte to be written. 67 * must be non-negative and no larger than <tt>buf.length-4</tt> 68 */ toDosTime(final long t, final byte[] buf, final int offset)69 public static void toDosTime(final long t, final byte[] buf, final int offset) { 70 toDosTime(Calendar.getInstance(), t, buf, offset); 71 } 72 toDosTime(final Calendar c, final long t, final byte[] buf, final int offset)73 static void toDosTime(final Calendar c, final long t, final byte[] buf, final int offset) { 74 c.setTimeInMillis(t); 75 76 final int year = c.get(Calendar.YEAR); 77 if (year < 1980) { 78 System.arraycopy(DOS_TIME_MIN, 0, buf, offset, DOS_TIME_MIN.length);// stop callers from changing the array 79 return; 80 } 81 final int month = c.get(Calendar.MONTH) + 1; 82 final long value = ((year - 1980) << 25) 83 | (month << 21) 84 | (c.get(Calendar.DAY_OF_MONTH) << 16) 85 | (c.get(Calendar.HOUR_OF_DAY) << 11) 86 | (c.get(Calendar.MINUTE) << 5) 87 | (c.get(Calendar.SECOND) >> 1); 88 ZipLong.putLong(value, buf, offset); 89 } 90 91 92 /** 93 * Assumes a negative integer really is a positive integer that 94 * has wrapped around and re-creates the original value. 95 * 96 * @param i the value to treat as unsigned int. 97 * @return the unsigned int as a long. 98 */ adjustToLong(final int i)99 public static long adjustToLong(final int i) { 100 if (i < 0) { 101 return 2 * ((long) Integer.MAX_VALUE) + 2 + i; 102 } 103 return i; 104 } 105 106 /** 107 * Reverses a byte[] array. Reverses in-place (thus provided array is 108 * mutated), but also returns same for convenience. 109 * 110 * @param array to reverse (mutated in-place, but also returned for 111 * convenience). 112 * 113 * @return the reversed array (mutated in-place, but also returned for 114 * convenience). 115 * @since 1.5 116 */ reverse(final byte[] array)117 public static byte[] reverse(final byte[] array) { 118 final int z = array.length - 1; // position of last element 119 for (int i = 0; i < array.length / 2; i++) { 120 final byte x = array[i]; 121 array[i] = array[z - i]; 122 array[z - i] = x; 123 } 124 return array; 125 } 126 127 /** 128 * Converts a BigInteger into a long, and blows up 129 * (NumberFormatException) if the BigInteger is too big. 130 * 131 * @param big BigInteger to convert. 132 * @return long representation of the BigInteger. 133 */ bigToLong(final BigInteger big)134 static long bigToLong(final BigInteger big) { 135 if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit. 136 return big.longValue(); 137 } 138 throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]"); 139 } 140 141 /** 142 * <p> 143 * Converts a long into a BigInteger. Negative numbers between -1 and 144 * -2^31 are treated as unsigned 32 bit (e.g., positive) integers. 145 * Negative numbers below -2^31 cause an IllegalArgumentException 146 * to be thrown. 147 * </p> 148 * 149 * @param l long to convert to BigInteger. 150 * @return BigInteger representation of the provided long. 151 */ longToBig(long l)152 static BigInteger longToBig(long l) { 153 if (l < Integer.MIN_VALUE) { 154 throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]"); 155 } else if (l < 0 && l >= Integer.MIN_VALUE) { 156 // If someone passes in a -2, they probably mean 4294967294 157 // (For example, Unix UID/GID's are 32 bit unsigned.) 158 l = ZipUtil.adjustToLong((int) l); 159 } 160 return BigInteger.valueOf(l); 161 } 162 163 /** 164 * Converts a signed byte into an unsigned integer representation 165 * (e.g., -1 becomes 255). 166 * 167 * @param b byte to convert to int 168 * @return int representation of the provided byte 169 * @since 1.5 170 */ signedByteToUnsignedInt(final byte b)171 public static int signedByteToUnsignedInt(final byte b) { 172 if (b >= 0) { 173 return b; 174 } 175 return 256 + b; 176 } 177 178 /** 179 * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1). 180 * 181 * @param i integer to convert to byte 182 * @return byte representation of the provided int 183 * @throws IllegalArgumentException if the provided integer is not inside the range [0,255]. 184 * @since 1.5 185 */ unsignedIntToSignedByte(final int i)186 public static byte unsignedIntToSignedByte(final int i) { 187 if (i > 255 || i < 0) { 188 throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]"); 189 } 190 if (i < 128) { 191 return (byte) i; 192 } 193 return (byte) (i - 256); 194 } 195 196 /** 197 * Convert a DOS date/time field to a Date object. 198 * 199 * @param zipDosTime contains the stored DOS time. 200 * @return a Date instance corresponding to the given time. 201 */ fromDosTime(final ZipLong zipDosTime)202 public static Date fromDosTime(final ZipLong zipDosTime) { 203 final long dosTime = zipDosTime.getValue(); 204 return new Date(dosToJavaTime(dosTime)); 205 } 206 207 /** 208 * Converts DOS time to Java time (number of milliseconds since 209 * epoch). 210 * @param dosTime time to convert 211 * @return converted time 212 */ dosToJavaTime(final long dosTime)213 public static long dosToJavaTime(final long dosTime) { 214 final Calendar cal = Calendar.getInstance(); 215 // CheckStyle:MagicNumberCheck OFF - no point 216 cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980); 217 cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1); 218 cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); 219 cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); 220 cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); 221 cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); 222 cal.set(Calendar.MILLISECOND, 0); 223 // CheckStyle:MagicNumberCheck ON 224 return cal.getTime().getTime(); 225 } 226 227 /** 228 * If the entry has Unicode*ExtraFields and the CRCs of the 229 * names/comments match those of the extra fields, transfer the 230 * known Unicode values from the extra field. 231 */ setNameAndCommentFromExtraFields(final ZipArchiveEntry ze, final byte[] originalNameBytes, final byte[] commentBytes)232 static void setNameAndCommentFromExtraFields(final ZipArchiveEntry ze, 233 final byte[] originalNameBytes, 234 final byte[] commentBytes) { 235 final UnicodePathExtraField name = (UnicodePathExtraField) 236 ze.getExtraField(UnicodePathExtraField.UPATH_ID); 237 final String newName = getUnicodeStringIfOriginalMatches(name, 238 originalNameBytes); 239 if (newName != null) { 240 ze.setName(newName); 241 ze.setNameSource(ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD); 242 } 243 244 if (commentBytes != null && commentBytes.length > 0) { 245 final UnicodeCommentExtraField cmt = (UnicodeCommentExtraField) 246 ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); 247 final String newComment = 248 getUnicodeStringIfOriginalMatches(cmt, commentBytes); 249 if (newComment != null) { 250 ze.setComment(newComment); 251 ze.setCommentSource(ZipArchiveEntry.CommentSource.UNICODE_EXTRA_FIELD); 252 } 253 } 254 } 255 256 /** 257 * If the stored CRC matches the one of the given name, return the 258 * Unicode name of the given field. 259 * 260 * <p>If the field is null or the CRCs don't match, return null 261 * instead.</p> 262 */ 263 private static getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f, final byte[] orig)264 String getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f, 265 final byte[] orig) { 266 if (f != null) { 267 final CRC32 crc32 = new CRC32(); 268 crc32.update(orig); 269 final long origCRC32 = crc32.getValue(); 270 271 if (origCRC32 == f.getNameCRC32()) { 272 try { 273 return ZipEncodingHelper 274 .UTF8_ZIP_ENCODING.decode(f.getUnicodeName()); 275 } catch (final IOException ex) { 276 // UTF-8 unsupported? should be impossible the 277 // Unicode*ExtraField must contain some bad bytes 278 279 // TODO log this anywhere? 280 return null; 281 } 282 } 283 } 284 return null; 285 } 286 287 /** 288 * Create a copy of the given array - or return null if the 289 * argument is null. 290 */ copy(final byte[] from)291 static byte[] copy(final byte[] from) { 292 if (from != null) { 293 final byte[] to = new byte[from.length]; 294 System.arraycopy(from, 0, to, 0, to.length); 295 return to; 296 } 297 return null; 298 } 299 copy(final byte[] from, final byte[] to, final int offset)300 static void copy(final byte[] from, final byte[] to, final int offset) { 301 if (from != null) { 302 System.arraycopy(from, 0, to, offset, from.length); 303 } 304 } 305 306 307 /** 308 * Whether this library is able to read or write the given entry. 309 */ canHandleEntryData(final ZipArchiveEntry entry)310 static boolean canHandleEntryData(final ZipArchiveEntry entry) { 311 return supportsEncryptionOf(entry) && supportsMethodOf(entry); 312 } 313 314 /** 315 * Whether this library supports the encryption used by the given 316 * entry. 317 * 318 * @return true if the entry isn't encrypted at all 319 */ supportsEncryptionOf(final ZipArchiveEntry entry)320 private static boolean supportsEncryptionOf(final ZipArchiveEntry entry) { 321 return !entry.getGeneralPurposeBit().usesEncryption(); 322 } 323 324 /** 325 * Whether this library supports the compression method used by 326 * the given entry. 327 * 328 * @return true if the compression method is supported 329 */ supportsMethodOf(final ZipArchiveEntry entry)330 private static boolean supportsMethodOf(final ZipArchiveEntry entry) { 331 return entry.getMethod() == ZipEntry.STORED 332 || entry.getMethod() == ZipMethod.UNSHRINKING.getCode() 333 || entry.getMethod() == ZipMethod.IMPLODING.getCode() 334 || entry.getMethod() == ZipEntry.DEFLATED 335 || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode() 336 || entry.getMethod() == ZipMethod.BZIP2.getCode(); 337 } 338 339 /** 340 * Checks whether the entry requires features not (yet) supported 341 * by the library and throws an exception if it does. 342 */ checkRequestedFeatures(final ZipArchiveEntry ze)343 static void checkRequestedFeatures(final ZipArchiveEntry ze) 344 throws UnsupportedZipFeatureException { 345 if (!supportsEncryptionOf(ze)) { 346 throw 347 new UnsupportedZipFeatureException(UnsupportedZipFeatureException 348 .Feature.ENCRYPTION, ze); 349 } 350 if (!supportsMethodOf(ze)) { 351 final ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod()); 352 if (m == null) { 353 throw 354 new UnsupportedZipFeatureException(UnsupportedZipFeatureException 355 .Feature.METHOD, ze); 356 } 357 throw new UnsupportedZipFeatureException(m, ze); 358 } 359 } 360 } 361