diff --git a/crates/collab-net/src/invite.rs b/crates/collab-net/src/invite.rs index 47ca1850..e5d86bf2 100644 --- a/crates/collab-net/src/invite.rs +++ b/crates/collab-net/src/invite.rs @@ -240,6 +240,9 @@ fn percent_decode(s: &str) -> Option { while let Some(&b) = bytes.get(i) { if b == b'%' { let hex = bytes.get(i + 1..i + 3)?; + if !hex.iter().all(u8::is_ascii_hexdigit) { + return None; + } out.push(u8::from_str_radix(std::str::from_utf8(hex).ok()?, 16).ok()?); i += 3; } else { diff --git a/crates/collab-net/src/invite_tests.rs b/crates/collab-net/src/invite_tests.rs index d4d13296..323857d3 100644 --- a/crates/collab-net/src/invite_tests.rs +++ b/crates/collab-net/src/invite_tests.rs @@ -131,3 +131,11 @@ fn port_must_be_all_digits() { assert!(Endpoint::parse(bad).is_err(), "accepted {bad}"); } } + +#[test] +fn plus_or_sign_inside_a_percent_escape_is_broken() { + assert!(Endpoint::parse("ws://example.org/a/?key=%41").is_ok()); + for bad in ["%+1", "%-1", "%1", "%+a"] { + assert!(Endpoint::parse(&format!("ws://example.org/a/?key={bad}")).is_err(), "{bad}"); + } +}