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