2023-03-11 23:30:48 +00:00
|
|
|
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
|
|
|
|
2023-09-30 15:43:46 +00:00
|
|
|
use anyhow::{Result, anyhow as ffail};
|
|
|
|
|
|
|
|
|
2023-03-07 13:56:31 +00:00
|
|
|
use crate::prelude::*;
|
2023-09-30 15:43:46 +00:00
|
|
|
use crate::xml::*;
|
2023-03-07 13:56:31 +00:00
|
|
|
|
2023-04-09 21:31:43 +00:00
|
|
|
use super::bind::Jid;
|
|
|
|
|
2023-03-11 23:30:48 +00:00
|
|
|
pub const XMLNS: &'static str = "jabber:client";
|
2023-03-07 13:56:31 +00:00
|
|
|
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
pub struct Message {
|
2023-04-09 21:31:43 +00:00
|
|
|
pub from: Option<Jid>,
|
2023-03-07 15:27:09 +00:00
|
|
|
pub id: Option<String>,
|
2023-04-09 21:31:43 +00:00
|
|
|
pub to: Option<Jid>,
|
2023-03-07 13:56:31 +00:00
|
|
|
// default is Normal
|
2023-03-07 15:27:09 +00:00
|
|
|
pub r#type: MessageType,
|
2023-04-13 22:38:26 +00:00
|
|
|
pub lang: Option<Str>,
|
2023-03-07 13:56:31 +00:00
|
|
|
|
2023-04-13 22:38:26 +00:00
|
|
|
pub subject: Option<Str>,
|
|
|
|
pub body: Str,
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
2023-03-11 23:30:48 +00:00
|
|
|
|
2023-03-12 13:15:13 +00:00
|
|
|
impl FromXmlTag for Message {
|
|
|
|
const NS: &'static str = XMLNS;
|
|
|
|
const NAME: &'static str = "message";
|
2023-03-11 23:30:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl FromXml for Message {
|
|
|
|
type P = MessageParser;
|
|
|
|
|
|
|
|
fn parse() -> Self::P {
|
|
|
|
MessageParserInner::Init.into()
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-11 23:30:48 +00:00
|
|
|
#[derive(From)]
|
|
|
|
pub struct MessageParser(MessageParserInner);
|
|
|
|
|
2023-03-07 13:56:31 +00:00
|
|
|
#[derive(Default)]
|
2023-03-11 23:30:48 +00:00
|
|
|
enum MessageParserInner {
|
2023-03-07 13:56:31 +00:00
|
|
|
#[default]
|
|
|
|
Init,
|
|
|
|
Outer(MessageParserState),
|
|
|
|
InSubject(MessageParserState),
|
|
|
|
InBody(MessageParserState),
|
|
|
|
}
|
|
|
|
#[derive(Default)]
|
|
|
|
struct MessageParserState {
|
2023-04-09 21:31:43 +00:00
|
|
|
from: Option<Jid>,
|
2023-03-07 13:56:31 +00:00
|
|
|
id: Option<String>,
|
2023-04-09 21:31:43 +00:00
|
|
|
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>,
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
|
|
|
impl Parser for MessageParser {
|
|
|
|
type Output = Result<Message>;
|
|
|
|
|
2023-03-11 15:07:02 +00:00
|
|
|
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
|
2023-03-11 23:30:48 +00:00
|
|
|
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 = Default::default();
|
|
|
|
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));
|
2023-04-09 21:31:43 +00:00
|
|
|
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));
|
2023-04-09 21:31:43 +00:00
|
|
|
let value = fail_fast!(Jid::from_string(value));
|
|
|
|
state.to = Some(value)
|
2023-03-07 15:27:09 +00:00
|
|
|
} 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
|
|
|
}
|
|
|
|
}
|
2023-03-11 23:30:48 +00:00
|
|
|
Continuation::Continue(Outer(state).into())
|
2023-03-07 13:56:31 +00:00
|
|
|
} else {
|
|
|
|
Continuation::Final(Err(ffail!("Expected start")))
|
|
|
|
}
|
|
|
|
}
|
2023-03-11 23:30:48 +00:00
|
|
|
Outer(state) => match event {
|
2023-03-07 15:27:09 +00:00
|
|
|
Event::Start(ref bytes) => {
|
|
|
|
if bytes.name().0 == b"subject" {
|
2023-03-11 23:30:48 +00:00
|
|
|
Continuation::Continue(InSubject(state).into())
|
2023-03-07 15:27:09 +00:00
|
|
|
} else if bytes.name().0 == b"body" {
|
2023-03-11 23:30:48 +00:00
|
|
|
Continuation::Continue(InBody(state).into())
|
2023-03-07 15:27:09 +00:00
|
|
|
} else {
|
2023-03-21 00:16:02 +00:00
|
|
|
Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}")))
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
2023-03-07 15:27:09 +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,
|
|
|
|
}))
|
|
|
|
} else {
|
|
|
|
Continuation::Final(Err(ffail!("Body not found")))
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
|
|
|
}
|
2023-03-07 15:27:09 +00:00
|
|
|
_ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
|
2023-03-07 13:56:31 +00:00
|
|
|
},
|
2023-03-11 23:30:48 +00:00
|
|
|
InSubject(mut state) => match event {
|
2023-03-07 15:27:09 +00:00
|
|
|
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());
|
2023-03-11 23:30:48 +00:00
|
|
|
Continuation::Continue(InSubject(state).into())
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
2023-03-11 23:30:48 +00:00
|
|
|
Event::End(_) => Continuation::Continue(Outer(state).into()),
|
2023-03-07 15:27:09 +00:00
|
|
|
_ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
|
|
|
|
},
|
2023-03-11 23:30:48 +00:00
|
|
|
InBody(mut state) => match event {
|
2023-03-07 15:27:09 +00:00
|
|
|
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());
|
2023-03-11 23:30:48 +00:00
|
|
|
Continuation::Continue(InBody(state).into())
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
2023-03-07 15:27:09 +00:00
|
|
|
Err(err) => Continuation::Final(Err(err.into())),
|
|
|
|
},
|
2023-03-11 23:30:48 +00:00
|
|
|
Event::End(_) => Continuation::Continue(Outer(state).into()),
|
2023-03-07 15:27:09 +00:00
|
|
|
_ => Continuation::Final(Err(ffail!("Unexpected XML event: {event:?}"))),
|
|
|
|
},
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-09 21:31:43 +00:00
|
|
|
impl ToXml for Message {
|
|
|
|
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 {
|
2023-03-07 15:27:09 +00:00
|
|
|
pub fn from_str(s: &[u8]) -> Result<MessageType> {
|
2023-03-07 13:56:31 +00:00
|
|
|
use MessageType::*;
|
2023-03-07 15:27:09 +00:00
|
|
|
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}")),
|
|
|
|
}
|
|
|
|
}
|
2023-04-09 21:31:43 +00:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-03-11 23:30:48 +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-11 23:30:48 +00:00
|
|
|
}
|
|
|
|
|
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>>;
|
|
|
|
|
2023-03-11 15:07:02 +00:00
|
|
|
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 {
|
2023-03-11 23:30:48 +00:00
|
|
|
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) => {
|
2023-03-11 15:07:02 +00:00
|
|
|
match parser.consume(namespace, event) {
|
2023-03-08 18:56:53 +00:00
|
|
|
Continuation::Final(f) => {
|
|
|
|
let body = fail_fast!(f);
|
|
|
|
state.body = Some(body);
|
|
|
|
Continuation::Continue(IqParser(IqParserInner::Final(state)))
|
|
|
|
}
|
|
|
|
Continuation::Continue(parser) => {
|
|
|
|
Continuation::Continue(IqParser(IqParserInner::ParsingBody(state, parser)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IqParserInner::Final(state) => {
|
|
|
|
if let Event::End(ref bytes) = event {
|
2023-09-30 15:43:46 +00:00
|
|
|
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:?}")))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-11 23:30:48 +00:00
|
|
|
#[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
|
|
|
}
|
|
|
|
|
2023-03-20 16:25:14 +00:00
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
|
|
pub struct Presence<T> {
|
2023-04-09 21:31:43 +00:00
|
|
|
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>,
|
2023-03-20 16:25:14 +00:00
|
|
|
pub custom: Vec<T>,
|
2023-04-09 21:31:43 +00:00
|
|
|
pub r#type: Option<String>,
|
2023-03-20 16:25:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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(),
|
2023-04-09 21:31:43 +00:00
|
|
|
r#type: None,
|
2023-03-20 16:25:14 +00:00
|
|
|
}
|
|
|
|
}
|
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> {
|
2023-04-09 21:31:43 +00:00
|
|
|
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();
|
2023-04-09 21:31:43 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-20 16:25:14 +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;
|
|
|
|
}
|
|
|
|
|
2023-03-20 16:25:14 +00:00
|
|
|
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"),
|
2023-04-09 21:31:43 +00:00
|
|
|
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"),
|
2023-04-09 21:31:43 +00:00
|
|
|
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 {
|
2023-09-30 15:43:46 +00:00
|
|
|
use crate::bind::{BindRequest, Name, Resource, Server};
|
2023-03-11 23:30:48 +00:00
|
|
|
|
2023-03-07 13:56:31 +00:00
|
|
|
use super::*;
|
|
|
|
use quick_xml::NsReader;
|
|
|
|
|
|
|
|
#[tokio::test]
|
|
|
|
async fn parse_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![];
|
2023-03-11 23:30:48 +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 = Message::parse().consume(ns, &event);
|
2023-03-07 13:56:31 +00:00
|
|
|
let result = loop {
|
|
|
|
match parser {
|
|
|
|
Continuation::Final(res) => break res,
|
|
|
|
Continuation::Continue(next) => {
|
2023-03-11 23:30:48 +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);
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
|
|
|
result,
|
|
|
|
Message {
|
|
|
|
from: None,
|
|
|
|
id: Some("aacea".to_string()),
|
2023-04-09 21:31:43 +00:00
|
|
|
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()),
|
2023-04-09 21:31:43 +00:00
|
|
|
resource: None
|
|
|
|
}),
|
2023-03-07 15:27:09 +00:00
|
|
|
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(),
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2023-03-11 23:30:48 +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 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 = Iq::<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,
|
|
|
|
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-11 23:30:48 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2023-03-07 13:56:31 +00:00
|
|
|
}
|