001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019
020package org.apache.commons.compress.utils;
021
022import static java.nio.charset.StandardCharsets.US_ASCII;
023
024import java.util.Arrays;
025
026import org.apache.commons.compress.archivers.ArchiveEntry;
027
028/**
029 * Generic Archive utilities
030 */
031public class ArchiveUtils {
032
033    private static final int MAX_SANITIZED_NAME_LENGTH = 255;
034
035    /**
036     * Returns true if the first N bytes of an array are all zero.
037     *
038     * @param a    The array to check.
039     * @param size 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     * @deprecated Use {@link Arrays#equals(byte[], byte[])}.
058     */
059    @Deprecated
060    public static boolean isEqual(final byte[] buffer1, final byte[] buffer2) {
061        return Arrays.equals(buffer1, buffer2);
062    }
063
064    /**
065     * Compare byte buffers, optionally ignoring trailing nulls.
066     *
067     * @param buffer1             the first buffer.
068     * @param buffer2             the second buffer.
069     * @param ignoreTrailingNulls whether to ignore trailing nulls.
070     * @return {@code true} if buffer1 and buffer2 have same contents.
071     */
072    public static boolean isEqual(final byte[] buffer1, final byte[] buffer2, final boolean ignoreTrailingNulls) {
073        return isEqual(buffer1, 0, buffer1.length, buffer2, 0, buffer2.length, ignoreTrailingNulls);
074    }
075
076    /**
077     * Compare byte buffers.
078     *
079     * @param buffer1 the first buffer.
080     * @param offset1 the first offset.
081     * @param length1 the first length.
082     * @param buffer2 the second buffer.
083     * @param offset2 the second offset.
084     * @param length2 the second length.
085     * @return {@code true} if buffer1 and buffer2 have same contents.
086     */
087    public static boolean isEqual(final byte[] buffer1, final int offset1, final int length1, 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(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2, final int length2,
104            final boolean ignoreTrailingNulls) {
105        final int minLen = Math.min(length1, length2);
106        for (int i = 0; i < minLen; i++) {
107            if (buffer1[offset1 + i] != buffer2[offset2 + i]) {
108                return false;
109            }
110        }
111        if (length1 == length2) {
112            return true;
113        }
114        if (ignoreTrailingNulls) {
115            if (length1 > length2) {
116                for (int i = length2; i < length1; i++) {
117                    if (buffer1[offset1 + i] != 0) {
118                        return false;
119                    }
120                }
121            } else {
122                for (int i = length1; i < length2; i++) {
123                    if (buffer2[offset2 + i] != 0) {
124                        return false;
125                    }
126                }
127            }
128            return true;
129        }
130        return false;
131    }
132
133    /**
134     * Compare byte buffers, ignoring trailing nulls.
135     *
136     * @param buffer1 the first buffer.
137     * @param offset1 the first offset.
138     * @param length1 the first length.
139     * @param buffer2 the second buffer.
140     * @param offset2 the second offset.
141     * @param length2 the second length.
142     * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls.
143     */
144    public static boolean isEqualWithNull(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2,
145            final int length2) {
146        return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, true);
147    }
148
149    /**
150     * Check if buffer contents matches ASCII String.
151     *
152     * @param expected the expected string.
153     * @param buffer   the buffer.
154     * @return {@code true} if buffer is the same as the expected string.
155     */
156    public static boolean matchAsciiBuffer(final String expected, final byte[] buffer) {
157        return matchAsciiBuffer(expected, buffer, 0, buffer.length);
158    }
159
160    /**
161     * Check if buffer contents matches ASCII String.
162     *
163     * @param expected expected string.
164     * @param buffer   the buffer.
165     * @param offset   offset to read from.
166     * @param length   length of the buffer.
167     * @return {@code true} if buffer is the same as the expected string.
168     */
169    public static boolean matchAsciiBuffer(final String expected, final byte[] buffer, final int offset, final int length) {
170        final byte[] buffer1 = expected.getBytes(US_ASCII);
171        return isEqual(buffer1, 0, buffer1.length, buffer, offset, length, false);
172    }
173
174    /**
175     * Returns a "sanitized" version of the string given as arguments, where sanitized means non-printable characters have been replaced with a question mark
176     * and the outcome is not longer than 255 chars.
177     *
178     * <p>
179     * This method is used to clean up file names when they are used in exception messages as they may end up in log files or as console output and may have
180     * been read from a corrupted input.
181     * </p>
182     *
183     * @param s the string to sanitize.
184     * @return a sanitized version of the argument.
185     * @since 1.12
186     */
187    public static String sanitize(final String s) {
188        final char[] cs = s.toCharArray();
189        final char[] chars = cs.length <= MAX_SANITIZED_NAME_LENGTH ? cs : Arrays.copyOf(cs, MAX_SANITIZED_NAME_LENGTH);
190        if (cs.length > MAX_SANITIZED_NAME_LENGTH) {
191            Arrays.fill(chars, MAX_SANITIZED_NAME_LENGTH - 3, MAX_SANITIZED_NAME_LENGTH, '.');
192        }
193        final StringBuilder sb = new StringBuilder();
194        for (final char c : chars) {
195            if (!Character.isISOControl(c)) {
196                final Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
197                if (block != null && block != Character.UnicodeBlock.SPECIALS) {
198                    sb.append(c);
199                    continue;
200                }
201            }
202            sb.append('?');
203        }
204        return sb.toString();
205    }
206
207    /**
208     * Convert a string to ASCII bytes. Used for comparing "magic" strings which need to be independent of the default Locale.
209     *
210     * @param inputString string to convert.
211     * @return the bytes.
212     */
213    public static byte[] toAsciiBytes(final String inputString) {
214        return inputString.getBytes(US_ASCII);
215    }
216
217    /**
218     * Convert an input byte array to a String using the ASCII character set.
219     *
220     * @param inputBytes bytes to convert.
221     * @return the bytes, interpreted as an ASCII string.
222     */
223    public static String toAsciiString(final byte[] inputBytes) {
224        return new String(inputBytes, US_ASCII);
225    }
226
227    /**
228     * Convert an input byte array to a String using the ASCII character set.
229     *
230     * @param inputBytes input byte array.
231     * @param offset     offset within array.
232     * @param length     length of array.
233     * @return the bytes, interpreted as an ASCII string.
234     */
235    public static String toAsciiString(final byte[] inputBytes, final int offset, final int length) {
236        return new String(inputBytes, offset, length, US_ASCII);
237    }
238
239    /**
240     * Generates a string containing the name, isDirectory setting and size of an entry.
241     * <p>
242     * For example:
243     *
244     * <pre>
245     * -    2000 main.c
246     * d     100 testfiles
247     * </pre>
248     *
249     * @param entry the entry.
250     * @return the representation of the entry.
251     */
252    public static String toString(final ArchiveEntry entry) {
253        final StringBuilder sb = new StringBuilder();
254        sb.append(entry.isDirectory() ? 'd' : '-'); // c.f. "ls -l" output
255        final String size = Long.toString(entry.getSize());
256        sb.append(' ');
257        // Pad output to 7 places, leading spaces
258        for (int i = 7; i > size.length(); i--) {
259            sb.append(' ');
260        }
261        sb.append(size);
262        sb.append(' ').append(entry.getName());
263        return sb.toString();
264    }
265
266    /** Private constructor to prevent instantiation of this utility class. */
267    private ArchiveUtils() {
268    }
269
270}