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