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