001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2016-2020 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.IOException;
021import java.nio.CharBuffer;
022import java.nio.charset.StandardCharsets;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Random;
028import java.util.regex.Pattern;
029
030/**
031 * A collection of utility methods for String objects.
032 */
033public class StringUtils {
034
035    public static final String MD5 = "MD5";
036    public static final String SHA1 = "SHA-1";
037
038    /**
039     * Deprecated, do not use.
040     *
041     * @deprecated use StandardCharsets.UTF_8 instead.
042     */
043    // TODO: Remove in Smack 4.5.
044    @Deprecated
045    public static final String UTF8 = "UTF-8";
046
047    /**
048     * Deprecated, do not use.
049     *
050     * @deprecated use StandardCharsets.US_ASCII instead.
051     */
052    // TODO: Remove in Smack 4.5.
053    @Deprecated
054    public static final String USASCII = "US-ASCII";
055
056    public static final String QUOTE_ENCODE = """;
057    public static final String APOS_ENCODE = "'";
058    public static final String AMP_ENCODE = "&";
059    public static final String LT_ENCODE = "<";
060    public static final String GT_ENCODE = ">";
061
062    public static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
063
064    /**
065     * Escape <code>input</code> for XML.
066     *
067     * @param input the input to escape.
068     * @return the XML escaped variant of <code>input</code>.
069     */
070    public static CharSequence escapeForXml(CharSequence input) {
071        return escapeForXml(input, XmlEscapeMode.safe);
072    }
073
074    /**
075     * Escape <code>input</code> for XML.
076     *
077     * @param input the input to escape.
078     * @return the XML escaped variant of <code>input</code>.
079     * @since 4.2
080     */
081    public static CharSequence escapeForXmlAttribute(CharSequence input) {
082        return escapeForXml(input, XmlEscapeMode.forAttribute);
083    }
084
085    /**
086     * Escape <code>input</code> for XML.
087     * <p>
088     * This is an optimized variant of {@link #escapeForXmlAttribute(CharSequence)} for XML where the
089     * XML attribute is quoted using ''' (Apos).
090     * </p>
091     *
092     * @param input the input to escape.
093     * @return the XML escaped variant of <code>input</code>.
094     * @since 4.2
095     */
096    public static CharSequence escapeForXmlAttributeApos(CharSequence input) {
097        return escapeForXml(input, XmlEscapeMode.forAttributeApos);
098    }
099
100    /**
101     * Escape <code>input</code> for XML.
102     *
103     * @param input the input to escape.
104     * @return the XML escaped variant of <code>input</code>.
105     * @since 4.2
106     */
107    public static CharSequence escapeForXmlText(CharSequence input) {
108        return escapeForXml(input, XmlEscapeMode.forText);
109    }
110
111    private enum XmlEscapeMode {
112        safe,
113        forAttribute,
114        forAttributeApos,
115        forText,
116    }
117
118    /**
119     * Escapes all necessary characters in the CharSequence so that it can be used
120     * in an XML doc.
121     *
122     * @param input the CharSequence to escape.
123     * @return the string with appropriate characters escaped.
124     */
125    private static CharSequence escapeForXml(final CharSequence input, final XmlEscapeMode xmlEscapeMode) {
126        if (input == null) {
127            return null;
128        }
129        final int len = input.length();
130        final StringBuilder out = new StringBuilder((int) (len * 1.3));
131        CharSequence toAppend;
132        char ch;
133        int last = 0;
134        int i = 0;
135        while (i < len) {
136            toAppend = null;
137            ch = input.charAt(i);
138            switch (xmlEscapeMode) {
139            case safe:
140                switch (ch) {
141                case '<':
142                    toAppend = LT_ENCODE;
143                    break;
144                case '>':
145                    toAppend = GT_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 forAttribute:
161                // No need to escape '>' for attributes.
162                switch (ch) {
163                case '<':
164                    toAppend = LT_ENCODE;
165                    break;
166                case '&':
167                    toAppend = AMP_ENCODE;
168                    break;
169                case '"':
170                    toAppend = QUOTE_ENCODE;
171                    break;
172                case '\'':
173                    toAppend = APOS_ENCODE;
174                    break;
175                default:
176                    break;
177                }
178                break;
179            case forAttributeApos:
180                // No need to escape '>' and '"' for attributes using '\'' as quote.
181                switch (ch) {
182                case '<':
183                    toAppend = LT_ENCODE;
184                    break;
185                case '&':
186                    toAppend = AMP_ENCODE;
187                    break;
188                case '\'':
189                    toAppend = APOS_ENCODE;
190                    break;
191                default:
192                    break;
193                }
194                break;
195            case forText:
196                // No need to escape '"', '\'', and '>' for text.
197                switch (ch) {
198                case '<':
199                    toAppend = LT_ENCODE;
200                    break;
201                case '&':
202                    toAppend = AMP_ENCODE;
203                    break;
204                default:
205                    break;
206                }
207                break;
208            }
209            if (toAppend != null) {
210                if (i > last) {
211                    out.append(input, last, i);
212                }
213                out.append(toAppend);
214                last = ++i;
215            } else {
216                i++;
217            }
218        }
219        if (last == 0) {
220            return input;
221        }
222        if (i > last) {
223            out.append(input, last, i);
224        }
225        return out;
226    }
227
228    /**
229     * Hashes a String using the SHA-1 algorithm and returns the result as a
230     * String of hexadecimal numbers. This method is synchronized to avoid
231     * excessive MessageDigest object creation. If calling this method becomes
232     * a bottleneck in your code, you may wish to maintain a pool of
233     * MessageDigest objects instead of using this method.
234     * <p>
235     * A hash is a one-way function -- that is, given an
236     * input, an output is easily computed. However, given the output, the
237     * input is almost impossible to compute. This is useful for passwords
238     * since we can store the hash and a hacker will then have a very hard time
239     * determining the original password.
240     *
241     * @param data the String to compute the hash of.
242     * @return a hashed version of the passed-in String
243     * @deprecated use {@link org.jivesoftware.smack.util.SHA1#hex(String)} instead.
244     */
245    @Deprecated
246    public static synchronized String hash(String data) {
247        return org.jivesoftware.smack.util.SHA1.hex(data);
248    }
249
250    /**
251     * Encodes an array of bytes as String representation of hexadecimal.
252     *
253     * @param bytes an array of bytes to convert to a hex string.
254     * @return generated hex string.
255     */
256    public static String encodeHex(byte[] bytes) {
257        char[] hexChars = new char[bytes.length * 2];
258        for (int j = 0; j < bytes.length; j++) {
259            int v = bytes[j] & 0xFF;
260            hexChars[j * 2] = HEX_CHARS[v >>> 4];
261            hexChars[j * 2 + 1] = HEX_CHARS[v & 0x0F];
262        }
263        return new String(hexChars);
264    }
265
266    public static byte[] toUtf8Bytes(String string) {
267        return string.getBytes(StandardCharsets.UTF_8);
268    }
269
270    /**
271     * 24 upper case characters from the latin alphabet and numbers without '0' and 'O'.
272     */
273    public static final String UNAMBIGUOUS_NUMBERS_AND_LETTERS_STRING = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ";
274
275    /**
276     * 24 upper case characters from the latin alphabet and numbers without '0' and 'O'.
277     */
278    private static final char[] UNAMBIGUOUS_NUMBERS_AND_LETTERS = UNAMBIGUOUS_NUMBERS_AND_LETTERS_STRING.toCharArray();
279
280    /**
281     * Returns a random String of numbers and letters (lower and upper case)
282     * of the specified length. The method uses the Random class that is
283     * built-in to Java which is suitable for low to medium grade security uses.
284     * This means that the output is only pseudo random, i.e., each number is
285     * mathematically generated so is not truly random.<p>
286     *
287     * The specified length must be at least one. If not, the method will return
288     * null.
289     *
290     * @param length the desired length of the random String to return.
291     * @return a random String of numbers and letters of the specified length.
292     */
293    public static String insecureRandomString(int length) {
294        return randomString(length, RandomUtil.RANDOM.get());
295    }
296
297    public static String secureOnlineAttackSafeRandomString() {
298        // 34^10 = 2.06e15 possible combinations. Which is enough to protect against online brute force attacks.
299        // See also https://www.grc.com/haystack.htm
300        final int REQUIRED_LENGTH = 10;
301
302        return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTERS, REQUIRED_LENGTH);
303    }
304
305    public static String secureUniqueRandomString() {
306        // 34^13 = 8.11e19 possible combinations, which is > 2^64.
307        final int REQUIRED_LENGTH = 13;
308
309        return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTERS, REQUIRED_LENGTH);
310    }
311
312    /**
313     * Generate a secure random string with is human readable. The resulting string consists of 24 upper case characters
314     * from the Latin alphabet and numbers without '0' and 'O', grouped into 4-characters chunks, e.g.
315     * "TWNK-KD5Y-MT3T-E1GS-DRDB-KVTW". The characters are randomly selected by a cryptographically secure pseudorandom
316     * number generator (CSPRNG).
317     * <p>
318     * The string can be used a backup "code" for secrets, and is in fact the same as the one backup code specified in
319     * XEP-0373 and the one used by the <a href="https://github.com/open-keychain/open-keychain/wiki/Backups">Backup
320     * Format v2 of OpenKeychain</a>.
321     * </p>
322     *
323     * @see <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption"> XEP-0373 §5.4 Encrypting the Secret
324     *      Key Backup</a>
325     * @return a human readable secure random string.
326     */
327    public static String secureOfflineAttackSafeRandomString() {
328        // 34^24 = 2^122.10 possible combinations. Which is enough to protect against offline brute force attacks.
329        // See also https://www.grc.com/haystack.htm
330        final int REQUIRED_LENGTH = 24;
331
332        return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTERS, REQUIRED_LENGTH);
333    }
334
335    private static final int RANDOM_STRING_CHUNK_SIZE = 4;
336
337    private static String randomString(Random random, char[] alphabet, int numRandomChars) {
338        // The buffer most hold the size of the requested number of random chars and the chunk separators ('-').
339        int bufferSize = numRandomChars + ((numRandomChars - 1) / RANDOM_STRING_CHUNK_SIZE);
340        CharBuffer charBuffer = CharBuffer.allocate(bufferSize);
341
342        try {
343            randomString(charBuffer, random, alphabet, numRandomChars);
344        } catch (IOException e) {
345            // This should never happen if we calcuate the buffer size correctly.
346            throw new AssertionError(e);
347        }
348
349        return charBuffer.flip().toString();
350    }
351
352    private static void randomString(Appendable appendable, Random random, char[] alphabet, int numRandomChars)
353                    throws IOException {
354        for (int randomCharNum = 1; randomCharNum <= numRandomChars; randomCharNum++) {
355            int randomIndex = random.nextInt(alphabet.length);
356            char randomChar = alphabet[randomIndex];
357            appendable.append(randomChar);
358
359            if (randomCharNum % RANDOM_STRING_CHUNK_SIZE == 0 && randomCharNum < numRandomChars) {
360                appendable.append('-');
361            }
362        }
363    }
364
365    public static String randomString(final int length) {
366        return randomString(length, RandomUtil.SECURE_RANDOM.get());
367    }
368
369    public static String randomString(final int length, Random random) {
370        if (length == 0) {
371            return "";
372        }
373
374        char[] randomChars = new char[length];
375        for (int i = 0; i < length; i++) {
376            int index = random.nextInt(UNAMBIGUOUS_NUMBERS_AND_LETTERS.length);
377            randomChars[i] = UNAMBIGUOUS_NUMBERS_AND_LETTERS[index];
378        }
379        return new String(randomChars);
380    }
381
382    /**
383     * Returns true if CharSequence is not null and is not empty, false otherwise.
384     * Examples:
385     *    isNotEmpty(null) - false
386     *    isNotEmpty("") - false
387     *    isNotEmpty(" ") - true
388     *    isNotEmpty("empty") - true
389     *
390     * @param cs checked CharSequence
391     * @return true if string is not null and is not empty, false otherwise
392     */
393    public static boolean isNotEmpty(CharSequence cs) {
394        return !isNullOrEmpty(cs);
395    }
396
397    /**
398     * Returns true if the given CharSequence is null or empty.
399     *
400     * @param cs TODO javadoc me please
401     * @return true if the given CharSequence is null or empty
402     */
403    public static boolean isNullOrEmpty(CharSequence cs) {
404        return cs == null || isEmpty(cs);
405    }
406
407    /**
408     * Returns true if all given CharSequences are not empty.
409     *
410     * @param css the CharSequences to test.
411     * @return true if all given CharSequences are not empty.
412     */
413    public static boolean isNotEmpty(CharSequence... css) {
414        for (CharSequence cs : css) {
415            if (StringUtils.isNullOrEmpty(cs)) {
416                return false;
417            }
418        }
419        return true;
420    }
421
422    /**
423     * Returns true if all given CharSequences are either null or empty.
424     *
425     * @param css the CharSequences to test.
426     * @return true if all given CharSequences are null or empty.
427     */
428    public static boolean isNullOrEmpty(CharSequence... css) {
429        for (CharSequence cs : css) {
430            if (StringUtils.isNotEmpty(cs)) {
431                return false;
432            }
433        }
434        return true;
435    }
436
437    public static boolean isNullOrNotEmpty(CharSequence cs) {
438        if (cs == null) {
439            return true;
440        }
441        return !cs.toString().isEmpty();
442    }
443
444    /**
445     * Returns true if the given CharSequence is empty.
446     *
447     * @param cs TODO javadoc me please
448     * @return true if the given CharSequence is empty
449     */
450    public static boolean isEmpty(CharSequence cs) {
451        return cs.length() == 0;
452    }
453
454    /**
455     * Transform a collection of objects to a whitespace delimited String.
456     *
457     * @param collection the collection to transform.
458     * @return a String with all the elements of the collection.
459     */
460    public static String collectionToString(Collection<? extends Object> collection) {
461        return toStringBuilder(collection, " ").toString();
462    }
463
464    /**
465     * Transform a collection of objects to a delimited String.
466     *
467     * @param collection the collection to transform.
468     * @param delimiter the delimiter used to delimit the Strings.
469     * @return a StringBuilder with all the elements of the collection.
470     */
471    public static StringBuilder toStringBuilder(Collection<? extends Object> collection, String delimiter) {
472        StringBuilder sb = new StringBuilder(collection.size() * 20);
473        appendTo(collection, delimiter, sb);
474        return sb;
475    }
476
477    public static void appendTo(Collection<? extends Object> collection, StringBuilder sb) {
478        appendTo(collection, ", ", sb);
479    }
480
481    public static <O extends Object> void appendTo(Collection<O> collection, StringBuilder sb,
482                    Consumer<O> appendFunction) {
483        appendTo(collection, ", ", sb, appendFunction);
484    }
485
486    public static void appendTo(Collection<? extends Object> collection, String delimiter, StringBuilder sb) {
487        appendTo(collection, delimiter, sb, o -> sb.append(o));
488    }
489
490    public static <O extends Object> void appendTo(Collection<O> collection, String delimiter, StringBuilder sb,
491                    Consumer<O> appendFunction) {
492        for (Iterator<O> it = collection.iterator(); it.hasNext();) {
493            O cs = it.next();
494            appendFunction.accept(cs);
495            if (it.hasNext()) {
496                sb.append(delimiter);
497            }
498        }
499    }
500
501    public static String returnIfNotEmptyTrimmed(String string) {
502        if (string == null)
503            return null;
504        String trimmedString = string.trim();
505        if (trimmedString.length() > 0) {
506            return trimmedString;
507        } else {
508            return null;
509        }
510    }
511
512    public static boolean nullSafeCharSequenceEquals(CharSequence csOne, CharSequence csTwo) {
513        return nullSafeCharSequenceComparator(csOne, csTwo) == 0;
514    }
515
516    public static int nullSafeCharSequenceComparator(CharSequence csOne, CharSequence csTwo) {
517        if (csOne == null ^ csTwo == null) {
518            return (csOne == null) ? -1 : 1;
519        }
520        if (csOne == null && csTwo == null) {
521            return 0;
522        }
523        return csOne.toString().compareTo(csTwo.toString());
524    }
525
526    /**
527     * Require a {@link CharSequence} to be neither null, nor empty.
528     *
529     * @deprecated use {@link #requireNotNullNorEmpty(CharSequence, String)} instead.
530     * @param cs CharSequence
531     * @param message error message
532     * @param <CS> CharSequence type
533     * @return cs TODO javadoc me please
534     */
535    @Deprecated
536    public static <CS extends CharSequence> CS requireNotNullOrEmpty(CS cs, String message) {
537        return requireNotNullNorEmpty(cs, message);
538    }
539
540    /**
541     * Require a {@link CharSequence} to be neither null, nor empty.
542     *
543     * @param cs CharSequence
544     * @param message error message
545     * @param <CS> CharSequence type
546     * @return cs TODO javadoc me please
547     */
548    public static <CS extends CharSequence> CS requireNotNullNorEmpty(CS cs, String message) {
549        if (isNullOrEmpty(cs)) {
550            throw new IllegalArgumentException(message);
551        }
552        return cs;
553    }
554
555    public static <CS extends CharSequence> CS requireNullOrNotEmpty(CS cs, String message) {
556        if (cs == null) {
557            return null;
558        }
559        if (isEmpty(cs)) {
560            throw new IllegalArgumentException(message);
561        }
562        return cs;
563    }
564
565    /**
566     * Return the String representation of the given char sequence if it is not null.
567     *
568     * @param cs the char sequence or null.
569     * @return the String representation of <code>cs</code> or null.
570     */
571    public static String maybeToString(CharSequence cs) {
572        if (cs == null) {
573            return null;
574        }
575        return cs.toString();
576    }
577
578    /**
579     * Defined by XML 1.0 § 2.3 as:
580     *  S      ::=      (#x20 | #x9 | #xD | #xA)+
581     *
582     * @see <a href="https://www.w3.org/TR/xml/#sec-white-space">XML 1.0 § 2.3</a>
583     */
584    private static final Pattern XML_WHITESPACE = Pattern.compile("[\t\n\r ]");
585
586    public static String deleteXmlWhitespace(String string) {
587        return XML_WHITESPACE.matcher(string).replaceAll("");
588    }
589
590    public static Appendable appendHeading(Appendable appendable, String heading) throws IOException {
591        return appendHeading(appendable, heading, '-');
592    }
593
594    public static Appendable appendHeading(Appendable appendable, String heading, char underlineChar) throws IOException {
595        appendable.append(heading).append('\n');
596        for (int i = 0; i < heading.length(); i++) {
597            appendable.append(underlineChar);
598        }
599        return appendable.append('\n');
600    }
601
602    public static final String PORTABLE_NEWLINE_REGEX = "\\r?\\n";
603
604    public static List<String> splitLinesPortable(String input) {
605        String[] lines = input.split(PORTABLE_NEWLINE_REGEX);
606        return Arrays.asList(lines);
607    }
608}