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                    "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").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        if (length < 1) {
305            return null;
306        }
307
308        final Random random = randGen.get();
309        // Create a char buffer to put random letters and numbers in.
310        char[] randBuffer = new char[length];
311        for (int i = 0; i < randBuffer.length; i++) {
312            randBuffer[i] = numbersAndLetters[random.nextInt(numbersAndLetters.length)];
313        }
314        return new String(randBuffer);
315    }
316
317    private static final ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() {
318        @Override
319        protected SecureRandom initialValue() {
320            return new SecureRandom();
321        }
322    };
323
324    public static String randomString(final int length) {
325        if (length < 1) {
326            return null;
327        }
328
329        byte[] randomBytes = new byte[length];
330        SECURE_RANDOM.get().nextBytes(randomBytes);
331        char[] randomChars = new char[length];
332        for (int i = 0; i < length; i++) {
333            randomChars[i] = getPrintableChar(randomBytes[i]);
334        }
335        return new String(randomChars);
336    }
337
338    private static char getPrintableChar(byte indexByte) {
339        assert (numbersAndLetters.length < Byte.MAX_VALUE * 2);
340
341        // Convert indexByte as it where an unsigned byte by promoting it to int
342        // and masking it with 0xff. Yields results from 0 - 254.
343        int index = indexByte & 0xff;
344        return numbersAndLetters[index % numbersAndLetters.length];
345    }
346
347    /**
348     * Returns true if CharSequence is not null and is not empty, false otherwise.
349     * Examples:
350     *    isNotEmpty(null) - false
351     *    isNotEmpty("") - false
352     *    isNotEmpty(" ") - true
353     *    isNotEmpty("empty") - true
354     *
355     * @param cs checked CharSequence
356     * @return true if string is not null and is not empty, false otherwise
357     */
358    public static boolean isNotEmpty(CharSequence cs) {
359        return !isNullOrEmpty(cs);
360    }
361
362    /**
363     * Returns true if the given CharSequence is null or empty.
364     *
365     * @param cs
366     * @return true if the given CharSequence is null or empty
367     */
368    public static boolean isNullOrEmpty(CharSequence cs) {
369        return cs == null || isEmpty(cs);
370    }
371
372    /**
373     * Returns true if all given CharSequences are not empty.
374     *
375     * @param css the CharSequences to test.
376     * @return true if all given CharSequences are not empty.
377     */
378    public static boolean isNotEmpty(CharSequence... css) {
379        for (CharSequence cs : css) {
380            if (StringUtils.isNullOrEmpty(cs)) {
381                return false;
382            }
383        }
384        return true;
385    }
386
387    /**
388     * Returns true if all given CharSequences are either null or empty.
389     *
390     * @param css the CharSequences to test.
391     * @return true if all given CharSequences are null or empty.
392     */
393    public static boolean isNullOrEmpty(CharSequence... css) {
394        for (CharSequence cs : css) {
395            if (StringUtils.isNotEmpty(cs)) {
396                return false;
397            }
398        }
399        return true;
400    }
401
402    /**
403     * Returns true if the given CharSequence is empty.
404     * 
405     * @param cs
406     * @return true if the given CharSequence is empty
407     */
408    public static boolean isEmpty(CharSequence cs) {
409        return cs.length() == 0;
410    }
411
412    /**
413     * Transform a collection of objects to a whitespace delimited String.
414     *
415     * @param collection the collection to transform.
416     * @return a String with all the elements of the collection.
417     */
418    public static String collectionToString(Collection<? extends Object> collection) {
419        return toStringBuilder(collection, " ").toString();
420    }
421
422    /**
423     * Transform a collection of objects to a delimited String.
424     *
425     * @param collection the collection to transform.
426     * @param delimiter the delimiter used to delimit the Strings.
427     * @return a StringBuilder with all the elements of the collection.
428     */
429    public static StringBuilder toStringBuilder(Collection<? extends Object> collection, String delimiter) {
430        StringBuilder sb = new StringBuilder(collection.size() * 20);
431        for (Iterator<? extends Object> it = collection.iterator(); it.hasNext();) {
432            Object cs = it.next();
433            sb.append(cs);
434            if (it.hasNext()) {
435                sb.append(delimiter);
436            }
437        }
438        return sb;
439    }
440
441    public static String returnIfNotEmptyTrimmed(String string) {
442        if (string == null)
443            return null;
444        String trimmedString = string.trim();
445        if (trimmedString.length() > 0) {
446            return trimmedString;
447        } else {
448            return null;
449        }
450    }
451
452    public static boolean nullSafeCharSequenceEquals(CharSequence csOne, CharSequence csTwo) {
453        return nullSafeCharSequenceComperator(csOne, csTwo) == 0;
454    }
455
456    public static int nullSafeCharSequenceComperator(CharSequence csOne, CharSequence csTwo) {
457        if (csOne == null ^ csTwo == null) {
458            return (csOne == null) ? -1 : 1;
459        }
460        if (csOne == null && csTwo == null) {
461            return 0;
462        }
463        return csOne.toString().compareTo(csTwo.toString());
464    }
465
466    public static <CS extends CharSequence> CS requireNotNullOrEmpty(CS cs, String message) {
467        if (isNullOrEmpty(cs)) {
468            throw new IllegalArgumentException(message);
469        }
470        return cs;
471    }
472
473    /**
474     * Return the String representation of the given char sequence if it is not null.
475     *
476     * @param cs the char sequence or null.
477     * @return the String representation of <code>cs</code> or null.
478     */
479    public static String maybeToString(CharSequence cs) {
480        if (cs == null) {
481            return null;
482        }
483        return cs.toString();
484    }
485}