    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     */
    018    package org.apache.commons.compress.archivers.zip;
    019
    020    import java.io.IOException;
    021    import java.util.Calendar;
    022    import java.util.Date;
    023    import java.util.zip.CRC32;
    024
    025    /**
    026     * Utility class for handling DOS and Java time conversions.
    027     * @Immutable
    028     */
    029    public abstract class ZipUtil {
    030        /**
    031         * Smallest date/time ZIP can handle.
    032         */
    033        private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
    034
    035        /**
    036         * Convert a Date object to a DOS date/time field.
    037         * @param time the <code>Date</code> to convert
    038         * @return the date as a <code>ZipLong</code>
    039         */
    040        public static ZipLong toDosTime(Date time) {
    041            return new ZipLong(toDosTime(time.getTime()));
    042        }
    043
    044        /**
    045         * Convert a Date object to a DOS date/time field.
    046         *
    047         * <p>Stolen from InfoZip's <code>fileio.c</code></p>
    048         * @param t number of milliseconds since the epoch
    049         * @return the date as a byte array
    050         */
    051        public static byte[] toDosTime(long t) {
    052            Calendar c = Calendar.getInstance();
    053            c.setTimeInMillis(t);
    054
    055            int year = c.get(Calendar.YEAR);
    056            if (year < 1980) {
    057                return copy(DOS_TIME_MIN); // stop callers from changing the array
    058            }
    059            int month = c.get(Calendar.MONTH) + 1;
    060            long value =  ((year - 1980) << 25)
    061                |         (month << 21)
    062                |         (c.get(Calendar.DAY_OF_MONTH) << 16)
    063                |         (c.get(Calendar.HOUR_OF_DAY) << 11)
    064                |         (c.get(Calendar.MINUTE) << 5)
    065                |         (c.get(Calendar.SECOND) >> 1);
    066            return ZipLong.getBytes(value);
    067        }
    068
    069        /**
    070         * Assumes a negative integer really is a positive integer that
    071         * has wrapped around and re-creates the original value.
    072         * @param i the value to treat as unsigned int.
    073         * @return the unsigned int as a long.
    074         */
    075        public static long adjustToLong(int i) {
    076            if (i < 0) {
    077                return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
    078            } else {
    079                return i;
    080            }
    081        }
    082
    083        /**
    084         * Convert a DOS date/time field to a Date object.
    085         *
    086         * @param zipDosTime contains the stored DOS time.
    087         * @return a Date instance corresponding to the given time.
    088         */
    089        public static Date fromDosTime(ZipLong zipDosTime) {
    090            long dosTime = zipDosTime.getValue();
    091            return new Date(dosToJavaTime(dosTime));
    092        }
    093
    094        /**
    095         * Converts DOS time to Java time (number of milliseconds since
    096         * epoch).
    097         */
    098        public static long dosToJavaTime(long dosTime) {
    099            Calendar cal = Calendar.getInstance();
    100            // CheckStyle:MagicNumberCheck OFF - no point
    101            cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
    102            cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
    103            cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
    104            cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
    105            cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
    106            cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
    107            // CheckStyle:MagicNumberCheck ON
    108            return cal.getTime().getTime();
    109        }
    110
    111        /**
    112         * If the entry has Unicode*ExtraFields and the CRCs of the
    113         * names/comments match those of the extra fields, transfer the
    114         * known Unicode values from the extra field.
    115         */
    116        static void setNameAndCommentFromExtraFields(ZipArchiveEntry ze,
    117                                                     byte[] originalNameBytes,
    118                                                     byte[] commentBytes) {
    119            UnicodePathExtraField name = (UnicodePathExtraField)
    120                ze.getExtraField(UnicodePathExtraField.UPATH_ID);
    121            String originalName = ze.getName();
    122            String newName = getUnicodeStringIfOriginalMatches(name,
    123                                                               originalNameBytes);
    124            if (newName != null && !originalName.equals(newName)) {
    125                ze.setName(newName);
    126            }
    127
    128            if (commentBytes != null && commentBytes.length > 0) {
    129                UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)
    130                    ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
    131                String newComment =
    132                    getUnicodeStringIfOriginalMatches(cmt, commentBytes);
    133                if (newComment != null) {
    134                    ze.setComment(newComment);
    135                }
    136            }
    137        }
    138
    139        /**
    140         * If the stored CRC matches the one of the given name, return the
    141         * Unicode name of the given field.
    142         *
    143         * <p>If the field is null or the CRCs don't match, return null
    144         * instead.</p>
    145         */
    146        private static
    147            String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f,
    148                                                     byte[] orig) {
    149            if (f != null) {
    150                CRC32 crc32 = new CRC32();
    151                crc32.update(orig);
    152                long origCRC32 = crc32.getValue();
    153
    154                if (origCRC32 == f.getNameCRC32()) {
    155                    try {
    156                        return ZipEncodingHelper
    157                            .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
    158                    } catch (IOException ex) {
    159                        // UTF-8 unsupported?  should be impossible the
    160                        // Unicode*ExtraField must contain some bad bytes
    161
    162                        // TODO log this anywhere?
    163                        return null;
    164                    }
    165                }
    166            }
    167            return null;
    168        }
    169
    170        /**
    171         * Create a copy of the given array - or return null if the
    172         * argument is null.
    173         */
    174        static byte[] copy(byte[] from) {
    175            if (from != null) {
    176                byte[] to = new byte[from.length];
    177                System.arraycopy(from, 0, to, 0, to.length);
    178                return to;
    179            }
    180            return null;
    181        }
    182
    183        /**
    184         * Whether this library is able to read or write the given entry.
    185         */
    186        static boolean canHandleEntryData(ZipArchiveEntry entry) {
    187            return supportsEncryptionOf(entry) && supportsMethodOf(entry);
    188        }
    189
    190        /**
    191         * Whether this library supports the encryption used by the given
    192         * entry.
    193         *
    194         * @return true if the entry isn't encrypted at all
    195         */
    196        private static boolean supportsEncryptionOf(ZipArchiveEntry entry) {
    197            return !entry.getGeneralPurposeBit().usesEncryption();
    198        }
    199
    200        /**
    201         * Whether this library supports the compression method used by
    202         * the given entry.
    203         *
    204         * @return true if the compression method is STORED or DEFLATED
    205         */
    206        private static boolean supportsMethodOf(ZipArchiveEntry entry) {
    207            return entry.getMethod() == ZipArchiveEntry.STORED
    208                || entry.getMethod() == ZipArchiveEntry.DEFLATED;
    209        }
    210
    211        /**
    212         * Checks whether the entry requires features not (yet) supported
    213         * by the library and throws an exception if it does.
    214         */
    215        static void checkRequestedFeatures(ZipArchiveEntry ze)
    216            throws UnsupportedZipFeatureException {
    217            if (!supportsEncryptionOf(ze)) {
    218                throw
    219                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
    220                                                       .Feature.ENCRYPTION, ze);
    221            }
    222            if (!supportsMethodOf(ze)) {
    223                throw
    224                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
    225                                                       .Feature.METHOD, ze);
    226            }
    227        }
    228    }