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.util.Calendar; 023import java.util.Date; 024import java.util.zip.CRC32; 025import java.util.zip.ZipEntry; 026 027/** 028 * Utility class for handling DOS and Java time conversions. 029 * @Immutable 030 */ 031public abstract class ZipUtil { 032 /** 033 * Smallest date/time ZIP can handle. 034 */ 035 private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L); 036 037 /** 038 * Convert a Date object to a DOS date/time field. 039 * @param time the <code>Date</code> to convert 040 * @return the date as a <code>ZipLong</code> 041 */ 042 public static ZipLong toDosTime(Date time) { 043 return new ZipLong(toDosTime(time.getTime())); 044 } 045 046 /** 047 * Convert a Date object to a DOS date/time field. 048 * 049 * <p>Stolen from InfoZip's <code>fileio.c</code></p> 050 * @param t number of milliseconds since the epoch 051 * @return the date as a byte array 052 */ 053 public static byte[] toDosTime(long t) { 054 byte[] result = new byte[4]; 055 toDosTime(t, result, 0); 056 return result; 057 } 058 059 /** 060 * Convert a Date object to a DOS date/time field. 061 * 062 * <p>Stolen from InfoZip's <code>fileio.c</code></p> 063 * @param t number of milliseconds since the epoch 064 * @param buf the output buffer 065 * @param offset 066 * The offset within the output buffer of the first byte to be written. 067 * must be non-negative and no larger than <tt>buf.length-4</tt> 068 */ 069 public static void toDosTime(long t, byte[] buf, int offset) { 070 toDosTime(Calendar.getInstance(), t, buf, offset); 071 } 072 073 static void toDosTime(Calendar c, long t, byte[] buf, int offset) { 074 c.setTimeInMillis(t); 075 076 int year = c.get(Calendar.YEAR); 077 if (year < 1980) { 078 System.arraycopy(DOS_TIME_MIN, 0, buf, offset, DOS_TIME_MIN.length);// stop callers from changing the array 079 return; 080 } 081 int month = c.get(Calendar.MONTH) + 1; 082 long value = ((year - 1980) << 25) 083 | (month << 21) 084 | (c.get(Calendar.DAY_OF_MONTH) << 16) 085 | (c.get(Calendar.HOUR_OF_DAY) << 11) 086 | (c.get(Calendar.MINUTE) << 5) 087 | (c.get(Calendar.SECOND) >> 1); 088 ZipLong.putLong(value, buf, offset); 089 } 090 091 092 /** 093 * Assumes a negative integer really is a positive integer that 094 * has wrapped around and re-creates the original value. 095 * 096 * @param i the value to treat as unsigned int. 097 * @return the unsigned int as a long. 098 */ 099 public static long adjustToLong(int i) { 100 if (i < 0) { 101 return 2 * ((long) Integer.MAX_VALUE) + 2 + i; 102 } else { 103 return i; 104 } 105 } 106 107 /** 108 * Reverses a byte[] array. Reverses in-place (thus provided array is 109 * mutated), but also returns same for convenience. 110 * 111 * @param array to reverse (mutated in-place, but also returned for 112 * convenience). 113 * 114 * @return the reversed array (mutated in-place, but also returned for 115 * convenience). 116 * @since 1.5 117 */ 118 public static byte[] reverse(final byte[] array) { 119 final int z = array.length - 1; // position of last element 120 for (int i = 0; i < array.length / 2; i++) { 121 byte x = array[i]; 122 array[i] = array[z - i]; 123 array[z - i] = x; 124 } 125 return array; 126 } 127 128 /** 129 * Converts a BigInteger into a long, and blows up 130 * (NumberFormatException) if the BigInteger is too big. 131 * 132 * @param big BigInteger to convert. 133 * @return long representation of the BigInteger. 134 */ 135 static long bigToLong(BigInteger big) { 136 if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit. 137 return big.longValue(); 138 } else { 139 throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]"); 140 } 141 } 142 143 /** 144 * <p> 145 * Converts a long into a BigInteger. Negative numbers between -1 and 146 * -2^31 are treated as unsigned 32 bit (e.g., positive) integers. 147 * Negative numbers below -2^31 cause an IllegalArgumentException 148 * to be thrown. 149 * </p> 150 * 151 * @param l long to convert to BigInteger. 152 * @return BigInteger representation of the provided long. 153 */ 154 static BigInteger longToBig(long l) { 155 if (l < Integer.MIN_VALUE) { 156 throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]"); 157 } else if (l < 0 && l >= Integer.MIN_VALUE) { 158 // If someone passes in a -2, they probably mean 4294967294 159 // (For example, Unix UID/GID's are 32 bit unsigned.) 160 l = ZipUtil.adjustToLong((int) l); 161 } 162 return BigInteger.valueOf(l); 163 } 164 165 /** 166 * Converts a signed byte into an unsigned integer representation 167 * (e.g., -1 becomes 255). 168 * 169 * @param b byte to convert to int 170 * @return int representation of the provided byte 171 * @since 1.5 172 */ 173 public static int signedByteToUnsignedInt(byte b) { 174 if (b >= 0) { 175 return b; 176 } else { 177 return 256 + b; 178 } 179 } 180 181 /** 182 * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1). 183 * 184 * @param i integer to convert to byte 185 * @return byte representation of the provided int 186 * @throws IllegalArgumentException if the provided integer is not inside the range [0,255]. 187 * @since 1.5 188 */ 189 public static byte unsignedIntToSignedByte(int i) { 190 if (i > 255 || i < 0) { 191 throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]"); 192 } 193 if (i < 128) { 194 return (byte) i; 195 } else { 196 return (byte) (i - 256); 197 } 198 } 199 200 /** 201 * Convert a DOS date/time field to a Date object. 202 * 203 * @param zipDosTime contains the stored DOS time. 204 * @return a Date instance corresponding to the given time. 205 */ 206 public static Date fromDosTime(ZipLong zipDosTime) { 207 long dosTime = zipDosTime.getValue(); 208 return new Date(dosToJavaTime(dosTime)); 209 } 210 211 /** 212 * Converts DOS time to Java time (number of milliseconds since 213 * epoch). 214 */ 215 public static long dosToJavaTime(long dosTime) { 216 Calendar cal = Calendar.getInstance(); 217 // CheckStyle:MagicNumberCheck OFF - no point 218 cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980); 219 cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1); 220 cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); 221 cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); 222 cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); 223 cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); 224 cal.set(Calendar.MILLISECOND, 0); 225 // CheckStyle:MagicNumberCheck ON 226 return cal.getTime().getTime(); 227 } 228 229 /** 230 * If the entry has Unicode*ExtraFields and the CRCs of the 231 * names/comments match those of the extra fields, transfer the 232 * known Unicode values from the extra field. 233 */ 234 static void setNameAndCommentFromExtraFields(ZipArchiveEntry ze, 235 byte[] originalNameBytes, 236 byte[] commentBytes) { 237 UnicodePathExtraField name = (UnicodePathExtraField) 238 ze.getExtraField(UnicodePathExtraField.UPATH_ID); 239 String originalName = ze.getName(); 240 String newName = getUnicodeStringIfOriginalMatches(name, 241 originalNameBytes); 242 if (newName != null && !originalName.equals(newName)) { 243 ze.setName(newName); 244 } 245 246 if (commentBytes != null && commentBytes.length > 0) { 247 UnicodeCommentExtraField cmt = (UnicodeCommentExtraField) 248 ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); 249 String newComment = 250 getUnicodeStringIfOriginalMatches(cmt, commentBytes); 251 if (newComment != null) { 252 ze.setComment(newComment); 253 } 254 } 255 } 256 257 /** 258 * If the stored CRC matches the one of the given name, return the 259 * Unicode name of the given field. 260 * 261 * <p>If the field is null or the CRCs don't match, return null 262 * instead.</p> 263 */ 264 private static 265 String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f, 266 byte[] orig) { 267 if (f != null) { 268 CRC32 crc32 = new CRC32(); 269 crc32.update(orig); 270 long origCRC32 = crc32.getValue(); 271 272 if (origCRC32 == f.getNameCRC32()) { 273 try { 274 return ZipEncodingHelper 275 .UTF8_ZIP_ENCODING.decode(f.getUnicodeName()); 276 } catch (IOException ex) { 277 // UTF-8 unsupported? should be impossible the 278 // Unicode*ExtraField must contain some bad bytes 279 280 // TODO log this anywhere? 281 return null; 282 } 283 } 284 } 285 return null; 286 } 287 288 /** 289 * Create a copy of the given array - or return null if the 290 * argument is null. 291 */ 292 static byte[] copy(byte[] from) { 293 if (from != null) { 294 byte[] to = new byte[from.length]; 295 System.arraycopy(from, 0, to, 0, to.length); 296 return to; 297 } 298 return null; 299 } 300 static void copy(byte[] from, byte[] to, 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 */ 310 static boolean canHandleEntryData(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 */ 320 private static boolean supportsEncryptionOf(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 STORED or DEFLATED 329 */ 330 private static boolean supportsMethodOf(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 } 336 337 /** 338 * Checks whether the entry requires features not (yet) supported 339 * by the library and throws an exception if it does. 340 */ 341 static void checkRequestedFeatures(ZipArchiveEntry ze) 342 throws UnsupportedZipFeatureException { 343 if (!supportsEncryptionOf(ze)) { 344 throw 345 new UnsupportedZipFeatureException(UnsupportedZipFeatureException 346 .Feature.ENCRYPTION, ze); 347 } 348 if (!supportsMethodOf(ze)) { 349 ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod()); 350 if (m == null) { 351 throw 352 new UnsupportedZipFeatureException(UnsupportedZipFeatureException 353 .Feature.METHOD, ze); 354 } else { 355 throw new UnsupportedZipFeatureException(m, ze); 356 } 357 } 358 } 359}