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 018package org.apache.commons.compress.utils; 019 020import static java.nio.charset.StandardCharsets.US_ASCII; 021 022import java.util.Arrays; 023 024import org.apache.commons.compress.archivers.ArchiveEntry; 025 026/** 027 * Generic Archive utilities 028 */ 029public class ArchiveUtils { 030 031 private static final int MAX_SANITIZED_NAME_LENGTH = 255; 032 033 /** 034 * Returns true if the first N bytes of an array are all zero 035 * 036 * @param a The array to check 037 * @param size The number of characters to check (not the size of the array) 038 * @return true if the first N bytes are zero 039 */ 040 public static boolean isArrayZero(final byte[] a, final int size) { 041 for (int i = 0; i < size; i++) { 042 if (a[i] != 0) { 043 return false; 044 } 045 } 046 return true; 047 } 048 049 /** 050 * Compare byte buffers 051 * 052 * @param buffer1 the first buffer 053 * @param buffer2 the second buffer 054 * @return {@code true} if buffer1 and buffer2 have same contents 055 * @deprecated Use {@link Arrays#equals(byte[], byte[])}. 056 */ 057 @Deprecated 058 public static boolean isEqual(final byte[] buffer1, final byte[] buffer2) { 059 return Arrays.equals(buffer1, buffer2); 060 } 061 062 /** 063 * Compare byte buffers, optionally ignoring trailing nulls 064 * 065 * @param buffer1 the first buffer 066 * @param buffer2 the second buffer 067 * @param ignoreTrailingNulls whether to ignore trailing nulls 068 * @return {@code true} if buffer1 and buffer2 have same contents 069 */ 070 public static boolean isEqual(final byte[] buffer1, final byte[] buffer2, final boolean ignoreTrailingNulls) { 071 return isEqual(buffer1, 0, buffer1.length, buffer2, 0, buffer2.length, ignoreTrailingNulls); 072 } 073 074 /** 075 * Compare byte buffers 076 * 077 * @param buffer1 the first buffer 078 * @param offset1 the first offset 079 * @param length1 the first length 080 * @param buffer2 the second buffer 081 * @param offset2 the second offset 082 * @param length2 the second length 083 * @return {@code true} if buffer1 and buffer2 have same contents 084 */ 085 public static boolean isEqual(final byte[] buffer1, final int offset1, final int length1, final byte[] buffer2, final int offset2, final int length2) { 086 return isEqual(buffer1, offset1, length1, buffer2, offset2, length2, false); 087 } 088 089 /** 090 * Compare byte buffers, optionally ignoring trailing nulls 091 * 092 * @param buffer1 first buffer 093 * @param offset1 first offset 094 * @param length1 first length 095 * @param buffer2 second buffer 096 * @param offset2 second offset 097 * @param length2 second length 098 * @param ignoreTrailingNulls whether to ignore trailing nulls 099 * @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}