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}