forked from lavina/lavina
1
0
Fork 0

feat(xmpp): initial parsing of ordinary stream events

This commit is contained in:
Nikita Vilunov 2023-03-07 16:27:09 +01:00
parent d1dad72c08
commit 27bbabbbbd
5 changed files with 116 additions and 73 deletions

View File

@ -7,15 +7,15 @@ pub static XMLNS: &'static str = "jabber:client";
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub struct Message { pub struct Message {
from: Option<String>, pub from: Option<String>,
id: Option<String>, pub id: Option<String>,
to: Option<String>, pub to: Option<String>,
// default is Normal // default is Normal
r#type: MessageType, pub r#type: MessageType,
lang: Option<String>, pub lang: Option<String>,
subject: Option<String>, pub subject: Option<String>,
body: String, pub body: String,
} }
impl Message { impl Message {
pub fn parse() -> impl Parser<Output = Result<Self>> { pub fn parse() -> impl Parser<Output = Result<Self>> {
@ -61,6 +61,9 @@ impl Parser for MessageParser {
} else if attr.key.0 == b"to" { } else if attr.key.0 == b"to" {
let value = fail_fast!(std::str::from_utf8(&*attr.value)); let value = fail_fast!(std::str::from_utf8(&*attr.value));
state.to = Some(value.to_string()) state.to = Some(value.to_string())
} else if attr.key.0 == b"type" {
let value = fail_fast!(MessageType::from_str(&*attr.value));
state.r#type = value;
} }
} }
Continuation::Continue(MessageParser::Outer(state)) Continuation::Continue(MessageParser::Outer(state))
@ -68,8 +71,7 @@ impl Parser for MessageParser {
Continuation::Final(Err(ffail!("Expected start"))) Continuation::Final(Err(ffail!("Expected start")))
} }
} }
MessageParser::Outer(state) => { MessageParser::Outer(state) => match event {
match event {
Event::Start(ref bytes) => { Event::Start(ref bytes) => {
if bytes.name().0 == b"subject" { if bytes.name().0 == b"subject" {
Continuation::Continue(MessageParser::InSubject(state)) Continuation::Continue(MessageParser::InSubject(state))
@ -94,45 +96,28 @@ impl Parser for MessageParser {
Continuation::Final(Err(ffail!("Body not found"))) Continuation::Final(Err(ffail!("Body not found")))
} }
} }
_ => { _ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}")))
}
}
}, },
MessageParser::InSubject(mut state) => { MessageParser::InSubject(mut state) => match event {
match event { Event::Text(ref bytes) => {
Event::Text(ref bytes) =>{
let subject = fail_fast!(std::str::from_utf8(&*bytes)); let subject = fail_fast!(std::str::from_utf8(&*bytes));
state.subject = Some(subject.to_string()); state.subject = Some(subject.to_string());
Continuation::Continue(MessageParser::InSubject(state)) Continuation::Continue(MessageParser::InSubject(state))
} }
Event::End(_) => { Event::End(_) => Continuation::Continue(MessageParser::Outer(state)),
Continuation::Continue(MessageParser::Outer(state)) _ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
} },
_ => { MessageParser::InBody(mut state) => match event {
Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))) Event::Text(ref bytes) => match std::str::from_utf8(&*bytes) {
}
}
}
MessageParser::InBody(mut state) => {
match event {
Event::Text(ref bytes) =>{
match std::str::from_utf8(&*bytes) {
Ok(subject) => { Ok(subject) => {
state.body = Some(subject.to_string()); state.body = Some(subject.to_string());
Continuation::Continue(MessageParser::InBody(state)) Continuation::Continue(MessageParser::InBody(state))
} }
Err(err) => Continuation::Final(Err(err.into())), Err(err) => Continuation::Final(Err(err.into())),
} },
} Event::End(_) => Continuation::Continue(MessageParser::Outer(state)),
Event::End(_) => { _ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
Continuation::Continue(MessageParser::Outer(state)) },
}
_ => {
Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}")))
}
}
}
} }
} }
} }
@ -153,8 +138,9 @@ impl Default for MessageType {
} }
impl MessageType { impl MessageType {
pub fn from_str(s: &str) -> Result<MessageType> { pub fn from_str(s: &[u8]) -> Result<MessageType> {
use MessageType::*; use MessageType::*;
let s = std::str::from_utf8(s)?;
match s { match s {
"chat" => Ok(Chat), "chat" => Ok(Chat),
"error" => Ok(Error), "error" => Ok(Error),
@ -193,7 +179,7 @@ mod tests {
from: None, from: None,
id: Some("aacea".to_string()), id: Some("aacea".to_string()),
to: Some("nikita@vlnv.dev".to_string()), to: Some("nikita@vlnv.dev".to_string()),
r#type: MessageType::Normal, r#type: MessageType::Chat,
lang: None, lang: None,
subject: Some("daa".to_string()), subject: Some("daa".to_string()),
body: "bbb".to_string(), body: "bbb".to_string(),

View File

@ -1,8 +1,8 @@
pub mod client; pub mod client;
pub mod sasl; pub mod sasl;
pub mod stanzaerror;
pub mod stream; pub mod stream;
pub mod tls; pub mod tls;
pub mod stanzaerror;
// Implemented as a macro instead of a fn due to borrowck limitations // Implemented as a macro instead of a fn due to borrowck limitations
macro_rules! skip_text { macro_rules! skip_text {

View File

@ -4,8 +4,10 @@ use quick_xml::name::{Namespace, QName, ResolveResult};
use quick_xml::{NsReader, Writer}; use quick_xml::{NsReader, Writer};
use tokio::io::{AsyncBufRead, AsyncWrite}; use tokio::io::{AsyncBufRead, AsyncWrite};
use super::client::Message;
use super::skip_text; use super::skip_text;
use crate::prelude::*; use crate::prelude::*;
use crate::util::xml::{Continuation, Parser};
pub static XMLNS: &'static str = "http://etherx.jabber.org/streams"; pub static XMLNS: &'static str = "http://etherx.jabber.org/streams";
pub static PREFIX: &'static str = "stream"; pub static PREFIX: &'static str = "stream";
@ -160,8 +162,43 @@ impl Features {
} }
} }
#[derive(PartialEq, Eq, Debug)]
pub enum FromClient {
Message(Message),
}
impl FromClient {
pub async fn parse(
reader: &mut NsReader<impl AsyncBufRead + Unpin>,
buf: &mut Vec<u8>,
) -> Result<FromClient> {
let incoming = skip_text!(reader, buf);
let start = if let Event::Start(ref bytes) = incoming {
bytes
} else {
return Err(ffail!("Unexpected XML event: {incoming:?}"));
};
let (ns, name) = reader.resolve_element(start.name());
if name.as_ref() == b"message" {
let mut parser = Message::parse().consume(&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())
}
}
}?;
Ok(FromClient::Message(result))
} else {
Err(ffail!("Unknown XML tag: {name:?}"))
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::protos::xmpp::client::MessageType;
use super::*; use super::*;
#[tokio::test] #[tokio::test]
@ -195,4 +232,24 @@ mod test {
input.write_xml(&mut writer).await.unwrap(); input.write_xml(&mut writer).await.unwrap();
assert_eq!(std::str::from_utf8(&output).unwrap(), expected); assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
} }
#[tokio::test]
async fn client_message() {
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 res = FromClient::parse(&mut reader, &mut buf).await.unwrap();
assert_eq!(
res,
FromClient::Message(Message {
from: None,
id: Some("aacea".to_string()),
r#type: MessageType::Chat,
to: Some("nikita@vlnv.dev".to_string()),
lang: None,
subject: Some("daa".to_string()),
body: "bbb".to_string(),
})
)
}
} }

View File

@ -3,9 +3,9 @@ use crate::prelude::*;
pub mod http; pub mod http;
pub mod table; pub mod table;
pub mod telemetry; pub mod telemetry;
pub mod xml;
#[cfg(test)] #[cfg(test)]
pub mod testkit; pub mod testkit;
pub mod xml;
pub struct Terminator { pub struct Terminator {
signal: Promise<()>, signal: Promise<()>,

View File

@ -15,7 +15,7 @@ macro_rules! fail_fast {
($errorable: expr) => { ($errorable: expr) => {
match $errorable { match $errorable {
Ok(i) => i, Ok(i) => i,
Err(e) => return Continuation::Final(Err(e.into())) Err(e) => return Continuation::Final(Err(e.into())),
} }
}; };
} }