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 */
017package org.apache.commons.compress.archivers.zip;
018
019import java.io.IOException;
020import java.math.BigInteger;
021import java.time.Instant;
022import java.time.LocalDateTime;
023import java.time.ZoneId;
024import java.util.Arrays;
025import java.util.Calendar;
026import java.util.Date;
027import java.util.zip.CRC32;
028import java.util.zip.ZipEntry;
029
030/**
031 * Utility class for handling DOS and Java time conversions.
032 * @Immutable
033 */
034public abstract class ZipUtil {
035
036    /**
037     * DOS time constant for representing timestamps before 1980.
038     * Smallest date/time ZIP can handle.
039     * <p>
040     * MS-DOS records file dates and times as packed 16-bit values. An MS-DOS date has the following format.
041     * </p>
042     * <p>
043     * Bits     Contents
044     * </p>
045     * <ul>
046     *   <li>0-4: Day of the month (1-31).</li>
047     *   <li>5-8: Month (1 = January, 2 = February, and so on).</li>
048     *   <li>9-15: Year offset from 1980 (add 1980 to get the actual year).</li>
049     * </ul>
050     *
051     * An MS-DOS time has the following format.
052     * <p>
053     * Bits     Contents
054     * </p>
055     * <ul>
056     *   <li>0-4: Second divided by 2.</li>
057     *   <li>5-10: Minute (0-59).</li>
058     *   <li>11-15: Hour (0-23 on a 24-hour clock).</li>
059     * </ul>
060     *
061     * This constant expresses the minimum DOS date of January 1st 1980 at 00:00:00 or, bit-by-bit:
062     * <ul>
063     * <li>Year: 0000000</li>
064     * <li>Month: 0001</li>
065     * <li>Day: 00001</li>
066     * <li>Hour: 00000</li>
067     * <li>Minute: 000000</li>
068     * <li>Seconds: 00000</li>
069     * </ul>
070     *
071     * <p>
072     * This was copied from {@link ZipEntry}.
073     * </p>
074     *
075     * @since 1.23
076     */
077    private static final long DOSTIME_BEFORE_1980 = (1 << 21) | (1 << 16); // 0x210000
078
079    /**
080     * Approximately 128 years, in milliseconds (ignoring leap years, etc.).
081     *
082     * <p>
083     * This establish an approximate high-bound value for DOS times in
084     * milliseconds since epoch, used to enable an efficient but
085     * sufficient bounds check to avoid generating extended last modified
086     * time entries.
087     * </p>
088     * <p>
089     * Calculating the exact number is locale dependent, would require loading
090     * TimeZone data eagerly, and would make little practical sense. Since DOS
091     * times theoretically go to 2107 - with compatibility not guaranteed
092     * after 2099 - setting this to a time that is before but near 2099
093     * should be sufficient.
094     * </p>
095     *
096     * <p>
097     * This was copied from {@link ZipEntry}.
098     * </p>
099     *
100     * @since 1.23
101     */
102    private static final long UPPER_DOSTIME_BOUND = 128L * 365 * 24 * 60 * 60 * 1000;
103
104    /**
105     * Assumes a negative integer really is a positive integer that
106     * has wrapped around and re-creates the original value.
107     *
108     * @param i the value to treat as unsigned int.
109     * @return the unsigned int as a long.
110     */
111    public static long adjustToLong(final int i) {
112        if (i < 0) {
113            return 2 * ((long) Integer.MAX_VALUE) + 2 + i;
114        }
115        return i;
116    }
117
118    /**
119     * Converts a BigInteger into a long, and blows up
120     * (NumberFormatException) if the BigInteger is too big.
121     *
122     * @param big BigInteger to convert.
123     * @return long representation of the BigInteger.
124     */
125    static long bigToLong(final BigInteger big) {
126        if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit.
127            return big.longValue();
128        }
129        throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]");
130    }
131
132    /**
133     * Tests if this library is able to read or write the given entry.
134     */
135    static boolean canHandleEntryData(final ZipArchiveEntry entry) {
136        return supportsEncryptionOf(entry) && supportsMethodOf(entry);
137    }
138
139    /**
140     * Checks whether the entry requires features not (yet) supported
141     * by the library and throws an exception if it does.
142     */
143    static void checkRequestedFeatures(final ZipArchiveEntry ze)
144        throws UnsupportedZipFeatureException {
145        if (!supportsEncryptionOf(ze)) {
146            throw
147                new UnsupportedZipFeatureException(UnsupportedZipFeatureException
148                                                   .Feature.ENCRYPTION, ze);
149        }
150        if (!supportsMethodOf(ze)) {
151            final ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod());
152            if (m == null) {
153                throw
154                    new UnsupportedZipFeatureException(UnsupportedZipFeatureException
155                                                       .Feature.METHOD, ze);
156            }
157            throw new UnsupportedZipFeatureException(m, ze);
158        }
159    }
160
161    /**
162     * Creates a copy of the given array - or return null if the
163     * argument is null.
164     */
165    static byte[] copy(final byte[] from) {
166        if (from != null) {
167            return Arrays.copyOf(from, from.length);
168        }
169        return null;
170    }
171
172
173    static void copy(final byte[] from, final byte[] to, final int offset) {
174        if (from != null) {
175            System.arraycopy(from, 0, to, offset, from.length);
176        }
177    }
178
179    private static Date dosToJavaDate(final long dosTime) {
180        final Calendar cal = Calendar.getInstance();
181        // CheckStyle:MagicNumberCheck OFF - no point
182        cal.set(Calendar.YEAR, (int) ((dosTime >> 25) & 0x7f) + 1980);
183        cal.set(Calendar.MONTH, (int) ((dosTime >> 21) & 0x0f) - 1);
184        cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
185        cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
186        cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
187        cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
188        cal.set(Calendar.MILLISECOND, 0);
189        // CheckStyle:MagicNumberCheck ON
190        return cal.getTime();
191    }
192
193    /**
194     * Converts DOS time to Java time (number of milliseconds since
195     * epoch).
196     * @param dosTime time to convert
197     * @return converted time
198     */
199    public static long dosToJavaTime(final long dosTime) {
200        return dosToJavaDate(dosTime).getTime();
201    }
202
203    /**
204     * Converts a DOS date/time field to a Date object.
205     *
206     * @param zipDosTime contains the stored DOS time.
207     * @return a Date instance corresponding to the given time.
208     */
209    public static Date fromDosTime(final ZipLong zipDosTime) {
210        final long dosTime = zipDosTime.getValue();
211        return dosToJavaDate(dosTime);
212    }
213
214    /**
215     * If the stored CRC matches the one of the given name, return the
216     * Unicode name of the given field.
217     *
218     * <p>If the field is null or the CRCs don't match, return null
219     * instead.</p>
220     */
221    private static
222        String getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f,
223                                                 final byte[] orig) {
224        if (f != null) {
225            final CRC32 crc32 = new CRC32();
226            crc32.update(orig);
227            final long origCRC32 = crc32.getValue();
228
229            if (origCRC32 == f.getNameCRC32()) {
230                try {
231                    return ZipEncodingHelper
232                        .UTF8_ZIP_ENCODING.decode(f.getUnicodeName());
233                } catch (final IOException ex) {
234                    // UTF-8 unsupported?  should be impossible the
235                    // Unicode*ExtraField must contain some bad bytes
236                }
237            }
238        }
239        // TODO log this anywhere?
240        return null;
241    }
242
243    /**
244     * Tests whether a given time (in milliseconds since Epoch) can be safely represented as DOS time
245     *
246     * @param time time in milliseconds since epoch
247     * @return true if the time can be safely represented as DOS time, false otherwise
248     * @since 1.23
249     */
250    public static boolean isDosTime(final long time) {
251        return time <= UPPER_DOSTIME_BOUND && javaToDosTime(time) != DOSTIME_BEFORE_1980;
252    }
253
254    private static LocalDateTime javaEpochToLocalDateTime(final long time) {
255        final Instant instant = Instant.ofEpochMilli(time);
256        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
257    }
258
259    // version with integer overflow fixed - see https://bugs.openjdk.org/browse/JDK-8130914
260    private static long javaToDosTime(final long t) {
261        final LocalDateTime ldt = javaEpochToLocalDateTime(t);
262        if (ldt.getYear() < 1980) {
263            return DOSTIME_BEFORE_1980;
264        }
265        return ((ldt.getYear() - 1980) << 25
266                | ldt.getMonthValue() << 21
267                | ldt.getDayOfMonth() << 16
268                | ldt.getHour() << 11
269                | ldt.getMinute() << 5
270                | ldt.getSecond() >> 1) & 0xffffffffL;
271    }
272
273    /**
274     * <p>
275     * Converts a long into a BigInteger.  Negative numbers between -1 and
276     * -2^31 are treated as unsigned 32 bit (e.g., positive) integers.
277     * Negative numbers below -2^31 cause an IllegalArgumentException
278     * to be thrown.
279     * </p>
280     *
281     * @param l long to convert to BigInteger.
282     * @return BigInteger representation of the provided long.
283     */
284    static BigInteger longToBig(long l) {
285        if (l < Integer.MIN_VALUE) {
286            throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]");
287        }
288        if (l < 0 && l >= Integer.MIN_VALUE) {
289            // If someone passes in a -2, they probably mean 4294967294
290            // (For example, Unix UID/GID's are 32 bit unsigned.)
291            l = ZipUtil.adjustToLong((int) l);
292        }
293        return BigInteger.valueOf(l);
294    }
295
296    /**
297     * Reverses a byte[] array.  Reverses in-place (thus provided array is
298     * mutated), but also returns same for convenience.
299     *
300     * @param array to reverse (mutated in-place, but also returned for
301     *        convenience).
302     *
303     * @return the reversed array (mutated in-place, but also returned for
304     *        convenience).
305     * @since 1.5
306     */
307    public static byte[] reverse(final byte[] array) {
308        final int z = array.length - 1; // position of last element
309        for (int i = 0; i < array.length / 2; i++) {
310            final byte x = array[i];
311            array[i] = array[z - i];
312            array[z - i] = x;
313        }
314        return array;
315    }
316
317    /**
318     * If the entry has Unicode*ExtraFields and the CRCs of the
319     * names/comments match those of the extra fields, transfer the
320     * known Unicode values from the extra field.
321     */
322    static void setNameAndCommentFromExtraFields(final ZipArchiveEntry ze,
323                                                 final byte[] originalNameBytes,
324                                                 final byte[] commentBytes) {
325        final ZipExtraField nameCandidate = ze.getExtraField(UnicodePathExtraField.UPATH_ID);
326        final UnicodePathExtraField name = nameCandidate instanceof UnicodePathExtraField
327            ? (UnicodePathExtraField) nameCandidate : null;
328        final String newName = getUnicodeStringIfOriginalMatches(name,
329                                                           originalNameBytes);
330        if (newName != null) {
331            ze.setName(newName);
332            ze.setNameSource(ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
333        }
334
335        if (commentBytes != null && commentBytes.length > 0) {
336            final ZipExtraField cmtCandidate = ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
337            final UnicodeCommentExtraField cmt = cmtCandidate instanceof UnicodeCommentExtraField
338                ? (UnicodeCommentExtraField) cmtCandidate : null;
339            final String newComment =
340                getUnicodeStringIfOriginalMatches(cmt, commentBytes);
341            if (newComment != null) {
342                ze.setComment(newComment);
343                ze.setCommentSource(ZipArchiveEntry.CommentSource.UNICODE_EXTRA_FIELD);
344            }
345        }
346    }
347
348    /**
349     * Converts a signed byte into an unsigned integer representation
350     * (e.g., -1 becomes 255).
351     *
352     * @param b byte to convert to int
353     * @return int representation of the provided byte
354     * @since 1.5
355     */
356    public static int signedByteToUnsignedInt(final byte b) {
357        if (b >= 0) {
358            return b;
359        }
360        return 256 + b;
361    }
362
363    /**
364     * Tests if this library supports the encryption used by the given
365     * entry.
366     *
367     * @return true if the entry isn't encrypted at all
368     */
369    private static boolean supportsEncryptionOf(final ZipArchiveEntry entry) {
370        return !entry.getGeneralPurposeBit().usesEncryption();
371    }
372
373    /**
374     * Tests if this library supports the compression method used by
375     * the given entry.
376     *
377     * @return true if the compression method is supported
378     */
379    private static boolean supportsMethodOf(final ZipArchiveEntry entry) {
380        return entry.getMethod() == ZipEntry.STORED
381            || entry.getMethod() == ZipMethod.UNSHRINKING.getCode()
382            || entry.getMethod() == ZipMethod.IMPLODING.getCode()
383            || entry.getMethod() == ZipEntry.DEFLATED
384            || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode()
385            || entry.getMethod() == ZipMethod.BZIP2.getCode();
386    }
387
388
389    /**
390     * Converts a Date object to a DOS date/time field.
391     *
392     * @param time the {@code Date} to convert
393     * @return the date as a {@code ZipLong}
394     */
395    public static ZipLong toDosTime(final Date time) {
396        return new ZipLong(toDosTime(time.getTime()));
397    }
398
399    /**
400     * Converts a Date object to a DOS date/time field.
401     *
402     * <p>Stolen from InfoZip's {@code fileio.c}</p>
403     * @param t number of milliseconds since the epoch
404     * @return the date as a byte array
405     */
406    public static byte[] toDosTime(final long t) {
407        final byte[] result = new byte[4];
408        toDosTime(t, result, 0);
409        return result;
410    }
411
412    /**
413     * Converts a Date object to a DOS date/time field.
414     *
415     * <p>Stolen from InfoZip's {@code fileio.c}</p>
416     * @param t number of milliseconds since the epoch
417     * @param buf the output buffer
418     * @param offset
419     *         The offset within the output buffer of the first byte to be written.
420     *         must be non-negative and no larger than {@code buf.length-4}
421     */
422    public static void toDosTime(final long t, final byte[] buf, final int offset) {
423        ZipLong.putLong(javaToDosTime(t), buf, offset);
424    }
425
426    /**
427     * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1).
428     *
429     * @param i integer to convert to byte
430     * @return byte representation of the provided int
431     * @throws IllegalArgumentException if the provided integer is not inside the range [0,255].
432     * @since 1.5
433     */
434    public static byte unsignedIntToSignedByte(final int i) {
435        if (i > 255 || i < 0) {
436            throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]");
437        }
438        if (i < 128) {
439            return (byte) i;
440        }
441        return (byte) (i - 256);
442    }
443}