diff options
Diffstat (limited to 'src/stanza')
| -rw-r--r-- | src/stanza/bind.rs | 98 | ||||
| -rw-r--r-- | src/stanza/client/error.rs | 83 | ||||
| -rw-r--r-- | src/stanza/client/iq.rs | 124 | ||||
| -rw-r--r-- | src/stanza/client/message.rs | 37 | ||||
| -rw-r--r-- | src/stanza/client/mod.rs | 6 | ||||
| -rw-r--r-- | src/stanza/client/presence.rs | 48 | ||||
| -rw-r--r-- | src/stanza/error.rs | 126 | ||||
| -rw-r--r-- | src/stanza/iq.rs | 1 | ||||
| -rw-r--r-- | src/stanza/message.rs | 1 | ||||
| -rw-r--r-- | src/stanza/mod.rs | 7 | ||||
| -rw-r--r-- | src/stanza/presence.rs | 1 | ||||
| -rw-r--r-- | src/stanza/starttls.rs | 8 | ||||
| -rw-r--r-- | src/stanza/stream.rs | 9 | 
13 files changed, 540 insertions, 9 deletions
| diff --git a/src/stanza/bind.rs b/src/stanza/bind.rs index 8b13789..0e67a83 100644 --- a/src/stanza/bind.rs +++ b/src/stanza/bind.rs @@ -1 +1,99 @@ +use peanuts::{ +    element::{FromElement, IntoElement}, +    Element, +}; +use crate::JID; + +pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-bind"; + +#[derive(Clone)] +pub struct Bind { +    pub r#type: Option<BindType>, +} + +impl FromElement for Bind { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        element.check_name("bind"); +        element.check_name(XMLNS); + +        let r#type = element.pop_child_opt()?; + +        Ok(Bind { r#type }) +    } +} + +impl IntoElement for Bind { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        Element::builder("bind", Some(XMLNS)).push_child_opt(self.r#type.clone()) +    } +} + +#[derive(Clone)] +pub enum BindType { +    Resource(ResourceType), +    Jid(FullJidType), +} + +impl FromElement for BindType { +    fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        match element.identify() { +            (Some(XMLNS), "resource") => { +                Ok(BindType::Resource(ResourceType::from_element(element)?)) +            } +            (Some(XMLNS), "jid") => Ok(BindType::Jid(FullJidType::from_element(element)?)), +            _ => Err(peanuts::DeserializeError::UnexpectedElement(element)), +        } +    } +} + +impl IntoElement for BindType { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        match self { +            BindType::Resource(resource_type) => resource_type.builder(), +            BindType::Jid(full_jid_type) => full_jid_type.builder(), +        } +    } +} + +// minLength 8 maxLength 3071 +#[derive(Clone)] +pub struct FullJidType(pub JID); + +impl FromElement for FullJidType { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        element.check_name("jid"); +        element.check_namespace(XMLNS); + +        let jid = element.pop_value()?; + +        Ok(FullJidType(jid)) +    } +} + +impl IntoElement for FullJidType { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        Element::builder("jid", Some(XMLNS)).push_text(self.0.clone()) +    } +} + +// minLength 1 maxLength 1023 +#[derive(Clone)] +pub struct ResourceType(pub String); + +impl FromElement for ResourceType { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        element.check_name("resource")?; +        element.check_namespace(XMLNS)?; + +        let resource = element.pop_value()?; + +        Ok(ResourceType(resource)) +    } +} + +impl IntoElement for ResourceType { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        Element::builder("resource", Some(XMLNS)).push_text(self.0.clone()) +    } +} diff --git a/src/stanza/client/error.rs b/src/stanza/client/error.rs new file mode 100644 index 0000000..fc5ed21 --- /dev/null +++ b/src/stanza/client/error.rs @@ -0,0 +1,83 @@ +use std::str::FromStr; + +use peanuts::element::{FromElement, IntoElement}; +use peanuts::{DeserializeError, Element}; + +use crate::stanza::error::Text; +use crate::stanza::Error as StanzaError; + +use super::XMLNS; + +#[derive(Clone, Debug)] +pub struct Error { +    by: Option<String>, +    r#type: ErrorType, +    // children (sequence) +    error: StanzaError, +    text: Option<Text>, +} + +impl FromElement for Error { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        element.check_name("error")?; +        element.check_name(XMLNS)?; + +        let by = element.attribute_opt("by")?; +        let r#type = element.attribute("type")?; +        let error = element.pop_child_one()?; +        let text = element.pop_child_opt()?; + +        Ok(Error { +            by, +            r#type, +            error, +            text, +        }) +    } +} + +impl IntoElement for Error { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        Element::builder("error", Some(XMLNS)) +            .push_attribute_opt("by", self.by.clone()) +            .push_attribute("type", self.r#type) +            .push_child(self.error.clone()) +            .push_child_opt(self.text.clone()) +    } +} + +#[derive(Copy, Clone, Debug)] +pub enum ErrorType { +    Auth, +    Cancel, +    Continue, +    Modify, +    Wait, +} + +impl FromStr for ErrorType { +    type Err = DeserializeError; + +    fn from_str(s: &str) -> Result<Self, Self::Err> { +        match s { +            "auth" => Ok(ErrorType::Auth), +            "cancel" => Ok(ErrorType::Cancel), +            "continue" => Ok(ErrorType::Continue), +            "modify" => Ok(ErrorType::Modify), +            "wait" => Ok(ErrorType::Wait), +            _ => Err(DeserializeError::FromStr(s.to_string())), +        } +    } +} + +impl ToString for ErrorType { +    fn to_string(&self) -> String { +        match self { +            ErrorType::Auth => "auth".to_string(), +            ErrorType::Cancel => "cancel".to_string(), +            ErrorType::Continue => "continue".to_string(), +            ErrorType::Modify => "modify".to_string(), +            ErrorType::Wait => "wait".to_string(), +        } +    } +} diff --git a/src/stanza/client/iq.rs b/src/stanza/client/iq.rs new file mode 100644 index 0000000..b23f8b7 --- /dev/null +++ b/src/stanza/client/iq.rs @@ -0,0 +1,124 @@ +use std::str::FromStr; + +use peanuts::{ +    element::{FromElement, IntoElement}, +    DeserializeError, Element, XML_NS, +}; + +use crate::{ +    stanza::{ +        bind::{self, Bind}, +        client::error::Error, +    }, +    JID, +}; + +use super::XMLNS; + +pub struct Iq { +    pub from: Option<JID>, +    pub id: String, +    pub to: Option<JID>, +    pub r#type: IqType, +    pub lang: Option<String>, +    // children +    // ##other +    pub query: Option<Query>, +    pub errors: Vec<Error>, +} + +#[derive(Clone)] +pub enum Query { +    Bind(Bind), +    Unsupported, +} + +impl FromElement for Query { +    fn from_element(element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        match element.identify() { +            (Some(bind::XMLNS), "bind") => Ok(Query::Bind(Bind::from_element(element)?)), +            _ => Ok(Query::Unsupported), +        } +    } +} + +impl IntoElement for Query { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        match self { +            Query::Bind(bind) => bind.builder(), +            // TODO: consider what to do if attempt to serialize unsupported +            Query::Unsupported => todo!(), +        } +    } +} + +impl FromElement for Iq { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        element.check_name("iq")?; +        element.check_namespace(XMLNS)?; + +        let from = element.attribute_opt("from")?; +        let id = element.attribute("id")?; +        let to = element.attribute_opt("to")?; +        let r#type = element.attribute("type")?; +        let lang = element.attribute_opt_namespaced("lang", XML_NS)?; +        let query = element.pop_child_opt()?; +        let errors = element.pop_children()?; + +        Ok(Iq { +            from, +            id, +            to, +            r#type, +            lang, +            query, +            errors, +        }) +    } +} + +impl IntoElement for Iq { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        Element::builder("iq", Some(XMLNS)) +            .push_attribute_opt("from", self.from.clone()) +            .push_attribute("id", self.id.clone()) +            .push_attribute_opt("to", self.to.clone()) +            .push_attribute("type", self.r#type) +            .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) +            .push_child_opt(self.query.clone()) +            .push_children(self.errors.clone()) +    } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum IqType { +    Error, +    Get, +    Result, +    Set, +} + +impl FromStr for IqType { +    type Err = DeserializeError; + +    fn from_str(s: &str) -> Result<Self, Self::Err> { +        match s { +            "error" => Ok(IqType::Error), +            "get" => Ok(IqType::Get), +            "result" => Ok(IqType::Result), +            "set" => Ok(IqType::Set), +            _ => Err(DeserializeError::FromStr(s.to_string())), +        } +    } +} + +impl ToString for IqType { +    fn to_string(&self) -> String { +        match self { +            IqType::Error => "error".to_string(), +            IqType::Get => "get".to_string(), +            IqType::Result => "result".to_string(), +            IqType::Set => "set".to_string(), +        } +    } +} diff --git a/src/stanza/client/message.rs b/src/stanza/client/message.rs new file mode 100644 index 0000000..cdfda5d --- /dev/null +++ b/src/stanza/client/message.rs @@ -0,0 +1,37 @@ +use crate::JID; + +pub struct Message { +    from: Option<JID>, +    id: Option<String>, +    to: Option<JID>, +    r#type: Option<MessageType>, +    // children +    subject: Option<Subject>, +    body: Option<Body>, +    thread: Option<Thread>, +    lang: Option<String>, +} + +pub enum MessageType { +    Chat, +    Error, +    Groupchat, +    Headline, +    Normal, +} + +pub struct Body { +    lang: Option<String>, +    body: Option<String>, +} + +pub struct Subject { +    lang: Option<String>, +    subject: Option<String>, +} + +pub struct Thread { +    // TODO: NOT DONE +    parent: Option<String>, +    thread: Option<String>, +} diff --git a/src/stanza/client/mod.rs b/src/stanza/client/mod.rs new file mode 100644 index 0000000..7b25b15 --- /dev/null +++ b/src/stanza/client/mod.rs @@ -0,0 +1,6 @@ +pub mod error; +pub mod iq; +pub mod message; +pub mod presence; + +pub const XMLNS: &str = "jabber:client"; diff --git a/src/stanza/client/presence.rs b/src/stanza/client/presence.rs new file mode 100644 index 0000000..46194f3 --- /dev/null +++ b/src/stanza/client/presence.rs @@ -0,0 +1,48 @@ +use peanuts::element::{FromElement, IntoElement}; + +use crate::JID; + +use super::error::Error; + +pub struct Presence { +    from: Option<JID>, +    id: Option<String>, +    to: Option<JID>, +    r#type: PresenceType, +    lang: Option<String>, +    // children +    show: Option<Show>, +    status: Option<Status>, +    priority: Option<Priority>, +    errors: Vec<Error>, +    // ##other +    // content: Vec<Box<dyn AsElement>>, +} + +pub enum PresenceType { +    Error, +    Probe, +    Subscribe, +    Subscribed, +    Unavailable, +    Unsubscribe, +    Unsubscribed, +} + +pub enum Show { +    Away, +    Chat, +    Dnd, +    Xa, +} + +pub struct Status { +    lang: Option<String>, +    status: String1024, +} + +// minLength 1 maxLength 1024 +pub struct String1024(String); + +// xs:byte +pub struct Priority(u8); diff --git a/src/stanza/error.rs b/src/stanza/error.rs new file mode 100644 index 0000000..99c1f15 --- /dev/null +++ b/src/stanza/error.rs @@ -0,0 +1,126 @@ +// https://datatracker.ietf.org/doc/html/rfc6120#appendix-A.8 + +use peanuts::{ +    element::{FromElement, IntoElement}, +    Element, XML_NS, +}; + +pub const XMLNS: &str = "urn:ietf:params:xml:ns:xmpp-stanzas"; + +#[derive(Clone, Debug)] +pub enum Error { +    BadRequest, +    Conflict, +    FeatureNotImplemented, +    Forbidden, +    Gone(Option<String>), +    InternalServerError, +    ItemNotFound, +    JidMalformed, +    NotAcceptable, +    NotAllowed, +    NotAuthorized, +    PolicyViolation, +    RecipientUnavailable, +    Redirect(Option<String>), +    RegistrationRequired, +    RemoteServerNotFound, +    RemoteServerTimeout, +    ResourceConstraint, +    ServiceUnavailable, +    SubscriptionRequired, +    UndefinedCondition, +    UnexpectedRequest, +} + +impl FromElement for Error { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        let error; +        match element.identify() { +            (Some(XMLNS), "bad-request") => error = Error::BadRequest, +            (Some(XMLNS), "conflict") => error = Error::Conflict, +            (Some(XMLNS), "feature-not-implemented") => error = Error::FeatureNotImplemented, +            (Some(XMLNS), "forbidden") => error = Error::Forbidden, +            (Some(XMLNS), "gone") => return Ok(Error::Gone(element.pop_value_opt()?)), +            (Some(XMLNS), "internal-server-error") => error = Error::InternalServerError, +            (Some(XMLNS), "item-not-found") => error = Error::ItemNotFound, +            (Some(XMLNS), "jid-malformed") => error = Error::JidMalformed, +            (Some(XMLNS), "not-acceptable") => error = Error::NotAcceptable, +            (Some(XMLNS), "not-allowed") => error = Error::NotAllowed, +            (Some(XMLNS), "not-authorized") => error = Error::NotAuthorized, +            (Some(XMLNS), "policy-violation") => error = Error::PolicyViolation, +            (Some(XMLNS), "recipient-unavailable") => error = Error::RecipientUnavailable, +            (Some(XMLNS), "redirect") => return Ok(Error::Redirect(element.pop_value_opt()?)), +            (Some(XMLNS), "registration-required") => error = Error::RegistrationRequired, +            (Some(XMLNS), "remote-server-not-found") => error = Error::RemoteServerNotFound, +            (Some(XMLNS), "remote-server-timeout") => error = Error::RemoteServerTimeout, +            (Some(XMLNS), "resource-constraint") => error = Error::ResourceConstraint, +            (Some(XMLNS), "service-unavailable") => error = Error::ServiceUnavailable, +            (Some(XMLNS), "subscription-required") => error = Error::SubscriptionRequired, +            (Some(XMLNS), "undefined-condition") => error = Error::UndefinedCondition, +            (Some(XMLNS), "unexpected-request") => error = Error::UnexpectedRequest, +            _ => return Err(peanuts::DeserializeError::UnexpectedElement(element)), +        } +        element.no_more_content()?; +        return Ok(error); +    } +} + +impl IntoElement for Error { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        match self { +            Error::BadRequest => Element::builder("bad-request", Some(XMLNS)), +            Error::Conflict => Element::builder("conflict", Some(XMLNS)), +            Error::FeatureNotImplemented => { +                Element::builder("feature-not-implemented", Some(XMLNS)) +            } +            Error::Forbidden => Element::builder("forbidden", Some(XMLNS)), +            Error::Gone(r) => Element::builder("gone", Some(XMLNS)).push_text_opt(r.clone()), +            Error::InternalServerError => Element::builder("internal-server-error", Some(XMLNS)), +            Error::ItemNotFound => Element::builder("item-not-found", Some(XMLNS)), +            Error::JidMalformed => Element::builder("jid-malformed", Some(XMLNS)), +            Error::NotAcceptable => Element::builder("not-acceptable", Some(XMLNS)), +            Error::NotAllowed => Element::builder("not-allowed", Some(XMLNS)), +            Error::NotAuthorized => Element::builder("not-authorized", Some(XMLNS)), +            Error::PolicyViolation => Element::builder("policy-violation", Some(XMLNS)), +            Error::RecipientUnavailable => Element::builder("recipient-unavailable", Some(XMLNS)), +            Error::Redirect(r) => { +                Element::builder("redirect", Some(XMLNS)).push_text_opt(r.clone()) +            } +            Error::RegistrationRequired => Element::builder("registration-required", Some(XMLNS)), +            Error::RemoteServerNotFound => Element::builder("remote-server-not-found", Some(XMLNS)), +            Error::RemoteServerTimeout => Element::builder("remote-server-timeout", Some(XMLNS)), +            Error::ResourceConstraint => Element::builder("resource-constraint", Some(XMLNS)), +            Error::ServiceUnavailable => Element::builder("service-unavailable", Some(XMLNS)), +            Error::SubscriptionRequired => Element::builder("subscription-required", Some(XMLNS)), +            Error::UndefinedCondition => Element::builder("undefined-condition", Some(XMLNS)), +            Error::UnexpectedRequest => Element::builder("unexpected-request", Some(XMLNS)), +        } +    } +} + +#[derive(Clone, Debug)] +pub struct Text { +    lang: Option<String>, +    text: Option<String>, +} + +impl FromElement for Text { +    fn from_element(mut element: peanuts::Element) -> peanuts::element::DeserializeResult<Self> { +        element.check_name("text")?; +        element.check_name(XMLNS)?; + +        let lang = element.attribute_opt_namespaced("lang", XML_NS)?; +        let text = element.pop_value_opt()?; + +        Ok(Text { lang, text }) +    } +} + +impl IntoElement for Text { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        Element::builder("text", Some(XMLNS)) +            .push_attribute_opt_namespaced(XML_NS, "lang", self.lang.clone()) +            .push_text_opt(self.text.clone()) +    } +} diff --git a/src/stanza/iq.rs b/src/stanza/iq.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/stanza/iq.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/stanza/message.rs b/src/stanza/message.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/stanza/message.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/stanza/mod.rs b/src/stanza/mod.rs index 4f1ce48..84e80ab 100644 --- a/src/stanza/mod.rs +++ b/src/stanza/mod.rs @@ -1,11 +1,12 @@  use peanuts::declaration::VersionInfo;  pub mod bind; -pub mod iq; -pub mod message; -pub mod presence; +pub mod client; +pub mod error;  pub mod sasl;  pub mod starttls;  pub mod stream;  pub static XML_VERSION: VersionInfo = VersionInfo::One; + +pub use error::Error; diff --git a/src/stanza/presence.rs b/src/stanza/presence.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/stanza/presence.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/stanza/starttls.rs b/src/stanza/starttls.rs index 33721ab..fb66711 100644 --- a/src/stanza/starttls.rs +++ b/src/stanza/starttls.rs @@ -17,7 +17,7 @@ impl IntoElement for StartTls {          let mut builder = Element::builder("starttls", Some(XMLNS));          if self.required { -            builder = builder.push_child(Element::builder("required", Some(XMLNS))) +            builder = builder.push_child(Required)          }          builder @@ -52,6 +52,12 @@ impl FromElement for Required {      }  } +impl IntoElement for Required { +    fn builder(&self) -> peanuts::element::ElementBuilder { +        Element::builder("required", Some(XMLNS)) +    } +} +  #[derive(Debug)]  pub struct Proceed; diff --git a/src/stanza/stream.rs b/src/stanza/stream.rs index fecace5..c49a2bc 100644 --- a/src/stanza/stream.rs +++ b/src/stanza/stream.rs @@ -5,13 +5,14 @@ use peanuts::XML_NS;  use peanuts::{element::Name, Element};  use tracing::debug; +use crate::stanza::bind;  use crate::{Error, JID}; +use super::client;  use super::sasl::{self, Mechanisms};  use super::starttls::{self, StartTls};  pub const XMLNS: &str = "http://etherx.jabber.org/streams"; -pub const XMLNS_CLIENT: &str = "jabber:client";  // MUST be qualified by stream namespace  // #[derive(XmlSerialize, XmlDeserialize)] @@ -53,7 +54,7 @@ impl IntoElement for Stream {      fn builder(&self) -> ElementBuilder {          Element::builder("stream", Some(XMLNS.to_string()))              .push_namespace_declaration_override(Some("stream"), XMLNS) -            .push_namespace_declaration_override(None::<&str>, XMLNS_CLIENT) +            .push_namespace_declaration_override(None::<&str>, client::XMLNS)              .push_attribute_opt("to", self.to.clone())              .push_attribute_opt("from", self.from.clone())              .push_attribute_opt("id", self.id.clone()) @@ -150,6 +151,10 @@ impl FromElement for Feature {                  debug!("identified mechanisms");                  Ok(Feature::Sasl(Mechanisms::from_element(element)?))              } +            (Some(bind::XMLNS), "bind") => { +                debug!("identified bind"); +                Ok(Feature::Bind) +            }              _ => {                  debug!("identified unknown feature");                  Ok(Feature::Unknown) | 
