2023-03-28 23:12:12 +00:00
|
|
|
use std::fmt::Display;
|
|
|
|
|
2023-09-30 15:43:46 +00:00
|
|
|
use anyhow::{anyhow, Result};
|
2023-03-12 12:25:23 +00:00
|
|
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
|
2023-03-11 15:07:02 +00:00
|
|
|
use quick_xml::name::{Namespace, ResolveResult};
|
|
|
|
|
|
|
|
use crate::prelude::*;
|
2023-09-30 15:43:46 +00:00
|
|
|
use crate::xml::*;
|
2023-03-11 15:07:02 +00:00
|
|
|
|
|
|
|
pub const XMLNS: &'static str = "urn:ietf:params:xml:ns:xmpp-bind";
|
|
|
|
|
2023-03-12 12:25:23 +00:00
|
|
|
// TODO remove `pub` in newtypes, introduce validation
|
|
|
|
|
2024-04-07 12:06:23 +00:00
|
|
|
/// Name (node identifier) of an XMPP entity. Placed before the `@` in a JID.
|
2023-04-09 21:31:43 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
2023-04-13 22:38:26 +00:00
|
|
|
pub struct Name(pub Str);
|
2023-03-11 15:07:02 +00:00
|
|
|
|
2024-04-07 12:06:23 +00:00
|
|
|
/// Server name of an XMPP entity. Placed after the `@` and before the `/` in a JID.
|
2023-04-09 21:31:43 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
2023-04-13 22:38:26 +00:00
|
|
|
pub struct Server(pub Str);
|
2023-03-11 15:07:02 +00:00
|
|
|
|
2024-04-07 12:06:23 +00:00
|
|
|
/// Resource of an XMPP entity. Placed after the `/` in a JID.
|
2023-04-09 21:31:43 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
2023-04-13 22:38:26 +00:00
|
|
|
pub struct Resource(pub Str);
|
2023-03-11 15:07:02 +00:00
|
|
|
|
2023-04-09 21:31:43 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug, Clone)]
|
2023-03-11 15:07:02 +00:00
|
|
|
pub struct Jid {
|
2023-03-28 23:12:12 +00:00
|
|
|
pub name: Option<Name>,
|
2023-03-11 15:07:02 +00:00
|
|
|
pub server: Server,
|
2023-03-28 23:12:12 +00:00
|
|
|
pub resource: Option<Resource>,
|
2023-03-11 15:07:02 +00:00
|
|
|
}
|
|
|
|
|
2023-03-28 23:12:12 +00:00
|
|
|
impl Display for Jid {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
if let Some(name) = &self.name {
|
|
|
|
write!(f, "{}@", &name.0)?;
|
|
|
|
}
|
|
|
|
write!(f, "{}", &self.server.0)?;
|
|
|
|
if let Some(resource) = &self.resource {
|
|
|
|
write!(f, "/{}", &resource.0)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
2023-03-12 12:25:23 +00:00
|
|
|
}
|
2023-03-28 23:12:12 +00:00
|
|
|
}
|
2023-03-27 21:45:44 +00:00
|
|
|
|
2023-03-28 23:12:12 +00:00
|
|
|
impl Jid {
|
2023-03-27 21:45:44 +00:00
|
|
|
pub fn from_string(i: &str) -> Result<Jid> {
|
2023-08-26 15:50:05 +00:00
|
|
|
use lazy_static::lazy_static;
|
2024-03-20 18:59:15 +00:00
|
|
|
use regex::Regex;
|
2023-08-26 15:50:05 +00:00
|
|
|
lazy_static! {
|
|
|
|
static ref RE: Regex = Regex::new(r"^(([a-zA-Z]+)@)?([a-zA-Z.]+)(/([a-zA-Z\-]+))?$").unwrap();
|
|
|
|
}
|
2024-03-20 18:59:15 +00:00
|
|
|
let m = RE.captures(i).ok_or(anyhow!("Incorrectly format jid: {i}"))?;
|
2023-03-27 21:45:44 +00:00
|
|
|
|
2023-04-13 19:15:48 +00:00
|
|
|
let name = m.get(2).map(|name| Name(name.as_str().into()));
|
2023-03-28 23:12:12 +00:00
|
|
|
let server = m.get(3).unwrap();
|
2023-04-13 22:38:26 +00:00
|
|
|
let server = Server(server.as_str().into());
|
2024-03-20 18:59:15 +00:00
|
|
|
let resource = m.get(5).map(|resource| Resource(resource.as_str().into()));
|
|
|
|
|
|
|
|
Ok(Jid { name, server, resource })
|
2023-03-27 21:45:44 +00:00
|
|
|
}
|
2023-03-12 12:25:23 +00:00
|
|
|
}
|
|
|
|
|
2023-03-11 15:07:02 +00:00
|
|
|
/// Request to bind to a resource.
|
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
/// ```xml
|
|
|
|
/// <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
|
|
|
/// <resource>mobile</resource>
|
|
|
|
/// </bind>
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2023-03-11 23:30:48 +00:00
|
|
|
pub struct BindRequest(pub Resource);
|
2023-03-11 15:07:02 +00:00
|
|
|
|
2023-03-12 13:15:13 +00:00
|
|
|
impl FromXmlTag for BindRequest {
|
|
|
|
const NS: &'static str = XMLNS;
|
|
|
|
const NAME: &'static str = "bind";
|
2023-03-11 17:36:38 +00:00
|
|
|
}
|
|
|
|
|
2023-03-11 15:07:02 +00:00
|
|
|
impl FromXml for BindRequest {
|
2023-09-30 16:02:18 +00:00
|
|
|
type P = impl Parser<Output = Result<Self>>;
|
2023-03-11 15:07:02 +00:00
|
|
|
|
|
|
|
fn parse() -> Self::P {
|
2023-09-30 16:02:18 +00:00
|
|
|
|(namespace, event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
|
|
|
|
let mut resource: Option<Str> = None;
|
|
|
|
let Event::Start(bytes) = event else {
|
|
|
|
return Err(anyhow!("Unexpected XML event: {event:?}"));
|
|
|
|
};
|
|
|
|
if bytes.name().0 != BindRequest::NAME.as_bytes() {
|
|
|
|
return Err(anyhow!("Unexpected XML tag: {:?}", bytes.name()));
|
2023-03-11 15:07:02 +00:00
|
|
|
}
|
2023-09-30 16:02:18 +00:00
|
|
|
let ResolveResult::Bound(Namespace(ns)) = namespace else {
|
|
|
|
return Err(anyhow!("No namespace provided"));
|
|
|
|
};
|
|
|
|
if ns != XMLNS.as_bytes() {
|
|
|
|
return Err(anyhow!("Incorrect namespace"));
|
|
|
|
}
|
|
|
|
loop {
|
|
|
|
let (namespace, event) = yield;
|
|
|
|
match event {
|
|
|
|
Event::Start(bytes) if bytes.name().0 == b"resource" => {
|
|
|
|
let (namespace, event) = yield;
|
|
|
|
let Event::Text(text) = event else {
|
|
|
|
return Err(anyhow!("Unexpected XML event: {event:?}"));
|
2023-03-11 15:07:02 +00:00
|
|
|
};
|
2023-09-30 16:02:18 +00:00
|
|
|
resource = Some(std::str::from_utf8(&*text)?.into());
|
|
|
|
let (namespace, event) = yield;
|
|
|
|
let Event::End(bytes) = event else {
|
|
|
|
return Err(anyhow!("Unexpected XML event: {event:?}"));
|
|
|
|
};
|
|
|
|
if bytes.name().0 != b"resource" {
|
|
|
|
return Err(anyhow!("Unexpected XML tag: {:?}", bytes.name()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Event::End(bytes) if bytes.name().0 == BindRequest::NAME.as_bytes() => {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
|
2023-03-11 15:07:02 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-30 16:02:18 +00:00
|
|
|
let Some(resource) = resource else {
|
|
|
|
return Err(anyhow!("No resource was provided"));
|
|
|
|
};
|
|
|
|
Ok(BindRequest(Resource(resource)))
|
2023-03-11 15:07:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-12 12:25:23 +00:00
|
|
|
pub struct BindResponse(pub Jid);
|
|
|
|
|
|
|
|
impl ToXml for BindResponse {
|
|
|
|
fn serialize(&self, events: &mut Vec<Event<'static>>) {
|
|
|
|
events.extend_from_slice(&[
|
2024-03-20 18:59:15 +00:00
|
|
|
Event::Start(BytesStart::new(r#"bind xmlns="urn:ietf:params:xml:ns:xmpp-bind""#)),
|
2023-03-12 12:25:23 +00:00
|
|
|
Event::Start(BytesStart::new(r#"jid"#)),
|
|
|
|
Event::Text(BytesText::new(self.0.to_string().as_str()).into_owned()),
|
|
|
|
Event::End(BytesEnd::new("jid")),
|
|
|
|
Event::End(BytesEnd::new("bind")),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
}
|
2023-03-11 15:07:02 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use quick_xml::NsReader;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn parse_message() {
|
2024-03-20 18:59:15 +00:00
|
|
|
let input = r#"<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>mobile</resource></bind>"#;
|
2023-03-11 15:07:02 +00:00
|
|
|
let mut reader = NsReader::from_reader(input.as_bytes());
|
|
|
|
let mut buf = vec![];
|
2024-03-20 18:59:15 +00:00
|
|
|
let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await.unwrap();
|
2023-03-11 15:07:02 +00:00
|
|
|
let mut parser = BindRequest::parse().consume(ns, &event);
|
|
|
|
let result = loop {
|
|
|
|
match parser {
|
|
|
|
Continuation::Final(res) => break res,
|
|
|
|
Continuation::Continue(next) => {
|
2024-03-20 18:59:15 +00:00
|
|
|
let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await.unwrap();
|
2023-03-11 15:07:02 +00:00
|
|
|
parser = next.consume(ns, &event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.unwrap();
|
2023-04-13 22:38:26 +00:00
|
|
|
assert_eq!(result, BindRequest(Resource("mobile".into())),)
|
2023-03-11 15:07:02 +00:00
|
|
|
}
|
2023-03-28 23:12:12 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn jid_parse_full() {
|
|
|
|
let input = "chelik@server.example/kek";
|
|
|
|
let expected = Jid {
|
|
|
|
name: Some(Name("chelik".into())),
|
|
|
|
server: Server("server.example".into()),
|
|
|
|
resource: Some(Resource("kek".into())),
|
|
|
|
};
|
|
|
|
let res = Jid::from_string(input).unwrap();
|
|
|
|
assert_eq!(res, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn jid_parse_user() {
|
|
|
|
let input = "chelik@server.example";
|
|
|
|
let expected = Jid {
|
|
|
|
name: Some(Name("chelik".into())),
|
|
|
|
server: Server("server.example".into()),
|
|
|
|
resource: None,
|
|
|
|
};
|
|
|
|
let res = Jid::from_string(input).unwrap();
|
|
|
|
assert_eq!(res, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn jid_parse_server() {
|
|
|
|
let input = "server.example";
|
|
|
|
let expected = Jid {
|
|
|
|
name: None,
|
|
|
|
server: Server("server.example".into()),
|
|
|
|
resource: None,
|
|
|
|
};
|
|
|
|
let res = Jid::from_string(input).unwrap();
|
|
|
|
assert_eq!(res, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn jid_parse_server_resource() {
|
|
|
|
let input = "server.example/kek";
|
|
|
|
let expected = Jid {
|
|
|
|
name: None,
|
|
|
|
server: Server("server.example".into()),
|
|
|
|
resource: Some(Resource("kek".into())),
|
|
|
|
};
|
|
|
|
let res = Jid::from_string(input).unwrap();
|
|
|
|
assert_eq!(res, expected);
|
|
|
|
}
|
2023-03-11 15:07:02 +00:00
|
|
|
}
|