    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 */
    017package org.apache.commons.compress.archivers.zip;
    018
    019import java.io.IOException;
    020import java.math.BigInteger;
    021import java.time.Instant;
    022import java.time.LocalDateTime;
    023import java.time.ZoneId;
    024import java.util.Arrays;
    025import java.util.Calendar;
    026import java.util.Date;
    027import java.util.zip.CRC32;
    028import java.util.zip.ZipEntry;
    029
    030/**
    031 * Utility class for handling DOS and Java time conversions.
    032 * @Immutable
    033 */
    034public abstract class ZipUtil {
    035
    036    /**
    037     * DOS time constant for representing timestamps before 1980.
    038     * Smallest date/time ZIP can handle.
    039     * <p>
    040     * MS-DOS records file dates and times as packed 16-bit values. An MS-DOS date has the following format.
    041     * </p>
    042     * <p>
    043     * Bits     Contents
    044     * </p>
    045     * <ul>
    046     *   <li>0-4: Day of the month (1-31).</li>
    047     *   <li>5-8: Month (1 = January, 2 = February, and so on).</li>
    048     *   <li>9-15: Year offset from 1980 (add 1980 to get the actual year).</li>
    049     * </ul>
    050     *
    051     * An MS-DOS time has the following format.
    052     * <p>
    053     * Bits     Contents
    054     * </p>
    055     * <ul>
    056     *   <li>0-4: Second divided by 2.</li>
    057     *   <li>5-10: Minute (0-59).</li>
    058     *   <li>11-15: Hour (0-23 on a 24-hour clock).</li>
    059     * </ul>
    060     *
    061     * This constant expresses the minimum DOS date of January 1st 1980 at 00:00:00 or, bit-by-bit:
    062     * <ul>
    063     * <li>Year: 0000000</li>
    064     * <li>Month: 0001</li>
    065     * <li>Day: 00001</li>
    066     * <li>Hour: 00000</li>
    067     * <li>Minute: 000000</li>
    068     * <li>Seconds: 00000</li>
    069     * </ul>
    070     *
    071     * <p>
    072     * This was copied from {@link ZipEntry}.
    073     * </p>
    074     *
    075     * @since 1.23
    076     */
    077    private static final long DOSTIME_BEFORE_1980 = 1 << 21 | 1 << 16; // 0x210000
    078
    079    /**
    080     * Approximately 128 years, in milliseconds (ignoring leap years, etc.).
    081     *
    082     * <p>
    083     * This establish an approximate high-bound value for DOS times in
    084     * milliseconds since epoch, used to enable an efficient but
    085     * sufficient bounds check to avoid generating extended last modified
    086     * time entries.
    087     * </p>
    088     * <p>
    089     * Calculating the exact number is locale dependent, would require loading
    090     * TimeZone data eagerly, and would make little practical sense. Since DOS
    091     * times theoretically go to 2107 - with compatibility not guaranteed
    092     * after 2099 - setting this to a time that is before but near 2099
    093     * should be sufficient.
    094     * </p>
    095     *
    096     * <p>
    097     * This was copied from {@link ZipEntry}.
    098     * </p>
    099     *
    100     * @since 1.23
    101     */
    102    private static final long UPPER_DOSTIME_BOUND = 128L * 365 * 24 * 60 * 60 * 1000;
    103
    104    /**
    105     * Assumes a negative integer really is a positive integer that
    106     * has wrapped around and re-creates the original value.
    107     *
    108     * @param i the value to treat as unsigned int.
    109     * @return the unsigned int as a long.
    110     */
    111    public static long adjustToLong(final int i) {
    112        if (i < 0) {
    113            return 2 * (long) Integer.MAX_VALUE + 2 + i;
    114        }
    115        return i;
    116    }
    117
    118    /**
    119     * Converts a BigInteger into a long, and blows up
    120     * (NumberFormatException) if the BigInteger is too big.
    121     *
    122     * @param big BigInteger to convert.
    123     * @return long representation of the BigInteger.
    124     */
    125    static long bigToLong(final BigInteger big) {
    126        if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit.
    127            return big.longValue();
    128        }
    129        throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]");
    130    }
    131
    132    /**
    133     * Tests if this library is able to read or write the given entry.
    134     */
    135    static boolean canHandleEntryData(final ZipArchiveEntry entry) {
    136        return supportsEncryptionOf(entry) && supportsMethodOf(entry);
    137    }
    138
    139    /**
    140     * Checks whether the entry requires features not (yet) supported
    141     * by the library and throws an exception if it does.
    142     */
    143    static void checkRequestedFeatures(final ZipArchiveEntry ze)
    144        throws UnsupportedZipFeatureException {
    145        if (!supportsEncryptionOf(ze)) {
    146            throw
    147                new UnsupportedZipFeatureException(UnsupportedZipFeatureException
    148                                                   .Feature.ENCRYPTION, ze);
    149        }
    150        if (!supportsMethodOf(ze)) {
    151            final ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod());
    152            if (m == null) {
    153                throw
    154                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
    155                                                       .Feature.METHOD, ze);
    156            }
    157            throw new UnsupportedZipFeatureException(m, ze);
    158        }
    159    }
    160
    161    /**
    162     * Creates a copy of the given array - or return null if the
    163     * argument is null.
    164     */
    165    static byte[] copy(final byte[] from) {
    166        if (from != null) {
    167            return Arrays.copyOf(from, from.length);
    168        }
    169        return null;
    170    }
    171
    172
    173    static void copy(final byte[] from, final byte[] to, final int offset) {
    174        if (from != null) {
    175            System.arraycopy(from, 0, to, offset, from.length);
    176        }
    177    }
    178
    179    private static Date dosToJavaDate(final long dosTime) {
    180        final Calendar cal = Calendar.getInstance();
    181        // CheckStyle:MagicNumberCheck OFF - no point
    182        cal.set(Calendar.YEAR, (int) (dosTime >> 25 & 0x7f) + 1980);
    183        cal.set(Calendar.MONTH, (int) (dosTime >> 21 & 0x0f) - 1);
    184        cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
    185        cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
    186        cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
    187        cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
    188        cal.set(Calendar.MILLISECOND, 0);
    189        // CheckStyle:MagicNumberCheck ON
    190        return cal.getTime();
    191    }
    192
    193    /**
    194     * Converts DOS time to Java time (number of milliseconds since
    195     * epoch).
    196     * @param dosTime time to convert
    197     * @return converted time
    198     */
    199    public static long dosToJavaTime(final long dosTime) {
    200        return dosToJavaDate(dosTime).getTime();
    201    }
    202
    203    /**
    204     * Converts a DOS date/time field to a Date object.
    205     *
    206     * @param zipDosTime contains the stored DOS time.
    207     * @return a Date instance corresponding to the given time.
    208     */
    209    public static Date fromDosTime(final ZipLong zipDosTime) {
    210        final long dosTime = zipDosTime.getValue();
    211        return dosToJavaDate(dosTime);
    212    }
    213
    214    /**
    215     * If the stored CRC matches the one of the given name, return the
    216     * Unicode name of the given field.
    217     *
    218     * <p>If the field is null or the CRCs don't match, return null
    219     * instead.</p>
    220     */
    221    private static
    222        String getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f,
    223                                                 final byte[] orig) {
    224        if (f != null) {
    225            final CRC32 crc32 = new CRC32();
    226            crc32.update(orig);
    227            final long origCRC32 = crc32.getValue();
    228
    229            if (origCRC32 == f.getNameCRC32()) {
    230                try {
    231                    return ZipEncodingHelper
    232                        .ZIP_ENCODING_UTF_8.decode(f.getUnicodeName());
    233                } catch (final IOException ex) {
    234                    // UTF-8 unsupported?  should be impossible the
    235                    // Unicode*ExtraField must contain some bad bytes
    236                }
    237            }
    238        }
    239        // TODO log this anywhere?
    240        return null;
    241    }
    242
    243    /**
    244     * Tests whether a given time (in milliseconds since Epoch) can be safely represented as DOS time
    245     *
    246     * @param time time in milliseconds since epoch
    247     * @return true if the time can be safely represented as DOS time, false otherwise
    248     * @since 1.23
    249     */
    250    public static boolean isDosTime(final long time) {
    251        return time <= UPPER_DOSTIME_BOUND && javaToDosTime(time) != DOSTIME_BEFORE_1980;
    252    }
    253
    254    private static LocalDateTime javaEpochToLocalDateTime(final long time) {
    255        final Instant instant = Instant.ofEpochMilli(time);
    256        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
    257    }
    258
    259    // version with integer overflow fixed - see https://bugs.openjdk.org/browse/JDK-8130914
    260    private static long javaToDosTime(final long t) {
    261        final LocalDateTime ldt = javaEpochToLocalDateTime(t);
    262        if (ldt.getYear() < 1980) {
    263            return DOSTIME_BEFORE_1980;
    264        }
    265        return (ldt.getYear() - 1980 << 25
    266                | ldt.getMonthValue() << 21
    267                | ldt.getDayOfMonth() << 16
    268                | ldt.getHour() << 11
    269                | ldt.getMinute() << 5
    270                | ldt.getSecond() >> 1) & 0xffffffffL;
    271    }
    272
    273    /**
    274     * <p>
    275     * Converts a long into a BigInteger.  Negative numbers between -1 and
    276     * -2^31 are treated as unsigned 32 bit (e.g., positive) integers.
    277     * Negative numbers below -2^31 cause an IllegalArgumentException
    278     * to be thrown.
    279     * </p>
    280     *
    281     * @param l long to convert to BigInteger.
    282     * @return BigInteger representation of the provided long.
    283     */
    284    static BigInteger longToBig(long l) {
    285        if (l < Integer.MIN_VALUE) {
    286            throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]");
    287        }
    288        if (l < 0 && l >= Integer.MIN_VALUE) {
    289            // If someone passes in a -2, they probably mean 4294967294
    290            // (For example, Unix UID/GID's are 32 bit unsigned.)
    291            l = ZipUtil.adjustToLong((int) l);
    292        }
    293        return BigInteger.valueOf(l);
    294    }
    295
    296    /**
    297     * Reverses a byte[] array.  Reverses in-place (thus provided array is
    298     * mutated), but also returns same for convenience.
    299     *
    300     * @param array to reverse (mutated in-place, but also returned for
    301     *        convenience).
    302     *
    303     * @return the reversed array (mutated in-place, but also returned for
    304     *        convenience).
    305     * @since 1.5
    306     */
    307    public static byte[] reverse(final byte[] array) {
    308        final int z = array.length - 1; // position of last element
    309        for (int i = 0; i < array.length / 2; i++) {
    310            final byte x = array[i];
    311            array[i] = array[z - i];
    312            array[z - i] = x;
    313        }
    314        return array;
    315    }
    316
    317    /**
    318     * If the entry has Unicode*ExtraFields and the CRCs of the
    319     * names/comments match those of the extra fields, transfer the
    320     * known Unicode values from the extra field.
    321     */
    322    static void setNameAndCommentFromExtraFields(final ZipArchiveEntry ze,
    323                                                 final byte[] originalNameBytes,
    324                                                 final byte[] commentBytes) {
    325        final ZipExtraField nameCandidate = ze.getExtraField(UnicodePathExtraField.UPATH_ID);
    326        final UnicodePathExtraField name = nameCandidate instanceof UnicodePathExtraField
    327            ? (UnicodePathExtraField) nameCandidate : null;
    328        final String newName = getUnicodeStringIfOriginalMatches(name,
    329                                                           originalNameBytes);
    330        if (newName != null) {
    331            ze.setName(newName);
    332            ze.setNameSource(ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
    333        }
    334
    335        if (commentBytes != null && commentBytes.length > 0) {
    336            final ZipExtraField cmtCandidate = ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
    337            final UnicodeCommentExtraField cmt = cmtCandidate instanceof UnicodeCommentExtraField
    338                ? (UnicodeCommentExtraField) cmtCandidate : null;
    339            final String newComment =
    340                getUnicodeStringIfOriginalMatches(cmt, commentBytes);
    341            if (newComment != null) {
    342                ze.setComment(newComment);
    343                ze.setCommentSource(ZipArchiveEntry.CommentSource.UNICODE_EXTRA_FIELD);
    344            }
    345        }
    346    }
    347
    348    /**
    349     * Converts a signed byte into an unsigned integer representation
    350     * (e.g., -1 becomes 255).
    351     *
    352     * @param b byte to convert to int
    353     * @return int representation of the provided byte
    354     * @since 1.5
    355     */
    356    public static int signedByteToUnsignedInt(final byte b) {
    357        if (b >= 0) {
    358            return b;
    359        }
    360        return 256 + b;
    361    }
    362
    363    /**
    364     * Tests if this library supports the encryption used by the given
    365     * entry.
    366     *
    367     * @return true if the entry isn't encrypted at all
    368     */
    369    private static boolean supportsEncryptionOf(final ZipArchiveEntry entry) {
    370        return !entry.getGeneralPurposeBit().usesEncryption();
    371    }
    372
    373    /**
    374     * Tests if this library supports the compression method used by
    375     * the given entry.
    376     *
    377     * @return true if the compression method is supported
    378     */
    379    private static boolean supportsMethodOf(final ZipArchiveEntry entry) {
    380        return entry.getMethod() == ZipEntry.STORED
    381            || entry.getMethod() == ZipMethod.UNSHRINKING.getCode()
    382            || entry.getMethod() == ZipMethod.IMPLODING.getCode()
    383            || entry.getMethod() == ZipEntry.DEFLATED
    384            || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode()
    385            || entry.getMethod() == ZipMethod.BZIP2.getCode();
    386    }
    387
    388
    389    /**
    390     * Converts a Date object to a DOS date/time field.
    391     *
    392     * @param time the {@code Date} to convert
    393     * @return the date as a {@code ZipLong}
    394     */
    395    public static ZipLong toDosTime(final Date time) {
    396        return new ZipLong(toDosTime(time.getTime()));
    397    }
    398
    399    /**
    400     * Converts a Date object to a DOS date/time field.
    401     *
    402     * <p>Stolen from InfoZip's {@code fileio.c}</p>
    403     * @param t number of milliseconds since the epoch
    404     * @return the date as a byte array
    405     */
    406    public static byte[] toDosTime(final long t) {
    407        final byte[] result = new byte[4];
    408        toDosTime(t, result, 0);
    409        return result;
    410    }
    411
    412    /**
    413     * Converts a Date object to a DOS date/time field.
    414     *
    415     * <p>Stolen from InfoZip's {@code fileio.c}</p>
    416     * @param t number of milliseconds since the epoch
    417     * @param buf the output buffer
    418     * @param offset
    419     *         The offset within the output buffer of the first byte to be written.
    420     *         must be non-negative and no larger than {@code buf.length-4}
    421     */
    422    public static void toDosTime(final long t, final byte[] buf, final int offset) {
    423        ZipLong.putLong(javaToDosTime(t), buf, offset);
    424    }
    425
    426    /**
    427     * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1).
    428     *
    429     * @param i integer to convert to byte
    430     * @return byte representation of the provided int
    431     * @throws IllegalArgumentException if the provided integer is not inside the range [0,255].
    432     * @since 1.5
    433     */
    434    public static byte unsignedIntToSignedByte(final int i) {
    435        if (i > 255 || i < 0) {
    436            throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]");
    437        }
    438        if (i < 128) {
    439            return (byte) i;
    440        }
    441        return (byte) (i - 256);
    442    }
    443}