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