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.Calendar;
023import java.util.Date;
024import java.util.zip.CRC32;
025import java.util.zip.ZipEntry;
026
027/**
028 * Utility class for handling DOS and Java time conversions.
029 * @Immutable
030 */
031public abstract class ZipUtil {
032    /**
033     * Smallest date/time ZIP can handle.
034     */
035    private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
036
037    /**
038     * Convert a Date object to a DOS date/time field.
039     * @param time the <code>Date</code> to convert
040     * @return the date as a <code>ZipLong</code>
041     */
042    public static ZipLong toDosTime(final Date time) {
043        return new ZipLong(toDosTime(time.getTime()));
044    }
045
046    /**
047     * Convert a Date object to a DOS date/time field.
048     *
049     * <p>Stolen from InfoZip's <code>fileio.c</code></p>
050     * @param t number of milliseconds since the epoch
051     * @return the date as a byte array
052     */
053    public static byte[] toDosTime(final long t) {
054        final byte[] result = new byte[4];
055        toDosTime(t, result, 0);
056        return result;
057    }
058
059    /**
060     * Convert a Date object to a DOS date/time field.
061     *
062     * <p>Stolen from InfoZip's <code>fileio.c</code></p>
063     * @param t number of milliseconds since the epoch
064     * @param buf the output buffer
065     * @param offset
066     *         The offset within the output buffer of the first byte to be written.
067     *         must be non-negative and no larger than <tt>buf.length-4</tt>
068     */
069    public static void toDosTime(final long t, final byte[] buf, final int offset) {
070        toDosTime(Calendar.getInstance(), t, buf, offset);
071    }
072
073    static void toDosTime(final Calendar c, final long t, final byte[] buf, final int offset) {
074        c.setTimeInMillis(t);
075
076        final int year = c.get(Calendar.YEAR);
077        if (year < 1980) {
078            System.arraycopy(DOS_TIME_MIN, 0, buf, offset, DOS_TIME_MIN.length);// stop callers from changing the array
079            return;
080        }
081        final int month = c.get(Calendar.MONTH) + 1;
082        final long value =  ((year - 1980) << 25)
083                |         (month << 21)
084                |         (c.get(Calendar.DAY_OF_MONTH) << 16)
085                |         (c.get(Calendar.HOUR_OF_DAY) << 11)
086                |         (c.get(Calendar.MINUTE) << 5)
087                |         (c.get(Calendar.SECOND) >> 1);
088        ZipLong.putLong(value, buf, offset);
089    }
090
091
092    /**
093     * Assumes a negative integer really is a positive integer that
094     * has wrapped around and re-creates the original value.
095     *
096     * @param i the value to treat as unsigned int.
097     * @return the unsigned int as a long.
098     */
099    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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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 originalName = ze.getName();
238        final String newName = getUnicodeStringIfOriginalMatches(name,
239                                                           originalNameBytes);
240        if (newName != null && !originalName.equals(newName)) {
241            ze.setName(newName);
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            }
252        }
253    }
254
255    /**
256     * If the stored CRC matches the one of the given name, return the
257     * Unicode name of the given field.
258     *
259     * <p>If the field is null or the CRCs don't match, return null
260     * instead.</p>
261     */
262    private static 
263        String getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f,
264                                                 final byte[] orig) {
265        if (f != null) {
266            final CRC32 crc32 = new CRC32();
267            crc32.update(orig);
268            final long origCRC32 = crc32.getValue();
269
270            if (origCRC32 == f.getNameCRC32()) {
271                try {
272                    return ZipEncodingHelper
273                        .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
274                } catch (final IOException ex) {
275                    // UTF-8 unsupported?  should be impossible the
276                    // Unicode*ExtraField must contain some bad bytes
277
278                    // TODO log this anywhere?
279                    return null;
280                }
281            }
282        }
283        return null;
284    }
285
286    /**
287     * Create a copy of the given array - or return null if the
288     * argument is null.
289     */
290    static byte[] copy(final byte[] from) {
291        if (from != null) {
292            final byte[] to = new byte[from.length];
293            System.arraycopy(from, 0, to, 0, to.length);
294            return to;
295        }
296        return null;
297    }
298    static void copy(final byte[] from, final byte[] to, final int offset) {
299        if (from != null) {
300            System.arraycopy(from, 0, to, offset, from.length);
301        }
302    }
303
304
305    /**
306     * Whether this library is able to read or write the given entry.
307     */
308    static boolean canHandleEntryData(final ZipArchiveEntry entry) {
309        return supportsEncryptionOf(entry) && supportsMethodOf(entry);
310    }
311
312    /**
313     * Whether this library supports the encryption used by the given
314     * entry.
315     *
316     * @return true if the entry isn't encrypted at all
317     */
318    private static boolean supportsEncryptionOf(final ZipArchiveEntry entry) {
319        return !entry.getGeneralPurposeBit().usesEncryption();
320    }
321
322    /**
323     * Whether this library supports the compression method used by
324     * the given entry.
325     *
326     * @return true if the compression method is STORED or DEFLATED
327     */
328    private static boolean supportsMethodOf(final ZipArchiveEntry entry) {
329        return entry.getMethod() == ZipEntry.STORED
330            || entry.getMethod() == ZipMethod.UNSHRINKING.getCode()
331            || entry.getMethod() == ZipMethod.IMPLODING.getCode()
332            || entry.getMethod() == ZipEntry.DEFLATED
333            || entry.getMethod() == ZipMethod.BZIP2.getCode();
334    }
335
336    /**
337     * Checks whether the entry requires features not (yet) supported
338     * by the library and throws an exception if it does.
339     */
340    static void checkRequestedFeatures(final ZipArchiveEntry ze)
341        throws UnsupportedZipFeatureException {
342        if (!supportsEncryptionOf(ze)) {
343            throw
344                new UnsupportedZipFeatureException(UnsupportedZipFeatureException
345                                                   .Feature.ENCRYPTION, ze);
346        }
347        if (!supportsMethodOf(ze)) {
348            final ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod());
349            if (m == null) {
350                throw
351                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
352                                                       .Feature.METHOD, ze);
353            }
354            throw new UnsupportedZipFeatureException(m, ze);
355        }
356    }
357}