mirror of
https://github.com/AsamK/signal-cli.git
synced 2026-11-01 09:49:27 +00:00
Avoid regex backtracking in duration parsing
Consume the duration text token by token instead of validating the whole string with a nested-quantifier regex, which could backtrack exponentially on crafted input.
This commit is contained in:
parent
8534a14602
commit
0c08d120c7
@ -31,7 +31,6 @@ import java.util.regex.Pattern;
|
||||
public final class TimeParser {
|
||||
|
||||
private static final Pattern DURATION_TOKEN = Pattern.compile("(\\d+(?:[.,]\\d+)?|\\ban?\\b)\\s*([a-zA-Z]+)");
|
||||
private static final Pattern DURATION_ONLY = Pattern.compile("^(?:(?:\\d+(?:[.,]\\d+)?|an?)\\s*[a-zA-Z]+\\s*)+$");
|
||||
private static final Pattern DURATION_COLON = Pattern.compile("^(\\d+):(\\d{1,2})(?::(\\d{1,2}))?$");
|
||||
private static final Pattern UNIX_TIMESTAMP = Pattern.compile("^\\d{9,}$");
|
||||
private static final Pattern TIME_OF_DAY = Pattern.compile(
|
||||
@ -113,12 +112,19 @@ public final class TimeParser {
|
||||
|
||||
// Separators between the parts: "2 hours, 30 minutes" / "2 hours and 30 minutes"
|
||||
text = text.replaceAll("\\band\\b|(?<!\\d),|,(?!\\d)", " ").replaceAll("\\s+", " ").trim();
|
||||
if (!DURATION_ONLY.matcher(text).matches()) {
|
||||
throw new TimeParseException("Invalid duration: " + input);
|
||||
}
|
||||
// Consume the text token by token, so that everything has to be part of an amount/unit pair
|
||||
final var matcher = DURATION_TOKEN.matcher(text);
|
||||
var result = Duration.ZERO;
|
||||
while (matcher.find()) {
|
||||
var position = 0;
|
||||
while (position < text.length()) {
|
||||
matcher.region(position, text.length());
|
||||
if (!matcher.lookingAt()) {
|
||||
throw new TimeParseException("Invalid duration: " + input);
|
||||
}
|
||||
position = matcher.end();
|
||||
while (position < text.length() && text.charAt(position) == ' ') {
|
||||
position++;
|
||||
}
|
||||
final var amountText = matcher.group(1);
|
||||
final var unitText = matcher.group(2).toLowerCase(Locale.ROOT);
|
||||
final var unit = UNITS.get(unitText);
|
||||
|
||||
@ -77,6 +77,11 @@ class TimeParserTest {
|
||||
assertThrows(TimeParser.TimeParseException.class, () -> duration("18:00pm"));
|
||||
assertThrows(TimeParser.TimeParseException.class, () -> duration("PT"));
|
||||
assertThrows(TimeParser.TimeParseException.class, () -> duration("soon"));
|
||||
assertThrows(TimeParser.TimeParseException.class, () -> duration("2h xyz"));
|
||||
assertThrows(TimeParser.TimeParseException.class, () -> duration("2h 30"));
|
||||
assertThrows(TimeParser.TimeParseException.class, () -> duration("hours 2"));
|
||||
// Must fail fast, without regex backtracking blowing up
|
||||
assertThrows(TimeParser.TimeParseException.class, () -> duration("a" + "Aa".repeat(5000)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user