message.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2//
  3// This Source Code Form is subject to the terms of the Mozilla Public
  4// License, v. 2.0. If a copy of the MPL was not distributed with this
  5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6
  7use std::convert::TryFrom;
  8use std::str::FromStr;
  9use std::collections::BTreeMap;
 10
 11use minidom::{Element, IntoAttributeValue};
 12
 13use jid::Jid;
 14
 15use error::Error;
 16
 17use ns;
 18
 19use stanza_error::StanzaError;
 20use chatstates::ChatState;
 21use receipts::Receipt;
 22use delay::Delay;
 23use attention::Attention;
 24use message_correct::Replace;
 25use eme::ExplicitMessageEncryption;
 26
 27/// Lists every known payload of a `<message/>`.
 28#[derive(Debug, Clone)]
 29pub enum MessagePayload {
 30    StanzaError(StanzaError),
 31    ChatState(ChatState),
 32    Receipt(Receipt),
 33    Delay(Delay),
 34    Attention(Attention),
 35    MessageCorrect(Replace),
 36    ExplicitMessageEncryption(ExplicitMessageEncryption),
 37}
 38
 39#[derive(Debug, Clone, PartialEq)]
 40pub enum MessageType {
 41    Chat,
 42    Error,
 43    Groupchat,
 44    Headline,
 45    Normal,
 46}
 47
 48impl Default for MessageType {
 49    fn default() -> MessageType {
 50        MessageType::Normal
 51    }
 52}
 53
 54impl FromStr for MessageType {
 55    type Err = Error;
 56
 57    fn from_str(s: &str) -> Result<MessageType, Error> {
 58        Ok(match s {
 59            "chat" => MessageType::Chat,
 60            "error" => MessageType::Error,
 61            "groupchat" => MessageType::Groupchat,
 62            "headline" => MessageType::Headline,
 63            "normal" => MessageType::Normal,
 64
 65            _ => return Err(Error::ParseError("Invalid 'type' attribute on message element.")),
 66        })
 67    }
 68}
 69
 70impl IntoAttributeValue for MessageType {
 71    fn into_attribute_value(self) -> Option<String> {
 72        Some(match self {
 73            MessageType::Chat => "chat",
 74            MessageType::Error => "error",
 75            MessageType::Groupchat => "groupchat",
 76            MessageType::Headline => "headline",
 77            MessageType::Normal => "normal",
 78        }.to_owned())
 79    }
 80}
 81
 82#[derive(Debug, Clone)]
 83pub enum MessagePayloadType {
 84    XML(Element),
 85    Parsed(MessagePayload),
 86}
 87
 88type Lang = String;
 89type Body = String;
 90
 91#[derive(Debug, Clone)]
 92pub struct Message {
 93    pub from: Option<Jid>,
 94    pub to: Option<Jid>,
 95    pub id: Option<String>,
 96    pub type_: MessageType,
 97    pub bodies: BTreeMap<Lang, Body>,
 98    pub payloads: Vec<MessagePayloadType>,
 99}
100
101impl<'a> TryFrom<&'a Element> for Message {
102    type Error = Error;
103
104    fn try_from(root: &'a Element) -> Result<Message, Error> {
105        if !root.is("message", ns::JABBER_CLIENT) {
106            return Err(Error::ParseError("This is not a message element."));
107        }
108        let from = root.attr("from")
109            .and_then(|value| value.parse().ok());
110        let to = root.attr("to")
111            .and_then(|value| value.parse().ok());
112        let id = root.attr("id")
113            .and_then(|value| value.parse().ok());
114        let type_ = match root.attr("type") {
115            Some(type_) => type_.parse()?,
116            None => Default::default(),
117        };
118        let mut bodies = BTreeMap::new();
119        let mut payloads = vec!();
120        for elem in root.children() {
121            if elem.is("body", ns::JABBER_CLIENT) {
122                for _ in elem.children() {
123                    return Err(Error::ParseError("Unknown child in body element."));
124                }
125                let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
126                if let Some(_) = bodies.insert(lang, elem.text()) {
127                    return Err(Error::ParseError("Body element present twice for the same xml:lang."));
128                }
129            } else {
130                let payload = if let Ok(stanza_error) = StanzaError::try_from(elem) {
131                    Some(MessagePayload::StanzaError(stanza_error))
132                } else if let Ok(chatstate) = ChatState::try_from(elem) {
133                    Some(MessagePayload::ChatState(chatstate))
134                } else if let Ok(receipt) = Receipt::try_from(elem) {
135                    Some(MessagePayload::Receipt(receipt))
136                } else if let Ok(delay) = Delay::try_from(elem) {
137                    Some(MessagePayload::Delay(delay))
138                } else if let Ok(attention) = Attention::try_from(elem) {
139                    Some(MessagePayload::Attention(attention))
140                } else if let Ok(replace) = Replace::try_from(elem) {
141                    Some(MessagePayload::MessageCorrect(replace))
142                } else if let Ok(eme) = ExplicitMessageEncryption::try_from(elem) {
143                    Some(MessagePayload::ExplicitMessageEncryption(eme))
144                } else {
145                    None
146                };
147                payloads.push(match payload {
148                    Some(payload) => MessagePayloadType::Parsed(payload),
149                    None => MessagePayloadType::XML(elem.clone()),
150                });
151            }
152        }
153        Ok(Message {
154            from: from,
155            to: to,
156            id: id,
157            type_: type_,
158            bodies: BTreeMap::new(),
159            payloads: payloads,
160        })
161    }
162}
163
164impl<'a> Into<Element> for &'a MessagePayload {
165    fn into(self) -> Element {
166        match *self {
167            MessagePayload::StanzaError(ref stanza_error) => stanza_error.into(),
168            MessagePayload::Attention(ref attention) => attention.into(),
169            MessagePayload::ChatState(ref chatstate) => chatstate.into(),
170            MessagePayload::Receipt(ref receipt) => receipt.into(),
171            MessagePayload::Delay(ref delay) => delay.into(),
172            MessagePayload::MessageCorrect(ref replace) => replace.into(),
173            MessagePayload::ExplicitMessageEncryption(ref eme) => eme.into(),
174        }
175    }
176}
177
178impl<'a> Into<Element> for &'a Message {
179    fn into(self) -> Element {
180        let mut stanza = Element::builder("message")
181                                 .ns(ns::JABBER_CLIENT)
182                                 .attr("from", self.from.clone().and_then(|value| Some(String::from(value))))
183                                 .attr("to", self.to.clone().and_then(|value| Some(String::from(value))))
184                                 .attr("id", self.id.clone())
185                                 .attr("type", self.type_.clone())
186                                 .append(self.bodies.iter()
187                                                    .map(|(lang, body)| {
188                                                         Element::builder("body")
189                                                                 .ns(ns::JABBER_CLIENT)
190                                                                 .attr("xml:lang", match lang.as_ref() {
191                                                                      "" => None,
192                                                                      lang => Some(lang),
193                                                                  })
194                                                                 .append(body.clone())
195                                                                 .build() })
196                                                    .collect::<Vec<_>>())
197                                 .build();
198        for child in self.payloads.clone() {
199            let elem = match child {
200                MessagePayloadType::XML(elem) => elem,
201                MessagePayloadType::Parsed(payload) => (&payload).into(),
202            };
203            stanza.append_child(elem);
204        }
205        stanza
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn test_simple() {
215        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
216        let message = Message::try_from(&elem).unwrap();
217        assert_eq!(message.from, None);
218        assert_eq!(message.to, None);
219        assert_eq!(message.id, None);
220        assert_eq!(message.type_, MessageType::Normal);
221        assert!(message.payloads.is_empty());
222    }
223
224    #[test]
225    fn test_serialise() {
226        let elem: Element = "<message xmlns='jabber:client' type='normal'/>".parse().unwrap();
227        let message = Message {
228            from: None,
229            to: None,
230            id: None,
231            type_: MessageType::Normal,
232            bodies: BTreeMap::new(),
233            payloads: vec!(),
234        };
235        let elem2 = (&message).into();
236        assert_eq!(elem, elem2);
237    }
238
239    #[test]
240    fn test_body() {
241        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
242        Message::try_from(&elem).unwrap();
243    }
244
245    #[test]
246    fn test_serialise_body() {
247        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
248        let mut bodies = BTreeMap::new();
249        bodies.insert(String::from(""), String::from("Hello world!"));
250        let message = Message {
251            from: None,
252            to: Some(Jid::from_str("coucou@example.org").unwrap()),
253            id: None,
254            type_: MessageType::Chat,
255            bodies: bodies,
256            payloads: vec!(),
257        };
258        let elem2 = (&message).into();
259        assert_eq!(elem, elem2);
260    }
261
262    #[test]
263    fn test_attention() {
264        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
265        let message = Message::try_from(&elem).unwrap();
266        let elem2 = (&message).into();
267        assert_eq!(elem, elem2);
268    }
269}