From 5e7ad720c32630c2b42dbf8a9afaf31e8a35fe83 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sun, 23 Apr 2017 15:13:03 +0100 Subject: [PATCH] Add a message parser, along with a dependency on jid. --- Cargo.toml | 1 + src/lib.rs | 43 ++------- src/message.rs | 234 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+), 37 deletions(-) create mode 100644 src/message.rs diff --git a/Cargo.toml b/Cargo.toml index 7102bc64..f513c3b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Emmanuel Gil Peyrot "] [dependencies] minidom = "0.1.1" +jid = "0.1.0" base64 = "0.4.1" sha2 = "0.5.0" sha3 = "0.5.0" diff --git a/src/lib.rs b/src/lib.rs index 8efa52a3..bb9dae7a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,14 @@ //! A crate parsing common XMPP elements into Rust structures. //! -//! The main entrypoint is `parse_message_payload`, it takes a minidom -//! `Element` reference and optionally returns `Some(MessagePayload)` if the -//! parsing succeeded. +//! Each module implements a `parse` function, which takes a minidom +//! `Element` reference and returns `Some(MessagePayload)` if the parsing +//! succeeded, None otherwise. //! //! Parsed structs can then be manipulated internally, and serialised back //! before being sent over the wire. extern crate minidom; +extern crate jid; extern crate base64; use minidom::Element; @@ -16,6 +17,8 @@ pub mod error; /// XML namespace definitions used through XMPP. pub mod ns; +/// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core +pub mod message; /// RFC 6120: Extensible Messaging and Presence Protocol (XMPP): Core pub mod body; @@ -66,37 +69,3 @@ pub mod eme; /// XEP-0390: Entity Capabilities 2.0 pub mod ecaps2; - -/// Lists every known payload of a ``. -#[derive(Debug)] -pub enum MessagePayload { - Body(body::Body), - ChatState(chatstates::ChatState), - Receipt(receipts::Receipt), - Delay(delay::Delay), - Attention(attention::Attention), - MessageCorrect(message_correct::Replace), - ExplicitMessageEncryption(eme::ExplicitMessageEncryption), -} - -/// Parse one of the payloads of a `` element, and return `Some` of a -/// `MessagePayload` if parsing it succeeded, `None` otherwise. -pub fn parse_message_payload(elem: &Element) -> Option { - if let Ok(body) = body::parse_body(elem) { - Some(MessagePayload::Body(body)) - } else if let Ok(chatstate) = chatstates::parse_chatstate(elem) { - Some(MessagePayload::ChatState(chatstate)) - } else if let Ok(receipt) = receipts::parse_receipt(elem) { - Some(MessagePayload::Receipt(receipt)) - } else if let Ok(delay) = delay::parse_delay(elem) { - Some(MessagePayload::Delay(delay)) - } else if let Ok(attention) = attention::parse_attention(elem) { - Some(MessagePayload::Attention(attention)) - } else if let Ok(replace) = message_correct::parse_replace(elem) { - Some(MessagePayload::MessageCorrect(replace)) - } else if let Ok(eme) = eme::parse_explicit_message_encryption(elem) { - Some(MessagePayload::ExplicitMessageEncryption(eme)) - } else { - None - } -} diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 00000000..985b3eb6 --- /dev/null +++ b/src/message.rs @@ -0,0 +1,234 @@ +use std::str::FromStr; + +use minidom::Element; +use minidom::IntoAttributeValue; + +use jid::Jid; + +use error::Error; + +use ns; + +use body; +use chatstates; +use receipts; +use delay; +use attention; +use message_correct; +use eme; + +/// Lists every known payload of a ``. +#[derive(Debug, Clone)] +pub enum MessagePayload { + Body(body::Body), + ChatState(chatstates::ChatState), + Receipt(receipts::Receipt), + Delay(delay::Delay), + Attention(attention::Attention), + MessageCorrect(message_correct::Replace), + ExplicitMessageEncryption(eme::ExplicitMessageEncryption), +} + +#[derive(Debug, Clone, PartialEq)] +pub enum MessageType { + Chat, + Error, + Groupchat, + Headline, + Normal, +} + +impl Default for MessageType { + fn default() -> MessageType { + MessageType::Normal + } +} + +impl FromStr for MessageType { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "chat" => MessageType::Chat, + "error" => MessageType::Error, + "groupchat" => MessageType::Groupchat, + "headline" => MessageType::Headline, + "normal" => MessageType::Normal, + + _ => return Err(Error::ParseError("Invalid 'type' attribute on message element.")), + }) + } +} + +impl IntoAttributeValue for MessageType { + fn into_attribute_value(self) -> Option { + Some(match self { + MessageType::Chat => "chat", + MessageType::Error => "error", + MessageType::Groupchat => "groupchat", + MessageType::Headline => "headline", + MessageType::Normal => "normal", + }.to_owned()) + } +} + +#[derive(Debug, Clone)] +pub enum MessagePayloadType { + XML(Element), + Parsed(MessagePayload), +} + +#[derive(Debug, Clone)] +pub struct Message { + pub from: Option, + pub to: Option, + pub id: Option, + pub type_: MessageType, + pub payloads: Vec, +} + +pub fn parse_message(root: &Element) -> Result { + if !root.is("message", ns::JABBER_CLIENT) { + return Err(Error::ParseError("This is not a message element.")); + } + let from = root.attr("from") + .and_then(|value| value.parse().ok()); + let to = root.attr("to") + .and_then(|value| value.parse().ok()); + let id = root.attr("id") + .and_then(|value| value.parse().ok()); + let type_ = match root.attr("type") { + Some(type_) => type_.parse()?, + None => Default::default(), + }; + let mut payloads = vec!(); + for elem in root.children() { + let payload = if let Ok(body) = body::parse_body(elem) { + Some(MessagePayload::Body(body)) + } else if let Ok(chatstate) = chatstates::parse_chatstate(elem) { + Some(MessagePayload::ChatState(chatstate)) + } else if let Ok(receipt) = receipts::parse_receipt(elem) { + Some(MessagePayload::Receipt(receipt)) + } else if let Ok(delay) = delay::parse_delay(elem) { + Some(MessagePayload::Delay(delay)) + } else if let Ok(attention) = attention::parse_attention(elem) { + Some(MessagePayload::Attention(attention)) + } else if let Ok(replace) = message_correct::parse_replace(elem) { + Some(MessagePayload::MessageCorrect(replace)) + } else if let Ok(eme) = eme::parse_explicit_message_encryption(elem) { + Some(MessagePayload::ExplicitMessageEncryption(eme)) + } else { + None + }; + payloads.push(match payload { + Some(payload) => MessagePayloadType::Parsed(payload), + None => MessagePayloadType::XML(elem.clone()), + }); + } + Ok(Message { + from: from, + to: to, + id: id, + type_: type_, + payloads: payloads, + }) +} + +pub fn serialise_payload(payload: &MessagePayload) -> Element { + match *payload { + MessagePayload::Body(ref body) => body::serialise(body), + MessagePayload::Attention(ref attention) => attention::serialise(attention), + MessagePayload::ChatState(ref chatstate) => chatstates::serialise(chatstate), + MessagePayload::Receipt(ref receipt) => receipts::serialise(receipt), + MessagePayload::Delay(ref delay) => delay::serialise(delay), + MessagePayload::MessageCorrect(ref replace) => message_correct::serialise(replace), + MessagePayload::ExplicitMessageEncryption(ref eme) => eme::serialise(eme), + } +} + +pub fn serialise(message: &Message) -> Element { + let mut stanza = Element::builder("message") + .ns(ns::JABBER_CLIENT) + .attr("from", message.from.clone().and_then(|value| Some(String::from(value)))) + .attr("to", message.to.clone().and_then(|value| Some(String::from(value)))) + .attr("id", message.id.clone()) + .attr("type", message.type_.clone()) + .build(); + for child in message.payloads.clone() { + let elem = match child { + MessagePayloadType::XML(elem) => elem, + MessagePayloadType::Parsed(payload) => serialise_payload(&payload), + }; + stanza.append_child(elem); + } + stanza +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + use minidom::Element; + use error::Error; + use jid::Jid; + use message; + + #[test] + fn test_simple() { + let elem: Element = "".parse().unwrap(); + let message = message::parse_message(&elem).unwrap(); + assert_eq!(message.from, None); + assert_eq!(message.to, None); + assert_eq!(message.id, None); + assert_eq!(message.type_, message::MessageType::Normal); + assert!(message.payloads.is_empty()); + } + + #[test] + fn test_serialise() { + let elem: Element = "".parse().unwrap(); + let message = message::parse_message(&elem).unwrap(); + let message2 = message::Message { + from: None, + to: None, + id: None, + type_: message::MessageType::Normal, + payloads: vec!(), + }; + let elem2 = message::serialise(&message2); + assert_eq!(elem, elem2); + println!("{:#?}", message); + } + + #[test] + fn test_body() { + let elem: Element = "Hello world!".parse().unwrap(); + let message = message::parse_message(&elem).unwrap(); + println!("{:#?}", message); + } + + #[test] + fn test_serialise_body() { + let elem: Element = "Hello world!".parse().unwrap(); + let message = message::parse_message(&elem).unwrap(); + let message2 = message::Message { + from: None, + to: Some(Jid::from_str("coucou@example.org").unwrap()), + id: None, + type_: message::MessageType::Chat, + payloads: vec!( + message::MessagePayloadType::Parsed(message::MessagePayload::Body("Hello world!".to_owned())), + ), + }; + let elem2 = message::serialise(&message2); + assert_eq!(elem, elem2); + println!("{:#?}", message); + } + + #[test] + fn test_attention() { + let elem: Element = "".parse().unwrap(); + let message = message::parse_message(&elem).unwrap(); + let elem2 = message::serialise(&message); + assert_eq!(elem, elem2); + } +}