Skip to content

Commit dfdc2ac

Browse files
committed
refactor: Replace IPv4 and IPv6 regexes with procedural parsers
SonarQube flagged the IPv4 and IPv6 patterns as exceeding the allowed regex complexity (21 and 99 respectively, limit is 20). Parsing syntactically by splitting on the separator and validating each segment is both lower-complexity and easier to read.
1 parent cc7fd42 commit dfdc2ac

1 file changed

Lines changed: 82 additions & 20 deletions

File tree

src/main/java/com/retailsvc/http/validate/DefaultValidator.java

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,6 @@ private record FormatCheck(Predicate<String> isValid, String message) {}
4949

5050
private static final Pattern EMAIL = Pattern.compile("^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$");
5151

52-
private static final Pattern IPV4 =
53-
Pattern.compile(
54-
"^((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)$");
55-
56-
private static final Pattern IPV6 =
57-
Pattern.compile(
58-
"^("
59-
+ "([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}"
60-
+ "|([0-9a-fA-F]{1,4}:){1,7}:"
61-
+ "|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}"
62-
+ "|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}"
63-
+ "|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}"
64-
+ "|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}"
65-
+ "|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}"
66-
+ "|[0-9a-fA-F]{1,4}:(:[0-9a-fA-F]{1,4}){1,6}"
67-
+ "|:((:[0-9a-fA-F]{1,4}){1,7}|:)"
68-
+ ")$");
69-
7052
private static final Pattern HOSTNAME =
7153
Pattern.compile(
7254
"^(?=.{1,253}$)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?"
@@ -86,8 +68,8 @@ private record FormatCheck(Predicate<String> isValid, String message) {}
8668
Map.entry(
8769
"hostname",
8870
new FormatCheck(s -> HOSTNAME.matcher(s).matches(), "not a valid hostname")),
89-
Map.entry("ipv4", new FormatCheck(s -> IPV4.matcher(s).matches(), "not a valid ipv4")),
90-
Map.entry("ipv6", new FormatCheck(s -> IPV6.matcher(s).matches(), "not a valid ipv6")),
71+
Map.entry("ipv4", new FormatCheck(DefaultValidator::isIpv4, "not a valid ipv4")),
72+
Map.entry("ipv6", new FormatCheck(DefaultValidator::isIpv6, "not a valid ipv6")),
9173
Map.entry("regex", new FormatCheck(DefaultValidator::isRegex, "not a valid regex")),
9274
Map.entry("byte", new FormatCheck(DefaultValidator::isByte, "not valid base64")),
9375
Map.entry("binary", new FormatCheck(s -> true, "not valid binary")),
@@ -234,6 +216,86 @@ private static boolean isUriReference(String s) {
234216
}
235217
}
236218

219+
private static boolean isIpv4(String s) {
220+
String[] parts = s.split("\\.", -1);
221+
if (parts.length != 4) {
222+
return false;
223+
}
224+
for (String part : parts) {
225+
if (!isIpv4Octet(part)) {
226+
return false;
227+
}
228+
}
229+
return true;
230+
}
231+
232+
private static boolean isIpv4Octet(String part) {
233+
int len = part.length();
234+
if (len == 0 || len > 3) {
235+
return false;
236+
}
237+
if (len > 1 && part.charAt(0) == '0') {
238+
return false;
239+
}
240+
int n = 0;
241+
for (int i = 0; i < len; i++) {
242+
char c = part.charAt(i);
243+
if (c < '0' || c > '9') {
244+
return false;
245+
}
246+
n = n * 10 + (c - '0');
247+
}
248+
return n <= 255;
249+
}
250+
251+
private static boolean isIpv6(String s) {
252+
int doubleColon = s.indexOf("::");
253+
if (doubleColon != s.lastIndexOf("::")) {
254+
return false;
255+
}
256+
boolean compressed = doubleColon >= 0;
257+
String[] left;
258+
String[] right;
259+
if (compressed) {
260+
String l = s.substring(0, doubleColon);
261+
String r = s.substring(doubleColon + 2);
262+
left = l.isEmpty() ? new String[0] : l.split(":", -1);
263+
right = r.isEmpty() ? new String[0] : r.split(":", -1);
264+
} else {
265+
left = s.split(":", -1);
266+
right = new String[0];
267+
}
268+
int total = left.length + right.length;
269+
if (compressed ? total > 7 : total != 8) {
270+
return false;
271+
}
272+
return allHextets(left) && allHextets(right);
273+
}
274+
275+
private static boolean allHextets(String[] parts) {
276+
for (String hextet : parts) {
277+
if (!isHextet(hextet)) {
278+
return false;
279+
}
280+
}
281+
return true;
282+
}
283+
284+
private static boolean isHextet(String hextet) {
285+
int len = hextet.length();
286+
if (len == 0 || len > 4) {
287+
return false;
288+
}
289+
for (int i = 0; i < len; i++) {
290+
char c = hextet.charAt(i);
291+
boolean hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
292+
if (!hex) {
293+
return false;
294+
}
295+
}
296+
return true;
297+
}
298+
237299
private void validateInteger(Object value, IntegerSchema s, String pointer) {
238300
long n;
239301
switch (value) {

0 commit comments

Comments
 (0)