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}