// Copyright (c) 2017 Emmanuel Gil Peyrot // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. use std::convert::TryFrom; use std::str::FromStr; use std::collections::BTreeMap; use minidom::{Element, IntoAttributeValue}; use jid::Jid; use error::Error; use ns; use stanza_error::StanzaError; use chatstates::ChatState; use receipts::Receipt; use delay::Delay; use attention::Attention; use message_correct::Replace; use eme::ExplicitMessageEncryption; use stanza_id::StanzaId; /// Lists every known payload of a ``. #[derive(Debug, Clone)] pub enum MessagePayload { StanzaError(StanzaError), ChatState(ChatState), Receipt(Receipt), Delay(Delay), Attention(Attention), MessageCorrect(Replace), ExplicitMessageEncryption(ExplicitMessageEncryption), StanzaId(StanzaId), } impl<'a> TryFrom<&'a Element> for MessagePayload { type Error = Error; fn try_from(elem: &'a Element) -> Result { Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) { ("error", ns::JABBER_CLIENT) => MessagePayload::StanzaError(StanzaError::try_from(elem)?), // XEP-0085 ("active", ns::CHATSTATES) => MessagePayload::ChatState(ChatState::try_from(elem)?), ("inactive", ns::CHATSTATES) => MessagePayload::ChatState(ChatState::try_from(elem)?), ("composing", ns::CHATSTATES) => MessagePayload::ChatState(ChatState::try_from(elem)?), ("paused", ns::CHATSTATES) => MessagePayload::ChatState(ChatState::try_from(elem)?), ("gone", ns::CHATSTATES) => MessagePayload::ChatState(ChatState::try_from(elem)?), // XEP-0184 ("request", ns::RECEIPTS) => MessagePayload::Receipt(Receipt::try_from(elem)?), ("received", ns::RECEIPTS) => MessagePayload::Receipt(Receipt::try_from(elem)?), // XEP-0203 ("delay", ns::DELAY) => MessagePayload::Delay(Delay::try_from(elem)?), // XEP-0224 ("attention", ns::ATTENTION) => MessagePayload::Attention(Attention::try_from(elem)?), // XEP-0308 ("replace", ns::MESSAGE_CORRECT) => MessagePayload::MessageCorrect(Replace::try_from(elem)?), // XEP-0359 ("stanza-id", ns::SID) => MessagePayload::StanzaId(StanzaId::try_from(elem)?), // XEP-0380 ("encryption", ns::EME) => MessagePayload::ExplicitMessageEncryption(ExplicitMessageEncryption::try_from(elem)?), _ => return Err(Error::ParseError("Unknown message payload.")) }) } } impl<'a> Into for &'a MessagePayload { fn into(self) -> Element { match *self { MessagePayload::StanzaError(ref stanza_error) => stanza_error.into(), MessagePayload::Attention(ref attention) => attention.into(), MessagePayload::ChatState(ref chatstate) => chatstate.into(), MessagePayload::Receipt(ref receipt) => receipt.into(), MessagePayload::Delay(ref delay) => delay.into(), MessagePayload::MessageCorrect(ref replace) => replace.into(), MessagePayload::ExplicitMessageEncryption(ref eme) => eme.into(), MessagePayload::StanzaId(ref stanza_id) => stanza_id.into(), } } } #[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()) } } type Lang = String; type Body = String; type Subject = String; type Thread = String; #[derive(Debug, Clone)] pub struct Message { pub from: Option, pub to: Option, pub id: Option, pub type_: MessageType, pub bodies: BTreeMap, pub subjects: BTreeMap, pub thread: Option, pub payloads: Vec, } impl<'a> TryFrom<&'a Element> for Message { type Error = Error; fn try_from(root: &'a 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 bodies = BTreeMap::new(); let mut subjects = BTreeMap::new(); let mut thread = None; let mut payloads = vec!(); for elem in root.children() { if elem.is("body", ns::JABBER_CLIENT) { for _ in elem.children() { return Err(Error::ParseError("Unknown child in body element.")); } let lang = elem.attr("xml:lang").unwrap_or("").to_owned(); if bodies.insert(lang, elem.text()).is_some() { return Err(Error::ParseError("Body element present twice for the same xml:lang.")); } } else if elem.is("subject", ns::JABBER_CLIENT) { for _ in elem.children() { return Err(Error::ParseError("Unknown child in subject element.")); } let lang = elem.attr("xml:lang").unwrap_or("").to_owned(); if subjects.insert(lang, elem.text()).is_some() { return Err(Error::ParseError("Subject element present twice for the same xml:lang.")); } } else if elem.is("thread", ns::JABBER_CLIENT) { if thread.is_some() { return Err(Error::ParseError("Thread element present twice.")); } for _ in elem.children() { return Err(Error::ParseError("Unknown child in thread element.")); } thread = Some(elem.text()); } else { payloads.push(elem.clone()) } } Ok(Message { from: from, to: to, id: id, type_: type_, bodies: bodies, subjects: subjects, thread: thread, payloads: payloads, }) } } impl<'a> Into for &'a Message { fn into(self) -> Element { let mut stanza = Element::builder("message") .ns(ns::JABBER_CLIENT) .attr("from", self.from.clone().and_then(|value| Some(String::from(value)))) .attr("to", self.to.clone().and_then(|value| Some(String::from(value)))) .attr("id", self.id.clone()) .attr("type", self.type_.clone()) .append(self.subjects.iter() .map(|(lang, subject)| { Element::builder("subject") .ns(ns::JABBER_CLIENT) .attr("xml:lang", match lang.as_ref() { "" => None, lang => Some(lang), }) .append(subject.clone()) .build() }) .collect::>()) .append(self.bodies.iter() .map(|(lang, body)| { Element::builder("body") .ns(ns::JABBER_CLIENT) .attr("xml:lang", match lang.as_ref() { "" => None, lang => Some(lang), }) .append(body.clone()) .build() }) .collect::>()) .build(); for child in self.payloads.clone() { stanza.append_child(child); } stanza } } #[cfg(test)] mod tests { use super::*; #[test] fn test_simple() { let elem: Element = "".parse().unwrap(); let message = Message::try_from(&elem).unwrap(); assert_eq!(message.from, None); assert_eq!(message.to, None); assert_eq!(message.id, None); assert_eq!(message.type_, MessageType::Normal); assert!(message.payloads.is_empty()); } #[test] fn test_serialise() { let elem: Element = "".parse().unwrap(); let message = Message { from: None, to: None, id: None, type_: MessageType::Normal, bodies: BTreeMap::new(), subjects: BTreeMap::new(), thread: None, payloads: vec!(), }; let elem2 = (&message).into(); assert_eq!(elem, elem2); } #[test] fn test_body() { let elem: Element = "Hello world!".parse().unwrap(); let message = Message::try_from(&elem).unwrap(); assert_eq!(message.bodies[""], "Hello world!"); let elem2 = (&message).into(); assert_eq!(elem, elem2); } #[test] fn test_serialise_body() { let elem: Element = "Hello world!".parse().unwrap(); let mut bodies = BTreeMap::new(); bodies.insert(String::from(""), String::from("Hello world!")); let message = Message { from: None, to: Some(Jid::from_str("coucou@example.org").unwrap()), id: None, type_: MessageType::Chat, bodies: bodies, subjects: BTreeMap::new(), thread: None, payloads: vec!(), }; let elem2 = (&message).into(); assert_eq!(elem, elem2); } #[test] fn test_subject() { let elem: Element = "Hello world!".parse().unwrap(); let message = Message::try_from(&elem).unwrap(); assert_eq!(message.subjects[""], "Hello world!"); let elem2 = (&message).into(); assert_eq!(elem, elem2); } #[test] fn test_attention() { let elem: Element = "".parse().unwrap(); let message = Message::try_from(&elem).unwrap(); let elem2 = (&message).into(); assert_eq!(elem, elem2); } }