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