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;
 90type Subject = String;
 91
 92#[derive(Debug, Clone)]
 93pub struct Message {
 94    pub from: Option<Jid>,
 95    pub to: Option<Jid>,
 96    pub id: Option<String>,
 97    pub type_: MessageType,
 98    pub bodies: BTreeMap<Lang, Body>,
 99    pub subjects: BTreeMap<Lang, Subject>,
100    pub payloads: Vec<MessagePayloadType>,
101}
102
103impl<'a> TryFrom<&'a Element> for Message {
104    type Error = Error;
105
106    fn try_from(root: &'a Element) -> Result<Message, Error> {
107        if !root.is("message", ns::JABBER_CLIENT) {
108            return Err(Error::ParseError("This is not a message element."));
109        }
110        let from = root.attr("from")
111            .and_then(|value| value.parse().ok());
112        let to = root.attr("to")
113            .and_then(|value| value.parse().ok());
114        let id = root.attr("id")
115            .and_then(|value| value.parse().ok());
116        let type_ = match root.attr("type") {
117            Some(type_) => type_.parse()?,
118            None => Default::default(),
119        };
120        let mut bodies = BTreeMap::new();
121        let mut subjects = BTreeMap::new();
122        let mut payloads = vec!();
123        for elem in root.children() {
124            if elem.is("body", ns::JABBER_CLIENT) {
125                for _ in elem.children() {
126                    return Err(Error::ParseError("Unknown child in body element."));
127                }
128                let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
129                if let Some(_) = bodies.insert(lang, elem.text()) {
130                    return Err(Error::ParseError("Body element present twice for the same xml:lang."));
131                }
132            } else if elem.is("subject", ns::JABBER_CLIENT) {
133                for _ in elem.children() {
134                    return Err(Error::ParseError("Unknown child in subject element."));
135                }
136                let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
137                if let Some(_) = subjects.insert(lang, elem.text()) {
138                    return Err(Error::ParseError("Subject element present twice for the same xml:lang."));
139                }
140            } else {
141                let payload = if let Ok(stanza_error) = StanzaError::try_from(elem) {
142                    Some(MessagePayload::StanzaError(stanza_error))
143                } else if let Ok(chatstate) = ChatState::try_from(elem) {
144                    Some(MessagePayload::ChatState(chatstate))
145                } else if let Ok(receipt) = Receipt::try_from(elem) {
146                    Some(MessagePayload::Receipt(receipt))
147                } else if let Ok(delay) = Delay::try_from(elem) {
148                    Some(MessagePayload::Delay(delay))
149                } else if let Ok(attention) = Attention::try_from(elem) {
150                    Some(MessagePayload::Attention(attention))
151                } else if let Ok(replace) = Replace::try_from(elem) {
152                    Some(MessagePayload::MessageCorrect(replace))
153                } else if let Ok(eme) = ExplicitMessageEncryption::try_from(elem) {
154                    Some(MessagePayload::ExplicitMessageEncryption(eme))
155                } else {
156                    None
157                };
158                payloads.push(match payload {
159                    Some(payload) => MessagePayloadType::Parsed(payload),
160                    None => MessagePayloadType::XML(elem.clone()),
161                });
162            }
163        }
164        Ok(Message {
165            from: from,
166            to: to,
167            id: id,
168            type_: type_,
169            bodies: BTreeMap::new(),
170            subjects: subjects,
171            payloads: payloads,
172        })
173    }
174}
175
176impl<'a> Into<Element> for &'a MessagePayload {
177    fn into(self) -> Element {
178        match *self {
179            MessagePayload::StanzaError(ref stanza_error) => stanza_error.into(),
180            MessagePayload::Attention(ref attention) => attention.into(),
181            MessagePayload::ChatState(ref chatstate) => chatstate.into(),
182            MessagePayload::Receipt(ref receipt) => receipt.into(),
183            MessagePayload::Delay(ref delay) => delay.into(),
184            MessagePayload::MessageCorrect(ref replace) => replace.into(),
185            MessagePayload::ExplicitMessageEncryption(ref eme) => eme.into(),
186        }
187    }
188}
189
190impl<'a> Into<Element> for &'a Message {
191    fn into(self) -> Element {
192        let mut stanza = Element::builder("message")
193                                 .ns(ns::JABBER_CLIENT)
194                                 .attr("from", self.from.clone().and_then(|value| Some(String::from(value))))
195                                 .attr("to", self.to.clone().and_then(|value| Some(String::from(value))))
196                                 .attr("id", self.id.clone())
197                                 .attr("type", self.type_.clone())
198                                 .append(self.subjects.iter()
199                                                    .map(|(lang, subject)| {
200                                                         Element::builder("subject")
201                                                                 .ns(ns::JABBER_CLIENT)
202                                                                 .attr("xml:lang", match lang.as_ref() {
203                                                                      "" => None,
204                                                                      lang => Some(lang),
205                                                                  })
206                                                                 .append(subject.clone())
207                                                                 .build() })
208                                                    .collect::<Vec<_>>())
209                                 .append(self.bodies.iter()
210                                                    .map(|(lang, body)| {
211                                                         Element::builder("body")
212                                                                 .ns(ns::JABBER_CLIENT)
213                                                                 .attr("xml:lang", match lang.as_ref() {
214                                                                      "" => None,
215                                                                      lang => Some(lang),
216                                                                  })
217                                                                 .append(body.clone())
218                                                                 .build() })
219                                                    .collect::<Vec<_>>())
220                                 .build();
221        for child in self.payloads.clone() {
222            let elem = match child {
223                MessagePayloadType::XML(elem) => elem,
224                MessagePayloadType::Parsed(payload) => (&payload).into(),
225            };
226            stanza.append_child(elem);
227        }
228        stanza
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn test_simple() {
238        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
239        let message = Message::try_from(&elem).unwrap();
240        assert_eq!(message.from, None);
241        assert_eq!(message.to, None);
242        assert_eq!(message.id, None);
243        assert_eq!(message.type_, MessageType::Normal);
244        assert!(message.payloads.is_empty());
245    }
246
247    #[test]
248    fn test_serialise() {
249        let elem: Element = "<message xmlns='jabber:client' type='normal'/>".parse().unwrap();
250        let message = Message {
251            from: None,
252            to: None,
253            id: None,
254            type_: MessageType::Normal,
255            bodies: BTreeMap::new(),
256            subjects: BTreeMap::new(),
257            payloads: vec!(),
258        };
259        let elem2 = (&message).into();
260        assert_eq!(elem, elem2);
261    }
262
263    #[test]
264    fn test_body() {
265        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
266        Message::try_from(&elem).unwrap();
267    }
268
269    #[test]
270    fn test_serialise_body() {
271        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
272        let mut bodies = BTreeMap::new();
273        bodies.insert(String::from(""), String::from("Hello world!"));
274        let message = Message {
275            from: None,
276            to: Some(Jid::from_str("coucou@example.org").unwrap()),
277            id: None,
278            type_: MessageType::Chat,
279            bodies: bodies,
280            subjects: BTreeMap::new(),
281            payloads: vec!(),
282        };
283        let elem2 = (&message).into();
284        assert_eq!(elem, elem2);
285    }
286
287    #[test]
288    fn test_subject() {
289        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
290        let message = Message::try_from(&elem).unwrap();
291        assert_eq!(message.subjects[""], "Hello world!");
292
293        let elem2 = (&message).into();
294        assert_eq!(elem, elem2);
295    }
296
297    #[test]
298    fn test_attention() {
299        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
300        let message = Message::try_from(&elem).unwrap();
301        let elem2 = (&message).into();
302        assert_eq!(elem, elem2);
303    }
304}