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(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(long t) {
054        Calendar c = Calendar.getInstance();
055        c.setTimeInMillis(t);
056
057        int year = c.get(Calendar.YEAR);
058        if (year < 1980) {
059            return copy(DOS_TIME_MIN); // stop callers from changing the array
060        }
061        int month = c.get(Calendar.MONTH) + 1;
062        long value =  ((year - 1980) << 25)
063            |         (month << 21)
064            |         (c.get(Calendar.DAY_OF_MONTH) << 16)
065            |         (c.get(Calendar.HOUR_OF_DAY) << 11)
066            |         (c.get(Calendar.MINUTE) << 5)
067            |         (c.get(Calendar.SECOND) >> 1);
068        return ZipLong.getBytes(value);
069    }
070
071    /**
072     * Assumes a negative integer really is a positive integer that
073     * has wrapped around and re-creates the original value.
074     *
075     * @param i the value to treat as unsigned int.
076     * @return the unsigned int as a long.
077     */
078    public static long adjustToLong(int i) {
079        if (i < 0) {
080            return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
081        } else {
082            return i;
083        }
084    }
085
086    /**
087     * Reverses a byte[] array.  Reverses in-place (thus provided array is
088     * mutated), but also returns same for convenience.
089     *
090     * @param array to reverse (mutated in-place, but also returned for
091     *        convenience).
092     *
093     * @return the reversed array (mutated in-place, but also returned for
094     *        convenience).
095     * @since 1.5
096     */
097    public static byte[] reverse(final byte[] array) {
098        final int z = array.length - 1; // position of last element
099        for (int i = 0; i < array.length / 2; i++) {
100            byte x = array[i];
101            array[i] = array[z - i];
102            array[z - i] = x;
103        }
104        return array;
105    }
106
107    /**
108     * Converts a BigInteger into a long, and blows up
109     * (NumberFormatException) if the BigInteger is too big.
110     *
111     * @param big BigInteger to convert.
112     * @return long representation of the BigInteger.
113     */
114    static long bigToLong(BigInteger big) {
115        if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit.
116            return big.longValue();
117        } else {
118            throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]");
119        }
120    }
121
122    /**
123     * <p>
124     * Converts a long into a BigInteger.  Negative numbers between -1 and
125     * -2^31 are treated as unsigned 32 bit (e.g., positive) integers.
126     * Negative numbers below -2^31 cause an IllegalArgumentException
127     * to be thrown.
128     * </p>
129     *
130     * @param l long to convert to BigInteger.
131     * @return BigInteger representation of the provided long.
132     */
133    static BigInteger longToBig(long l) {
134        if (l < Integer.MIN_VALUE) {
135            throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]");
136        } else if (l < 0 && l >= Integer.MIN_VALUE) {
137            // If someone passes in a -2, they probably mean 4294967294
138            // (For example, Unix UID/GID's are 32 bit unsigned.)
139            l = ZipUtil.adjustToLong((int) l);
140        }
141        return BigInteger.valueOf(l);
142    }
143
144    /**
145     * Converts a signed byte into an unsigned integer representation
146     * (e.g., -1 becomes 255).
147     *
148     * @param b byte to convert to int
149     * @return int representation of the provided byte
150     * @since 1.5
151     */
152    public static int signedByteToUnsignedInt(byte b) {
153        if (b >= 0) {
154            return b;
155        } else {
156            return 256 + b;
157        }
158    }
159
160    /**
161     * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1).
162     *
163     * @param i integer to convert to byte
164     * @return byte representation of the provided int
165     * @throws IllegalArgumentException if the provided integer is not inside the range [0,255].
166     * @since 1.5
167     */
168    public static byte unsignedIntToSignedByte(int i) {
169        if (i > 255 || i < 0) {
170            throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]");
171        }
172        if (i < 128) {
173            return (byte) i;
174        } else {
175            return (byte) (i - 256);
176        }
177    }
178
179    /**
180     * Convert a DOS date/time field to a Date object.
181     *
182     * @param zipDosTime contains the stored DOS time.
183     * @return a Date instance corresponding to the given time.
184     */
185    public static Date fromDosTime(ZipLong zipDosTime) {
186        long dosTime = zipDosTime.getValue();
187        return new Date(dosToJavaTime(dosTime));
188    }
189
190    /**
191     * Converts DOS time to Java time (number of milliseconds since
192     * epoch).
193     */
194    public static long dosToJavaTime(long dosTime) {
195        Calendar cal = Calendar.getInstance();
196        // CheckStyle:MagicNumberCheck OFF - no point
197        cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
198        cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
199        cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
200        cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
201        cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
202        cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
203        cal.set(Calendar.MILLISECOND, 0);
204        // CheckStyle:MagicNumberCheck ON
205        return cal.getTime().getTime();
206    }
207
208    /**
209     * If the entry has Unicode*ExtraFields and the CRCs of the
210     * names/comments match those of the extra fields, transfer the
211     * known Unicode values from the extra field.
212     */
213    static void setNameAndCommentFromExtraFields(ZipArchiveEntry ze,
214                                                 byte[] originalNameBytes,
215                                                 byte[] commentBytes) {
216        UnicodePathExtraField name = (UnicodePathExtraField)
217            ze.getExtraField(UnicodePathExtraField.UPATH_ID);
218        String originalName = ze.getName();
219        String newName = getUnicodeStringIfOriginalMatches(name,
220                                                           originalNameBytes);
221        if (newName != null && !originalName.equals(newName)) {
222            ze.setName(newName);
223        }
224
225        if (commentBytes != null && commentBytes.length > 0) {
226            UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)
227                ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
228            String newComment =
229                getUnicodeStringIfOriginalMatches(cmt, commentBytes);
230            if (newComment != null) {
231                ze.setComment(newComment);
232            }
233        }
234    }
235
236    /**
237     * If the stored CRC matches the one of the given name, return the
238     * Unicode name of the given field.
239     *
240     * <p>If the field is null or the CRCs don't match, return null
241     * instead.</p>
242     */
243    private static 
244        String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f,
245                                                 byte[] orig) {
246        if (f != null) {
247            CRC32 crc32 = new CRC32();
248            crc32.update(orig);
249            long origCRC32 = crc32.getValue();
250
251            if (origCRC32 == f.getNameCRC32()) {
252                try {
253                    return ZipEncodingHelper
254                        .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
255                } catch (IOException ex) {
256                    // UTF-8 unsupported?  should be impossible the
257                    // Unicode*ExtraField must contain some bad bytes
258
259                    // TODO log this anywhere?
260                    return null;
261                }
262            }
263        }
264        return null;
265    }
266
267    /**
268     * Create a copy of the given array - or return null if the
269     * argument is null.
270     */
271    static byte[] copy(byte[] from) {
272        if (from != null) {
273            byte[] to = new byte[from.length];
274            System.arraycopy(from, 0, to, 0, to.length);
275            return to;
276        }
277        return null;
278    }
279
280    /**
281     * Whether this library is able to read or write the given entry.
282     */
283    static boolean canHandleEntryData(ZipArchiveEntry entry) {
284        return supportsEncryptionOf(entry) && supportsMethodOf(entry);
285    }
286
287    /**
288     * Whether this library supports the encryption used by the given
289     * entry.
290     *
291     * @return true if the entry isn't encrypted at all
292     */
293    private static boolean supportsEncryptionOf(ZipArchiveEntry entry) {
294        return !entry.getGeneralPurposeBit().usesEncryption();
295    }
296
297    /**
298     * Whether this library supports the compression method used by
299     * the given entry.
300     *
301     * @return true if the compression method is STORED or DEFLATED
302     */
303    private static boolean supportsMethodOf(ZipArchiveEntry entry) {
304        return entry.getMethod() == ZipEntry.STORED
305            || entry.getMethod() == ZipMethod.UNSHRINKING.getCode()
306            || entry.getMethod() == ZipMethod.IMPLODING.getCode()
307            || entry.getMethod() == ZipEntry.DEFLATED;
308    }
309
310    /**
311     * Checks whether the entry requires features not (yet) supported
312     * by the library and throws an exception if it does.
313     */
314    static void checkRequestedFeatures(ZipArchiveEntry ze)
315        throws UnsupportedZipFeatureException {
316        if (!supportsEncryptionOf(ze)) {
317            throw
318                new UnsupportedZipFeatureException(UnsupportedZipFeatureException
319                                                   .Feature.ENCRYPTION, ze);
320        }
321        if (!supportsMethodOf(ze)) {
322            ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod());
323            if (m == null) {
324                throw
325                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
326                                                       .Feature.METHOD, ze);
327            } else {
328                throw new UnsupportedZipFeatureException(m, ze);
329            }
330        }
331    }
332}