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