forked from lavina/lavina
feat(xmpp): parsing of bind request
This commit is contained in:
parent
d444fc407b
commit
f131454cb2
|
@ -28,3 +28,5 @@ Make sure `xmpp.key` starts and ends with:
|
|||
## Protocol Specs
|
||||
|
||||
XMPP XSDs - [https://xmpp.org/schemas/index.shtml]
|
||||
|
||||
IRC modern spec - [https://modern.ircdocs.horse/]
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
use nom::AsBytes;
|
||||
use quick_xml::events::Event;
|
||||
use quick_xml::name::{Namespace, ResolveResult};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::util::xml::{Continuation, FromXml, Parser};
|
||||
|
||||
pub const XMLNS: &'static str = "urn:ietf:params:xml:ns:xmpp-bind";
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Name(String);
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Server(String);
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Resource(String);
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct Jid {
|
||||
pub name: Name,
|
||||
pub server: Server,
|
||||
pub resource: Resource,
|
||||
}
|
||||
|
||||
/// 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)]
|
||||
pub struct BindRequest(Resource);
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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:?}")));
|
||||
};
|
||||
if bytes.name().0 != b"bind" {
|
||||
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))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BindResponse(Jid);
|
||||
|
||||
#[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())),)
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
use quick_xml::events::Event;
|
||||
use quick_xml::name::ResolveResult;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::util::xml::*;
|
||||
|
@ -44,7 +45,11 @@ struct MessageParserState {
|
|||
impl Parser for MessageParser {
|
||||
type Output = Result<Message>;
|
||||
|
||||
fn consume<'a>(self: Self, event: &Event<'a>) -> Continuation<Self, Self::Output> {
|
||||
fn consume<'a>(
|
||||
self: Self,
|
||||
namespace: ResolveResult,
|
||||
event: &Event<'a>,
|
||||
) -> Continuation<Self, Self::Output> {
|
||||
// TODO validate tag name and namespace at each stage
|
||||
match self {
|
||||
MessageParser::Init => {
|
||||
|
@ -186,7 +191,11 @@ struct IqParserState<T> {
|
|||
impl<T: FromXml> Parser for IqParser<T> {
|
||||
type Output = Result<Iq<T>>;
|
||||
|
||||
fn consume<'a>(self: Self, event: &Event<'a>) -> Continuation<Self, Self::Output> {
|
||||
fn consume<'a>(
|
||||
self: Self,
|
||||
namespace: ResolveResult,
|
||||
event: &Event<'a>,
|
||||
) -> Continuation<Self, Self::Output> {
|
||||
match self.0 {
|
||||
IqParserInner::Init => {
|
||||
if let Event::Start(ref bytes) = event {
|
||||
|
@ -213,7 +222,7 @@ impl<T: FromXml> Parser for IqParser<T> {
|
|||
}
|
||||
}
|
||||
IqParserInner::ParsingBody(mut state, parser) => {
|
||||
match parser.consume(event) {
|
||||
match parser.consume(namespace, event) {
|
||||
Continuation::Final(f) => {
|
||||
let body = fail_fast!(f);
|
||||
state.body = Some(body);
|
||||
|
@ -275,13 +284,14 @@ mod tests {
|
|||
let input = r#"<message id="aacea" type="chat" to="nikita@vlnv.dev"><subject>daa</subject><body>bbb</body></message>"#;
|
||||
let mut reader = NsReader::from_reader(input.as_bytes());
|
||||
let mut buf = vec![];
|
||||
let event = reader.read_event_into_async(&mut buf).await.unwrap();
|
||||
let mut parser = Message::parse().consume(&event);
|
||||
let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await.unwrap();
|
||||
let mut parser = Message::parse().consume(ns, &event);
|
||||
let result = loop {
|
||||
match parser {
|
||||
Continuation::Final(res) => break res,
|
||||
Continuation::Continue(next) => {
|
||||
parser = next.consume(&reader.read_event_into_async(&mut buf).await.unwrap())
|
||||
let (ns, event) = reader.read_resolved_event_into_async(&mut buf).await.unwrap();
|
||||
parser = next.consume(ns, &event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod bind;
|
||||
pub mod client;
|
||||
pub mod sasl;
|
||||
pub mod stanzaerror;
|
||||
|
|
|
@ -179,12 +179,13 @@ impl FromClient {
|
|||
};
|
||||
let (ns, name) = reader.resolve_element(start.name());
|
||||
if name.as_ref() == b"message" {
|
||||
let mut parser = Message::parse().consume(&incoming);
|
||||
let mut parser = Message::parse().consume(ns, &incoming);
|
||||
let result = loop {
|
||||
match parser {
|
||||
Continuation::Final(res) => break res,
|
||||
Continuation::Continue(next) => {
|
||||
parser = next.consume(&reader.read_event_into_async(buf).await.unwrap())
|
||||
let (ns, event) = reader.read_resolved_event_into_async(buf).await.unwrap();
|
||||
parser = next.consume(ns, &event);
|
||||
}
|
||||
}
|
||||
}?;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use quick_xml::events::Event;
|
||||
use quick_xml::name::ResolveResult;
|
||||
|
||||
use crate::prelude::Result;
|
||||
|
||||
|
@ -11,7 +12,11 @@ pub trait FromXml: Sized {
|
|||
pub trait Parser: Sized {
|
||||
type Output;
|
||||
|
||||
fn consume<'a>(self: Self, event: &Event<'a>) -> Continuation<Self, Self::Output>;
|
||||
fn consume<'a>(
|
||||
self: Self,
|
||||
namespace: ResolveResult,
|
||||
event: &Event<'a>,
|
||||
) -> Continuation<Self, Self::Output>;
|
||||
}
|
||||
|
||||
pub enum Continuation<Parser, Res> {
|
||||
|
|
Loading…
Reference in New Issue