| 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><script>alert(1)</script></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("&", "&") |
| 45 | .replace("<", "<") | |
| 46 | .replace(">", ">"); | |
| 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('XSS Attack') | |
| 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("&", "&") |
| 78 | .replace("<", "<") | |
| 79 | .replace("\"", """) | |
| 80 | .replace("'", "'"); | |
| 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 |
|
| 44 |
1.1 |
|
| 74 |
1.1 |
|
| 77 |
1.1 |
|
| 87 |
1.1 |
|
| 88 |
1.1 |
|
| 90 |
1.1 |
|
| 100 |
1.1 |
|
| 101 |
1.1 |
|
| 103 |
1.1 |
|
| 112 |
1.1 2.2 |
|
| 115 |
1.1 2.2 |
|
| 121 |
1.1 2.2 |
|
| 122 |
1.1 |
|
| 131 |
1.1 2.2 |
|
| 134 |
1.1 2.2 |
|
| 137 |
1.1 |
|
| 144 |
1.1 2.2 |
|
| 145 |
1.1 |
|
| 156 |
1.1 2.2 |
|
| 157 |
1.1 |
|
| 159 |
1.1 2.2 |
|
| 161 |
1.1 2.2 3.3 4.4 |
|
| 162 |
1.1 |
|
| 165 |
1.1 |