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}