    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 {@code buf.length-4}
    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        }
    157        if (l < 0 && l >= Integer.MIN_VALUE) {
    158            // If someone passes in a -2, they probably mean 4294967294
    159            // (For example, Unix UID/GID's are 32 bit unsigned.)
    160            l = ZipUtil.adjustToLong((int) l);
    161        }
    162        return BigInteger.valueOf(l);
    163    }
    164
    165    /**
    166     * Converts a signed byte into an unsigned integer representation
    167     * (e.g., -1 becomes 255).
    168     *
    169     * @param b byte to convert to int
    170     * @return int representation of the provided byte
    171     * @since 1.5
    172     */
    173    public static int signedByteToUnsignedInt(final byte b) {
    174        if (b >= 0) {
    175            return b;
    176        }
    177        return 256 + b;
    178    }
    179
    180    /**
    181     * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1).
    182     *
    183     * @param i integer to convert to byte
    184     * @return byte representation of the provided int
    185     * @throws IllegalArgumentException if the provided integer is not inside the range [0,255].
    186     * @since 1.5
    187     */
    188    public static byte unsignedIntToSignedByte(final int i) {
    189        if (i > 255 || i < 0) {
    190            throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]");
    191        }
    192        if (i < 128) {
    193            return (byte) i;
    194        }
    195        return (byte) (i - 256);
    196    }
    197
    198    /**
    199     * Convert a DOS date/time field to a Date object.
    200     *
    201     * @param zipDosTime contains the stored DOS time.
    202     * @return a Date instance corresponding to the given time.
    203     */
    204    public static Date fromDosTime(final ZipLong zipDosTime) {
    205        final long dosTime = zipDosTime.getValue();
    206        return new Date(dosToJavaTime(dosTime));
    207    }
    208
    209    /**
    210     * Converts DOS time to Java time (number of milliseconds since
    211     * epoch).
    212     * @param dosTime time to convert
    213     * @return converted time
    214     */
    215    public static long dosToJavaTime(final long dosTime) {
    216        final Calendar cal = Calendar.getInstance();
    217        // CheckStyle:MagicNumberCheck OFF - no point
    218        cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
    219        cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
    220        cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
    221        cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
    222        cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
    223        cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
    224        cal.set(Calendar.MILLISECOND, 0);
    225        // CheckStyle:MagicNumberCheck ON
    226        return cal.getTime().getTime();
    227    }
    228
    229    /**
    230     * If the entry has Unicode*ExtraFields and the CRCs of the
    231     * names/comments match those of the extra fields, transfer the
    232     * known Unicode values from the extra field.
    233     */
    234    static void setNameAndCommentFromExtraFields(final ZipArchiveEntry ze,
    235                                                 final byte[] originalNameBytes,
    236                                                 final byte[] commentBytes) {
    237        final ZipExtraField nameCandidate = ze.getExtraField(UnicodePathExtraField.UPATH_ID);
    238        final UnicodePathExtraField name = nameCandidate instanceof UnicodePathExtraField
    239            ? (UnicodePathExtraField) nameCandidate : null;
    240        final String newName = getUnicodeStringIfOriginalMatches(name,
    241                                                           originalNameBytes);
    242        if (newName != null) {
    243            ze.setName(newName);
    244            ze.setNameSource(ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
    245        }
    246
    247        if (commentBytes != null && commentBytes.length > 0) {
    248            final ZipExtraField cmtCandidate = ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
    249            final UnicodeCommentExtraField cmt = cmtCandidate instanceof UnicodeCommentExtraField
    250                ? (UnicodeCommentExtraField) cmtCandidate : null;
    251            final String newComment =
    252                getUnicodeStringIfOriginalMatches(cmt, commentBytes);
    253            if (newComment != null) {
    254                ze.setComment(newComment);
    255                ze.setCommentSource(ZipArchiveEntry.CommentSource.UNICODE_EXTRA_FIELD);
    256            }
    257        }
    258    }
    259
    260    /**
    261     * If the stored CRC matches the one of the given name, return the
    262     * Unicode name of the given field.
    263     *
    264     * <p>If the field is null or the CRCs don't match, return null
    265     * instead.</p>
    266     */
    267    private static
    268        String getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f,
    269                                                 final byte[] orig) {
    270        if (f != null) {
    271            final CRC32 crc32 = new CRC32();
    272            crc32.update(orig);
    273            final long origCRC32 = crc32.getValue();
    274
    275            if (origCRC32 == f.getNameCRC32()) {
    276                try {
    277                    return ZipEncodingHelper
    278                        .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
    279                } catch (final IOException ex) {
    280                    // UTF-8 unsupported?  should be impossible the
    281                    // Unicode*ExtraField must contain some bad bytes
    282                }
    283            }
    284        }
    285        // TODO log this anywhere?
    286        return null;
    287    }
    288
    289    /**
    290     * Create a copy of the given array - or return null if the
    291     * argument is null.
    292     */
    293    static byte[] copy(final byte[] from) {
    294        if (from != null) {
    295            return Arrays.copyOf(from, from.length);
    296        }
    297        return null;
    298    }
    299
    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     */
    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     */
    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     */
    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     */
    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}