    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
    018package org.apache.commons.compress.utils;
    019
    020import static java.nio.charset.StandardCharsets.US_ASCII;
    021
    022import java.util.Arrays;
    023
    024import org.apache.commons.compress.archivers.ArchiveEntry;
    025
    026/**
    027 * Generic Archive utilities
    028 */
    029public class ArchiveUtils {
    030
    031    private static final int MAX_SANITIZED_NAME_LENGTH = 255;
    032
    033    /**
    034     * Returns true if the first N bytes of an array are all zero
    035     *
    036     * @param a    The array to check
    037     * @param size The number of characters to check (not the size of the array)
    038     * @return true if the first N bytes are zero
    039     */
    040    public static boolean isArrayZero(final byte[] a, final int size) {
    041        for (int i = 0; i < size; i++) {
    042            if (a[i] != 0) {
    043                return false;
    044            }
    045        }
    046        return true;
    047    }
    048
    049    /**
    050     * Compare byte buffers
    051     *
    052     * @param buffer1 the first buffer
    053     * @param buffer2 the second buffer
    054     * @return {@code true} if buffer1 and buffer2 have same contents
    055     * @deprecated Use {@link Arrays#equals(byte[], byte[])}.
    056     */
    057    @Deprecated
    058    public static boolean isEqual(final byte[] buffer1, final byte[] buffer2) {
    059        return Arrays.equals(buffer1, buffer2);
    060    }
    061
    062    /**
    063     * Compare byte buffers, optionally ignoring trailing nulls
    064     *
    065     * @param buffer1             the first buffer
    066     * @param buffer2             the second buffer
    067     * @param ignoreTrailingNulls whether to ignore trailing nulls
    068     * @return {@code true} if buffer1 and buffer2 have same contents
    069     */
    070    public static boolean isEqual(final byte[] buffer1, final byte[] buffer2, final boolean ignoreTrailingNulls) {
    071        return isEqual(buffer1, 0, buffer1.length, buffer2, 0, buffer2.length, ignoreTrailingNulls);
    072    }
    073
    074    /**
    075     * Compare byte buffers
    076     *
    077     * @param buffer1 the first buffer
    078     * @param offset1 the first offset
    079     * @param length1 the first length
    080     * @param buffer2 the second buffer
    081     * @param offset2 the second offset
    082     * @param length2 the second length
    083     * @return {@code true} if buffer1 and buffer2 have same contents
    084     */
    085    public static boolean isEqual(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2, final int length2) {
    086        return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, false);
    087    }
    088
    089    /**
    090     * Compare byte buffers, optionally ignoring trailing nulls
    091     *
    092     * @param buffer1             first buffer
    093     * @param offset1             first offset
    094     * @param length1             first length
    095     * @param buffer2             second buffer
    096     * @param offset2             second offset
    097     * @param length2             second length
    098     * @param ignoreTrailingNulls whether to ignore trailing nulls
    099     * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
    100     */
    101    public static boolean isEqual(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2, final int length2,
    102            final boolean ignoreTrailingNulls) {
    103        final int minLen = Math.min(length1, length2);
    104        for (int i = 0; i < minLen; i++) {
    105            if (buffer1[offset1 + i] != buffer2[offset2 + i]) {
    106                return false;
    107            }
    108        }
    109        if (length1 == length2) {
    110            return true;
    111        }
    112        if (ignoreTrailingNulls) {
    113            if (length1 > length2) {
    114                for (int i = length2; i < length1; i++) {
    115                    if (buffer1[offset1 + i] != 0) {
    116                        return false;
    117                    }
    118                }
    119            } else {
    120                for (int i = length1; i < length2; i++) {
    121                    if (buffer2[offset2 + i] != 0) {
    122                        return false;
    123                    }
    124                }
    125            }
    126            return true;
    127        }
    128        return false;
    129    }
    130
    131    /**
    132     * Compare byte buffers, ignoring trailing nulls
    133     *
    134     * @param buffer1 the first buffer
    135     * @param offset1 the first offset
    136     * @param length1 the first length
    137     * @param buffer2 the second buffer
    138     * @param offset2 the second offset
    139     * @param length2 the second length
    140     * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
    141     */
    142    public static boolean isEqualWithNull(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2,
    143            final int length2) {
    144        return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, true);
    145    }
    146
    147    /**
    148     * Check if buffer contents matches ASCII String.
    149     *
    150     * @param expected the expected string
    151     * @param buffer   the buffer
    152     * @return {@code true} if buffer is the same as the expected string
    153     */
    154    public static boolean matchAsciiBuffer(final String expected, final byte[] buffer) {
    155        return matchAsciiBuffer(expected, buffer, 0, buffer.length);
    156    }
    157
    158    /**
    159     * Check if buffer contents matches ASCII String.
    160     *
    161     * @param expected expected string
    162     * @param buffer   the buffer
    163     * @param offset   offset to read from
    164     * @param length   length of the buffer
    165     * @return {@code true} if buffer is the same as the expected string
    166     */
    167    public static boolean matchAsciiBuffer(final String expected, final byte[] buffer, final int offset, final int length) {
    168        final byte[] buffer1;
    169        buffer1 = expected.getBytes(US_ASCII);
    170        return isEqual(buffer1, 0, buffer1.length, buffer, offset, length, false);
    171    }
    172
    173    /**
    174     * Returns a "sanitized" version of the string given as arguments, where sanitized means non-printable characters have been replaced with a question mark
    175     * and the outcome is not longer than 255 chars.
    176     *
    177     * <p>
    178     * This method is used to clean up file names when they are used in exception messages as they may end up in log files or as console output and may have
    179     * been read from a corrupted input.
    180     * </p>
    181     *
    182     * @param s the string to sanitize
    183     * @return a sanitized version of the argument
    184     * @since 1.12
    185     */
    186    public static String sanitize(final String s) {
    187        final char[] cs = s.toCharArray();
    188        final char[] chars = cs.length <= MAX_SANITIZED_NAME_LENGTH ? cs : Arrays.copyOf(cs, MAX_SANITIZED_NAME_LENGTH);
    189        if (cs.length > MAX_SANITIZED_NAME_LENGTH) {
    190            Arrays.fill(chars, MAX_SANITIZED_NAME_LENGTH - 3, MAX_SANITIZED_NAME_LENGTH, '.');
    191        }
    192        final StringBuilder sb = new StringBuilder();
    193        for (final char c : chars) {
    194            if (!Character.isISOControl(c)) {
    195                final Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
    196                if (block != null && block != Character.UnicodeBlock.SPECIALS) {
    197                    sb.append(c);
    198                    continue;
    199                }
    200            }
    201            sb.append('?');
    202        }
    203        return sb.toString();
    204    }
    205
    206    /**
    207     * Convert a string to ASCII bytes. Used for comparing "magic" strings which need to be independent of the default Locale.
    208     *
    209     * @param inputString string to convert
    210     * @return the bytes
    211     */
    212    public static byte[] toAsciiBytes(final String inputString) {
    213        return inputString.getBytes(US_ASCII);
    214    }
    215
    216    /**
    217     * Convert an input byte array to a String using the ASCII character set.
    218     *
    219     * @param inputBytes bytes to convert
    220     * @return the bytes, interpreted as an ASCII string
    221     */
    222    public static String toAsciiString(final byte[] inputBytes) {
    223        return new String(inputBytes, US_ASCII);
    224    }
    225
    226    /**
    227     * Convert an input byte array to a String using the ASCII character set.
    228     *
    229     * @param inputBytes input byte array
    230     * @param offset     offset within array
    231     * @param length     length of array
    232     * @return the bytes, interpreted as an ASCII string
    233     */
    234    public static String toAsciiString(final byte[] inputBytes, final int offset, final int length) {
    235        return new String(inputBytes, offset, length, US_ASCII);
    236    }
    237
    238    /**
    239     * Generates a string containing the name, isDirectory setting and size of an entry.
    240     * <p>
    241     * For example:
    242     *
    243     * <pre>
    244     * -    2000 main.c
    245     * d     100 testfiles
    246     * </pre>
    247     *
    248     * @param entry the entry
    249     * @return the representation of the entry
    250     */
    251    public static String toString(final ArchiveEntry entry) {
    252        final StringBuilder sb = new StringBuilder();
    253        sb.append(entry.isDirectory() ? 'd' : '-'); // c.f. "ls -l" output
    254        final String size = Long.toString(entry.getSize());
    255        sb.append(' ');
    256        // Pad output to 7 places, leading spaces
    257        for (int i = 7; i > size.length(); i--) {
    258            sb.append(' ');
    259        }
    260        sb.append(size);
    261        sb.append(' ').append(entry.getName());
    262        return sb.toString();
    263    }
    264
    265    /** Private constructor to prevent instantiation of this utility class. */
    266    private ArchiveUtils() {
    267    }
    268
    269}