From 301df2a57ec1bceb163e65441e2ddb65f26f5235 Mon Sep 17 00:00:00 2001 From: Justin Jones Date: Mon, 24 Aug 2026 12:32:57 -0600 Subject: [PATCH] Fix quadratic scanning of scheme-less input that cannot be a host When require_host is set and the current candidate can no longer be a valid host, stop scanning instead of running to the end of the input. Each candidate used to scan to the end, which made link extraction with url_must_have_scheme(false) quadratic on inputs like "a.a~" repeated. Fixes #115 --- src/domains.rs | 3 +++ tests/domains.rs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/src/domains.rs b/src/domains.rs index 92c2eb3..f65a740 100644 --- a/src/domains.rs +++ b/src/domains.rs @@ -98,6 +98,9 @@ pub(crate) fn find_authority_end( let mut chars = s.char_indices(); while let Some((i, c)) = chars.next() { + if require_host && !userinfo_allowed && !maybe_host { + break; + } let can_be_last = match c { // ALPHA 'a'..='z' | 'A'..='Z' | '\u{80}'..=char::MAX => { diff --git a/tests/domains.rs b/tests/domains.rs index f8c7f44..5d9b2c0 100644 --- a/tests/domains.rs +++ b/tests/domains.rs @@ -181,6 +181,15 @@ pub fn test_international_allowed() { assert_eq!(link.as_str(), "\u{A1}\u{A2}example.com"); } +#[test] +fn no_quadratic_scan_of_schemeless_hosts() { + // Each `~` makes the current candidate invalid as a host. Scanning has to stop there + // instead of running to the end of the input for every candidate, otherwise this test + // takes hours instead of milliseconds. + let input = "a.a~".repeat(2_000_000); + assert_not_linked(&input); +} + fn assert_linked(input: &str, expected: &str) { let mut finder = LinkFinder::new(); finder.url_must_have_scheme(false);