forked from lavina/lavina
1
0
Fork 0
lavina/crates/proto-xmpp/src/client.rs

679 lines
24 KiB
Rust
Raw Normal View History

use derive_more::From;
2023-03-12 12:25:23 +00:00
use quick_xml::events::attributes::Attribute;
2023-03-15 14:27:48 +00:00
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
2023-03-12 12:25:23 +00:00
use quick_xml::name::{QName, ResolveResult};
2023-03-07 13:56:31 +00:00
use anyhow::{anyhow as ffail, Result};
2023-03-07 13:56:31 +00:00
use crate::prelude::*;
use crate::xml::*;
2023-03-07 13:56:31 +00:00
use super::bind::Jid;
pub const XMLNS: &'static str = "jabber:client";
2023-03-07 13:56:31 +00:00
#[derive(PartialEq, Eq, Debug)]
pub struct Message<T> {
pub from: Option<Jid>,
pub id: Option<String>,
pub to: Option<Jid>,
2023-03-07 13:56:31 +00:00
// default is Normal
pub r#type: MessageType,
2023-04-13 22:38:26 +00:00
pub lang: Option<Str>,
pub subject: Option<Str>,
pub body: Str,
pub custom: Vec<T>,
2023-03-07 13:56:31 +00:00
}
impl<T: FromXml> FromXmlTag for Message<T> {
2023-03-12 13:15:13 +00:00
const NS: &'static str = XMLNS;
const NAME: &'static str = "message";
}
impl<T: FromXml> FromXml for Message<T> {
type P = impl Parser<Output = Result<Self>>;
fn parse() -> Self::P {
MessageParser(MessageParserInner::Init)
2023-03-07 13:56:31 +00:00
}
}
#[derive(From)]
struct MessageParser<T: FromXml>(MessageParserInner<T>);
2023-03-07 13:56:31 +00:00
#[derive(Default)]
enum MessageParserInner<T: FromXml> {
2023-03-07 13:56:31 +00:00
#[default]
Init,
Outer(MessageParserState<T>),
InSubject(MessageParserState<T>),
InBody(MessageParserState<T>),
InCustom(MessageParserState<T>, T::P),
2023-03-07 13:56:31 +00:00
}
#[derive(Default)]
struct MessageParserState<T> {
from: Option<Jid>,
2023-03-07 13:56:31 +00:00
id: Option<String>,
to: Option<Jid>,
2023-03-07 13:56:31 +00:00
r#type: MessageType,
2023-04-13 22:38:26 +00:00
lang: Option<Str>,
subject: Option<Str>,
body: Option<Str>,
custom: Vec<T>,
2023-03-07 13:56:31 +00:00
}
impl<T: FromXml> Parser for MessageParser<T> {
type Output = Result<Message<T>>;
fn consume<'a>(self: Self, namespace: ResolveResult, event: &Event<'a>) -> Continuation<Self, Self::Output> {
2023-03-07 13:56:31 +00:00
// TODO validate tag name and namespace at each stage
use MessageParserInner::*;
match self.0 {
Init => {
2023-03-07 13:56:31 +00:00
if let Event::Start(ref bytes) = event {
let mut state: MessageParserState<T> = MessageParserState {
from: None,
id: None,
to: None,
r#type: MessageType::Normal,
lang: None,
subject: None,
body: None,
custom: vec![],
};
2023-03-07 13:56:31 +00:00
for attr in bytes.attributes() {
let attr = fail_fast!(attr);
if attr.key.0 == b"from" {
let value = fail_fast!(std::str::from_utf8(&*attr.value));
let value = fail_fast!(Jid::from_string(value));
state.from = Some(value)
2023-03-07 13:56:31 +00:00
} else if attr.key.0 == b"id" {
let value = fail_fast!(std::str::from_utf8(&*attr.value));
state.id = Some(value.to_string())
} else if attr.key.0 == b"to" {
let value = fail_fast!(std::str::from_utf8(&*attr.value));
let value = fail_fast!(Jid::from_string(value));
state.to = Some(value)
} else if attr.key.0 == b"type" {
let value = fail_fast!(MessageType::from_str(&*attr.value));
state.r#type = value;
2023-03-07 13:56:31 +00:00
}
}
Continuation::Continue(Outer(state).into())
2023-03-07 13:56:31 +00:00
} else {
Continuation::Final(Err(ffail!("Expected start")))
}
}
Outer(mut state) => match event {
Event::Start(ref bytes) => {
if bytes.name().0 == b"subject" {
Continuation::Continue(InSubject(state).into())
} else if bytes.name().0 == b"body" {
Continuation::Continue(InBody(state).into())
} else {
let parser = T::parse();
match parser.consume(namespace, event) {
Continuation::Final(Ok(e)) => {
state.custom.push(e);
Continuation::Continue(Outer(state).into())
}
Continuation::Final(Err(e)) => Continuation::Final(Err(e)),
Continuation::Continue(p) => Continuation::Continue(InCustom(state, p).into()),
}
2023-03-07 13:56:31 +00:00
}
}
Event::End(_) => {
if let Some(body) = state.body {
Continuation::Final(Ok(Message {
from: state.from,
id: state.id,
to: state.to,
r#type: state.r#type,
lang: state.lang,
subject: state.subject,
body,
custom: state.custom,
}))
} else {
Continuation::Final(Err(ffail!("Body not found")))
2023-03-07 13:56:31 +00:00
}
}
Event::Empty(_) => {
let parser = T::parse();
match parser.consume(namespace, event) {
Continuation::Final(Ok(e)) => {
state.custom.push(e);
Continuation::Continue(Outer(state).into())
}
Continuation::Final(Err(e)) => Continuation::Final(Err(e)),
Continuation::Continue(p) => Continuation::Continue(InCustom(state, p).into()),
}
}
_ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
2023-03-07 13:56:31 +00:00
},
InSubject(mut state) => match event {
Event::Text(ref bytes) => {
let subject = fail_fast!(std::str::from_utf8(&*bytes));
2023-04-13 22:38:26 +00:00
state.subject = Some(subject.into());
Continuation::Continue(InSubject(state).into())
2023-03-07 13:56:31 +00:00
}
Event::End(_) => Continuation::Continue(Outer(state).into()),
_ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
},
InBody(mut state) => match event {
Event::Text(ref bytes) => match std::str::from_utf8(&*bytes) {
Ok(subject) => {
2023-04-13 22:38:26 +00:00
state.body = Some(subject.into());
Continuation::Continue(InBody(state).into())
2023-03-07 13:56:31 +00:00
}
Err(err) => Continuation::Final(Err(err.into())),
},
Event::End(_) => Continuation::Continue(Outer(state).into()),
_ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
},
InCustom(mut state, custom) => match custom.consume(namespace, event) {
Continuation::Final(Ok(e)) => {
state.custom.push(e);
Continuation::Continue(Outer(state).into())
}
Continuation::Final(Err(e)) => Continuation::Final(Err(e)),
Continuation::Continue(c) => Continuation::Continue(InCustom(state, c).into()),
},
2023-03-07 13:56:31 +00:00
}
}
}
impl<T: ToXml> ToXml for Message<T> {
fn serialize(&self, events: &mut Vec<Event<'static>>) {
let mut bytes = BytesStart::new(format!(r#"message xmlns="{}""#, XMLNS));
if let Some(from) = &self.from {
bytes.push_attribute(Attribute {
key: QName(b"from"),
value: from.to_string().into_bytes().into(),
});
}
if let Some(to) = &self.to {
bytes.push_attribute(Attribute {
key: QName(b"to"),
value: to.to_string().into_bytes().into(),
});
}
if let Some(id) = &self.id {
bytes.push_attribute(Attribute {
key: QName(b"id"),
value: id.clone().into_bytes().into(),
});
}
bytes.push_attribute(Attribute {
key: QName(b"type"),
value: self.r#type.as_str().as_bytes().into(),
});
events.push(Event::Start(bytes));
events.push(Event::Start(BytesStart::new("body")));
events.push(Event::Text(BytesText::new(&self.body).into_owned()));
events.push(Event::End(BytesEnd::new("body")));
events.push(Event::End(BytesEnd::new("message")));
}
}
2023-03-07 13:56:31 +00:00
#[derive(PartialEq, Eq, Debug)]
pub enum MessageType {
Chat,
Error,
Groupchat,
Headline,
Normal,
}
impl Default for MessageType {
fn default() -> Self {
MessageType::Normal
}
}
impl MessageType {
pub fn from_str(s: &[u8]) -> Result<MessageType> {
2023-03-07 13:56:31 +00:00
use MessageType::*;
let s = std::str::from_utf8(s)?;
2023-03-07 13:56:31 +00:00
match s {
"chat" => Ok(Chat),
"error" => Ok(Error),
"groupchat" => Ok(Groupchat),
"headline" => Ok(Headline),
"normal" => Ok(Normal),
t => Err(ffail!("Unknown message type: {t}")),
}
}
pub fn as_str(&self) -> &'static str {
match self {
MessageType::Chat => "chat",
MessageType::Error => "error",
MessageType::Groupchat => "groupchat",
MessageType::Headline => "headline",
MessageType::Normal => "normal",
}
}
2023-03-07 13:56:31 +00:00
}
#[derive(PartialEq, Eq, Debug)]
2023-03-08 18:56:53 +00:00
pub struct Iq<T> {
pub from: Option<String>,
pub id: String,
pub to: Option<String>,
pub r#type: IqType,
pub body: T,
}
2023-03-12 13:15:13 +00:00
impl<T: FromXml> FromXmlTag for Iq<T> {
const NAME: &'static str = "iq";
const NS: &'static str = XMLNS;
}
2023-03-08 18:56:53 +00:00
impl<T: FromXml> FromXml for Iq<T> {
type P = IqParser<T>;
fn parse() -> Self::P {
IqParser(IqParserInner::Init)
}
}
pub struct IqParser<T: FromXml>(IqParserInner<T>);
enum IqParserInner<T: FromXml> {
Init,
ParsingBody(IqParserState<T>, T::P),
Final(IqParserState<T>),
}
struct IqParserState<T> {
pub from: Option<String>,
pub id: Option<String>,
pub to: Option<String>,
pub r#type: Option<IqType>,
pub body: Option<T>,
}
impl<T: FromXml> Parser for IqParser<T> {
type Output = Result<Iq<T>>;
fn consume<'a>(self: Self, namespace: ResolveResult, event: &Event<'a>) -> Continuation<Self, Self::Output> {
2023-03-08 18:56:53 +00:00
match self.0 {
IqParserInner::Init => {
if let Event::Start(ref bytes) = event {
let mut state: IqParserState<T> = IqParserState {
from: None,
id: None,
to: None,
r#type: None,
body: None,
};
2023-03-08 18:56:53 +00:00
for attr in bytes.attributes() {
let attr = fail_fast!(attr);
if attr.key.0 == b"from" {
let value = fail_fast!(std::str::from_utf8(&*attr.value));
state.from = Some(value.to_string())
} else if attr.key.0 == b"id" {
let value = fail_fast!(std::str::from_utf8(&*attr.value));
state.id = Some(value.to_string())
} else if attr.key.0 == b"to" {
let value = fail_fast!(std::str::from_utf8(&*attr.value));
state.to = Some(value.to_string())
} else if attr.key.0 == b"type" {
let value = fail_fast!(IqType::from_str(&*attr.value));
state.r#type = Some(value);
}
}
Continuation::Continue(IqParser(IqParserInner::ParsingBody(state, T::parse())))
} else {
Continuation::Final(Err(ffail!("Expected start")))
}
}
IqParserInner::ParsingBody(mut state, parser) => match parser.consume(namespace, event) {
Continuation::Final(f) => {
let body = fail_fast!(f);
state.body = Some(body);
Continuation::Continue(IqParser(IqParserInner::Final(state)))
2023-03-08 18:56:53 +00:00
}
Continuation::Continue(parser) => {
Continuation::Continue(IqParser(IqParserInner::ParsingBody(state, parser)))
}
},
2023-03-08 18:56:53 +00:00
IqParserInner::Final(state) => {
if let Event::End(ref bytes) = event {
let id = fail_fast!(state.id.ok_or_else(|| ffail!("No id provided")));
let r#type = fail_fast!(state.r#type.ok_or_else(|| ffail!("No type provided")));
let body = fail_fast!(state.body.ok_or_else(|| ffail!("No body provided")));
2023-03-08 18:56:53 +00:00
Continuation::Final(Ok(Iq {
from: state.from,
id,
to: state.to,
r#type,
body,
}))
} else {
Continuation::Final(Err(ffail!("Unexpected event: {event:?}")))
}
}
}
}
}
#[derive(PartialEq, Eq, Debug)]
2023-03-08 18:56:53 +00:00
pub enum IqType {
Error,
Get,
Result,
Set,
}
impl IqType {
pub fn from_str(s: &[u8]) -> Result<IqType> {
use IqType::*;
let s = std::str::from_utf8(s)?;
match s {
"error" => Ok(Error),
"get" => Ok(Get),
"result" => Ok(Result),
"set" => Ok(Set),
t => Err(ffail!("Unknown iq type: {t}")),
}
}
2023-03-12 12:25:23 +00:00
pub fn as_str(&self) -> &'static str {
match self {
IqType::Error => "error",
IqType::Get => "get",
IqType::Result => "result",
IqType::Set => "set",
}
}
}
impl<T: ToXml> ToXml for Iq<T> {
fn serialize(&self, events: &mut Vec<Event<'static>>) {
let start = format!(r#"iq xmlns="{}""#, XMLNS);
let mut start = BytesStart::new(start);
let mut attrs = vec![];
if let Some(ref from) = self.from {
attrs.push(Attribute {
key: QName(b"from"),
value: from.as_bytes().into(),
});
};
if let Some(ref to) = self.to {
attrs.push(Attribute {
key: QName(b"to"),
value: to.as_bytes().into(),
});
}
attrs.push(Attribute {
key: QName(b"id"),
value: self.id.as_bytes().into(),
});
attrs.push(Attribute {
key: QName(b"type"),
value: self.r#type.as_str().as_bytes().into(),
});
start.extend_attributes(attrs.into_iter());
events.push(Event::Start(start));
self.body.serialize(events);
events.push(Event::End(BytesEnd::new("iq")));
}
2023-03-08 18:56:53 +00:00
}
#[derive(PartialEq, Eq, Debug)]
pub struct Presence<T> {
pub to: Option<Jid>,
pub from: Option<Jid>,
2023-03-15 14:27:48 +00:00
pub priority: Option<PresencePriority>,
pub show: Option<PresenceShow>,
pub status: Vec<String>,
pub custom: Vec<T>,
pub r#type: Option<String>,
}
impl<T> Default for Presence<T> {
fn default() -> Self {
Self {
to: Default::default(),
from: Default::default(),
priority: Default::default(),
show: Default::default(),
status: Default::default(),
custom: Default::default(),
r#type: None,
}
}
2023-03-15 14:27:48 +00:00
}
#[derive(PartialEq, Eq, Debug)]
pub enum PresenceShow {
Away,
Chat,
Dnd,
Xa,
}
/// Presence priority is an integer number in range [-128; 127].
///
/// Presence priority < 0 means that the bound resource will never be chosen unless it was asked for specifically.
#[derive(PartialEq, Eq, Debug)]
pub struct PresencePriority(pub i8);
impl PresenceShow {
pub fn from_str(s: &[u8]) -> Result<Self> {
use PresenceShow::*;
let s = std::str::from_utf8(s)?;
match s {
"away" => Ok(Away),
"chat" => Ok(Chat),
"dnd" => Ok(Dnd),
"xa" => Ok(Xa),
t => Err(ffail!("Unknown presence show type: {t}")),
}
}
pub fn as_str(&self) -> &'static str {
use PresenceShow::*;
match self {
Away => "away",
Chat => "chat",
Dnd => "dnd",
Xa => "xa",
}
}
}
2023-03-23 01:20:30 +00:00
impl<T: FromXml> FromXml for Presence<T> {
type P = impl Parser<Output = Result<Presence<T>>>;
2023-03-15 14:27:48 +00:00
2023-03-23 01:20:30 +00:00
fn parse() -> Self::P {
|(namespace, event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
let (bytes, end) = match event {
Event::Start(bytes) => (bytes, false),
Event::Empty(bytes) => (bytes, true),
2023-03-23 01:20:30 +00:00
_ => return Err(ffail!("Unexpected XML event: {event:?}")),
};
let mut p = Presence::<T>::default();
for attr in bytes.attributes() {
let attr = attr?;
match attr.key.0 {
b"to" => {
let s = std::str::from_utf8(&attr.value)?;
p.to = Some(Jid::from_string(s)?);
}
b"from" => {
let s = std::str::from_utf8(&attr.value)?;
p.to = Some(Jid::from_string(s)?);
}
b"type" => {
let s = std::str::from_utf8(&attr.value)?;
p.r#type = Some(s.into());
}
_ => {}
}
}
if end {
return Ok(p);
}
2023-03-23 01:20:30 +00:00
loop {
let (namespace, event) = yield;
match event {
Event::Start(bytes) => match bytes.name().0 {
b"show" => {
let (_, event) = yield;
let Event::Text(bytes) = event else {
return Err(ffail!("Unexpected XML event: {event:?}"));
};
let i = PresenceShow::from_str(bytes)?;
p.show = Some(i);
let (_, event) = yield;
let Event::End(_) = event else {
return Err(ffail!("Unexpected XML event: {event:?}"));
};
2023-03-15 14:27:48 +00:00
}
2023-03-23 01:20:30 +00:00
b"status" => {
let (_, event) = yield;
let Event::Text(bytes) = event else {
return Err(ffail!("Unexpected XML event: {event:?}"));
};
let s = std::str::from_utf8(bytes)?;
p.status.push(s.to_string());
let (_, event) = yield;
let Event::End(_) = event else {
return Err(ffail!("Unexpected XML event: {event:?}"));
};
2023-03-15 14:27:48 +00:00
}
2023-03-23 01:20:30 +00:00
b"priority" => {
let (_, event) = yield;
let Event::Text(bytes) = event else {
return Err(ffail!("Unexpected XML event: {event:?}"));
};
let s = std::str::from_utf8(bytes)?;
let i = s.parse()?;
p.priority = Some(PresencePriority(i));
let (_, event) = yield;
let Event::End(_) = event else {
return Err(ffail!("Unexpected XML event: {event:?}"));
};
}
_ => {
let res = delegate_parsing!(T, namespace, event);
p.custom.push(res?);
2023-03-15 14:27:48 +00:00
}
2023-03-23 01:20:30 +00:00
},
Event::Empty(_) => {
let res = delegate_parsing!(T, namespace, event);
p.custom.push(res?);
2023-03-15 14:27:48 +00:00
}
2023-03-23 01:20:30 +00:00
Event::End(_) => return Ok(p),
_ => return Err(ffail!("Unexpected XML event: {event:?}")),
2023-03-15 14:27:48 +00:00
}
2023-03-23 01:20:30 +00:00
}
2023-03-15 14:27:48 +00:00
}
}
}
impl<T: FromXml> FromXmlTag for Presence<T> {
2023-03-15 14:27:48 +00:00
const NAME: &'static str = "presence";
const NS: &'static str = XMLNS;
}
impl<T: ToXml> ToXml for Presence<T> {
2023-03-15 14:27:48 +00:00
fn serialize(&self, events: &mut Vec<Event<'static>>) {
let mut start = BytesStart::new("presence");
if let Some(ref to) = self.to {
start.extend_attributes([Attribute {
key: QName(b"to"),
value: to.to_string().as_bytes().into(),
2023-03-15 14:27:48 +00:00
}]);
}
if let Some(ref from) = self.from {
start.extend_attributes([Attribute {
key: QName(b"from"),
value: from.to_string().as_bytes().into(),
2023-03-15 14:27:48 +00:00
}]);
}
events.push(Event::Start(start));
if let Some(ref priority) = self.priority {
let s = priority.0.to_string();
events.extend_from_slice(&[
Event::Start(BytesStart::new(r#"priority"#)),
Event::Text(BytesText::new(s.as_str()).into_owned()),
Event::End(BytesEnd::new("priority")),
]);
}
events.push(Event::End(BytesEnd::new("presence")));
}
}
2023-03-07 13:56:31 +00:00
#[cfg(test)]
mod tests {
use crate::bind::{BindRequest, Name, Resource, Server};
2023-03-07 13:56:31 +00:00
use super::*;
#[tokio::test]
async fn parse_message() {
let input = r#"<message id="aacea" type="chat" to="nikita@vlnv.dev"><subject>daa</subject><body>bbb</body><unknown-stuff></unknown-stuff></message>"#;
let result: Message<Ignore> = crate::xml::parse(input).unwrap();
assert_eq!(
result,
Message::<Ignore> {
from: None,
id: Some("aacea".to_string()),
to: Some(Jid {
name: Some(Name("nikita".into())),
server: Server("vlnv.dev".into()),
resource: None
}),
r#type: MessageType::Chat,
lang: None,
subject: Some("daa".into()),
body: "bbb".into(),
custom: vec![Ignore],
2023-03-07 13:56:31 +00:00
}
)
}
#[tokio::test]
async fn parse_message_empty_custom() {
let input = r#"<message id="aacea" type="chat" to="nikita@vlnv.dev"><subject>daa</subject><body>bbb</body><unknown-stuff/></message>"#;
let result: Message<Ignore> = crate::xml::parse(input).unwrap();
2023-03-07 13:56:31 +00:00
assert_eq!(
result,
Message::<Ignore> {
2023-03-07 13:56:31 +00:00
from: None,
id: Some("aacea".to_string()),
to: Some(Jid {
2023-04-13 19:15:48 +00:00
name: Some(Name("nikita".into())),
2023-04-13 22:38:26 +00:00
server: Server("vlnv.dev".into()),
resource: None
}),
r#type: MessageType::Chat,
2023-03-07 13:56:31 +00:00
lang: None,
2023-04-13 22:38:26 +00:00
subject: Some("daa".into()),
body: "bbb".into(),
custom: vec![Ignore],
2023-03-07 13:56:31 +00:00
}
)
}
#[tokio::test]
async fn parse_iq() {
let input = r#"<iq id="bind_1" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><resource>mobile</resource></bind></iq>"#;
let result: Iq<BindRequest> = crate::xml::parse(input).unwrap();
assert_eq!(
result,
Iq {
from: None,
id: "bind_1".to_string(),
to: None,
r#type: IqType::Set,
2023-04-13 22:38:26 +00:00
body: BindRequest(Resource("mobile".into()))
}
)
}
2023-03-07 13:56:31 +00:00
}