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}