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}