forked from lavina/lavina
338 lines
10 KiB
Rust
338 lines
10 KiB
Rust
#![allow(unused_variables)]
|
|
|
|
use quick_xml::events::{BytesEnd, BytesStart, Event};
|
|
use quick_xml::name::ResolveResult;
|
|
|
|
use crate::bind::Jid;
|
|
use crate::xml::*;
|
|
use anyhow::{anyhow, Result};
|
|
|
|
pub const XMLNS: &'static str = "http://jabber.org/protocol/muc";
|
|
pub const XMLNS_USER: &'static str = "http://jabber.org/protocol/muc#user";
|
|
|
|
#[derive(PartialEq, Eq, Debug, Default)]
|
|
pub struct History {
|
|
pub maxchars: Option<u32>,
|
|
pub maxstanzas: Option<u32>,
|
|
pub seconds: Option<u32>,
|
|
}
|
|
|
|
impl FromXml for History {
|
|
type P = impl Parser<Output = Result<Self>>;
|
|
|
|
fn parse() -> Self::P {
|
|
|(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
|
|
let mut history = History::default();
|
|
let (bytes, end) = match event {
|
|
Event::Start(bytes) if bytes.name().0 == Self::NAME.as_bytes() => (bytes, false),
|
|
Event::Empty(bytes) if bytes.name().0 == Self::NAME.as_bytes() => (bytes, true),
|
|
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
|
|
};
|
|
for attr in bytes.attributes() {
|
|
let attr = attr?;
|
|
match attr.key.0 {
|
|
b"maxchars" => {
|
|
let s = std::str::from_utf8(&attr.value)?;
|
|
let a = s.parse()?;
|
|
history.maxchars = Some(a)
|
|
}
|
|
b"maxstanzas" => {
|
|
let s = std::str::from_utf8(&attr.value)?;
|
|
let a = s.parse()?;
|
|
history.maxstanzas = Some(a)
|
|
}
|
|
b"seconds" => {
|
|
let s = std::str::from_utf8(&attr.value)?;
|
|
let a = s.parse()?;
|
|
history.seconds = Some(a)
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
if end {
|
|
return Ok(history);
|
|
}
|
|
|
|
(namespace, event) = yield;
|
|
let Event::End(bytes) = event else {
|
|
return Err(anyhow!("Unexpected XML event: {event:?}"));
|
|
};
|
|
Ok(history)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromXmlTag for History {
|
|
const NAME: &'static str = "history";
|
|
|
|
const NS: &'static str = XMLNS;
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
pub struct Password(pub String);
|
|
|
|
impl FromXml for Password {
|
|
type P = impl Parser<Output = Result<Self>>;
|
|
|
|
fn parse() -> Self::P {
|
|
|(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
|
|
let bytes = match event {
|
|
Event::Start(bytes) if bytes.name().0 == Self::NAME.as_bytes() => bytes,
|
|
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
|
|
};
|
|
(namespace, event) = yield;
|
|
let Event::Text(bytes) = event else {
|
|
return Err(anyhow!("Unexpected XML event: {event:?}"));
|
|
};
|
|
let s = std::str::from_utf8(bytes)?.to_string();
|
|
(namespace, event) = yield;
|
|
let Event::End(bytes) = event else {
|
|
return Err(anyhow!("Unexpected XML event: {event:?}"));
|
|
};
|
|
Ok(Password(s))
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromXmlTag for Password {
|
|
const NAME: &'static str = "password";
|
|
|
|
const NS: &'static str = XMLNS;
|
|
}
|
|
|
|
#[derive(PartialEq, Eq, Debug, Default)]
|
|
pub struct X {
|
|
pub history: Option<History>,
|
|
pub password: Option<Password>,
|
|
}
|
|
|
|
impl FromXml for X {
|
|
type P = impl Parser<Output = Result<Self>>;
|
|
|
|
fn parse() -> Self::P {
|
|
|(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result<Self> {
|
|
let mut res = X::default();
|
|
let (_, end) = match event {
|
|
Event::Start(bytes) => (bytes, false),
|
|
Event::Empty(bytes) => (bytes, true),
|
|
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
|
|
};
|
|
if end {
|
|
return Ok(res);
|
|
}
|
|
|
|
loop {
|
|
(namespace, event) = yield;
|
|
let bytes = match event {
|
|
Event::Start(bytes) => bytes,
|
|
Event::Empty(bytes) => bytes,
|
|
Event::End(_) => break,
|
|
_ => return Err(anyhow!("Unexpected XML event: {event:?}")),
|
|
};
|
|
if bytes.name().0 == Password::NAME.as_bytes() {
|
|
let password = delegate_parsing!(Password, namespace, event)?;
|
|
res.password = Some(password);
|
|
} else if bytes.name().0 == History::NAME.as_bytes() {
|
|
let history = delegate_parsing!(History, namespace, event)?;
|
|
res.history = Some(history);
|
|
} else {
|
|
return Err(anyhow!("Unexpected XML event: {event:?}"));
|
|
}
|
|
}
|
|
|
|
Ok(res)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Information about an MUC member. May contain [MUC status codes](https://xmpp.org/registrar/mucstatus.html).
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct XUser {
|
|
pub item: XUserItem,
|
|
/// Code 110. The receiver is the user referred to in the presence stanza.
|
|
pub self_presence: bool,
|
|
/// Code 201. The room from which the presence stanza was sent was just created.
|
|
pub just_created: bool,
|
|
}
|
|
impl ToXml for XUser {
|
|
fn serialize(&self, output: &mut Vec<Event<'static>>) {
|
|
let mut tag = BytesStart::new("x");
|
|
tag.push_attribute(("xmlns", XMLNS_USER));
|
|
output.push(Event::Start(tag));
|
|
self.item.serialize(output);
|
|
if self.self_presence {
|
|
let mut meg = BytesStart::new("status");
|
|
meg.push_attribute(("code", "110"));
|
|
output.push(Event::Empty(meg));
|
|
}
|
|
if self.just_created {
|
|
let mut meg = BytesStart::new("status");
|
|
meg.push_attribute(("code", "201"));
|
|
output.push(Event::Empty(meg));
|
|
}
|
|
output.push(Event::End(BytesEnd::new("x")));
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct XUserItem {
|
|
pub affiliation: Affiliation,
|
|
pub jid: Jid,
|
|
pub role: Role,
|
|
}
|
|
impl ToXml for XUserItem {
|
|
fn serialize(&self, output: &mut Vec<Event<'static>>) {
|
|
let mut meg = BytesStart::new("item");
|
|
meg.push_attribute(("affiliation", self.affiliation.to_str()));
|
|
meg.push_attribute(("role", self.role.to_str()));
|
|
meg.push_attribute(("jid", self.jid.to_string().as_str()));
|
|
output.push(Event::Empty(meg));
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum Affiliation {
|
|
Owner,
|
|
Admin,
|
|
Member,
|
|
Outcast,
|
|
None,
|
|
}
|
|
impl Affiliation {
|
|
pub fn from_str(s: &str) -> Option<Self> {
|
|
match s {
|
|
"owner" => Some(Self::Owner),
|
|
"admin" => Some(Self::Admin),
|
|
"member" => Some(Self::Member),
|
|
"outcast" => Some(Self::Outcast),
|
|
"none" => Some(Self::None),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn to_str(&self) -> &str {
|
|
match self {
|
|
Self::Owner => "owner",
|
|
Self::Admin => "admin",
|
|
Self::Member => "member",
|
|
Self::Outcast => "outcast",
|
|
Self::None => "none",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum Role {
|
|
Moderator,
|
|
Participant,
|
|
Visitor,
|
|
None,
|
|
}
|
|
impl Role {
|
|
pub fn from_str(s: &str) -> Option<Self> {
|
|
match s {
|
|
"moderator" => Some(Self::Moderator),
|
|
"participant" => Some(Self::Participant),
|
|
"visitor" => Some(Self::Visitor),
|
|
"none" => Some(Self::None),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn to_str(&self) -> &str {
|
|
match self {
|
|
Self::Moderator => "moderator",
|
|
Self::Participant => "participant",
|
|
Self::Visitor => "visitor",
|
|
Self::None => "none",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_history_success_empty() {
|
|
let input = "<history/>";
|
|
let res: History = parse(input).unwrap();
|
|
let expected = History {
|
|
maxchars: None,
|
|
maxstanzas: None,
|
|
seconds: None,
|
|
};
|
|
assert_eq!(res, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_history_success_empty_attrs() {
|
|
let input = r#"<history maxchars="1" maxstanzas="2" seconds="4"/>"#;
|
|
let res: History = parse(input).unwrap();
|
|
let expected = History {
|
|
maxchars: Some(1),
|
|
maxstanzas: Some(2),
|
|
seconds: Some(4),
|
|
};
|
|
assert_eq!(res, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_history_success_start_end() {
|
|
let input = r#"<history></history>"#;
|
|
let res: History = parse(input).unwrap();
|
|
let expected = History {
|
|
maxchars: None,
|
|
maxstanzas: None,
|
|
seconds: None,
|
|
};
|
|
assert_eq!(res, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_history_incorrect_empty() {
|
|
let input = r#"<iq/>"#;
|
|
parse::<History>(input).err().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_password_success() {
|
|
let input = "<password>olala</password>";
|
|
let res: Password = parse(input).unwrap();
|
|
let expected = Password("olala".into());
|
|
assert_eq!(res, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_password_incorrect() {
|
|
let input = r#"<iq>asdsd</iq>"#;
|
|
parse::<Password>(input).err().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_x_success_empty() {
|
|
let input = "<x/>";
|
|
let res: X = parse(input).unwrap();
|
|
let expected = X {
|
|
history: None,
|
|
password: None,
|
|
};
|
|
assert_eq!(res, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn test_x_success_full() {
|
|
let input = r#"<x><password>ololo</password><history maxchars="1"/></x>"#;
|
|
let res: X = parse(input).unwrap();
|
|
let expected = X {
|
|
history: Some(History {
|
|
maxchars: Some(1),
|
|
maxstanzas: None,
|
|
seconds: None,
|
|
}),
|
|
password: Some(Password("ololo".into())),
|
|
};
|
|
assert_eq!(res, expected);
|
|
}
|
|
}
|