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);