2023-03-11 15:07:02 +00:00
|
|
|
use nom::AsBytes;
|
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-03-12 13:15:13 +00:00
|
|
|
use crate::util::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
|
|
|
|
|
2023-03-11 15:07:02 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2023-03-12 12:25:23 +00:00
|
|
|
pub struct Name(pub String);
|
2023-03-11 15:07:02 +00:00
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2023-03-12 12:25:23 +00:00
|
|
|
pub struct Server(pub String);
|
2023-03-11 15:07:02 +00:00
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
2023-03-11 23:30:48 +00:00
|
|
|
pub struct Resource(pub(super) String);
|
2023-03-11 15:07:02 +00:00
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
pub struct Jid {
|
|
|
|
pub name: Name,
|
|
|
|
pub server: Server,
|
|
|
|
pub resource: Resource,
|
|
|
|
}
|
|
|
|
|
2023-03-12 12:25:23 +00:00
|
|
|
impl Jid {
|
|
|
|
pub fn to_string(&self) -> String {
|
|
|
|
format!("{}@{}/{}", self.name.0, self.server.0, self.resource.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
pub struct BindRequestParser(BindRequestParserInner);
|
|
|
|
|
|
|
|
enum BindRequestParserInner {
|
|
|
|
Initial,
|
|
|
|
/// Consumed <bind> start and expects <resource>
|
|
|
|
InBind(Option<String>),
|
|
|
|
/// Consumed <resource> start
|
|
|
|
InBindResourceInitial,
|
|
|
|
/// Consumer <resource> start and inner text
|
|
|
|
InBindResourceEnd(String),
|
|
|
|
}
|
|
|
|
|
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 {
|
|
|
|
type P = BindRequestParser;
|
|
|
|
|
|
|
|
fn parse() -> Self::P {
|
|
|
|
BindRequestParser(BindRequestParserInner::Initial)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Parser for BindRequestParser {
|
|
|
|
type Output = Result<BindRequest>;
|
|
|
|
|
|
|
|
fn consume<'a>(
|
|
|
|
self: Self,
|
|
|
|
namespace: ResolveResult,
|
|
|
|
event: &Event<'a>,
|
|
|
|
) -> Continuation<Self, Self::Output> {
|
|
|
|
// TODO validate tag names and namespaces
|
|
|
|
use BindRequestParserInner::*;
|
|
|
|
match self.0 {
|
|
|
|
Initial => {
|
|
|
|
let Event::Start(bytes) = event else {
|
|
|
|
return Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}")));
|
|
|
|
};
|
2023-03-11 17:36:38 +00:00
|
|
|
if bytes.name().0 != BindRequest::NAME.as_bytes() {
|
2023-03-11 15:07:02 +00:00
|
|
|
return Continuation::Final(Err(ffail!(
|
|
|
|
"Unexpected XML tag: {:?}",
|
|
|
|
bytes.name()
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
let ResolveResult::Bound(Namespace(ns)) = namespace else {
|
|
|
|
return Continuation::Final(Err(ffail!("No namespace provided")));
|
|
|
|
};
|
|
|
|
if ns != XMLNS.as_bytes() {
|
|
|
|
return Continuation::Final(Err(ffail!("Incorrect namespace")));
|
|
|
|
}
|
|
|
|
Continuation::Continue(BindRequestParser(InBind(None)))
|
|
|
|
}
|
|
|
|
InBind(resource) => match event {
|
|
|
|
Event::Start(bytes) => {
|
|
|
|
Continuation::Continue(BindRequestParser(InBindResourceInitial))
|
|
|
|
}
|
|
|
|
Event::End(bytes) => {
|
|
|
|
let Some(resource) = resource else {
|
|
|
|
return Continuation::Final(Err(ffail!("No resource was provided")));
|
|
|
|
};
|
|
|
|
Continuation::Final(Ok(BindRequest(Resource(resource))))
|
|
|
|
}
|
|
|
|
_ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
|
|
|
|
},
|
|
|
|
InBindResourceInitial => {
|
|
|
|
let Event::Text(text) = event else {
|
|
|
|
return Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}")));
|
|
|
|
};
|
|
|
|
let resource = match std::str::from_utf8(text.as_bytes()) {
|
|
|
|
Ok(e) => e.to_string(),
|
|
|
|
Err(err) => return Continuation::Final(Err(err.into())),
|
|
|
|
};
|
|
|
|
Continuation::Continue(BindRequestParser(InBindResourceEnd(resource)))
|
|
|
|
}
|
|
|
|
InBindResourceEnd(resource) => {
|
|
|
|
let Event::End(bytes) = event else {
|
|
|
|
return Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}")));
|
|
|
|
};
|
|
|
|
Continuation::Continue(BindRequestParser(InBind(Some(resource))))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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(&[
|
|
|
|
Event::Start(BytesStart::new(
|
|
|
|
r#"bind xmlns="urn:ietf:params:xml:ns:xmpp-bind""#,
|
|
|
|
)),
|
|
|
|
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() {
|
|
|
|
let input =
|
|
|
|
r#"<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>mobile</resource></bind>"#;
|
|
|
|
let mut reader = NsReader::from_reader(input.as_bytes());
|
|
|
|
let mut buf = vec![];
|
|
|
|
let (ns, event) = reader
|
|
|
|
.read_resolved_event_into_async(&mut buf)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
let mut parser = BindRequest::parse().consume(ns, &event);
|
|
|
|
let result = loop {
|
|
|
|
match parser {
|
|
|
|
Continuation::Final(res) => break res,
|
|
|
|
Continuation::Continue(next) => {
|
|
|
|
let (ns, event) = reader
|
|
|
|
.read_resolved_event_into_async(&mut buf)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
parser = next.consume(ns, &event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(result, BindRequest(Resource("mobile".to_string())),)
|
|
|
|
}
|
|
|
|
}
|