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 }