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