From 97a78cc5d6d9fc80348c559be485784faf126572 Mon Sep 17 00:00:00 2001 From: atlas Date: Thu, 4 Jun 2026 15:05:01 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20address=20argus=20review=20on=20matrix?= =?UTF-8?q?=20admin-room=20reset=20=E2=80=94=20ascii=20lowercase,=20tighte?= =?UTF-8?q?r=20markers,=20unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hive-c0re/src/matrix.rs | 59 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/hive-c0re/src/matrix.rs b/hive-c0re/src/matrix.rs index d7ba9f95..841732fa 100644 --- a/hive-c0re/src/matrix.rs +++ b/hive-c0re/src/matrix.rs @@ -354,14 +354,20 @@ async fn discover_admin_room_id( /// Try to parse the new password from an admin-room bot response. /// Conduwuit/tuwunel responds with a message like: /// "Done: Password of user @user:server has been reset. The new password is: " +/// +/// Uses [`str::to_ascii_lowercase`] for case folding — unlike `to_lowercase`, +/// ASCII lowercasing is guaranteed to produce a same-byte-length string, so the +/// byte offset from `find` is always a valid index into the original `bot_message` +/// and we never slice at a non-char boundary. fn extract_new_password(bot_message: &str) -> Option { - // Look for "new password is:" (case-insensitive) followed by whitespace + the password. - let lower = bot_message.to_lowercase(); + // ASCII lowercase: same byte length as the original, so positions from + // `lower.find(marker)` are valid byte indices into `bot_message`. + let lower = bot_message.to_ascii_lowercase(); for marker in &[ "new password is: ", "new password is:", "password is: ", - "password: ", + "password is:", ] { if let Some(pos) = lower.find(marker) { let rest = &bot_message[pos + marker.len()..]; @@ -378,6 +384,53 @@ fn extract_new_password(bot_message: &str) -> Option { None } +#[cfg(test)] +mod extract_new_password_tests { + use super::extract_new_password; + + #[test] + fn tuwunel_style_response() { + let msg = "Done: Password of user @atlas:pr1ma.darkest.space has been reset. The new password is: abc123XYZ!"; + assert_eq!(extract_new_password(msg).as_deref(), Some("abc123XYZ!")); + } + + #[test] + fn case_insensitive_marker() { + let msg = "Password reset complete. New Password Is: S3cr3tP@ss"; + assert_eq!(extract_new_password(msg).as_deref(), Some("S3cr3tP@ss")); + } + + #[test] + fn marker_without_trailing_space() { + let msg = "new password is:hunter2"; + assert_eq!(extract_new_password(msg).as_deref(), Some("hunter2")); + } + + #[test] + fn shorter_marker_variant() { + let msg = "Your password is: Tr0ub4dor&3"; + assert_eq!(extract_new_password(msg).as_deref(), Some("Tr0ub4dor&3")); + } + + #[test] + fn no_match_returns_none() { + let msg = "Command not recognised. Please try again."; + assert_eq!(extract_new_password(msg), None); + } + + #[test] + fn empty_after_marker_returns_none() { + let msg = "new password is: "; + assert_eq!(extract_new_password(msg), None); + } + + #[test] + fn password_stops_at_whitespace() { + let msg = "New password is: abc123 (save it now)"; + assert_eq!(extract_new_password(msg).as_deref(), Some("abc123")); + } +} + /// Reset a user's password via the Matrix admin room as a fallback for /// homeservers that do not implement the Synapse admin REST API (e.g. /// tuwunel 1.6.x). Sends `reset-password @:` to