StringUtils.java

1
package com.renomad.minum.utils;
2
3
import java.net.URLDecoder;
4
import java.net.URLEncoder;
5
import java.security.SecureRandom;
6
import java.util.List;
7
import java.util.stream.Collectors;
8
import java.util.stream.IntStream;
9
10
import static com.renomad.minum.utils.Invariants.mustNotBeNull;
11
import static java.nio.charset.StandardCharsets.UTF_8;
12
13
/**
14
 * Some helper methods for Strings.
15
 */
16
public final class StringUtils {
17
18
    private StringUtils() {
19
        // making this private to be clearer it isn't supposed to be instantiated.
20
    }
21
22
23
    /**
24
     * Returns text that has three symbols replaced -
25
     * the less-than, greater-than, and ampersand.
26
     * See <a href="https://www.w3.org/International/questions/qa-escapes#use">...</a>
27
     * <br>
28
     * <pre>{@code
29
     * This will protect against something like <div>$USERNAME</div> allowing
30
     * a username of
31
     *      <script>alert(1)</script>
32
     * becoming
33
     *      <div><script>alert(1)</script</div>
34
     * and instead becomes
35
     *      <div>&lt;script&gt;alert(1)&lt;/script&gt;</div>
36
     * }</pre>
37
     * If the text is going inside an attribute (e.g. {@code <div class="TEXT_GOES_HERE">} )
38
     * Then you need to escape slightly differently. In that case see [safeAttr]
39
     */
40
    public static String safeHtml(String input) {
41 1 1. safeHtml : negated conditional → KILLED
        if (input == null) {
42
            return "";
43
        }
44 1 1. safeHtml : replaced return value with "" for com/renomad/minum/utils/StringUtils::safeHtml → KILLED
        return input.replace("&", "&amp;")
45
            .replace("<", "&lt;")
46
            .replace(">", "&gt;");
47
    }
48
49
    /**
50
     * Replace dangerous text that would go inside an HTML attribute.
51
     * See {@link #safeHtml(String)}
52
     * <br><br>
53
     * If we get a null string, just return an empty string
54
     * <br><br>
55
     * <p>
56
     *     Note: When cleaning data which will be used in an HTML
57
     *     attribute, ensure that the output of this method will
58
     *     be placed inside single or double quotes. <br><br>
59
     *
60
     *     Valid use: the key word HERE in the following: {@code <foo class="HERE">} or
61
     *     {@code <foo class='HERE'>}<br><br>
62
     *
63
     *     Invalid use: {@code <foo class=HERE>}
64
     * </p>
65
     * <pre>{@code
66
     * example:
67
     *   Given
68
     *      alert('XSS Attack')
69
     *   Get
70
     *      alert(&apos;XSS Attack&apos;)
71
     * }</pre>
72
     */
73
    public static String safeAttr(String input) {
74 1 1. safeAttr : negated conditional → KILLED
        if (input == null) {
75
            return "";
76
        }
77 1 1. safeAttr : replaced return value with "" for com/renomad/minum/utils/StringUtils::safeAttr → KILLED
        return input.replace("&", "&amp;")
78
            .replace("<", "&lt;")
79
            .replace("\"", "&quot;")
80
            .replace("'", "&apos;");
81
    }
82
83
    /**
84
     * Encodes UTF-8 text using URL-encoding
85
     */
86
    public static String encode(String str) {
87 1 1. encode : negated conditional → KILLED
        if (str == null) {
88 1 1. encode : replaced return value with "" for com/renomad/minum/utils/StringUtils::encode → KILLED
            return "%NULL%";
89
        }
90 1 1. encode : replaced return value with "" for com/renomad/minum/utils/StringUtils::encode → KILLED
        return URLEncoder.encode(str, UTF_8);
91
    }
92
93
    /**
94
     * Decodes URL-encoded UTF-8 text, except that we
95
     * first check if the string value is the token %NULL%,
96
     * which is our way to signify null.
97
     */
98
    public static String decode(String str) {
99
        mustNotBeNull(str);
100 1 1. decode : negated conditional → KILLED
        if (str.equals("%NULL%")) {
101 1 1. decode : replaced return value with "" for com/renomad/minum/utils/StringUtils::decode → TIMED_OUT
            return null;
102
        }
103 1 1. decode : replaced return value with "" for com/renomad/minum/utils/StringUtils::decode → KILLED
        return URLDecoder.decode(str, UTF_8);
104
    }
105
106
    /**
107
     * Returns a string of random characters.
108
     * @param length a positive non-zero number less than 1000
109
     */
110
    public static String generateSecureRandomString(int length) {
111
        int maximum = 1000;
112 2 1. generateSecureRandomString : negated conditional → KILLED
2. generateSecureRandomString : changed conditional boundary → KILLED
        if (length <= 0) {
113
            throw new UtilsException("Input must be a positive non-zero number");
114
        }
115 2 1. generateSecureRandomString : changed conditional boundary → KILLED
2. generateSecureRandomString : negated conditional → KILLED
        if (length > maximum) {
116
            throw new UtilsException("Input must be less than " + maximum);
117
        }
118
        final var allowedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
119
        final var sr = new SecureRandom();
120
121 2 1. generateSecureRandomString : Replaced integer addition with subtraction → KILLED
2. generateSecureRandomString : replaced return value with "" for com/renomad/minum/utils/StringUtils::generateSecureRandomString → KILLED
        return IntStream.range(1, length+1)
122 1 1. lambda$generateSecureRandomString$0 : replaced Character return value with 0 for com/renomad/minum/utils/StringUtils::lambda$generateSecureRandomString$0 → KILLED
                .mapToObj (x -> allowedChars.charAt(sr.nextInt(allowedChars.length())))
123
                .map(Object::toString)
124
                .collect(Collectors.joining());
125
    }
126
127
    /**
128
     * Converts a list of bytes to a string. Returns null if the input is null.
129
     */
130
    public static String byteListToString(List<Byte> byteList) {
131 2 1. byteListToString : negated conditional → KILLED
2. byteListToString : replaced return value with "" for com/renomad/minum/utils/StringUtils::byteListToString → KILLED
        if (byteList == null) return null;
132
        final int size = byteList.size();
133
        final var buf = new byte[size];
134 2 1. byteListToString : changed conditional boundary → KILLED
2. byteListToString : negated conditional → KILLED
        for (int i = 0; i < size; i++) {
135
            buf[i] = byteList.get(i);
136
        }
137 1 1. byteListToString : replaced return value with "" for com/renomad/minum/utils/StringUtils::byteListToString → KILLED
        return new String(buf, UTF_8);
138
    }
139
140
    /**
141
     * Converts an array of bytes to a string. Returns null if the input is null.
142
     */
143
    public static String byteArrayToString(byte[] byteArray) {
144 2 1. byteArrayToString : negated conditional → KILLED
2. byteArrayToString : replaced return value with "" for com/renomad/minum/utils/StringUtils::byteArrayToString → KILLED
        if (byteArray == null) return null;
145 1 1. byteArrayToString : replaced return value with "" for com/renomad/minum/utils/StringUtils::byteArrayToString → KILLED
        return new String(byteArray, UTF_8);
146
    }
147
148
    /**
149
     * A helper method to assess whether the input consists of only digits.
150
     * This helps us in the case of checking the content-length header, since
151
     * it must be pure numbers, no "-" or "+" symbols preceding it.
152
     * @return true if the given string consists exclusively of digits, like "1234",
153
     * false if anything else, like "+1234" or "12.34" or "" or null.
154
     */
155
    public static boolean isPureDigits(String str) {
156 2 1. isPureDigits : negated conditional → KILLED
2. isPureDigits : negated conditional → KILLED
        if (str == null || str.isEmpty()) {
157 1 1. isPureDigits : replaced boolean return with true for com/renomad/minum/utils/StringUtils::isPureDigits → KILLED
            return false;
158
        }
159 2 1. isPureDigits : changed conditional boundary → KILLED
2. isPureDigits : negated conditional → KILLED
        for (int i = 0; i < str.length(); i++) {
160
            char c = str.charAt(i);
161 4 1. isPureDigits : changed conditional boundary → KILLED
2. isPureDigits : negated conditional → KILLED
3. isPureDigits : changed conditional boundary → KILLED
4. isPureDigits : negated conditional → KILLED
            if (c < '0' || c > '9') {
162 1 1. isPureDigits : replaced boolean return with true for com/renomad/minum/utils/StringUtils::isPureDigits → KILLED
                return false;
163
            }
164
        }
165 1 1. isPureDigits : replaced boolean return with false for com/renomad/minum/utils/StringUtils::isPureDigits → KILLED
        return true;
166
    }
167
168
169
}
170

Mutations

41

1.1
Location : safeHtml
Killed by : com.renomad.minum.utils.StringUtilsTests
negated conditional → KILLED

44

1.1
Location : safeHtml
Killed by : com.renomad.minum.utils.StringUtilsTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::safeHtml → KILLED

74

1.1
Location : safeAttr
Killed by : com.renomad.minum.utils.StringUtilsTests
negated conditional → KILLED

77

1.1
Location : safeAttr
Killed by : com.renomad.minum.utils.StringUtilsTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::safeAttr → KILLED

87

1.1
Location : encode
Killed by : com.renomad.minum.utils.SerializationUtilsTests
negated conditional → KILLED

88

1.1
Location : encode
Killed by : com.renomad.minum.utils.SerializationUtilsTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::encode → KILLED

90

1.1
Location : encode
Killed by : com.renomad.minum.utils.SerializationUtilsTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::encode → KILLED

100

1.1
Location : decode
Killed by : com.renomad.minum.web.BodyProcessorTests
negated conditional → KILLED

101

1.1
Location : decode
Killed by : none
replaced return value with "" for com/renomad/minum/utils/StringUtils::decode → TIMED_OUT

103

1.1
Location : decode
Killed by : com.renomad.minum.web.BodyProcessorTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::decode → KILLED

112

1.1
Location : generateSecureRandomString
Killed by : com.renomad.minum.utils.StringUtilsTests
negated conditional → KILLED

2.2
Location : generateSecureRandomString
Killed by : com.renomad.minum.utils.StringUtilsTests
changed conditional boundary → KILLED

115

1.1
Location : generateSecureRandomString
Killed by : com.renomad.minum.utils.StringUtilsTests
changed conditional boundary → KILLED

2.2
Location : generateSecureRandomString
Killed by : com.renomad.minum.utils.StringUtilsTests
negated conditional → KILLED

121

1.1
Location : generateSecureRandomString
Killed by : com.renomad.minum.utils.StringUtilsTests
Replaced integer addition with subtraction → KILLED

2.2
Location : generateSecureRandomString
Killed by : com.renomad.minum.utils.StringUtilsTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::generateSecureRandomString → KILLED

122

1.1
Location : lambda$generateSecureRandomString$0
Killed by : com.renomad.minum.FunctionalTests.testEndToEnd_Functional(com.renomad.minum.FunctionalTests)
replaced Character return value with 0 for com/renomad/minum/utils/StringUtils::lambda$generateSecureRandomString$0 → KILLED

131

1.1
Location : byteListToString
Killed by : com.renomad.minum.utils.StringUtilsTests
negated conditional → KILLED

2.2
Location : byteListToString
Killed by : com.renomad.minum.utils.StringUtilsTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::byteListToString → KILLED

134

1.1
Location : byteListToString
Killed by : com.renomad.minum.utils.StringUtilsTests
changed conditional boundary → KILLED

2.2
Location : byteListToString
Killed by : com.renomad.minum.utils.StringUtilsTests
negated conditional → KILLED

137

1.1
Location : byteListToString
Killed by : com.renomad.minum.utils.StringUtilsTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::byteListToString → KILLED

144

1.1
Location : byteArrayToString
Killed by : com.renomad.minum.utils.StringUtilsTests
negated conditional → KILLED

2.2
Location : byteArrayToString
Killed by : com.renomad.minum.utils.StringUtilsTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::byteArrayToString → KILLED

145

1.1
Location : byteArrayToString
Killed by : com.renomad.minum.web.BodyProcessorTests
replaced return value with "" for com/renomad/minum/utils/StringUtils::byteArrayToString → KILLED

156

1.1
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
negated conditional → KILLED

2.2
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
negated conditional → KILLED

157

1.1
Location : isPureDigits
Killed by : com.renomad.minum.utils.StringUtilsTests
replaced boolean return with true for com/renomad/minum/utils/StringUtils::isPureDigits → KILLED

159

1.1
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
changed conditional boundary → KILLED

2.2
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
negated conditional → KILLED

161

1.1
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
changed conditional boundary → KILLED

2.2
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
negated conditional → KILLED

3.3
Location : isPureDigits
Killed by : com.renomad.minum.utils.StringUtilsTests
changed conditional boundary → KILLED

4.4
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
negated conditional → KILLED

162

1.1
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
replaced boolean return with true for com/renomad/minum/utils/StringUtils::isPureDigits → KILLED

165

1.1
Location : isPureDigits
Killed by : com.renomad.minum.web.HeadersTests
replaced boolean return with false for com/renomad/minum/utils/StringUtils::isPureDigits → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0