diff --git a/src/padfoot/connection.rs b/src/padfoot/connection.rs index a980471..fe891d9 100644 --- a/src/padfoot/connection.rs +++ b/src/padfoot/connection.rs @@ -11,6 +11,9 @@ pub use self::contacts::*; mod contact_list; pub use self::contact_list::*; +mod escape; +use self::escape::escape; + mod requests; pub use self::requests::*; @@ -309,56 +312,3 @@ impl Connection { tree } } - -fn escape_one(b: u8) -> String { - format!("_{:0<2x}", b) -} - -// Some non-empty sequence of ASCII letters, digits and underscores -fn escape(s: String) -> String { - // Special-case the empty string - if s.is_empty() { - return "_".to_string(); - } - - let bytes = s.into_bytes(); - let mut iter = bytes.iter(); - let mut out = String::new(); - - // Only alphanumeric in the first byte - let x = *iter.next().expect("Already checked len > 0"); - let first = match x { - b'a'..=b'z' | b'A'..=b'Z' => unsafe { String::from_utf8_unchecked(vec![x]) }, - _ => escape_one(x), - }; - - out.push_str(&first); - - for x in iter { - let next = match x { - b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' => unsafe { - String::from_utf8_unchecked(vec![*x]) - }, - _ => escape_one(*x), - }; - - out.push_str(&next); - } - - out -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_escape() { - assert_eq!(escape("".to_string()), "_"); - assert_eq!(escape("foo".to_string()), "foo"); - assert_eq!(escape("foo@bar".to_string()), "foo_40bar"); - assert_eq!(escape("foo_bar".to_string()), "foo_5fbar"); - assert_eq!(escape("foo__@__bar".to_string()), "foo_5f_5f_40_5f_5fbar"); - assert_eq!(escape("1foo".to_string()), "_31foo"); - } -} diff --git a/src/padfoot/connection/escape.rs b/src/padfoot/connection/escape.rs new file mode 100644 index 0000000..808ba02 --- /dev/null +++ b/src/padfoot/connection/escape.rs @@ -0,0 +1,52 @@ +fn escape_one(b: u8) -> String { + format!("_{:0<2x}", b) +} + +// Some non-empty sequence of ASCII letters, digits and underscores +pub fn escape(s: String) -> String { + // Special-case the empty string + if s.is_empty() { + return "_".to_string(); + } + + let bytes = s.into_bytes(); + let mut iter = bytes.iter(); + let mut out = String::new(); + + // Only alphanumeric in the first byte + let x = *iter.next().expect("Already checked len > 0"); + let first = match x { + b'a'..=b'z' | b'A'..=b'Z' => unsafe { String::from_utf8_unchecked(vec![x]) }, + _ => escape_one(x), + }; + + out.push_str(&first); + + for x in iter { + let next = match x { + b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' => unsafe { + String::from_utf8_unchecked(vec![*x]) + }, + _ => escape_one(*x), + }; + + out.push_str(&next); + } + + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_escape() { + assert_eq!(escape("".to_string()), "_"); + assert_eq!(escape("foo".to_string()), "foo"); + assert_eq!(escape("foo@bar".to_string()), "foo_40bar"); + assert_eq!(escape("foo_bar".to_string()), "foo_5fbar"); + assert_eq!(escape("foo__@__bar".to_string()), "foo_5f_5f_40_5f_5fbar"); + assert_eq!(escape("1foo".to_string()), "_31foo"); + } +}