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