xmpp_parsers/
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 core::fmt;
8use core::ops::{Deref, DerefMut};
9use std::borrow::{Borrow, Cow};
10
11use crate::ns;
12use alloc::collections::BTreeMap;
13use jid::Jid;
14use minidom::Element;
15use xso::{
16    error::{Error, FromElementError},
17    AsOptionalXmlText, AsXml, FromXml, FromXmlText,
18};
19
20/// Should be implemented on every known payload of a `<message/>`.
21pub trait MessagePayload: TryFrom<Element> + Into<Element> {}
22
23generate_attribute!(
24    /// The type of a message.
25    MessageType, "type", {
26        /// Standard instant messaging message.
27        Chat => "chat",
28
29        /// Notifies that an error happened.
30        Error => "error",
31
32        /// Standard group instant messaging message.
33        Groupchat => "groupchat",
34
35        /// Used by servers to notify users when things happen.
36        Headline => "headline",
37
38        /// This is an email-like message, it usually contains a
39        /// [subject](struct.Subject.html).
40        Normal => "normal",
41    }, Default = Normal
42);
43
44generate_id!(
45    /// Id field in a [`Message`], if any.
46    ///
47    /// This field is not mandatory on incoming messages, but may be useful for moderation/correction,
48    /// especially for outgoing messages.
49    Id
50);
51
52/// Wrapper type to represent an optional `xml:lang` attribute.
53///
54/// This is necessary because we do not want to emit empty `xml:lang`
55/// attributes.
56#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
57pub struct Lang(pub String);
58
59impl Deref for Lang {
60    type Target = String;
61
62    fn deref(&self) -> &Self::Target {
63        &self.0
64    }
65}
66
67impl DerefMut for Lang {
68    fn deref_mut(&mut self) -> &mut Self::Target {
69        &mut self.0
70    }
71}
72
73impl fmt::Display for Lang {
74    fn fmt<'f>(&self, f: &'f mut fmt::Formatter) -> fmt::Result {
75        fmt::Display::fmt(&self.0, f)
76    }
77}
78
79impl FromXmlText for Lang {
80    fn from_xml_text(s: String) -> Result<Self, Error> {
81        Ok(Self(s))
82    }
83}
84
85impl AsOptionalXmlText for Lang {
86    fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, Error> {
87        if self.0.is_empty() {
88            Ok(None)
89        } else {
90            Ok(Some(Cow::Borrowed(&self.0)))
91        }
92    }
93}
94
95impl Borrow<str> for Lang {
96    fn borrow(&self) -> &str {
97        &self.0
98    }
99}
100
101impl From<String> for Lang {
102    fn from(other: String) -> Self {
103        Self(other)
104    }
105}
106
107impl From<&str> for Lang {
108    fn from(other: &str) -> Self {
109        Self(other.to_owned())
110    }
111}
112
113impl PartialEq<str> for Lang {
114    fn eq(&self, rhs: &str) -> bool {
115        self.0 == rhs
116    }
117}
118
119impl PartialEq<&str> for Lang {
120    fn eq(&self, rhs: &&str) -> bool {
121        self.0 == *rhs
122    }
123}
124
125impl Lang {
126    /// Create a new, empty `Lang`.
127    pub fn new() -> Self {
128        Self(String::new())
129    }
130}
131
132/// Threading meta-information.
133#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
134#[xml(namespace = ns::DEFAULT_NS, name = "thread")]
135pub struct Thread {
136    /// The parent of the thread, when creating a subthread.
137    #[xml(attribute(default))]
138    pub parent: Option<String>,
139
140    /// A thread identifier, so that other people can specify to which message
141    /// they are replying.
142    #[xml(text)]
143    pub id: String,
144}
145
146/// The main structure representing the `<message/>` stanza.
147#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
148#[xml(namespace = ns::DEFAULT_NS, name = "message")]
149pub struct Message {
150    /// The JID emitting this stanza.
151    #[xml(attribute(default))]
152    pub from: Option<Jid>,
153
154    /// The recipient of this stanza.
155    #[xml(attribute(default))]
156    pub to: Option<Jid>,
157
158    /// The @id attribute of this stanza, which is required in order to match a
159    /// request with its response.
160    #[xml(attribute(default))]
161    pub id: Option<Id>,
162
163    /// The type of this message.
164    #[xml(attribute(name = "type", default))]
165    pub type_: MessageType,
166
167    /// A list of bodies, sorted per language.  Use
168    /// [get_best_body()](#method.get_best_body) to access them on reception.
169    #[xml(extract(n = .., name = "body", fields(
170        attribute(name = "xml:lang", type_ = Lang, default),
171        text(type_ = String),
172    )))]
173    pub bodies: BTreeMap<Lang, String>,
174
175    /// A list of subjects, sorted per language.  Use
176    /// [get_best_subject()](#method.get_best_subject) to access them on
177    /// reception.
178    #[xml(extract(n = .., name = "subject", fields(
179        attribute(name = "xml:lang", type_ = Lang, default),
180        text(type_ = String),
181    )))]
182    pub subjects: BTreeMap<Lang, String>,
183
184    /// An optional thread identifier, so that other people can reply directly
185    /// to this message.
186    #[xml(child(default))]
187    pub thread: Option<Thread>,
188
189    /// A list of the extension payloads contained in this stanza.
190    #[xml(element(n = ..))]
191    pub payloads: Vec<Element>,
192}
193
194impl Message {
195    /// Creates a new `<message/>` stanza of type Chat for the given recipient.
196    /// This is equivalent to the [`Message::chat`] method.
197    pub fn new<J: Into<Option<Jid>>>(to: J) -> Message {
198        Message {
199            from: None,
200            to: to.into(),
201            id: None,
202            type_: MessageType::Chat,
203            bodies: BTreeMap::new(),
204            subjects: BTreeMap::new(),
205            thread: None,
206            payloads: vec![],
207        }
208    }
209
210    /// Creates a new `<message/>` stanza of a certain type for the given recipient.
211    pub fn new_with_type<J: Into<Option<Jid>>>(type_: MessageType, to: J) -> Message {
212        Message {
213            from: None,
214            to: to.into(),
215            id: None,
216            type_,
217            bodies: BTreeMap::new(),
218            subjects: BTreeMap::new(),
219            thread: None,
220            payloads: vec![],
221        }
222    }
223
224    /// Creates a Message of type Chat
225    pub fn chat<J: Into<Option<Jid>>>(to: J) -> Message {
226        Self::new_with_type(MessageType::Chat, to)
227    }
228
229    /// Creates a Message of type Error
230    pub fn error<J: Into<Option<Jid>>>(to: J) -> Message {
231        Self::new_with_type(MessageType::Error, to)
232    }
233
234    /// Creates a Message of type Groupchat
235    pub fn groupchat<J: Into<Option<Jid>>>(to: J) -> Message {
236        Self::new_with_type(MessageType::Groupchat, to)
237    }
238
239    /// Creates a Message of type Headline
240    pub fn headline<J: Into<Option<Jid>>>(to: J) -> Message {
241        Self::new_with_type(MessageType::Headline, to)
242    }
243
244    /// Creates a Message of type Normal
245    pub fn normal<J: Into<Option<Jid>>>(to: J) -> Message {
246        Self::new_with_type(MessageType::Normal, to)
247    }
248
249    /// Appends a body in given lang to the Message
250    pub fn with_body(mut self, lang: Lang, body: String) -> Message {
251        self.bodies.insert(lang, body);
252        self
253    }
254
255    /// Set a payload inside this message.
256    pub fn with_payload<P: MessagePayload>(mut self, payload: P) -> Message {
257        self.payloads.push(payload.into());
258        self
259    }
260
261    /// Set the payloads of this message.
262    pub fn with_payloads(mut self, payloads: Vec<Element>) -> Message {
263        self.payloads = payloads;
264        self
265    }
266
267    fn get_best<'a, T>(
268        map: &'a BTreeMap<Lang, T>,
269        preferred_langs: Vec<&str>,
270    ) -> Option<(Lang, &'a T)> {
271        if map.is_empty() {
272            return None;
273        }
274        for lang in preferred_langs {
275            if let Some(value) = map.get(lang) {
276                return Some((Lang::from(lang), value));
277            }
278        }
279        if let Some(value) = map.get("") {
280            return Some((Lang::new(), value));
281        }
282        map.iter().map(|(lang, value)| (lang.clone(), value)).next()
283    }
284
285    fn get_best_cloned<T: ToOwned<Owned = T>>(
286        map: &BTreeMap<Lang, T>,
287        preferred_langs: Vec<&str>,
288    ) -> Option<(Lang, T)> {
289        if let Some((lang, item)) = Self::get_best::<T>(map, preferred_langs) {
290            Some((lang, item.to_owned()))
291        } else {
292            None
293        }
294    }
295
296    /// Returns the best matching body from a list of languages.
297    ///
298    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
299    /// body without an xml:lang attribute, and you pass ["fr", "en"] as your preferred languages,
300    /// `Some(("fr", the_second_body))` will be returned.
301    ///
302    /// If no body matches, an undefined body will be returned.
303    pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &String)> {
304        Message::get_best::<String>(&self.bodies, preferred_langs)
305    }
306
307    /// Cloned variant of [`Message::get_best_body`]
308    pub fn get_best_body_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, String)> {
309        Message::get_best_cloned::<String>(&self.bodies, preferred_langs)
310    }
311
312    /// Returns the best matching subject from a list of languages.
313    ///
314    /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
315    /// subject without an xml:lang attribute, and you pass ["fr", "en"] as your preferred
316    /// languages, `Some(("fr", the_second_subject))` will be returned.
317    ///
318    /// If no subject matches, an undefined subject will be returned.
319    pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &String)> {
320        Message::get_best::<String>(&self.subjects, preferred_langs)
321    }
322
323    /// Cloned variant of [`Message::get_best_subject`]
324    pub fn get_best_subject_cloned(&self, preferred_langs: Vec<&str>) -> Option<(Lang, String)> {
325        Message::get_best_cloned::<String>(&self.subjects, preferred_langs)
326    }
327
328    /// Try to extract the given payload type from the message's payloads.
329    ///
330    /// Returns the first matching payload element as parsed struct or its
331    /// parse error. If no element matches, `Ok(None)` is returned. If an
332    /// element matches, but fails to parse, it is nonetheless removed from
333    /// the message.
334    ///
335    /// Elements which do not match the given type are not removed.
336    pub fn extract_payload<T: TryFrom<Element, Error = FromElementError>>(
337        &mut self,
338    ) -> Result<Option<T>, Error> {
339        let mut buf = Vec::with_capacity(self.payloads.len());
340        let mut iter = self.payloads.drain(..);
341        let mut result = Ok(None);
342        for item in &mut iter {
343            match T::try_from(item) {
344                Ok(v) => {
345                    result = Ok(Some(v));
346                    break;
347                }
348                Err(FromElementError::Mismatch(residual)) => {
349                    buf.push(residual);
350                }
351                Err(FromElementError::Invalid(other)) => {
352                    result = Err(other);
353                    break;
354                }
355            }
356        }
357        buf.extend(iter);
358        core::mem::swap(&mut buf, &mut self.payloads);
359        result
360    }
361
362    /// Tries to extract the payload, warning when parsing fails.
363    ///
364    /// This method uses [`Message::extract_payload`], but removes the error
365    /// case by simply warning to the current logger.
366    #[cfg(feature = "log")]
367    pub fn extract_valid_payload<T: TryFrom<Element, Error = FromElementError>>(
368        &mut self,
369    ) -> Option<T> {
370        match self.extract_payload::<T>() {
371            Ok(opt) => opt,
372            Err(e) => {
373                // TODO: xso should support human-readable name for T
374                log::warn!("Failed to parse payload: {e}");
375                None
376            }
377        }
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384
385    #[cfg(target_pointer_width = "32")]
386    #[test]
387    fn test_size() {
388        assert_size!(MessageType, 1);
389        assert_size!(Thread, 24);
390        assert_size!(Message, 108);
391    }
392
393    #[cfg(target_pointer_width = "64")]
394    #[test]
395    fn test_size() {
396        assert_size!(MessageType, 1);
397        assert_size!(Thread, 48);
398        assert_size!(Message, 216);
399    }
400
401    #[test]
402    fn test_simple() {
403        #[cfg(not(feature = "component"))]
404        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
405        #[cfg(feature = "component")]
406        let elem: Element = "<message xmlns='jabber:component:accept'/>"
407            .parse()
408            .unwrap();
409        let message = Message::try_from(elem).unwrap();
410        assert_eq!(message.from, None);
411        assert_eq!(message.to, None);
412        assert_eq!(message.id, None);
413        assert_eq!(message.type_, MessageType::Normal);
414        assert!(message.payloads.is_empty());
415    }
416
417    #[test]
418    fn test_serialise() {
419        #[cfg(not(feature = "component"))]
420        let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
421        #[cfg(feature = "component")]
422        let elem: Element = "<message xmlns='jabber:component:accept'/>"
423            .parse()
424            .unwrap();
425        let mut message = Message::new(None);
426        message.type_ = MessageType::Normal;
427        let elem2 = message.into();
428        assert_eq!(elem, elem2);
429    }
430
431    #[test]
432    fn test_body() {
433        #[cfg(not(feature = "component"))]
434        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
435        #[cfg(feature = "component")]
436        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
437        let elem1 = elem.clone();
438        let message = Message::try_from(elem).unwrap();
439        assert_eq!(message.bodies[""], "Hello world!");
440
441        {
442            let (lang, body) = message.get_best_body(vec!["en"]).unwrap();
443            assert_eq!(lang, "");
444            assert_eq!(body, &"Hello world!");
445        }
446
447        let elem2 = message.into();
448        assert_eq!(elem1, elem2);
449    }
450
451    #[test]
452    fn test_serialise_body() {
453        #[cfg(not(feature = "component"))]
454        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
455        #[cfg(feature = "component")]
456        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
457        let mut message = Message::new(Jid::new("coucou@example.org").unwrap());
458        message
459            .bodies
460            .insert(Lang::from(""), "Hello world!".to_owned());
461        let elem2 = message.into();
462        assert_eq!(elem, elem2);
463    }
464
465    #[test]
466    fn test_subject() {
467        #[cfg(not(feature = "component"))]
468        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
469        #[cfg(feature = "component")]
470        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
471        let elem1 = elem.clone();
472        let message = Message::try_from(elem).unwrap();
473        assert_eq!(message.subjects[""], "Hello world!",);
474
475        {
476            let (lang, subject) = message.get_best_subject(vec!["en"]).unwrap();
477            assert_eq!(lang, "");
478            assert_eq!(subject, "Hello world!");
479        }
480
481        // Test cloned variant.
482        {
483            let (lang, subject) = message.get_best_subject_cloned(vec!["en"]).unwrap();
484            assert_eq!(lang, "");
485            assert_eq!(subject, "Hello world!");
486        }
487
488        let elem2 = message.into();
489        assert_eq!(elem1, elem2);
490    }
491
492    #[test]
493    fn get_best_body() {
494        #[cfg(not(feature = "component"))]
495        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();
496        #[cfg(feature = "component")]
497        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
498        let message = Message::try_from(elem).unwrap();
499
500        // Tests basic feature.
501        {
502            let (lang, body) = message.get_best_body(vec!["fr"]).unwrap();
503            assert_eq!(lang, "fr");
504            assert_eq!(body, "Salut le monde !");
505        }
506
507        // Tests order.
508        {
509            let (lang, body) = message.get_best_body(vec!["en", "de"]).unwrap();
510            assert_eq!(lang, "de");
511            assert_eq!(body, "Hallo Welt!");
512        }
513
514        // Tests fallback.
515        {
516            let (lang, body) = message.get_best_body(vec![]).unwrap();
517            assert_eq!(lang, "");
518            assert_eq!(body, "Hello world!");
519        }
520
521        // Tests fallback.
522        {
523            let (lang, body) = message.get_best_body(vec!["ja"]).unwrap();
524            assert_eq!(lang, "");
525            assert_eq!(body, "Hello world!");
526        }
527
528        // Test cloned variant.
529        {
530            let (lang, body) = message.get_best_body_cloned(vec!["ja"]).unwrap();
531            assert_eq!(lang, "");
532            assert_eq!(body, "Hello world!");
533        }
534
535        let message = Message::new(None);
536
537        // Tests without a body.
538        assert_eq!(message.get_best_body(vec!("ja")), None);
539    }
540
541    #[test]
542    fn test_attention() {
543        #[cfg(not(feature = "component"))]
544        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
545        #[cfg(feature = "component")]
546        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
547        let elem1 = elem.clone();
548        let message = Message::try_from(elem).unwrap();
549        let elem2 = message.into();
550        assert_eq!(elem1, elem2);
551    }
552
553    #[test]
554    fn test_extract_payload() {
555        use super::super::attention::Attention;
556        use super::super::pubsub;
557
558        #[cfg(not(feature = "component"))]
559        let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
560        #[cfg(feature = "component")]
561        let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
562        let mut message = Message::try_from(elem).unwrap();
563        assert_eq!(message.payloads.len(), 1);
564        match message.extract_payload::<pubsub::Event>() {
565            Ok(None) => (),
566            other => panic!("unexpected result: {:?}", other),
567        };
568        assert_eq!(message.payloads.len(), 1);
569        match message.extract_payload::<Attention>() {
570            Ok(Some(_)) => (),
571            other => panic!("unexpected result: {:?}", other),
572        };
573        assert_eq!(message.payloads.len(), 0);
574    }
575}