View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.commons.compress.utils;
21  
22  import static java.nio.charset.StandardCharsets.US_ASCII;
23  
24  import java.util.Arrays;
25  
26  import org.apache.commons.compress.archivers.ArchiveEntry;
27  
28  /**
29   * Generic Archive utilities
30   */
31  public class ArchiveUtils {
32  
33      private static final int MAX_SANITIZED_NAME_LENGTH = 255;
34  
35      /**
36       * Returns true if the first N bytes of an array are all zero.
37       *
38       * @param a    The array to check.
39       * @param size The number of characters to check (not the size of the array).
40       * @return true if the first N bytes are zero.
41       */
42      public static boolean isArrayZero(final byte[] a, final int size) {
43          for (int i = 0; i < size; i++) {
44              if (a[i] != 0) {
45                  return false;
46              }
47          }
48          return true;
49      }
50  
51      /**
52       * Compare byte buffers.
53       *
54       * @param buffer1 the first buffer.
55       * @param buffer2 the second buffer.
56       * @return {@code true} if buffer1 and buffer2 have same contents.
57       * @deprecated Use {@link Arrays#equals(byte[], byte[])}.
58       */
59      @Deprecated
60      public static boolean isEqual(final byte[] buffer1, final byte[] buffer2) {
61          return Arrays.equals(buffer1, buffer2);
62      }
63  
64      /**
65       * Compare byte buffers, optionally ignoring trailing nulls.
66       *
67       * @param buffer1             the first buffer.
68       * @param buffer2             the second buffer.
69       * @param ignoreTrailingNulls whether to ignore trailing nulls.
70       * @return {@code true} if buffer1 and buffer2 have same contents.
71       */
72      public static boolean isEqual(final byte[] buffer1, final byte[] buffer2, final boolean ignoreTrailingNulls) {
73          return isEqual(buffer1, 0, buffer1.length, buffer2, 0, buffer2.length, ignoreTrailingNulls);
74      }
75  
76      /**
77       * Compare byte buffers.
78       *
79       * @param buffer1 the first buffer.
80       * @param offset1 the first offset.
81       * @param length1 the first length.
82       * @param buffer2 the second buffer.
83       * @param offset2 the second offset.
84       * @param length2 the second length.
85       * @return {@code true} if buffer1 and buffer2 have same contents.
86       */
87      public static boolean isEqual(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2, final int length2) {
88          return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, false);
89      }
90  
91      /**
92       * Compare byte buffers, optionally ignoring trailing nulls.
93       *
94       * @param buffer1             first buffer.
95       * @param offset1             first offset.
96       * @param length1             first length.
97       * @param buffer2             second buffer.
98       * @param offset2             second offset.
99       * @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 }