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.util.Calendar; 022 import java.util.Date; 023 import java.util.zip.CRC32; 024 import java.util.zip.ZipEntry; 025 026 /** 027 * Utility class for handling DOS and Java time conversions. 028 * @Immutable 029 */ 030 public abstract class ZipUtil { 031 /** 032 * Smallest date/time ZIP can handle. 033 */ 034 private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L); 035 036 /** 037 * Convert a Date object to a DOS date/time field. 038 * @param time the <code>Date</code> to convert 039 * @return the date as a <code>ZipLong</code> 040 */ 041 public static ZipLong toDosTime(Date time) { 042 return new ZipLong(toDosTime(time.getTime())); 043 } 044 045 /** 046 * Convert a Date object to a DOS date/time field. 047 * 048 * <p>Stolen from InfoZip's <code>fileio.c</code></p> 049 * @param t number of milliseconds since the epoch 050 * @return the date as a byte array 051 */ 052 public static byte[] toDosTime(long t) { 053 Calendar c = Calendar.getInstance(); 054 c.setTimeInMillis(t); 055 056 int year = c.get(Calendar.YEAR); 057 if (year < 1980) { 058 return copy(DOS_TIME_MIN); // stop callers from changing the array 059 } 060 int month = c.get(Calendar.MONTH) + 1; 061 long value = ((year - 1980) << 25) 062 | (month << 21) 063 | (c.get(Calendar.DAY_OF_MONTH) << 16) 064 | (c.get(Calendar.HOUR_OF_DAY) << 11) 065 | (c.get(Calendar.MINUTE) << 5) 066 | (c.get(Calendar.SECOND) >> 1); 067 return ZipLong.getBytes(value); 068 } 069 070 /** 071 * Assumes a negative integer really is a positive integer that 072 * has wrapped around and re-creates the original value. 073 * 074 * <p>This methods is no longer used as of Apache Commons Compress 075 * 1.3</p> 076 * 077 * @param i the value to treat as unsigned int. 078 * @return the unsigned int as a long. 079 */ 080 public static long adjustToLong(int i) { 081 if (i < 0) { 082 return 2 * ((long) Integer.MAX_VALUE) + 2 + i; 083 } else { 084 return i; 085 } 086 } 087 088 /** 089 * Convert a DOS date/time field to a Date object. 090 * 091 * @param zipDosTime contains the stored DOS time. 092 * @return a Date instance corresponding to the given time. 093 */ 094 public static Date fromDosTime(ZipLong zipDosTime) { 095 long dosTime = zipDosTime.getValue(); 096 return new Date(dosToJavaTime(dosTime)); 097 } 098 099 /** 100 * Converts DOS time to Java time (number of milliseconds since 101 * epoch). 102 */ 103 public static long dosToJavaTime(long dosTime) { 104 Calendar cal = Calendar.getInstance(); 105 // CheckStyle:MagicNumberCheck OFF - no point 106 cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980); 107 cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1); 108 cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f); 109 cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f); 110 cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f); 111 cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e); 112 // CheckStyle:MagicNumberCheck ON 113 return cal.getTime().getTime(); 114 } 115 116 /** 117 * If the entry has Unicode*ExtraFields and the CRCs of the 118 * names/comments match those of the extra fields, transfer the 119 * known Unicode values from the extra field. 120 */ 121 static void setNameAndCommentFromExtraFields(ZipArchiveEntry ze, 122 byte[] originalNameBytes, 123 byte[] commentBytes) { 124 UnicodePathExtraField name = (UnicodePathExtraField) 125 ze.getExtraField(UnicodePathExtraField.UPATH_ID); 126 String originalName = ze.getName(); 127 String newName = getUnicodeStringIfOriginalMatches(name, 128 originalNameBytes); 129 if (newName != null && !originalName.equals(newName)) { 130 ze.setName(newName); 131 } 132 133 if (commentBytes != null && commentBytes.length > 0) { 134 UnicodeCommentExtraField cmt = (UnicodeCommentExtraField) 135 ze.getExtraField(UnicodeCommentExtraField.UCOM_ID); 136 String newComment = 137 getUnicodeStringIfOriginalMatches(cmt, commentBytes); 138 if (newComment != null) { 139 ze.setComment(newComment); 140 } 141 } 142 } 143 144 /** 145 * If the stored CRC matches the one of the given name, return the 146 * Unicode name of the given field. 147 * 148 * <p>If the field is null or the CRCs don't match, return null 149 * instead.</p> 150 */ 151 private static 152 String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f, 153 byte[] orig) { 154 if (f != null) { 155 CRC32 crc32 = new CRC32(); 156 crc32.update(orig); 157 long origCRC32 = crc32.getValue(); 158 159 if (origCRC32 == f.getNameCRC32()) { 160 try { 161 return ZipEncodingHelper 162 .UTF8_ZIP_ENCODING.decode(f.getUnicodeName()); 163 } catch (IOException ex) { 164 // UTF-8 unsupported? should be impossible the 165 // Unicode*ExtraField must contain some bad bytes 166 167 // TODO log this anywhere? 168 return null; 169 } 170 } 171 } 172 return null; 173 } 174 175 /** 176 * Create a copy of the given array - or return null if the 177 * argument is null. 178 */ 179 static byte[] copy(byte[] from) { 180 if (from != null) { 181 byte[] to = new byte[from.length]; 182 System.arraycopy(from, 0, to, 0, to.length); 183 return to; 184 } 185 return null; 186 } 187 188 /** 189 * Whether this library is able to read or write the given entry. 190 */ 191 static boolean canHandleEntryData(ZipArchiveEntry entry) { 192 return supportsEncryptionOf(entry) && supportsMethodOf(entry); 193 } 194 195 /** 196 * Whether this library supports the encryption used by the given 197 * entry. 198 * 199 * @return true if the entry isn't encrypted at all 200 */ 201 private static boolean supportsEncryptionOf(ZipArchiveEntry entry) { 202 return !entry.getGeneralPurposeBit().usesEncryption(); 203 } 204 205 /** 206 * Whether this library supports the compression method used by 207 * the given entry. 208 * 209 * @return true if the compression method is STORED or DEFLATED 210 */ 211 private static boolean supportsMethodOf(ZipArchiveEntry entry) { 212 return entry.getMethod() == ZipEntry.STORED 213 || entry.getMethod() == ZipEntry.DEFLATED; 214 } 215 216 /** 217 * Checks whether the entry requires features not (yet) supported 218 * by the library and throws an exception if it does. 219 */ 220 static void checkRequestedFeatures(ZipArchiveEntry ze) 221 throws UnsupportedZipFeatureException { 222 if (!supportsEncryptionOf(ze)) { 223 throw 224 new UnsupportedZipFeatureException(UnsupportedZipFeatureException 225 .Feature.ENCRYPTION, ze); 226 } 227 if (!supportsMethodOf(ze)) { 228 throw 229 new UnsupportedZipFeatureException(UnsupportedZipFeatureException 230 .Feature.METHOD, ze); 231 } 232 } 233 }