jid: Add support for XEP-0106: JID Escaping
This is used by Slidge, through slixmpp, and could be useful for any other gateway based on this crate.
This commit is contained in:
parent
bc88134c41
commit
7f1dc96fac
2 changed files with 99 additions and 0 deletions
|
|
@ -1,4 +1,6 @@
|
|||
Version NEXT:
|
||||
* Changes:
|
||||
- Add a `NodeRef::unescape()` method, to implement XEP-0106 escaping.
|
||||
|
||||
Version 0.12.1, release 2025-11-02:
|
||||
* Changes:
|
||||
|
|
|
|||
|
|
@ -320,6 +320,54 @@ impl NodeRef {
|
|||
pub fn with_domain(&self, domain: &DomainRef) -> BareJid {
|
||||
BareJid::from_parts(Some(self), domain)
|
||||
}
|
||||
|
||||
/// Implements XEP-0106 unescape algorithm.
|
||||
pub fn unescape(&self) -> Result<Cow<'_, str>, Error> {
|
||||
fn hex_to_char(bytes: [u8; 2]) -> Result<char, ()> {
|
||||
Ok(match &[bytes[0], bytes[1]] {
|
||||
b"20" => ' ',
|
||||
b"22" => '"',
|
||||
b"26" => '&',
|
||||
b"27" => '\'',
|
||||
b"2f" => '/',
|
||||
b"3a" => ':',
|
||||
b"3c" => '<',
|
||||
b"3e" => '>',
|
||||
b"40" => '@',
|
||||
b"5c" => '\\',
|
||||
_ => return Err(()),
|
||||
})
|
||||
}
|
||||
|
||||
let bytes = self.0.as_bytes();
|
||||
let mut iter = memchr::memchr_iter(b'\\', bytes).peekable();
|
||||
|
||||
// Fast path in case there is no character to unescape.
|
||||
if iter.peek().is_none() {
|
||||
return Ok(Cow::Borrowed(self));
|
||||
}
|
||||
|
||||
let mut buf = String::with_capacity(bytes.len());
|
||||
let mut valid_up_to = 0;
|
||||
for index in iter {
|
||||
buf.push_str(&self.0[valid_up_to..index]);
|
||||
match hex_to_char([bytes[index + 1], bytes[index + 2]]) {
|
||||
Ok(char) => {
|
||||
buf.push(char);
|
||||
valid_up_to = index + 3;
|
||||
}
|
||||
// If the escape code wasn’t valid, we want to copy it as is.
|
||||
Err(()) => valid_up_to = index,
|
||||
}
|
||||
}
|
||||
buf.push_str(&self.0[valid_up_to..]);
|
||||
|
||||
if buf.starts_with(' ') || buf.ends_with(' ') {
|
||||
return Err(Error::NodePrep);
|
||||
}
|
||||
|
||||
Ok(Cow::Owned(buf))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -466,4 +514,53 @@ mod tests {
|
|||
"resource doesn’t pass resourceprep validation",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unescape() {
|
||||
let node = NodePart::new("foo\\40bar").unwrap();
|
||||
assert_eq!(node.unescape().unwrap(), "foo@bar");
|
||||
|
||||
let node = NodePart::new("\\22\\26\\27\\2f\\20\\3a\\3c\\3e\\40\\5c").unwrap();
|
||||
assert_eq!(node.unescape().unwrap(), "\"&'/ :<>@\\");
|
||||
|
||||
let node = NodePart::new("\\20foo").unwrap();
|
||||
node.unescape().unwrap_err();
|
||||
|
||||
let node = NodePart::new("foo\\20").unwrap();
|
||||
node.unescape().unwrap_err();
|
||||
|
||||
let jid = BareJid::new("tréville\\40musketeers.lit@smtp.gascon.fr").unwrap();
|
||||
let node = jid.node().unwrap();
|
||||
assert_eq!(node.unescape().unwrap(), "tréville@musketeers.lit");
|
||||
|
||||
// Those come from section 5.1 from the XEP.
|
||||
let data = [
|
||||
("space cadet@example.com", "space\\20cadet@example.com"),
|
||||
(
|
||||
"call me \"ishmael\"@example.com",
|
||||
"call\\20me\\20\\22ishmael\\22@example.com",
|
||||
),
|
||||
("at&t guy@example.com", "at\\26t\\20guy@example.com"),
|
||||
("d'artagnan@example.com", "d\\27artagnan@example.com"),
|
||||
("/.fanboy@example.com", "\\2f.fanboy@example.com"),
|
||||
("::foo::@example.com", "\\3a\\3afoo\\3a\\3a@example.com"),
|
||||
("<foo>@example.com", "\\3cfoo\\3e@example.com"),
|
||||
("user@host@example.com", "user\\40host@example.com"),
|
||||
("c:\\net@example.com", "c\\3a\\net@example.com"),
|
||||
("c:\\\\net@example.com", "c\\3a\\\\net@example.com"),
|
||||
(
|
||||
"c:\\cool stuff@example.com",
|
||||
"c\\3a\\cool\\20stuff@example.com",
|
||||
),
|
||||
("c:\\5commas@example.com", "c\\3a\\5c5commas@example.com"),
|
||||
];
|
||||
for (unescaped, escaped) in data {
|
||||
let jid = BareJid::new(escaped).unwrap();
|
||||
let node = jid.node().unwrap();
|
||||
assert_eq!(
|
||||
alloc::format!("{}@example.com", node.unescape().unwrap()),
|
||||
unescaped
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue