Cargo.toml 🔗
@@ -5,6 +5,7 @@ authors = ["Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>"]
[dependencies]
minidom = "0.1.1"
+jid = "0.1.0"
base64 = "0.4.1"
sha2 = "0.5.0"
sha3 = "0.5.0"
Emmanuel Gil Peyrot created
Cargo.toml | 1
src/lib.rs | 43 +--------
src/message.rs | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 241 insertions(+), 37 deletions(-)
@@ -5,6 +5,7 @@ authors = ["Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>"]
[dependencies]
minidom = "0.1.1"
+jid = "0.1.0"
base64 = "0.4.1"
sha2 = "0.5.0"
sha3 = "0.5.0"
@@ -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 `<message/>`.
-#[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 `<message/>` element, and return `Some` of a
-/// `MessagePayload` if parsing it succeeded, `None` otherwise.
-pub fn parse_message_payload(elem: &Element) -> Option<MessagePayload> {
- 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
- }
-}
@@ -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 `<message/>`.
+#[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<MessageType, Error> {
+ 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<String> {
+ 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<Jid>,
+ pub to: Option<Jid>,
+ pub id: Option<String>,
+ pub type_: MessageType,
+ pub payloads: Vec<MessagePayloadType>,
+}
+
+pub fn parse_message(root: &Element) -> Result<Message, Error> {
+ 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 = "<message xmlns='jabber:client'/>".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 = "<message xmlns='jabber:client' type='normal'/>".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 = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
+ let message = message::parse_message(&elem).unwrap();
+ println!("{:#?}", message);
+ }
+
+ #[test]
+ fn test_serialise_body() {
+ let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".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 = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
+ let message = message::parse_message(&elem).unwrap();
+ let elem2 = message::serialise(&message);
+ assert_eq!(elem, elem2);
+ }
+}