• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  */
18 package org.apache.commons.compress.archivers.zip;
19 
20 import java.io.IOException;
21 import java.math.BigInteger;
22 import java.util.Calendar;
23 import java.util.Date;
24 import java.util.zip.CRC32;
25 import java.util.zip.ZipEntry;
26 
27 /**
28  * Utility class for handling DOS and Java time conversions.
29  * @Immutable
30  */
31 public abstract class ZipUtil {
32     /**
33      * Smallest date/time ZIP can handle.
34      */
35     private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
36 
37     /**
38      * Convert a Date object to a DOS date/time field.
39      * @param time the <code>Date</code> to convert
40      * @return the date as a <code>ZipLong</code>
41      */
toDosTime(final Date time)42     public static ZipLong toDosTime(final Date time) {
43         return new ZipLong(toDosTime(time.getTime()));
44     }
45 
46     /**
47      * Convert a Date object to a DOS date/time field.
48      *
49      * <p>Stolen from InfoZip's <code>fileio.c</code></p>
50      * @param t number of milliseconds since the epoch
51      * @return the date as a byte array
52      */
toDosTime(final long t)53     public static byte[] toDosTime(final long t) {
54         final byte[] result = new byte[4];
55         toDosTime(t, result, 0);
56         return result;
57     }
58 
59     /**
60      * Convert a Date object to a DOS date/time field.
61      *
62      * <p>Stolen from InfoZip's <code>fileio.c</code></p>
63      * @param t number of milliseconds since the epoch
64      * @param buf the output buffer
65      * @param offset
66      *         The offset within the output buffer of the first byte to be written.
67      *         must be non-negative and no larger than <tt>buf.length-4</tt>
68      */
toDosTime(final long t, final byte[] buf, final int offset)69     public static void toDosTime(final long t, final byte[] buf, final int offset) {
70         toDosTime(Calendar.getInstance(), t, buf, offset);
71     }
72 
toDosTime(final Calendar c, final long t, final byte[] buf, final int offset)73     static void toDosTime(final Calendar c, final long t, final byte[] buf, final int offset) {
74         c.setTimeInMillis(t);
75 
76         final int year = c.get(Calendar.YEAR);
77         if (year < 1980) {
78             System.arraycopy(DOS_TIME_MIN, 0, buf, offset, DOS_TIME_MIN.length);// stop callers from changing the array
79             return;
80         }
81         final int month = c.get(Calendar.MONTH) + 1;
82         final long value =  ((year - 1980) << 25)
83                 |         (month << 21)
84                 |         (c.get(Calendar.DAY_OF_MONTH) << 16)
85                 |         (c.get(Calendar.HOUR_OF_DAY) << 11)
86                 |         (c.get(Calendar.MINUTE) << 5)
87                 |         (c.get(Calendar.SECOND) >> 1);
88         ZipLong.putLong(value, buf, offset);
89     }
90 
91 
92     /**
93      * Assumes a negative integer really is a positive integer that
94      * has wrapped around and re-creates the original value.
95      *
96      * @param i the value to treat as unsigned int.
97      * @return the unsigned int as a long.
98      */
adjustToLong(final int i)99     public static long adjustToLong(final int i) {
100         if (i < 0) {
101             return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
102         }
103         return i;
104     }
105 
106     /**
107      * Reverses a byte[] array.  Reverses in-place (thus provided array is
108      * mutated), but also returns same for convenience.
109      *
110      * @param array to reverse (mutated in-place, but also returned for
111      *        convenience).
112      *
113      * @return the reversed array (mutated in-place, but also returned for
114      *        convenience).
115      * @since 1.5
116      */
reverse(final byte[] array)117     public static byte[] reverse(final byte[] array) {
118         final int z = array.length - 1; // position of last element
119         for (int i = 0; i < array.length / 2; i++) {
120             final byte x = array[i];
121             array[i] = array[z - i];
122             array[z - i] = x;
123         }
124         return array;
125     }
126 
127     /**
128      * Converts a BigInteger into a long, and blows up
129      * (NumberFormatException) if the BigInteger is too big.
130      *
131      * @param big BigInteger to convert.
132      * @return long representation of the BigInteger.
133      */
bigToLong(final BigInteger big)134     static long bigToLong(final BigInteger big) {
135         if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit.
136             return big.longValue();
137         }
138         throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]");
139     }
140 
141     /**
142      * <p>
143      * Converts a long into a BigInteger.  Negative numbers between -1 and
144      * -2^31 are treated as unsigned 32 bit (e.g., positive) integers.
145      * Negative numbers below -2^31 cause an IllegalArgumentException
146      * to be thrown.
147      * </p>
148      *
149      * @param l long to convert to BigInteger.
150      * @return BigInteger representation of the provided long.
151      */
longToBig(long l)152     static BigInteger longToBig(long l) {
153         if (l < Integer.MIN_VALUE) {
154             throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]");
155         } else if (l < 0 && l >= Integer.MIN_VALUE) {
156             // If someone passes in a -2, they probably mean 4294967294
157             // (For example, Unix UID/GID's are 32 bit unsigned.)
158             l = ZipUtil.adjustToLong((int) l);
159         }
160         return BigInteger.valueOf(l);
161     }
162 
163     /**
164      * Converts a signed byte into an unsigned integer representation
165      * (e.g., -1 becomes 255).
166      *
167      * @param b byte to convert to int
168      * @return int representation of the provided byte
169      * @since 1.5
170      */
signedByteToUnsignedInt(final byte b)171     public static int signedByteToUnsignedInt(final byte b) {
172         if (b >= 0) {
173             return b;
174         }
175         return 256 + b;
176     }
177 
178     /**
179      * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1).
180      *
181      * @param i integer to convert to byte
182      * @return byte representation of the provided int
183      * @throws IllegalArgumentException if the provided integer is not inside the range [0,255].
184      * @since 1.5
185      */
unsignedIntToSignedByte(final int i)186     public static byte unsignedIntToSignedByte(final int i) {
187         if (i > 255 || i < 0) {
188             throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]");
189         }
190         if (i < 128) {
191             return (byte) i;
192         }
193         return (byte) (i - 256);
194     }
195 
196     /**
197      * Convert a DOS date/time field to a Date object.
198      *
199      * @param zipDosTime contains the stored DOS time.
200      * @return a Date instance corresponding to the given time.
201      */
fromDosTime(final ZipLong zipDosTime)202     public static Date fromDosTime(final ZipLong zipDosTime) {
203         final long dosTime = zipDosTime.getValue();
204         return new Date(dosToJavaTime(dosTime));
205     }
206 
207     /**
208      * Converts DOS time to Java time (number of milliseconds since
209      * epoch).
210      * @param dosTime time to convert
211      * @return converted time
212      */
dosToJavaTime(final long dosTime)213     public static long dosToJavaTime(final long dosTime) {
214         final Calendar cal = Calendar.getInstance();
215         // CheckStyle:MagicNumberCheck OFF - no point
216         cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
217         cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
218         cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
219         cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
220         cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
221         cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
222         cal.set(Calendar.MILLISECOND, 0);
223         // CheckStyle:MagicNumberCheck ON
224         return cal.getTime().getTime();
225     }
226 
227     /**
228      * If the entry has Unicode*ExtraFields and the CRCs of the
229      * names/comments match those of the extra fields, transfer the
230      * known Unicode values from the extra field.
231      */
setNameAndCommentFromExtraFields(final ZipArchiveEntry ze, final byte[] originalNameBytes, final byte[] commentBytes)232     static void setNameAndCommentFromExtraFields(final ZipArchiveEntry ze,
233                                                  final byte[] originalNameBytes,
234                                                  final byte[] commentBytes) {
235         final UnicodePathExtraField name = (UnicodePathExtraField)
236             ze.getExtraField(UnicodePathExtraField.UPATH_ID);
237         final String newName = getUnicodeStringIfOriginalMatches(name,
238                                                            originalNameBytes);
239         if (newName != null) {
240             ze.setName(newName);
241             ze.setNameSource(ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
242         }
243 
244         if (commentBytes != null && commentBytes.length > 0) {
245             final UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)
246                 ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
247             final String newComment =
248                 getUnicodeStringIfOriginalMatches(cmt, commentBytes);
249             if (newComment != null) {
250                 ze.setComment(newComment);
251                 ze.setCommentSource(ZipArchiveEntry.CommentSource.UNICODE_EXTRA_FIELD);
252             }
253         }
254     }
255 
256     /**
257      * If the stored CRC matches the one of the given name, return the
258      * Unicode name of the given field.
259      *
260      * <p>If the field is null or the CRCs don't match, return null
261      * instead.</p>
262      */
263     private static
getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f, final byte[] orig)264         String getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f,
265                                                  final byte[] orig) {
266         if (f != null) {
267             final CRC32 crc32 = new CRC32();
268             crc32.update(orig);
269             final long origCRC32 = crc32.getValue();
270 
271             if (origCRC32 == f.getNameCRC32()) {
272                 try {
273                     return ZipEncodingHelper
274                         .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
275                 } catch (final IOException ex) {
276                     // UTF-8 unsupported?  should be impossible the
277                     // Unicode*ExtraField must contain some bad bytes
278 
279                     // TODO log this anywhere?
280                     return null;
281                 }
282             }
283         }
284         return null;
285     }
286 
287     /**
288      * Create a copy of the given array - or return null if the
289      * argument is null.
290      */
copy(final byte[] from)291     static byte[] copy(final byte[] from) {
292         if (from != null) {
293             final byte[] to = new byte[from.length];
294             System.arraycopy(from, 0, to, 0, to.length);
295             return to;
296         }
297         return null;
298     }
299 
copy(final byte[] from, final byte[] to, final int offset)300     static void copy(final byte[] from, final byte[] to, final int offset) {
301         if (from != null) {
302             System.arraycopy(from, 0, to, offset, from.length);
303         }
304     }
305 
306 
307     /**
308      * Whether this library is able to read or write the given entry.
309      */
canHandleEntryData(final ZipArchiveEntry entry)310     static boolean canHandleEntryData(final ZipArchiveEntry entry) {
311         return supportsEncryptionOf(entry) && supportsMethodOf(entry);
312     }
313 
314     /**
315      * Whether this library supports the encryption used by the given
316      * entry.
317      *
318      * @return true if the entry isn't encrypted at all
319      */
supportsEncryptionOf(final ZipArchiveEntry entry)320     private static boolean supportsEncryptionOf(final ZipArchiveEntry entry) {
321         return !entry.getGeneralPurposeBit().usesEncryption();
322     }
323 
324     /**
325      * Whether this library supports the compression method used by
326      * the given entry.
327      *
328      * @return true if the compression method is supported
329      */
supportsMethodOf(final ZipArchiveEntry entry)330     private static boolean supportsMethodOf(final ZipArchiveEntry entry) {
331         return entry.getMethod() == ZipEntry.STORED
332             || entry.getMethod() == ZipMethod.UNSHRINKING.getCode()
333             || entry.getMethod() == ZipMethod.IMPLODING.getCode()
334             || entry.getMethod() == ZipEntry.DEFLATED
335             || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode()
336             || entry.getMethod() == ZipMethod.BZIP2.getCode();
337     }
338 
339     /**
340      * Checks whether the entry requires features not (yet) supported
341      * by the library and throws an exception if it does.
342      */
checkRequestedFeatures(final ZipArchiveEntry ze)343     static void checkRequestedFeatures(final ZipArchiveEntry ze)
344         throws UnsupportedZipFeatureException {
345         if (!supportsEncryptionOf(ze)) {
346             throw
347                 new UnsupportedZipFeatureException(UnsupportedZipFeatureException
348                                                    .Feature.ENCRYPTION, ze);
349         }
350         if (!supportsMethodOf(ze)) {
351             final ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod());
352             if (m == null) {
353                 throw
354                     new UnsupportedZipFeatureException(UnsupportedZipFeatureException
355                                                        .Feature.METHOD, ze);
356             }
357             throw new UnsupportedZipFeatureException(m, ze);
358         }
359     }
360 }
361