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.math.BigInteger;
022    import java.util.Calendar;
023    import java.util.Date;
024    import java.util.zip.CRC32;
025    import java.util.zip.ZipEntry;
026    
027    /**
028     * Utility class for handling DOS and Java time conversions.
029     * @Immutable
030     */
031    public abstract class ZipUtil {
032        /**
033         * Smallest date/time ZIP can handle.
034         */
035        private static final byte[] DOS_TIME_MIN = ZipLong.getBytes(0x00002100L);
036    
037        /**
038         * Convert a Date object to a DOS date/time field.
039         * @param time the <code>Date</code> to convert
040         * @return the date as a <code>ZipLong</code>
041         */
042        public static ZipLong toDosTime(Date time) {
043            return new ZipLong(toDosTime(time.getTime()));
044        }
045    
046        /**
047         * Convert a Date object to a DOS date/time field.
048         *
049         * <p>Stolen from InfoZip's <code>fileio.c</code></p>
050         * @param t number of milliseconds since the epoch
051         * @return the date as a byte array
052         */
053        public static byte[] toDosTime(long t) {
054            Calendar c = Calendar.getInstance();
055            c.setTimeInMillis(t);
056    
057            int year = c.get(Calendar.YEAR);
058            if (year < 1980) {
059                return copy(DOS_TIME_MIN); // stop callers from changing the array
060            }
061            int month = c.get(Calendar.MONTH) + 1;
062            long value =  ((year - 1980) << 25)
063                |         (month << 21)
064                |         (c.get(Calendar.DAY_OF_MONTH) << 16)
065                |         (c.get(Calendar.HOUR_OF_DAY) << 11)
066                |         (c.get(Calendar.MINUTE) << 5)
067                |         (c.get(Calendar.SECOND) >> 1);
068            return ZipLong.getBytes(value);
069        }
070    
071        /**
072         * Assumes a negative integer really is a positive integer that
073         * has wrapped around and re-creates the original value.
074         *
075         * @param i the value to treat as unsigned int.
076         * @return the unsigned int as a long.
077         */
078        public static long adjustToLong(int i) {
079            if (i < 0) {
080                return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
081            } else {
082                return i;
083            }
084        }
085    
086        /**
087         * Reverses a byte[] array.  Reverses in-place (thus provided array is
088         * mutated), but also returns same for convenience.
089         *
090         * @param array to reverse (mutated in-place, but also returned for
091         *        convenience).
092         *
093         * @return the reversed array (mutated in-place, but also returned for
094         *        convenience).
095         * @since 1.5
096         */
097        public static byte[] reverse(final byte[] array) {
098            final int z = array.length - 1; // position of last element
099            for (int i = 0; i < array.length / 2; i++) {
100                byte x = array[i];
101                array[i] = array[z - i];
102                array[z - i] = x;
103            }
104            return array;
105        }
106    
107        /**
108         * Converts a BigInteger into a long, and blows up
109         * (NumberFormatException) if the BigInteger is too big.
110         *
111         * @param big BigInteger to convert.
112         * @return long representation of the BigInteger.
113         */
114        static long bigToLong(BigInteger big) {
115            if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit.
116                return big.longValue();
117            } else {
118                throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]");
119            }
120        }
121    
122        /**
123         * <p>
124         * Converts a long into a BigInteger.  Negative numbers between -1 and
125         * -2^31 are treated as unsigned 32 bit (e.g., positive) integers.
126         * Negative numbers below -2^31 cause an IllegalArgumentException
127         * to be thrown.
128         * </p>
129         *
130         * @param l long to convert to BigInteger.
131         * @return BigInteger representation of the provided long.
132         */
133        static BigInteger longToBig(long l) {
134            if (l < Integer.MIN_VALUE) {
135                throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]");
136            } else if (l < 0 && l >= Integer.MIN_VALUE) {
137                // If someone passes in a -2, they probably mean 4294967294
138                // (For example, Unix UID/GID's are 32 bit unsigned.)
139                l = ZipUtil.adjustToLong((int) l);
140            }
141            return BigInteger.valueOf(l);
142        }
143    
144        /**
145         * Converts a signed byte into an unsigned integer representation
146         * (e.g., -1 becomes 255).
147         *
148         * @param b byte to convert to int
149         * @return int representation of the provided byte
150         * @since 1.5
151         */
152        public static int signedByteToUnsignedInt(byte b) {
153            if (b >= 0) {
154                return b;
155            } else {
156                return 256 + b;
157            }
158        }
159    
160        /**
161         * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1).
162         *
163         * @param i integer to convert to byte
164         * @return byte representation of the provided int
165         * @throws IllegalArgumentException if the provided integer is not inside the range [0,255].
166         * @since 1.5
167         */
168        public static byte unsignedIntToSignedByte(int i) {
169            if (i > 255 || i < 0) {
170                throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]");
171            }
172            if (i < 128) {
173                return (byte) i;
174            } else {
175                return (byte) (i - 256);
176            }
177        }
178    
179        /**
180         * Convert a DOS date/time field to a Date object.
181         *
182         * @param zipDosTime contains the stored DOS time.
183         * @return a Date instance corresponding to the given time.
184         */
185        public static Date fromDosTime(ZipLong zipDosTime) {
186            long dosTime = zipDosTime.getValue();
187            return new Date(dosToJavaTime(dosTime));
188        }
189    
190        /**
191         * Converts DOS time to Java time (number of milliseconds since
192         * epoch).
193         */
194        public static long dosToJavaTime(long dosTime) {
195            Calendar cal = Calendar.getInstance();
196            // CheckStyle:MagicNumberCheck OFF - no point
197            cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
198            cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
199            cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
200            cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
201            cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
202            cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
203            // CheckStyle:MagicNumberCheck ON
204            return cal.getTime().getTime();
205        }
206    
207        /**
208         * If the entry has Unicode*ExtraFields and the CRCs of the
209         * names/comments match those of the extra fields, transfer the
210         * known Unicode values from the extra field.
211         */
212        static void setNameAndCommentFromExtraFields(ZipArchiveEntry ze,
213                                                     byte[] originalNameBytes,
214                                                     byte[] commentBytes) {
215            UnicodePathExtraField name = (UnicodePathExtraField)
216                ze.getExtraField(UnicodePathExtraField.UPATH_ID);
217            String originalName = ze.getName();
218            String newName = getUnicodeStringIfOriginalMatches(name,
219                                                               originalNameBytes);
220            if (newName != null && !originalName.equals(newName)) {
221                ze.setName(newName);
222            }
223    
224            if (commentBytes != null && commentBytes.length > 0) {
225                UnicodeCommentExtraField cmt = (UnicodeCommentExtraField)
226                    ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
227                String newComment =
228                    getUnicodeStringIfOriginalMatches(cmt, commentBytes);
229                if (newComment != null) {
230                    ze.setComment(newComment);
231                }
232            }
233        }
234    
235        /**
236         * If the stored CRC matches the one of the given name, return the
237         * Unicode name of the given field.
238         *
239         * <p>If the field is null or the CRCs don't match, return null
240         * instead.</p>
241         */
242        private static 
243            String getUnicodeStringIfOriginalMatches(AbstractUnicodeExtraField f,
244                                                     byte[] orig) {
245            if (f != null) {
246                CRC32 crc32 = new CRC32();
247                crc32.update(orig);
248                long origCRC32 = crc32.getValue();
249    
250                if (origCRC32 == f.getNameCRC32()) {
251                    try {
252                        return ZipEncodingHelper
253                            .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
254                    } catch (IOException ex) {
255                        // UTF-8 unsupported?  should be impossible the
256                        // Unicode*ExtraField must contain some bad bytes
257    
258                        // TODO log this anywhere?
259                        return null;
260                    }
261                }
262            }
263            return null;
264        }
265    
266        /**
267         * Create a copy of the given array - or return null if the
268         * argument is null.
269         */
270        static byte[] copy(byte[] from) {
271            if (from != null) {
272                byte[] to = new byte[from.length];
273                System.arraycopy(from, 0, to, 0, to.length);
274                return to;
275            }
276            return null;
277        }
278    
279        /**
280         * Whether this library is able to read or write the given entry.
281         */
282        static boolean canHandleEntryData(ZipArchiveEntry entry) {
283            return supportsEncryptionOf(entry) && supportsMethodOf(entry);
284        }
285    
286        /**
287         * Whether this library supports the encryption used by the given
288         * entry.
289         *
290         * @return true if the entry isn't encrypted at all
291         */
292        private static boolean supportsEncryptionOf(ZipArchiveEntry entry) {
293            return !entry.getGeneralPurposeBit().usesEncryption();
294        }
295    
296        /**
297         * Whether this library supports the compression method used by
298         * the given entry.
299         *
300         * @return true if the compression method is STORED or DEFLATED
301         */
302        private static boolean supportsMethodOf(ZipArchiveEntry entry) {
303            return entry.getMethod() == ZipEntry.STORED
304                || entry.getMethod() == ZipEntry.DEFLATED;
305        }
306    
307        /**
308         * Checks whether the entry requires features not (yet) supported
309         * by the library and throws an exception if it does.
310         */
311        static void checkRequestedFeatures(ZipArchiveEntry ze)
312            throws UnsupportedZipFeatureException {
313            if (!supportsEncryptionOf(ze)) {
314                throw
315                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
316                                                       .Feature.ENCRYPTION, ze);
317            }
318            if (!supportsMethodOf(ze)) {
319                ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod());
320                if (m == null) {
321                    throw
322                        new UnsupportedZipFeatureException(UnsupportedZipFeatureException
323                                                           .Feature.METHOD, ze);
324                } else {
325                    throw new UnsupportedZipFeatureException(m, ze);
326                }
327            }
328        }
329    }