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