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;
 26use stanza_id::StanzaId;
 27use mam::Result_ as MamResult;
 28
 29/// Lists every known payload of a `<message/>`.
 30#[derive(Debug, Clone)]
 31pub enum MessagePayload {
 32    StanzaError(StanzaError),
 33    ChatState(ChatState),
 34    Receipt(Receipt),
 35    Delay(Delay),
 36    Attention(Attention),
 37    MessageCorrect(Replace),
 38    ExplicitMessageEncryption(ExplicitMessageEncryption),
 39    StanzaId(StanzaId),
 40    MamResult(MamResult),
 41
 42    Unknown(Element),
 43}
 44
 45impl<'a> TryFrom<&'a Element> for MessagePayload {
 46    type Error = Error;
 47
 48    fn try_from(elem: &'a Element) -> Result<MessagePayload, Error> {
 49        Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
 50            ("error", ns::JABBER_CLIENT) => MessagePayload::StanzaError(StanzaError::try_from(elem)?),
 51
 52            // XEP-0085
 53            ("active", ns::CHATSTATES)
 54          | ("inactive", ns::CHATSTATES)
 55          | ("composing", ns::CHATSTATES)
 56          | ("paused", ns::CHATSTATES)
 57          | ("gone", ns::CHATSTATES) => MessagePayload::ChatState(ChatState::try_from(elem)?),
 58
 59            // XEP-0184
 60            ("request", ns::RECEIPTS)
 61          | ("received", ns::RECEIPTS) => MessagePayload::Receipt(Receipt::try_from(elem)?),
 62
 63            // XEP-0203
 64            ("delay", ns::DELAY) => MessagePayload::Delay(Delay::try_from(elem)?),
 65
 66            // XEP-0224
 67            ("attention", ns::ATTENTION) => MessagePayload::Attention(Attention::try_from(elem)?),
 68
 69            // XEP-0308
 70            ("replace", ns::MESSAGE_CORRECT) => MessagePayload::MessageCorrect(Replace::try_from(elem)?),
 71
 72            // XEP-0313
 73            ("result", ns::MAM) => MessagePayload::MamResult(MamResult::try_from(elem)?),
 74
 75            // XEP-0359
 76            ("stanza-id", ns::SID) => MessagePayload::StanzaId(StanzaId::try_from(elem)?),
 77
 78            // XEP-0380
 79            ("encryption", ns::EME) => MessagePayload::ExplicitMessageEncryption(ExplicitMessageEncryption::try_from(elem)?),
 80
 81            _ => MessagePayload::Unknown(elem.clone()),
 82        })
 83    }
 84}
 85
 86impl<'a> Into<Element> for &'a MessagePayload {
 87    fn into(self) -> Element {
 88        match *self {
 89            MessagePayload::StanzaError(ref stanza_error) => stanza_error.into(),
 90            MessagePayload::Attention(ref attention) => attention.into(),
 91            MessagePayload::ChatState(ref chatstate) => chatstate.into(),
 92            MessagePayload::Receipt(ref receipt) => receipt.into(),
 93            MessagePayload::Delay(ref delay) => delay.into(),
 94            MessagePayload::MessageCorrect(ref replace) => replace.into(),
 95            MessagePayload::ExplicitMessageEncryption(ref eme) => eme.into(),
 96            MessagePayload::StanzaId(ref stanza_id) => stanza_id.into(),
 97            MessagePayload::MamResult(ref result) => result.into(),
 98
 99            MessagePayload::Unknown(ref elem) => elem.clone(),
100        }
101    }
102}
103
104#[derive(Debug, Clone, PartialEq)]
105pub enum MessageType {
106    Chat,
107    Error,
108    Groupchat,
109    Headline,
110    Normal,
111}
112
113impl Default for MessageType {
114    fn default() -> MessageType {
115        MessageType::Normal
116    }
117}
118
119impl FromStr for MessageType {
120    type Err = Error;
121
122    fn from_str(s: &str) -> Result<MessageType, Error> {
123        Ok(match s {
124            "chat" => MessageType::Chat,
125            "error" => MessageType::Error,
126            "groupchat" => MessageType::Groupchat,
127            "headline" => MessageType::Headline,
128            "normal" => MessageType::Normal,
129
130            _ => return Err(Error::ParseError("Invalid 'type' attribute on message element.")),
131        })
132    }
133}
134
135impl IntoAttributeValue for MessageType {
136    fn into_attribute_value(self) -> Option<String> {
137        Some(match self {
138            MessageType::Chat => "chat",
139            MessageType::Error => "error",
140            MessageType::Groupchat => "groupchat",
141            MessageType::Headline => "headline",
142            MessageType::Normal => "normal",
143        }.to_owned())
144    }
145}
146
147type Lang = String;
148type Body = String;
149type Subject = String;
150type Thread = String;
151
152#[derive(Debug, Clone)]
153pub struct Message {
154    pub from: Option<Jid>,
155    pub to: Option<Jid>,
156    pub id: Option<String>,
157    pub type_: MessageType,
158    pub bodies: BTreeMap<Lang, Body>,
159    pub subjects: BTreeMap<Lang, Subject>,
160    pub thread: Option<Thread>,
161    pub payloads: Vec<Element>,
162}
163
164impl<'a> TryFrom<&'a Element> for Message {
165    type Error = Error;
166
167    fn try_from(root: &'a Element) -> Result<Message, Error> {
168        if !root.is("message", ns::JABBER_CLIENT) {
169            return Err(Error::ParseError("This is not a message element."));
170        }
171        let from = root.attr("from")
172            .and_then(|value| value.parse().ok());
173        let to = root.attr("to")
174            .and_then(|value| value.parse().ok());
175        let id = root.attr("id")
176            .and_then(|value| value.parse().ok());
177        let type_ = match root.attr("type") {
178            Some(type_) => type_.parse()?,
179            None => Default::default(),
180        };
181        let mut bodies = BTreeMap::new();
182        let mut subjects = BTreeMap::new();
183        let mut thread = None;
184        let mut payloads = vec!();
185        for elem in root.children() {
186            if elem.is("body", ns::JABBER_CLIENT) {
187                for _ in elem.children() {
188                    return Err(Error::ParseError("Unknown child in body element."));
189                }
190                let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
191                if bodies.insert(lang, elem.text()).is_some() {
192                    return Err(Error::ParseError("Body element present twice for the same xml:lang."));
193                }
194            } else if elem.is("subject", ns::JABBER_CLIENT) {
195                for _ in elem.children() {
196                    return Err(Error::ParseError("Unknown child in subject element."));
197                }
198                let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
199                if subjects.insert(lang, elem.text()).is_some() {
200                    return Err(Error::ParseError("Subject element present twice for the same xml:lang."));
201                }
202            } else if elem.is("thread", ns::JABBER_CLIENT) {
203                if thread.is_some() {
204                    return Err(Error::ParseError("Thread element present twice."));
205                }
206                for _ in elem.children() {
207                    return Err(Error::ParseError("Unknown child in thread element."));
208                }
209                thread = Some(elem.text());
210            } else {
211                payloads.push(elem.clone())
212            }
213        }
214        Ok(Message {
215            from: from,
216            to: to,
217            id: id,
218            type_: type_,
219            bodies: bodies,
220            subjects: subjects,
221            thread: thread,
222            payloads: payloads,
223        })
224    }
225}
226
227impl<'a> Into<Element> for &'a Message {
228    fn into(self) -> Element {
229        let mut stanza = Element::builder("message")
230                                 .ns(ns::JABBER_CLIENT)
231                                 .attr("from", self.from.clone().and_then(|value| Some(String::from(value))))
232                                 .attr("to", self.to.clone().and_then(|value| Some(String::from(value))))
233                                 .attr("id", self.id.clone())
234                                 .attr("type", self.type_.clone())
235                                 .append(self.subjects.iter()
236                                                    .map(|(lang, subject)| {
237                                                         Element::builder("subject")
238                                                                 .ns(ns::JABBER_CLIENT)
239                                                                 .attr("xml:lang", match lang.as_ref() {
240                                                                      "" => None,
241                                                                      lang => Some(lang),
242                                                                  })
243                                                                 .append(subject.clone())
244                                                                 .build() })
245                                                    .collect::<Vec<_>>())
246                                 .append(self.bodies.iter()
247                                                    .map(|(lang, body)| {
248                                                         Element::builder("body")
249                                                                 .ns(ns::JABBER_CLIENT)
250                                                                 .attr("xml:lang", match lang.as_ref() {
251                                                                      "" => None,
252                                                                      lang => Some(lang),
253                                                                  })
254                                                                 .append(body.clone())
255                                                                 .build() })
256                                                    .collect::<Vec<_>>())
257                                 .build();
258        for child in self.payloads.clone() {
259            stanza.append_child(child);
260        }
261        stanza
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_simple() {
271        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
272        let message = Message::try_from(&elem).unwrap();
273        assert_eq!(message.from, None);
274        assert_eq!(message.to, None);
275        assert_eq!(message.id, None);
276        assert_eq!(message.type_, MessageType::Normal);
277        assert!(message.payloads.is_empty());
278    }
279
280    #[test]
281    fn test_serialise() {
282        let elem: Element = "<message xmlns='jabber:client' type='normal'/>".parse().unwrap();
283        let message = Message {
284            from: None,
285            to: None,
286            id: None,
287            type_: MessageType::Normal,
288            bodies: BTreeMap::new(),
289            subjects: BTreeMap::new(),
290            thread: None,
291            payloads: vec!(),
292        };
293        let elem2 = (&message).into();
294        assert_eq!(elem, elem2);
295    }
296
297    #[test]
298    fn test_body() {
299        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
300        let message = Message::try_from(&elem).unwrap();
301        assert_eq!(message.bodies[""], "Hello world!");
302
303        let elem2 = (&message).into();
304        assert_eq!(elem, elem2);
305    }
306
307    #[test]
308    fn test_serialise_body() {
309        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
310        let mut bodies = BTreeMap::new();
311        bodies.insert(String::from(""), String::from("Hello world!"));
312        let message = Message {
313            from: None,
314            to: Some(Jid::from_str("coucou@example.org").unwrap()),
315            id: None,
316            type_: MessageType::Chat,
317            bodies: bodies,
318            subjects: BTreeMap::new(),
319            thread: None,
320            payloads: vec!(),
321        };
322        let elem2 = (&message).into();
323        assert_eq!(elem, elem2);
324    }
325
326    #[test]
327    fn test_subject() {
328        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
329        let message = Message::try_from(&elem).unwrap();
330        assert_eq!(message.subjects[""], "Hello world!");
331
332        let elem2 = (&message).into();
333        assert_eq!(elem, elem2);
334    }
335
336    #[test]
337    fn test_attention() {
338        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
339        let message = Message::try_from(&elem).unwrap();
340        let elem2 = (&message).into();
341        assert_eq!(elem, elem2);
342    }
343}