From 0c08d120c70fadb1d225ef905be17baa1c096353 Mon Sep 17 00:00:00 2001 From: SiteRelEnby <125829806+SiteRelEnby@users.noreply.github.com> Date: Fri, 4 Sep 2026 19:14:24 -0400 Subject: [PATCH] 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. --- .../java/org/asamk/signal/util/TimeParser.java | 16 +++++++++++----- .../org/asamk/signal/util/TimeParserTest.java | 5 +++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/asamk/signal/util/TimeParser.java b/src/main/java/org/asamk/signal/util/TimeParser.java index b64250b5..146f9856 100644 --- a/src/main/java/org/asamk/signal/util/TimeParser.java +++ b/src/main/java/org/asamk/signal/util/TimeParser.java @@ -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|(? 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