    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         *
    073         * <p>This methods is no longer used as of Apache Commons Compress
    074         * 1.3</p>
    075         *
    076         * @param i the value to treat as unsigned int.
    077         * @return the unsigned int as a long.
    078         */
    079        public static long adjustToLong(int i) {
    080            if (i < 0) {
    081                return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
    082            } else {
    083                return i;
    084            }
    085        }
    086
    087        /**
    088         * Convert a DOS date/time field to a Date object.
    089         *
    090         * @param zipDosTime contains the stored DOS time.
    091         * @return a Date instance corresponding to the given time.
    092         */
    093        public static Date fromDosTime(ZipLong zipDosTime) {
    094            long dosTime = zipDosTime.getValue();
    095            return new Date(dosToJavaTime(dosTime));
    096        }
    097
    098        /**
    099         * Converts DOS time to Java time (number of milliseconds since
    100         * epoch).
    101         */
    102        public static long dosToJavaTime(long dosTime) {
    103            Calendar cal = Calendar.getInstance();
    104            // CheckStyle:MagicNumberCheck OFF - no point
    105            cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
    106            cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
    107            cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
    108            cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
    109            cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
    110            cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
    111            // CheckStyle:MagicNumberCheck ON
    112            return cal.getTime().getTime();
    113        }
    114
    115        /**
    116         * If the entry has Unicode*ExtraFields and the CRCs of the
    117         * names/comments match those of the extra fields, transfer the
    118         * known Unicode values from the extra field.
    119         */
    120        static void setNameAndCommentFromExtraFields(ZipArchiveEntry ze,
    121                                                     byte[] originalNameBytes,
    122                                                     byte[] commentBytes) {
    123            UnicodePathExtraField name = (UnicodePathExtraField)
    124                ze.getExtraField(UnicodePathExtraField.UPATH_ID);
    125            String originalName = ze.getName();
    126            String newName = getUnicodeStringIfOriginalMatches(name,
    127                                                               originalNameBytes);
    128            if (newName != null && !originalName.equals(newName)) {
    129                ze.setName(newName);
    130            }
    131
    132            if (commentBytes != null && commentBytes.length > 0) {
    133                UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)
    134                    ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
    135                String newComment =
    136                    getUnicodeStringIfOriginalMatches(cmt, commentBytes);
    137                if (newComment != null) {
    138                    ze.setComment(newComment);
    139                }
    140            }
    141        }
    142
    143        /**
    144         * If the stored CRC matches the one of the given name, return the
    145         * Unicode name of the given field.
    146         *
    147         * <p>If the field is null or the CRCs don't match, return null
    148         * instead.</p>
    149         */
    150        private static
    151            String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f,
    152                                                     byte[] orig) {
    153            if (f != null) {
    154                CRC32 crc32 = new CRC32();
    155                crc32.update(orig);
    156                long origCRC32 = crc32.getValue();
    157
    158                if (origCRC32 == f.getNameCRC32()) {
    159                    try {
    160                        return ZipEncodingHelper
    161                            .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
    162                    } catch (IOException ex) {
    163                        // UTF-8 unsupported?  should be impossible the
    164                        // Unicode*ExtraField must contain some bad bytes
    165
    166                        // TODO log this anywhere?
    167                        return null;
    168                    }
    169                }
    170            }
    171            return null;
    172        }
    173
    174        /**
    175         * Create a copy of the given array - or return null if the
    176         * argument is null.
    177         */
    178        static byte[] copy(byte[] from) {
    179            if (from != null) {
    180                byte[] to = new byte[from.length];
    181                System.arraycopy(from, 0, to, 0, to.length);
    182                return to;
    183            }
    184            return null;
    185        }
    186
    187        /**
    188         * Whether this library is able to read or write the given entry.
    189         */
    190        static boolean canHandleEntryData(ZipArchiveEntry entry) {
    191            return supportsEncryptionOf(entry) && supportsMethodOf(entry);
    192        }
    193
    194        /**
    195         * Whether this library supports the encryption used by the given
    196         * entry.
    197         *
    198         * @return true if the entry isn't encrypted at all
    199         */
    200        private static boolean supportsEncryptionOf(ZipArchiveEntry entry) {
    201            return !entry.getGeneralPurposeBit().usesEncryption();
    202        }
    203
    204        /**
    205         * Whether this library supports the compression method used by
    206         * the given entry.
    207         *
    208         * @return true if the compression method is STORED or DEFLATED
    209         */
    210        private static boolean supportsMethodOf(ZipArchiveEntry entry) {
    211            return entry.getMethod() == ZipArchiveEntry.STORED
    212                || entry.getMethod() == ZipArchiveEntry.DEFLATED;
    213        }
    214
    215        /**
    216         * Checks whether the entry requires features not (yet) supported
    217         * by the library and throws an exception if it does.
    218         */
    219        static void checkRequestedFeatures(ZipArchiveEntry ze)
    220            throws UnsupportedZipFeatureException {
    221            if (!supportsEncryptionOf(ze)) {
    222                throw
    223                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
    224                                                       .Feature.ENCRYPTION, ze);
    225            }
    226            if (!supportsMethodOf(ze)) {
    227                throw
    228                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
    229                                                       .Feature.METHOD, ze);
    230            }
    231        }
    232    }