View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.compress.utils;
19  
20  import static java.nio.charset.StandardCharsets.US_ASCII;
21  
22  import java.util.Arrays;
23  
24  import org.apache.commons.compress.archivers.ArchiveEntry;
25  
26  /**
27   * Generic Archive utilities
28   */
29  public class ArchiveUtils {
30  
31      private static final int MAX_SANITIZED_NAME_LENGTH = 255;
32  
33      /**
34       * Returns true if the first N bytes of an array are all zero
35       *
36       * @param a    The array to check
37       * @param size The number of characters to check (not the size of the array)
38       * @return true if the first N bytes are zero
39       */
40      public static boolean isArrayZero(final byte[] a, final int size) {
41          for (int i = 0; i < size; i++) {
42              if (a[i] != 0) {
43                  return false;
44              }
45          }
46          return true;
47      }
48  
49      /**
50       * Compare byte buffers
51       *
52       * @param buffer1 the first buffer
53       * @param buffer2 the second buffer
54       * @return {@code true} if buffer1 and buffer2 have same contents
55       * @deprecated Use {@link Arrays#equals(byte[], byte[])}.
56       */
57      @Deprecated
58      public static boolean isEqual(final byte[] buffer1, final byte[] buffer2) {
59          return Arrays.equals(buffer1, buffer2);
60      }
61  
62      /**
63       * Compare byte buffers, optionally ignoring trailing nulls
64       *
65       * @param buffer1             the first buffer
66       * @param buffer2             the second buffer
67       * @param ignoreTrailingNulls whether to ignore trailing nulls
68       * @return {@code true} if buffer1 and buffer2 have same contents
69       */
70      public static boolean isEqual(final byte[] buffer1, final byte[] buffer2, final boolean ignoreTrailingNulls) {
71          return isEqual(buffer1, 0, buffer1.length, buffer2, 0, buffer2.length, ignoreTrailingNulls);
72      }
73  
74      /**
75       * Compare byte buffers
76       *
77       * @param buffer1 the first buffer
78       * @param offset1 the first offset
79       * @param length1 the first length
80       * @param buffer2 the second buffer
81       * @param offset2 the second offset
82       * @param length2 the second length
83       * @return {@code true} if buffer1 and buffer2 have same contents
84       */
85      public static boolean isEqual(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2, final int length2) {
86          return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, false);
87      }
88  
89      /**
90       * Compare byte buffers, optionally ignoring trailing nulls
91       *
92       * @param buffer1             first buffer
93       * @param offset1             first offset
94       * @param length1             first length
95       * @param buffer2             second buffer
96       * @param offset2             second offset
97       * @param length2             second length
98       * @param ignoreTrailingNulls whether to ignore trailing nulls
99       * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
100      */
101     public static boolean isEqual(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2, final int length2,
102             final boolean ignoreTrailingNulls) {
103         final int minLen = Math.min(length1, length2);
104         for (int i = 0; i < minLen; i++) {
105             if (buffer1[offset1 + i] != buffer2[offset2 + i]) {
106                 return false;
107             }
108         }
109         if (length1 == length2) {
110             return true;
111         }
112         if (ignoreTrailingNulls) {
113             if (length1 > length2) {
114                 for (int i = length2; i < length1; i++) {
115                     if (buffer1[offset1 + i] != 0) {
116                         return false;
117                     }
118                 }
119             } else {
120                 for (int i = length1; i < length2; i++) {
121                     if (buffer2[offset2 + i] != 0) {
122                         return false;
123                     }
124                 }
125             }
126             return true;
127         }
128         return false;
129     }
130 
131     /**
132      * Compare byte buffers, ignoring trailing nulls
133      *
134      * @param buffer1 the first buffer
135      * @param offset1 the first offset
136      * @param length1 the first length
137      * @param buffer2 the second buffer
138      * @param offset2 the second offset
139      * @param length2 the second length
140      * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
141      */
142     public static boolean isEqualWithNull(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2,
143             final int length2) {
144         return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, true);
145     }
146 
147     /**
148      * Check if buffer contents matches ASCII String.
149      *
150      * @param expected the expected string
151      * @param buffer   the buffer
152      * @return {@code true} if buffer is the same as the expected string
153      */
154     public static boolean matchAsciiBuffer(final String expected, final byte[] buffer) {
155         return matchAsciiBuffer(expected, buffer, 0, buffer.length);
156     }
157 
158     /**
159      * Check if buffer contents matches ASCII String.
160      *
161      * @param expected expected string
162      * @param buffer   the buffer
163      * @param offset   offset to read from
164      * @param length   length of the buffer
165      * @return {@code true} if buffer is the same as the expected string
166      */
167     public static boolean matchAsciiBuffer(final String expected, final byte[] buffer, final int offset, final int length) {
168         final byte[] buffer1;
169         buffer1 = expected.getBytes(US_ASCII);
170         return isEqual(buffer1, 0, buffer1.length, buffer, offset, length, false);
171     }
172 
173     /**
174      * Returns a "sanitized" version of the string given as arguments, where sanitized means non-printable characters have been replaced with a question mark
175      * and the outcome is not longer than 255 chars.
176      *
177      * <p>
178      * 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
179      * been read from a corrupted input.
180      * </p>
181      *
182      * @param s the string to sanitize
183      * @return a sanitized version of the argument
184      * @since 1.12
185      */
186     public static String sanitize(final String s) {
187         final char[] cs = s.toCharArray();
188         final char[] chars = cs.length <= MAX_SANITIZED_NAME_LENGTH ? cs : Arrays.copyOf(cs, MAX_SANITIZED_NAME_LENGTH);
189         if (cs.length > MAX_SANITIZED_NAME_LENGTH) {
190             Arrays.fill(chars, MAX_SANITIZED_NAME_LENGTH - 3, MAX_SANITIZED_NAME_LENGTH, '.');
191         }
192         final StringBuilder sb = new StringBuilder();
193         for (final char c : chars) {
194             if (!Character.isISOControl(c)) {
195                 final Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
196                 if (block != null && block != Character.UnicodeBlock.SPECIALS) {
197                     sb.append(c);
198                     continue;
199                 }
200             }
201             sb.append('?');
202         }
203         return sb.toString();
204     }
205 
206     /**
207      * Convert a string to ASCII bytes. Used for comparing "magic" strings which need to be independent of the default Locale.
208      *
209      * @param inputString string to convert
210      * @return the bytes
211      */
212     public static byte[] toAsciiBytes(final String inputString) {
213         return inputString.getBytes(US_ASCII);
214     }
215 
216     /**
217      * Convert an input byte array to a String using the ASCII character set.
218      *
219      * @param inputBytes bytes to convert
220      * @return the bytes, interpreted as an ASCII string
221      */
222     public static String toAsciiString(final byte[] inputBytes) {
223         return new String(inputBytes, US_ASCII);
224     }
225 
226     /**
227      * Convert an input byte array to a String using the ASCII character set.
228      *
229      * @param inputBytes input byte array
230      * @param offset     offset within array
231      * @param length     length of array
232      * @return the bytes, interpreted as an ASCII string
233      */
234     public static String toAsciiString(final byte[] inputBytes, final int offset, final int length) {
235         return new String(inputBytes, offset, length, US_ASCII);
236     }
237 
238     /**
239      * Generates a string containing the name, isDirectory setting and size of an entry.
240      * <p>
241      * For example:
242      *
243      * <pre>
244      * -    2000 main.c
245      * d     100 testfiles
246      * </pre>
247      *
248      * @param entry the entry
249      * @return the representation of the entry
250      */
251     public static String toString(final ArchiveEntry entry) {
252         final StringBuilder sb = new StringBuilder();
253         sb.append(entry.isDirectory() ? 'd' : '-');// c.f. "ls -l" output
254         final String size = Long.toString(entry.getSize());
255         sb.append(' ');
256         // Pad output to 7 places, leading spaces
257         for (int i = 7; i > size.length(); i--) {
258             sb.append(' ');
259         }
260         sb.append(size);
261         sb.append(' ').append(entry.getName());
262         return sb.toString();
263     }
264 
265     /** Private constructor to prevent instantiation of this utility class. */
266     private ArchiveUtils() {
267     }
268 
269 }