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