use regex::{Regex, RegexBuilder};
const SIZE_LIMIT_BYTES: usize = 1 << 20; const DFA_SIZE_LIMIT_BYTES: usize = 1 << 20;
pub const TS_BODY_MAX: usize = 256;
pub const CLOCK_BODY_MAX: usize = 128;
pub fn compile_bounded(pattern: &str) -> Regex {
RegexBuilder::new(pattern)
.size_limit(SIZE_LIMIT_BYTES)
.dfa_size_limit(DFA_SIZE_LIMIT_BYTES)
.build()
.unwrap_or_else(|e| panic!("Failed to compile regex {pattern:?}: {e}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compile_bounded_accepts_normal_pattern() {
let re = compile_bounded(r"^\d{4}-\d{2}-\d{2}$");
assert!(re.is_match("2026-05-16"));
assert!(!re.is_match("not a date"));
}
#[test]
fn compile_bounded_handles_actual_production_pattern_sizes() {
let ts_range = format!(
r"^\s*<(\d{{4}}-\d{{2}}-\d{{2}}[^>]{{0,{TS_BODY_MAX}}})>--?-?<(\d{{4}}-\d{{2}}-\d{{2}}[^>]{{0,{TS_BODY_MAX}}})>"
);
let _ = compile_bounded(&ts_range);
let clock_full = format!(
r"CLOCK:\s*(?:\[([^\]<>]{{1,{CLOCK_BODY_MAX}}})\]|<([^\]<>]{{1,{CLOCK_BODY_MAX}}})>)(?:--(?:\[([^\]<>]{{1,{CLOCK_BODY_MAX}}})\]|<([^\]<>]{{1,{CLOCK_BODY_MAX}}})>))?(?:\s*=>\s*([0-9]{{1,5}}:[0-9]{{1,2}}))?"
);
let _ = compile_bounded(&clock_full);
}
#[test]
fn compile_bounded_rejects_oversized_pattern() {
let huge: String = "a".repeat(1_500_000);
let result = std::panic::catch_unwind(|| compile_bounded(&huge));
assert!(
result.is_err(),
"an oversized pattern must panic at build time, not compile silently"
);
}
#[test]
fn compile_bounded_pathological_input_terminates() {
let ts_anchor = format!(r"^\s*<(\d{{4}}-\d{{2}}-\d{{2}}[^>]{{0,{TS_BODY_MAX}}})>");
let re = compile_bounded(&ts_anchor);
let mut huge_input = String::from("<2024-12-05");
huge_input.push_str(&" ".repeat(1_000_000));
let start = std::time::Instant::now();
assert!(re.find(&huge_input).is_none());
assert!(
start.elapsed() < std::time::Duration::from_secs(5),
"pathological input took too long: {:?}",
start.elapsed()
);
}
}