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