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    }