    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.io.UnsupportedEncodingException;
    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        byte[] buffer1;
    075        try {
    076            buffer1 = expected.getBytes(CharsetNames.US_ASCII);
    077        } catch (final UnsupportedEncodingException e) {
    078            // Should not happen
    079            throw new RuntimeException(e); //NOSONAR
    080        }
    081        return isEqual(buffer1, 0, buffer1.length, buffer, offset, length, false);
    082    }
    083
    084    /**
    085     * Check if buffer contents matches Ascii String.
    086     *
    087     * @param expected the expected strin
    088     * @param buffer the buffer
    089     * @return {@code true} if buffer is the same as the expected string
    090     */
    091    public static boolean matchAsciiBuffer(final String expected, final byte[] buffer){
    092        return matchAsciiBuffer(expected, buffer, 0, buffer.length);
    093    }
    094
    095    /**
    096     * Convert a string to Ascii bytes.
    097     * Used for comparing "magic" strings which need to be independent of the default Locale.
    098     *
    099     * @param inputString string to convert
    100     * @return the bytes
    101     */
    102    public static byte[] toAsciiBytes(final String inputString){
    103        try {
    104            return inputString.getBytes(CharsetNames.US_ASCII);
    105        } catch (final UnsupportedEncodingException e) {
    106            // Should never happen
    107            throw new RuntimeException(e); //NOSONAR
    108        }
    109    }
    110
    111    /**
    112     * Convert an input byte array to a String using the ASCII character set.
    113     *
    114     * @param inputBytes bytes to convert
    115     * @return the bytes, interpreted as an Ascii string
    116     */
    117    public static String toAsciiString(final byte[] inputBytes){
    118        try {
    119            return new String(inputBytes, CharsetNames.US_ASCII);
    120        } catch (final UnsupportedEncodingException e) {
    121            // Should never happen
    122            throw new RuntimeException(e); //NOSONAR
    123        }
    124    }
    125
    126    /**
    127     * Convert an input byte array to a String using the ASCII character set.
    128     *
    129     * @param inputBytes input byte array
    130     * @param offset offset within array
    131     * @param length length of array
    132     * @return the bytes, interpreted as an Ascii string
    133     */
    134    public static String toAsciiString(final byte[] inputBytes, final int offset, final int length){
    135        try {
    136            return new String(inputBytes, offset, length, CharsetNames.US_ASCII);
    137        } catch (final UnsupportedEncodingException e) {
    138            // Should never happen
    139            throw new RuntimeException(e); //NOSONAR
    140        }
    141    }
    142
    143    /**
    144     * Compare byte buffers, optionally ignoring trailing nulls
    145     *
    146     * @param buffer1 first buffer
    147     * @param offset1 first offset
    148     * @param length1 first length
    149     * @param buffer2 second buffer
    150     * @param offset2 second offset
    151     * @param length2 second length
    152     * @param ignoreTrailingNulls whether to ignore trailing nulls
    153     * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
    154     */
    155    public static boolean isEqual(
    156            final byte[] buffer1, final int offset1, final int length1,
    157            final byte[] buffer2, final int offset2, final int length2,
    158            final boolean ignoreTrailingNulls){
    159        final int minLen=length1 < length2 ? length1 : length2;
    160        for (int i=0; i < minLen; i++){
    161            if (buffer1[offset1+i] != buffer2[offset2+i]){
    162                return false;
    163            }
    164        }
    165        if (length1 == length2){
    166            return true;
    167        }
    168        if (ignoreTrailingNulls){
    169            if (length1 > length2){
    170                for(int i = length2; i < length1; i++){
    171                    if (buffer1[offset1+i] != 0){
    172                        return false;
    173                    }
    174                }
    175            } else {
    176                for(int i = length1; i < length2; i++){
    177                    if (buffer2[offset2+i] != 0){
    178                        return false;
    179                    }
    180                }
    181            }
    182            return true;
    183        }
    184        return false;
    185    }
    186
    187    /**
    188     * Compare byte buffers
    189     *
    190     * @param buffer1 the first buffer
    191     * @param offset1 the first offset
    192     * @param length1 the first length
    193     * @param buffer2 the second buffer
    194     * @param offset2 the second offset
    195     * @param length2 the second length
    196     * @return {@code true} if buffer1 and buffer2 have same contents
    197     */
    198    public static boolean isEqual(
    199            final byte[] buffer1, final int offset1, final int length1,
    200            final byte[] buffer2, final int offset2, final int length2){
    201        return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, false);
    202    }
    203
    204    /**
    205     * Compare byte buffers
    206     *
    207     * @param buffer1 the first buffer
    208     * @param buffer2 the second buffer
    209     * @return {@code true} if buffer1 and buffer2 have same contents
    210     */
    211    public static boolean isEqual(final byte[] buffer1, final byte[] buffer2 ){
    212        return isEqual(buffer1, 0, buffer1.length, buffer2, 0, buffer2.length, false);
    213    }
    214
    215    /**
    216     * Compare byte buffers, optionally ignoring trailing nulls
    217     *
    218     * @param buffer1 the first buffer
    219     * @param buffer2 the second buffer
    220     * @param ignoreTrailingNulls whether to ignore tariling nulls
    221     * @return {@code true} if buffer1 and buffer2 have same contents
    222     */
    223    public static boolean isEqual(final byte[] buffer1, final byte[] buffer2, final boolean ignoreTrailingNulls){
    224        return isEqual(buffer1, 0, buffer1.length, buffer2, 0, buffer2.length, ignoreTrailingNulls);
    225    }
    226
    227    /**
    228     * Compare byte buffers, ignoring trailing nulls
    229     *
    230     * @param buffer1 the first buffer
    231     * @param offset1 the first offset
    232     * @param length1 the first length
    233     * @param buffer2 the second buffer
    234     * @param offset2 the second offset
    235     * @param length2 the second length
    236     * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
    237     */
    238    public static boolean isEqualWithNull(
    239            final byte[] buffer1, final int offset1, final int length1,
    240            final byte[] buffer2, final int offset2, final int length2){
    241        return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, true);
    242    }
    243
    244    /**
    245     * Returns true if the first N bytes of an array are all zero
    246     *
    247     * @param a
    248     *            The array to check
    249     * @param size
    250     *            The number of characters to check (not the size of the array)
    251     * @return true if the first N bytes are zero
    252     */
    253    public static boolean isArrayZero(final byte[] a, final int size) {
    254        for (int i = 0; i < size; i++) {
    255            if (a[i] != 0) {
    256                return false;
    257            }
    258        }
    259        return true;
    260    }
    261
    262    /**
    263     * Returns a "sanitized" version of the string given as arguments,
    264     * where sanitized means non-printable characters have been
    265     * replaced with a question mark and the outcome is not longer
    266     * than 255 chars.
    267     *
    268     * <p>This method is used to clean up file names when they are
    269     * used in exception messages as they may end up in log files or
    270     * as console output and may have been read from a corrupted
    271     * input.</p>
    272     *
    273     * @param s the string to sanitize
    274     * @return a sanitized version of the argument
    275     * @since Compress 1.12
    276     */
    277    public static String sanitize(final String s) {
    278        final char[] cs = s.toCharArray();
    279        final char[] chars = cs.length <= MAX_SANITIZED_NAME_LENGTH ? cs : Arrays.copyOf(cs, MAX_SANITIZED_NAME_LENGTH);
    280        if (cs.length > MAX_SANITIZED_NAME_LENGTH) {
    281            for (int i = MAX_SANITIZED_NAME_LENGTH - 3; i < MAX_SANITIZED_NAME_LENGTH; i++) {
    282                chars[i] = '.';
    283            }
    284        }
    285        final StringBuilder sb = new StringBuilder();
    286        for (final char c : chars) {
    287            if (!Character.isISOControl(c)) {
    288                final Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
    289                if (block != null && block != Character.UnicodeBlock.SPECIALS) {
    290                    sb.append(c);
    291                    continue;
    292                }
    293            }
    294            sb.append('?');
    295        }
    296        return sb.toString();
    297    }
    298
    299}