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