001/** 002 * 003 * Copyright 2003-2007 Jive Software, 2016-2018 Florian Schmaus. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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.jivesoftware.smack.util; 019 020import java.io.UnsupportedEncodingException; 021import java.security.SecureRandom; 022import java.util.Collection; 023import java.util.Iterator; 024import java.util.Random; 025 026/** 027 * A collection of utility methods for String objects. 028 */ 029public class StringUtils { 030 031 public static final String MD5 = "MD5"; 032 public static final String SHA1 = "SHA-1"; 033 public static final String UTF8 = "UTF-8"; 034 public static final String USASCII = "US-ASCII"; 035 036 public static final String QUOTE_ENCODE = """; 037 public static final String APOS_ENCODE = "'"; 038 public static final String AMP_ENCODE = "&"; 039 public static final String LT_ENCODE = "<"; 040 public static final String GT_ENCODE = ">"; 041 042 public static final char[] HEX_CHARS = "0123456789abcdef".toCharArray(); 043 044 /** 045 * Escape <code>input</code> for XML. 046 * 047 * @param input the input to escape. 048 * @return the XML escaped variant of <code>input</code>. 049 */ 050 public static CharSequence escapeForXml(CharSequence input) { 051 return escapeForXml(input, XmlEscapeMode.safe); 052 } 053 054 /** 055 * Escape <code>input</code> for XML. 056 * 057 * @param input the input to escape. 058 * @return the XML escaped variant of <code>input</code>. 059 * @since 4.2 060 */ 061 public static CharSequence escapeForXmlAttribute(CharSequence input) { 062 return escapeForXml(input, XmlEscapeMode.forAttribute); 063 } 064 065 /** 066 * Escape <code>input</code> for XML. 067 * <p> 068 * This is an optimized variant of {@link #escapeForXmlAttribute(CharSequence)} for XML where the 069 * XML attribute is quoted using ''' (Apos). 070 * </p> 071 * 072 * @param input the input to escape. 073 * @return the XML escaped variant of <code>input</code>. 074 * @since 4.2 075 */ 076 public static CharSequence escapeForXmlAttributeApos(CharSequence input) { 077 return escapeForXml(input, XmlEscapeMode.forAttributeApos); 078 } 079 080 /** 081 * Escape <code>input</code> for XML. 082 * 083 * @param input the input to escape. 084 * @return the XML escaped variant of <code>input</code>. 085 * @since 4.2 086 */ 087 public static CharSequence escapeForXmlText(CharSequence input) { 088 return escapeForXml(input, XmlEscapeMode.forText); 089 } 090 091 private enum XmlEscapeMode { 092 safe, 093 forAttribute, 094 forAttributeApos, 095 forText, 096 ; 097 } 098 099 /** 100 * Escapes all necessary characters in the CharSequence so that it can be used 101 * in an XML doc. 102 * 103 * @param input the CharSequence to escape. 104 * @return the string with appropriate characters escaped. 105 */ 106 private static CharSequence escapeForXml(final CharSequence input, final XmlEscapeMode xmlEscapeMode) { 107 if (input == null) { 108 return null; 109 } 110 final int len = input.length(); 111 final StringBuilder out = new StringBuilder((int) (len * 1.3)); 112 CharSequence toAppend; 113 char ch; 114 int last = 0; 115 int i = 0; 116 while (i < len) { 117 toAppend = null; 118 ch = input.charAt(i); 119 switch (xmlEscapeMode) { 120 case safe: 121 switch (ch) { 122 case '<': 123 toAppend = LT_ENCODE; 124 break; 125 case '>': 126 toAppend = GT_ENCODE; 127 break; 128 case '&': 129 toAppend = AMP_ENCODE; 130 break; 131 case '"': 132 toAppend = QUOTE_ENCODE; 133 break; 134 case '\'': 135 toAppend = APOS_ENCODE; 136 break; 137 default: 138 break; 139 } 140 break; 141 case forAttribute: 142 // No need to escape '>' for attributes. 143 switch (ch) { 144 case '<': 145 toAppend = LT_ENCODE; 146 break; 147 case '&': 148 toAppend = AMP_ENCODE; 149 break; 150 case '"': 151 toAppend = QUOTE_ENCODE; 152 break; 153 case '\'': 154 toAppend = APOS_ENCODE; 155 break; 156 default: 157 break; 158 } 159 break; 160 case forAttributeApos: 161 // No need to escape '>' and '"' for attributes using '\'' as quote. 162 switch (ch) { 163 case '<': 164 toAppend = LT_ENCODE; 165 break; 166 case '&': 167 toAppend = AMP_ENCODE; 168 break; 169 case '\'': 170 toAppend = APOS_ENCODE; 171 break; 172 default: 173 break; 174 } 175 break; 176 case forText: 177 // No need to escape '"', '\'', and '>' for text. 178 switch (ch) { 179 case '<': 180 toAppend = LT_ENCODE; 181 break; 182 case '&': 183 toAppend = AMP_ENCODE; 184 break; 185 default: 186 break; 187 } 188 break; 189 } 190 if (toAppend != null) { 191 if (i > last) { 192 out.append(input, last, i); 193 } 194 out.append(toAppend); 195 last = ++i; 196 } else { 197 i++; 198 } 199 } 200 if (last == 0) { 201 return input; 202 } 203 if (i > last) { 204 out.append(input, last, i); 205 } 206 return out; 207 } 208 209 /** 210 * Hashes a String using the SHA-1 algorithm and returns the result as a 211 * String of hexadecimal numbers. This method is synchronized to avoid 212 * excessive MessageDigest object creation. If calling this method becomes 213 * a bottleneck in your code, you may wish to maintain a pool of 214 * MessageDigest objects instead of using this method. 215 * <p> 216 * A hash is a one-way function -- that is, given an 217 * input, an output is easily computed. However, given the output, the 218 * input is almost impossible to compute. This is useful for passwords 219 * since we can store the hash and a hacker will then have a very hard time 220 * determining the original password. 221 * 222 * @param data the String to compute the hash of. 223 * @return a hashed version of the passed-in String 224 * @deprecated use {@link org.jivesoftware.smack.util.SHA1#hex(String)} instead. 225 */ 226 @Deprecated 227 public static synchronized String hash(String data) { 228 return org.jivesoftware.smack.util.SHA1.hex(data); 229 } 230 231 /** 232 * Encodes an array of bytes as String representation of hexadecimal. 233 * 234 * @param bytes an array of bytes to convert to a hex string. 235 * @return generated hex string. 236 */ 237 public static String encodeHex(byte[] bytes) { 238 char[] hexChars = new char[bytes.length * 2]; 239 for (int j = 0; j < bytes.length; j++) { 240 int v = bytes[j] & 0xFF; 241 hexChars[j * 2] = HEX_CHARS[v >>> 4]; 242 hexChars[j * 2 + 1] = HEX_CHARS[v & 0x0F]; 243 } 244 return new String(hexChars); 245 } 246 247 public static byte[] toUtf8Bytes(String string) { 248 try { 249 return string.getBytes(StringUtils.UTF8); 250 } 251 catch (UnsupportedEncodingException e) { 252 throw new IllegalStateException("UTF-8 encoding not supported by platform", e); 253 } 254 } 255 256 /** 257 * Pseudo-random number generator object for use with randomString(). 258 * The Random class is not considered to be cryptographically secure, so 259 * only use these random Strings for low to medium security applications. 260 */ 261 private static final ThreadLocal<Random> randGen = new ThreadLocal<Random>() { 262 @Override 263 protected Random initialValue() { 264 return new Random(); 265 } 266 }; 267 268 /** 269 * Array of numbers and letters of mixed case. Numbers appear in the list 270 * twice so that there is a more equal chance that a number will be picked. 271 * We can use the array to get a random number or letter by picking a random 272 * array index. 273 */ 274 private static final char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" + 275 "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); 276 277 /** 278 * Returns a random String of numbers and letters (lower and upper case) 279 * of the specified length. The method uses the Random class that is 280 * built-in to Java which is suitable for low to medium grade security uses. 281 * This means that the output is only pseudo random, i.e., each number is 282 * mathematically generated so is not truly random.<p> 283 * 284 * The specified length must be at least one. If not, the method will return 285 * null. 286 * 287 * @param length the desired length of the random String to return. 288 * @return a random String of numbers and letters of the specified length. 289 */ 290 public static String insecureRandomString(int length) { 291 return randomString(length, randGen.get()); 292 } 293 294 private static final ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() { 295 @Override 296 protected SecureRandom initialValue() { 297 return new SecureRandom(); 298 } 299 }; 300 301 public static String randomString(final int length) { 302 return randomString(length, SECURE_RANDOM.get()); 303 } 304 305 private static String randomString(final int length, Random random) { 306 if (length < 1) { 307 return null; 308 } 309 310 byte[] randomBytes = new byte[length]; 311 random.nextBytes(randomBytes); 312 char[] randomChars = new char[length]; 313 for (int i = 0; i < length; i++) { 314 randomChars[i] = getPrintableChar(randomBytes[i]); 315 } 316 return new String(randomChars); 317 } 318 319 private static char getPrintableChar(byte indexByte) { 320 assert (numbersAndLetters.length < Byte.MAX_VALUE * 2); 321 322 // Convert indexByte as it where an unsigned byte by promoting it to int 323 // and masking it with 0xff. Yields results from 0 - 254. 324 int index = indexByte & 0xff; 325 return numbersAndLetters[index % numbersAndLetters.length]; 326 } 327 328 /** 329 * Returns true if CharSequence is not null and is not empty, false otherwise. 330 * Examples: 331 * isNotEmpty(null) - false 332 * isNotEmpty("") - false 333 * isNotEmpty(" ") - true 334 * isNotEmpty("empty") - true 335 * 336 * @param cs checked CharSequence 337 * @return true if string is not null and is not empty, false otherwise 338 */ 339 public static boolean isNotEmpty(CharSequence cs) { 340 return !isNullOrEmpty(cs); 341 } 342 343 /** 344 * Returns true if the given CharSequence is null or empty. 345 * 346 * @param cs 347 * @return true if the given CharSequence is null or empty 348 */ 349 public static boolean isNullOrEmpty(CharSequence cs) { 350 return cs == null || isEmpty(cs); 351 } 352 353 /** 354 * Returns true if all given CharSequences are not empty. 355 * 356 * @param css the CharSequences to test. 357 * @return true if all given CharSequences are not empty. 358 */ 359 public static boolean isNotEmpty(CharSequence... css) { 360 for (CharSequence cs : css) { 361 if (StringUtils.isNullOrEmpty(cs)) { 362 return false; 363 } 364 } 365 return true; 366 } 367 368 /** 369 * Returns true if all given CharSequences are either null or empty. 370 * 371 * @param css the CharSequences to test. 372 * @return true if all given CharSequences are null or empty. 373 */ 374 public static boolean isNullOrEmpty(CharSequence... css) { 375 for (CharSequence cs : css) { 376 if (StringUtils.isNotEmpty(cs)) { 377 return false; 378 } 379 } 380 return true; 381 } 382 383 /** 384 * Returns true if the given CharSequence is empty. 385 * 386 * @param cs 387 * @return true if the given CharSequence is empty 388 */ 389 public static boolean isEmpty(CharSequence cs) { 390 return cs.length() == 0; 391 } 392 393 /** 394 * Transform a collection of objects to a whitespace delimited String. 395 * 396 * @param collection the collection to transform. 397 * @return a String with all the elements of the collection. 398 */ 399 public static String collectionToString(Collection<? extends Object> collection) { 400 return toStringBuilder(collection, " ").toString(); 401 } 402 403 /** 404 * Transform a collection of objects to a delimited String. 405 * 406 * @param collection the collection to transform. 407 * @param delimiter the delimiter used to delimit the Strings. 408 * @return a StringBuilder with all the elements of the collection. 409 */ 410 public static StringBuilder toStringBuilder(Collection<? extends Object> collection, String delimiter) { 411 StringBuilder sb = new StringBuilder(collection.size() * 20); 412 for (Iterator<? extends Object> it = collection.iterator(); it.hasNext();) { 413 Object cs = it.next(); 414 sb.append(cs); 415 if (it.hasNext()) { 416 sb.append(delimiter); 417 } 418 } 419 return sb; 420 } 421 422 public static String returnIfNotEmptyTrimmed(String string) { 423 if (string == null) 424 return null; 425 String trimmedString = string.trim(); 426 if (trimmedString.length() > 0) { 427 return trimmedString; 428 } else { 429 return null; 430 } 431 } 432 433 public static boolean nullSafeCharSequenceEquals(CharSequence csOne, CharSequence csTwo) { 434 return nullSafeCharSequenceComparator(csOne, csTwo) == 0; 435 } 436 437 public static int nullSafeCharSequenceComparator(CharSequence csOne, CharSequence csTwo) { 438 if (csOne == null ^ csTwo == null) { 439 return (csOne == null) ? -1 : 1; 440 } 441 if (csOne == null && csTwo == null) { 442 return 0; 443 } 444 return csOne.toString().compareTo(csTwo.toString()); 445 } 446 447 public static <CS extends CharSequence> CS requireNotNullOrEmpty(CS cs, String message) { 448 if (isNullOrEmpty(cs)) { 449 throw new IllegalArgumentException(message); 450 } 451 return cs; 452 } 453 454 public static <CS extends CharSequence> CS requireNullOrNotEmpty(CS cs, String message) { 455 if (cs == null) { 456 return null; 457 } 458 if (cs.toString().isEmpty()) { 459 throw new IllegalArgumentException(message); 460 } 461 return cs; 462 } 463 464 /** 465 * Return the String representation of the given char sequence if it is not null. 466 * 467 * @param cs the char sequence or null. 468 * @return the String representation of <code>cs</code> or null. 469 */ 470 public static String maybeToString(CharSequence cs) { 471 if (cs == null) { 472 return null; 473 } 474 return cs.toString(); 475 } 476}