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}