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 crate::util::error::Error;
  8use crate::ns;
  9use jid::Jid;
 10use minidom::Element;
 11use std::collections::BTreeMap;
 12use std::convert::TryFrom;
 13
 14/// Should be implemented on every known payload of a `<message/>`.
 15pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
 16
 17generate_attribute!(
 18    /// The type of a message.
 19    MessageType, "type", {
 20        /// Standard instant messaging message.
 21        Chat => "chat",
 22
 23        /// Notifies that an error happened.
 24        Error => "error",
 25
 26        /// Standard group instant messaging message.
 27        Groupchat => "groupchat",
 28
 29        /// Used by servers to notify users when things happen.
 30        Headline => "headline",
 31
 32        /// This is an email-like message, it usually contains a
 33        /// [subject](struct.Subject.html).
 34        Normal => "normal",
 35    }, Default = Normal
 36);
 37
 38type Lang = String;
 39
 40generate_elem_id!(
 41    /// Represents one `<body/>` element, that is the free form text content of
 42    /// a message.
 43    Body,
 44    "body",
 45    DEFAULT_NS
 46);
 47
 48generate_elem_id!(
 49    /// Defines the subject of a room, or of an email-like normal message.
 50    Subject,
 51    "subject",
 52    DEFAULT_NS
 53);
 54
 55generate_elem_id!(
 56    /// A thread identifier, so that other people can specify to which message
 57    /// they are replying.
 58    Thread,
 59    "thread",
 60    DEFAULT_NS
 61);
 62
 63/// The main structure representing the `<message/>` stanza.
 64#[derive(Debug, Clone)]
 65pub struct Message {
 66    /// The JID emitting this stanza.
 67    pub from: Option<Jid>,
 68
 69    /// The recipient of this stanza.
 70    pub to: Option<Jid>,
 71
 72    /// The @id attribute of this stanza, which is required in order to match a
 73    /// request with its response.
 74    pub id: Option<String>,
 75
 76    /// The type of this message.
 77    pub type_: MessageType,
 78
 79    /// A list of bodies, sorted per language.  Use
 80    /// [get_best_body()](#method.get_best_body) to access them on reception.
 81    pub bodies: BTreeMap<Lang, Body>,
 82
 83    /// A list of subjects, sorted per language.  Use
 84    /// [get_best_subject()](#method.get_best_subject) to access them on
 85    /// reception.
 86    pub subjects: BTreeMap<Lang, Subject>,
 87
 88    /// An optional thread identifier, so that other people can reply directly
 89    /// to this message.
 90    pub thread: Option<Thread>,
 91
 92    /// A list of the extension payloads contained in this stanza.
 93    pub payloads: Vec<Element>,
 94}
 95
 96impl Message {
 97    /// Creates a new `<message/>` stanza for the given recipient.
 98    pub fn new(to: Option<Jid>) -> Message {
 99        Message {
100            from: None,
101            to,
102            id: None,
103            type_: MessageType::Chat,
104            bodies: BTreeMap::new(),
105            subjects: BTreeMap::new(),
106            thread: None,
107            payloads: vec![],
108        }
109    }
110
111    fn get_best<'a, T>(
112        map: &'a BTreeMap<Lang, T>,
113        preferred_langs: Vec<&str>,
114    ) -> Option<(Lang, &'a T)> {
115        if map.is_empty() {
116            return None;
117        }
118        for lang in preferred_langs {
119            if let Some(value) = map.get(lang) {
120                return Some((Lang::from(lang), value));
121            }
122        }
123        if let Some(value) = map.get("") {
124            return Some((Lang::new(), value));
125        }
126        map.iter().map(|(lang, value)| (lang.clone(), value)).next()
127    }
128
129    /// Returns the best matching body from a list of languages.
130    ///
131    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
132    /// body without an xml:lang attribute, and you pass ["fr", "en"] as your preferred languages,
133    /// `Some(("fr", the_second_body))` will be returned.
134    ///
135    /// If no body matches, an undefined body will be returned.
136    pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Body)> {
137        Message::get_best::<Body>(&self.bodies, preferred_langs)
138    }
139
140    /// Returns the best matching subject from a list of languages.
141    ///
142    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
143    /// subject without an xml:lang attribute, and you pass ["fr", "en"] as your preferred
144    /// languages, `Some(("fr", the_second_subject))` will be returned.
145    ///
146    /// If no subject matches, an undefined subject will be returned.
147    pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Subject)> {
148        Message::get_best::<Subject>(&self.subjects, preferred_langs)
149    }
150}
151
152impl TryFrom<Element> for Message {
153    type Error = Error;
154
155    fn try_from(root: Element) -> Result<Message, Error> {
156        check_self!(root, "message", DEFAULT_NS);
157        let from = get_attr!(root, "from", Option);
158        let to = get_attr!(root, "to", Option);
159        let id = get_attr!(root, "id", Option);
160        let type_ = get_attr!(root, "type", Default);
161        let mut bodies = BTreeMap::new();
162        let mut subjects = BTreeMap::new();
163        let mut thread = None;
164        let mut payloads = vec![];
165        for elem in root.children() {
166            if elem.is("body", ns::DEFAULT_NS) {
167                check_no_children!(elem, "body");
168                let lang = get_attr!(elem, "xml:lang", Default);
169                let body = Body(elem.text());
170                if bodies.insert(lang, body).is_some() {
171                    return Err(Error::ParseError(
172                        "Body element present twice for the same xml:lang.",
173                    ));
174                }
175            } else if elem.is("subject", ns::DEFAULT_NS) {
176                check_no_children!(elem, "subject");
177                let lang = get_attr!(elem, "xml:lang", Default);
178                let subject = Subject(elem.text());
179                if subjects.insert(lang, subject).is_some() {
180                    return Err(Error::ParseError(
181                        "Subject element present twice for the same xml:lang.",
182                    ));
183                }
184            } else if elem.is("thread", ns::DEFAULT_NS) {
185                if thread.is_some() {
186                    return Err(Error::ParseError("Thread element present twice."));
187                }
188                check_no_children!(elem, "thread");
189                thread = Some(Thread(elem.text()));
190            } else {
191                payloads.push(elem.clone())
192            }
193        }
194        Ok(Message {
195            from,
196            to,
197            id,
198            type_,
199            bodies,
200            subjects,
201            thread,
202            payloads,
203        })
204    }
205}
206
207impl From<Message> for Element {
208    fn from(message: Message) -> Element {
209        Element::builder("message")
210            .ns(ns::DEFAULT_NS)
211            .attr("from", message.from)
212            .attr("to", message.to)
213            .attr("id", message.id)
214            .attr("type", message.type_)
215            .append(
216                message
217                    .subjects
218                    .into_iter()
219                    .map(|(lang, subject)| {
220                        let mut subject = Element::from(subject);
221                        subject.set_attr(
222                            "xml:lang",
223                            match lang.as_ref() {
224                                "" => None,
225                                lang => Some(lang),
226                            },
227                        );
228                        subject
229                    })
230                    .collect::<Vec<_>>(),
231            )
232            .append(
233                message
234                    .bodies
235                    .into_iter()
236                    .map(|(lang, body)| {
237                        let mut body = Element::from(body);
238                        body.set_attr(
239                            "xml:lang",
240                            match lang.as_ref() {
241                                "" => None,
242                                lang => Some(lang),
243                            },
244                        );
245                        body
246                    })
247                    .collect::<Vec<_>>(),
248            )
249            .append(message.payloads)
250            .build()
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use crate::util::compare_elements::NamespaceAwareCompare;
258    use std::str::FromStr;
259
260    #[cfg(target_pointer_width = "32")]
261    #[test]
262    fn test_size() {
263        assert_size!(MessageType, 1);
264        assert_size!(Body, 12);
265        assert_size!(Subject, 12);
266        assert_size!(Thread, 12);
267        assert_size!(Message, 136);
268    }
269
270    #[cfg(target_pointer_width = "64")]
271    #[test]
272    fn test_size() {
273        assert_size!(MessageType, 1);
274        assert_size!(Body, 24);
275        assert_size!(Subject, 24);
276        assert_size!(Thread, 24);
277        assert_size!(Message, 272);
278    }
279
280    #[test]
281    fn test_simple() {
282        #[cfg(not(feature = "component"))]
283        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
284        #[cfg(feature = "component")]
285        let elem: Element = "<message xmlns='jabber:component:accept'/>"
286            .parse()
287            .unwrap();
288        let message = Message::try_from(elem).unwrap();
289        assert_eq!(message.from, None);
290        assert_eq!(message.to, None);
291        assert_eq!(message.id, None);
292        assert_eq!(message.type_, MessageType::Normal);
293        assert!(message.payloads.is_empty());
294    }
295
296    #[test]
297    fn test_serialise() {
298        #[cfg(not(feature = "component"))]
299        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
300        #[cfg(feature = "component")]
301        let elem: Element = "<message xmlns='jabber:component:accept'/>"
302            .parse()
303            .unwrap();
304        let mut message = Message::new(None);
305        message.type_ = MessageType::Normal;
306        let elem2 = message.into();
307        assert_eq!(elem, elem2);
308    }
309
310    #[test]
311    fn test_body() {
312        #[cfg(not(feature = "component"))]
313        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
314        #[cfg(feature = "component")]
315        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
316        let elem1 = elem.clone();
317        let message = Message::try_from(elem).unwrap();
318        assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap());
319
320        {
321            let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
322            assert_eq!(lang, "");
323            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
324        }
325
326        let elem2 = message.into();
327        assert!(elem1.compare_to(&elem2));
328    }
329
330    #[test]
331    fn test_serialise_body() {
332        #[cfg(not(feature = "component"))]
333        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
334        #[cfg(feature = "component")]
335        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
336        let mut message = Message::new(Some(Jid::from_str("coucou@example.org").unwrap()));
337        message
338            .bodies
339            .insert(String::from(""), Body::from_str("Hello world!").unwrap());
340        let elem2 = message.into();
341        assert!(elem.compare_to(&elem2));
342    }
343
344    #[test]
345    fn test_subject() {
346        #[cfg(not(feature = "component"))]
347        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
348        #[cfg(feature = "component")]
349        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
350        let elem1 = elem.clone();
351        let message = Message::try_from(elem).unwrap();
352        assert_eq!(
353            message.subjects[""],
354            Subject::from_str("Hello world!").unwrap()
355        );
356
357        {
358            let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
359            assert_eq!(lang, "");
360            assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
361        }
362
363        let elem2 = message.into();
364        assert!(elem1.compare_to(&elem2));
365    }
366
367    #[test]
368    fn get_best_body() {
369        #[cfg(not(feature = "component"))]
370        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body xml:lang='de'>Hallo Welt!</body><body xml:lang='fr'>Salut le monde !</body><body>Hello world!</body></message>".parse().unwrap();
371        #[cfg(feature = "component")]
372        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
373        let message = Message::try_from(elem).unwrap();
374
375        // Tests basic feature.
376        {
377            let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
378            assert_eq!(lang, "fr");
379            assert_eq!(body, &Body::from_str("Salut le monde !").unwrap());
380        }
381
382        // Tests order.
383        {
384            let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
385            assert_eq!(lang, "de");
386            assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
387        }
388
389        // Tests fallback.
390        {
391            let (lang, body) = message.get_best_body(vec![]).unwrap();
392            assert_eq!(lang, "");
393            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
394        }
395
396        // Tests fallback.
397        {
398            let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
399            assert_eq!(lang, "");
400            assert_eq!(body, &Body::from_str("Hello world!").unwrap());
401        }
402
403        let message = Message::new(None);
404
405        // Tests without a body.
406        assert_eq!(message.get_best_body(vec!("ja")), None);
407    }
408
409    #[test]
410    fn test_attention() {
411        #[cfg(not(feature = "component"))]
412        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
413        #[cfg(feature = "component")]
414        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
415        let elem1 = elem.clone();
416        let message = Message::try_from(elem).unwrap();
417        let elem2 = message.into();
418        assert_eq!(elem1, elem2);
419    }
420}