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