    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}