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