ZipUtil.java

  1. /*
  2.  *  Licensed to the Apache Software Foundation (ASF) under one or more
  3.  *  contributor license agreements.  See the NOTICE file distributed with
  4.  *  this work for additional information regarding copyright ownership.
  5.  *  The ASF licenses this file to You under the Apache License, Version 2.0
  6.  *  (the "License"); you may not use this file except in compliance with
  7.  *  the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  *  Unless required by applicable law or agreed to in writing, software
  12.  *  distributed under the License is distributed on an "AS IS" BASIS,
  13.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  *  See the License for the specific language governing permissions and
  15.  *  limitations under the License.
  16.  */
  17. package org.apache.commons.compress.archivers.zip;

  18. import java.io.IOException;
  19. import java.math.BigInteger;
  20. import java.time.Instant;
  21. import java.time.LocalDateTime;
  22. import java.time.ZoneId;
  23. import java.util.Arrays;
  24. import java.util.Calendar;
  25. import java.util.Date;
  26. import java.util.zip.CRC32;
  27. import java.util.zip.ZipEntry;

  28. /**
  29.  * Utility class for handling DOS and Java time conversions.
  30.  *
  31.  * @Immutable
  32.  */
  33. public abstract class ZipUtil {

  34.     /**
  35.      * DOS time constant for representing timestamps before 1980. Smallest date/time ZIP can handle.
  36.      * <p>
  37.      * MS-DOS records file dates and times as packed 16-bit values. An MS-DOS date has the following format.
  38.      * </p>
  39.      * <p>
  40.      * Bits Contents
  41.      * </p>
  42.      * <ul>
  43.      * <li>0-4: Day of the month (1-31).</li>
  44.      * <li>5-8: Month (1 = January, 2 = February, and so on).</li>
  45.      * <li>9-15: Year offset from 1980 (add 1980 to get the actual year).</li>
  46.      * </ul>
  47.      *
  48.      * An MS-DOS time has the following format.
  49.      * <p>
  50.      * Bits Contents
  51.      * </p>
  52.      * <ul>
  53.      * <li>0-4: Second divided by 2.</li>
  54.      * <li>5-10: Minute (0-59).</li>
  55.      * <li>11-15: Hour (0-23 on a 24-hour clock).</li>
  56.      * </ul>
  57.      *
  58.      * This constant expresses the minimum DOS date of January 1st 1980 at 00:00:00 or, bit-by-bit:
  59.      * <ul>
  60.      * <li>Year: 0000000</li>
  61.      * <li>Month: 0001</li>
  62.      * <li>Day: 00001</li>
  63.      * <li>Hour: 00000</li>
  64.      * <li>Minute: 000000</li>
  65.      * <li>Seconds: 00000</li>
  66.      * </ul>
  67.      *
  68.      * <p>
  69.      * This was copied from {@link ZipEntry}.
  70.      * </p>
  71.      *
  72.      * @since 1.23
  73.      */
  74.     private static final long DOSTIME_BEFORE_1980 = 1 << 21 | 1 << 16; // 0x210000

  75.     /** Java time representation of the smallest date/time ZIP can handle */
  76.     private static final long DOSTIME_BEFORE_1980_AS_JAVA_TIME = dosToJavaTime(DOSTIME_BEFORE_1980);

  77.     /**
  78.      * Approximately 128 years, in milliseconds (ignoring leap years, etc.).
  79.      *
  80.      * <p>
  81.      * This establish an approximate high-bound value for DOS times in milliseconds since epoch, used to enable an efficient but sufficient bounds check to
  82.      * avoid generating extended last modified time entries.
  83.      * </p>
  84.      * <p>
  85.      * Calculating the exact number is locale dependent, would require loading TimeZone data eagerly, and would make little practical sense. Since DOS times
  86.      * theoretically go to 2107 - with compatibility not guaranteed after 2099 - setting this to a time that is before but near 2099 should be sufficient.
  87.      * </p>
  88.      *
  89.      * <p>
  90.      * This was copied from {@link ZipEntry}.
  91.      * </p>
  92.      *
  93.      * @since 1.23
  94.      */
  95.     private static final long UPPER_DOSTIME_BOUND = 128L * 365 * 24 * 60 * 60 * 1000;

  96.     /**
  97.      * Assumes a negative integer really is a positive integer that has wrapped around and re-creates the original value.
  98.      *
  99.      * @param i the value to treat as unsigned int.
  100.      * @return the unsigned int as a long.
  101.      */
  102.     public static long adjustToLong(final int i) {
  103.         if (i < 0) {
  104.             return 2 * (long) Integer.MAX_VALUE + 2 + i;
  105.         }
  106.         return i;
  107.     }

  108.     /**
  109.      * Converts a BigInteger into a long, and blows up (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(final BigInteger big) {
  115.         if (big.bitLength() <= 63) { // bitLength() doesn't count the sign bit.
  116.             return big.longValue();
  117.         }
  118.         throw new NumberFormatException("The BigInteger cannot fit inside a 64 bit java long: [" + big + "]");
  119.     }

  120.     /**
  121.      * Tests if this library is able to read or write the given entry.
  122.      */
  123.     static boolean canHandleEntryData(final ZipArchiveEntry entry) {
  124.         return supportsEncryptionOf(entry) && supportsMethodOf(entry);
  125.     }

  126.     /**
  127.      * Checks whether the entry requires features not (yet) supported by the library and throws an exception if it does.
  128.      */
  129.     static void checkRequestedFeatures(final ZipArchiveEntry ze) throws UnsupportedZipFeatureException {
  130.         if (!supportsEncryptionOf(ze)) {
  131.             throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.ENCRYPTION, ze);
  132.         }
  133.         if (!supportsMethodOf(ze)) {
  134.             final ZipMethod m = ZipMethod.getMethodByCode(ze.getMethod());
  135.             if (m == null) {
  136.                 throw new UnsupportedZipFeatureException(UnsupportedZipFeatureException.Feature.METHOD, ze);
  137.             }
  138.             throw new UnsupportedZipFeatureException(m, ze);
  139.         }
  140.     }

  141.     /**
  142.      * Creates a copy of the given array - or return null if the argument is null.
  143.      */
  144.     static byte[] copy(final byte[] from) {
  145.         if (from != null) {
  146.             return Arrays.copyOf(from, from.length);
  147.         }
  148.         return null;
  149.     }

  150.     static void copy(final byte[] from, final byte[] to, final int offset) {
  151.         if (from != null) {
  152.             System.arraycopy(from, 0, to, offset, from.length);
  153.         }
  154.     }

  155.     private static Date dosToJavaDate(final long dosTime) {
  156.         final Calendar cal = Calendar.getInstance();
  157.         // CheckStyle:MagicNumberCheck OFF - no point
  158.         cal.set(Calendar.YEAR, (int) (dosTime >> 25 & 0x7f) + 1980);
  159.         cal.set(Calendar.MONTH, (int) (dosTime >> 21 & 0x0f) - 1);
  160.         cal.set(Calendar.DATE, (int) (dosTime >> 16) & 0x1f);
  161.         cal.set(Calendar.HOUR_OF_DAY, (int) (dosTime >> 11) & 0x1f);
  162.         cal.set(Calendar.MINUTE, (int) (dosTime >> 5) & 0x3f);
  163.         cal.set(Calendar.SECOND, (int) (dosTime << 1) & 0x3e);
  164.         cal.set(Calendar.MILLISECOND, 0);
  165.         // CheckStyle:MagicNumberCheck ON
  166.         return cal.getTime();
  167.     }

  168.     /**
  169.      * Converts DOS time to Java time (number of milliseconds since epoch).
  170.      *
  171.      * @param dosTime time to convert
  172.      * @return converted time
  173.      */
  174.     public static long dosToJavaTime(final long dosTime) {
  175.         return dosToJavaDate(dosTime).getTime();
  176.     }

  177.     /**
  178.      * Converts a DOS date/time field to a Date object.
  179.      *
  180.      * @param zipDosTime contains the stored DOS time.
  181.      * @return a Date instance corresponding to the given time.
  182.      */
  183.     public static Date fromDosTime(final ZipLong zipDosTime) {
  184.         final long dosTime = zipDosTime.getValue();
  185.         return dosToJavaDate(dosTime);
  186.     }

  187.     /**
  188.      * If the stored CRC matches the one of the given name, return the Unicode name of the given field.
  189.      *
  190.      * <p>
  191.      * If the field is null or the CRCs don't match, return null instead.
  192.      * </p>
  193.      */
  194.     private static String getUnicodeStringIfOriginalMatches(final AbstractUnicodeExtraField f, final byte[] orig) {
  195.         if (f != null) {
  196.             final CRC32 crc32 = new CRC32();
  197.             crc32.update(orig);
  198.             final long origCRC32 = crc32.getValue();

  199.             if (origCRC32 == f.getNameCRC32()) {
  200.                 try {
  201.                     return ZipEncodingHelper.ZIP_ENCODING_UTF_8.decode(f.getUnicodeName());
  202.                 } catch (final IOException ignored) {
  203.                     // UTF-8 unsupported? should be impossible the
  204.                     // Unicode*ExtraField must contain some bad bytes
  205.                 }
  206.             }
  207.         }
  208.         // TODO log this anywhere?
  209.         return null;
  210.     }

  211.     /**
  212.      * Tests whether a given time (in milliseconds since Epoch) can be safely represented as DOS time
  213.      *
  214.      * @param time time in milliseconds since epoch
  215.      * @return true if the time can be safely represented as DOS time, false otherwise
  216.      * @since 1.23
  217.      */
  218.     public static boolean isDosTime(final long time) {
  219.         return time <= UPPER_DOSTIME_BOUND &&
  220.                 (time == DOSTIME_BEFORE_1980_AS_JAVA_TIME || javaToDosTime(time) != DOSTIME_BEFORE_1980);
  221.     }

  222.     private static LocalDateTime javaEpochToLocalDateTime(final long time) {
  223.         final Instant instant = Instant.ofEpochMilli(time);
  224.         return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
  225.     }

  226.     // version with integer overflow fixed - see https://bugs.openjdk.org/browse/JDK-8130914
  227.     private static long javaToDosTime(final long t) {
  228.         final LocalDateTime ldt = javaEpochToLocalDateTime(t);
  229.         if (ldt.getYear() < 1980) {
  230.             return DOSTIME_BEFORE_1980;
  231.         }
  232.         return (ldt.getYear() - 1980 << 25 | ldt.getMonthValue() << 21 | ldt.getDayOfMonth() << 16 | ldt.getHour() << 11 | ldt.getMinute() << 5
  233.                 | ldt.getSecond() >> 1) & 0xffffffffL;
  234.     }

  235.     /**
  236.      * <p>
  237.      * Converts a long into a BigInteger. Negative numbers between -1 and -2^31 are treated as unsigned 32 bit (e.g., positive) integers. Negative numbers below
  238.      * -2^31 cause an IllegalArgumentException to be thrown.
  239.      * </p>
  240.      *
  241.      * @param l long to convert to BigInteger.
  242.      * @return BigInteger representation of the provided long.
  243.      */
  244.     static BigInteger longToBig(long l) {
  245.         if (l < Integer.MIN_VALUE) {
  246.             throw new IllegalArgumentException("Negative longs < -2^31 not permitted: [" + l + "]");
  247.         }
  248.         if (l < 0 && l >= Integer.MIN_VALUE) {
  249.             // If someone passes in a -2, they probably mean 4294967294
  250.             // (For example, UNIX UID/GID's are 32 bit unsigned.)
  251.             l = adjustToLong((int) l);
  252.         }
  253.         return BigInteger.valueOf(l);
  254.     }

  255.     /**
  256.      * Reverses a byte[] array. Reverses in-place (thus provided array is mutated), but also returns same for convenience.
  257.      *
  258.      * @param array to reverse (mutated in-place, but also returned for convenience).
  259.      *
  260.      * @return the reversed array (mutated in-place, but also returned for convenience).
  261.      * @since 1.5
  262.      */
  263.     public static byte[] reverse(final byte[] array) {
  264.         final int z = array.length - 1; // position of last element
  265.         for (int i = 0; i < array.length / 2; i++) {
  266.             final byte x = array[i];
  267.             array[i] = array[z - i];
  268.             array[z - i] = x;
  269.         }
  270.         return array;
  271.     }

  272.     /**
  273.      * If the entry has Unicode*ExtraFields and the CRCs of the names/comments match those of the extra fields, transfer the known Unicode values from the extra
  274.      * field.
  275.      */
  276.     static void setNameAndCommentFromExtraFields(final ZipArchiveEntry ze, final byte[] originalNameBytes, final byte[] commentBytes) {
  277.         final ZipExtraField nameCandidate = ze.getExtraField(UnicodePathExtraField.UPATH_ID);
  278.         final UnicodePathExtraField name = nameCandidate instanceof UnicodePathExtraField ? (UnicodePathExtraField) nameCandidate : null;
  279.         final String newName = getUnicodeStringIfOriginalMatches(name, originalNameBytes);
  280.         if (newName != null) {
  281.             ze.setName(newName);
  282.             ze.setNameSource(ZipArchiveEntry.NameSource.UNICODE_EXTRA_FIELD);
  283.         }

  284.         if (commentBytes != null && commentBytes.length > 0) {
  285.             final ZipExtraField cmtCandidate = ze.getExtraField(UnicodeCommentExtraField.UCOM_ID);
  286.             final UnicodeCommentExtraField cmt = cmtCandidate instanceof UnicodeCommentExtraField ? (UnicodeCommentExtraField) cmtCandidate : null;
  287.             final String newComment = getUnicodeStringIfOriginalMatches(cmt, commentBytes);
  288.             if (newComment != null) {
  289.                 ze.setComment(newComment);
  290.                 ze.setCommentSource(ZipArchiveEntry.CommentSource.UNICODE_EXTRA_FIELD);
  291.             }
  292.         }
  293.     }

  294.     /**
  295.      * Converts a signed byte into an unsigned integer representation (e.g., -1 becomes 255).
  296.      *
  297.      * @param b byte to convert to int
  298.      * @return int representation of the provided byte
  299.      * @since 1.5
  300.      */
  301.     public static int signedByteToUnsignedInt(final byte b) {
  302.         if (b >= 0) {
  303.             return b;
  304.         }
  305.         return 256 + b;
  306.     }

  307.     /**
  308.      * Tests if this library supports the encryption used by the given entry.
  309.      *
  310.      * @return true if the entry isn't encrypted at all
  311.      */
  312.     private static boolean supportsEncryptionOf(final ZipArchiveEntry entry) {
  313.         return !entry.getGeneralPurposeBit().usesEncryption();
  314.     }

  315.     /**
  316.      * Tests if this library supports the compression method used by the given entry.
  317.      *
  318.      * @return true if the compression method is supported
  319.      */
  320.     private static boolean supportsMethodOf(final ZipArchiveEntry entry) {
  321.         return entry.getMethod() == ZipEntry.STORED || entry.getMethod() == ZipMethod.UNSHRINKING.getCode()
  322.                 || entry.getMethod() == ZipMethod.IMPLODING.getCode() || entry.getMethod() == ZipEntry.DEFLATED
  323.                 || entry.getMethod() == ZipMethod.ENHANCED_DEFLATED.getCode() || entry.getMethod() == ZipMethod.BZIP2.getCode();
  324.     }

  325.     /**
  326.      * Converts a Date object to a DOS date/time field.
  327.      *
  328.      * @param time the {@code Date} to convert
  329.      * @return the date as a {@code ZipLong}
  330.      */
  331.     public static ZipLong toDosTime(final Date time) {
  332.         return new ZipLong(toDosTime(time.getTime()));
  333.     }

  334.     /**
  335.      * Converts a Date object to a DOS date/time field.
  336.      *
  337.      * <p>
  338.      * Stolen from InfoZip's {@code fileio.c}
  339.      * </p>
  340.      *
  341.      * @param t number of milliseconds since the epoch
  342.      * @return the date as a byte array
  343.      */
  344.     public static byte[] toDosTime(final long t) {
  345.         final byte[] result = new byte[4];
  346.         toDosTime(t, result, 0);
  347.         return result;
  348.     }

  349.     /**
  350.      * Converts a Date object to a DOS date/time field.
  351.      *
  352.      * <p>
  353.      * Stolen from InfoZip's {@code fileio.c}
  354.      * </p>
  355.      *
  356.      * @param t      number of milliseconds since the epoch
  357.      * @param buf    the output buffer
  358.      * @param offset The offset within the output buffer of the first byte to be written. must be non-negative and no larger than {@code buf.length-4}
  359.      */
  360.     public static void toDosTime(final long t, final byte[] buf, final int offset) {
  361.         ZipLong.putLong(javaToDosTime(t), buf, offset);
  362.     }

  363.     /**
  364.      * Converts an unsigned integer to a signed byte (e.g., 255 becomes -1).
  365.      *
  366.      * @param i integer to convert to byte
  367.      * @return byte representation of the provided int
  368.      * @throws IllegalArgumentException if the provided integer is not inside the range [0,255].
  369.      * @since 1.5
  370.      */
  371.     public static byte unsignedIntToSignedByte(final int i) {
  372.         if (i > 255 || i < 0) {
  373.             throw new IllegalArgumentException("Can only convert non-negative integers between [0,255] to byte: [" + i + "]");
  374.         }
  375.         if (i < 128) {
  376.             return (byte) i;
  377.         }
  378.         return (byte) (i - 256);
  379.     }
  380. }