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