presence.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2// Copyright (c) 2017 Maxime “pep” Buquet <pep@bouah.net>
  3//
  4// This Source Code Form is subject to the terms of the Mozilla Public
  5// License, v. 2.0. If a copy of the MPL was not distributed with this
  6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
  7
  8use crate::util::error::Error;
  9use crate::ns;
 10use jid::Jid;
 11use minidom::{Element, ElementEmitter, IntoAttributeValue, IntoElements};
 12use std::collections::BTreeMap;
 13use std::str::FromStr;
 14use std::convert::TryFrom;
 15
 16/// Should be implemented on every known payload of a `<presence/>`.
 17pub trait PresencePayload: TryFrom<Element> + Into<Element> {}
 18
 19/// Specifies the availability of an entity or resource.
 20#[derive(Debug, Clone, PartialEq)]
 21pub enum Show {
 22    /// The entity or resource is temporarily away.
 23    Away,
 24
 25    /// The entity or resource is actively interested in chatting.
 26    Chat,
 27
 28    /// The entity or resource is busy (dnd = "Do Not Disturb").
 29    Dnd,
 30
 31    /// The entity or resource is away for an extended period (xa = "eXtended
 32    /// Away").
 33    Xa,
 34}
 35
 36impl FromStr for Show {
 37    type Err = Error;
 38
 39    fn from_str(s: &str) -> Result<Show, Error> {
 40        Ok(match s {
 41            "away" => Show::Away,
 42            "chat" => Show::Chat,
 43            "dnd" => Show::Dnd,
 44            "xa" => Show::Xa,
 45
 46            _ => return Err(Error::ParseError("Invalid value for show.")),
 47        })
 48    }
 49}
 50
 51impl IntoElements for Show {
 52    fn into_elements(self, emitter: &mut ElementEmitter) {
 53        emitter.append_child(
 54            Element::builder("show")
 55                .append(match self {
 56                    Show::Away => Some("away"),
 57                    Show::Chat => Some("chat"),
 58                    Show::Dnd => Some("dnd"),
 59                    Show::Xa => Some("xa"),
 60                })
 61                .build(),
 62        )
 63    }
 64}
 65
 66type Lang = String;
 67type Status = String;
 68
 69type Priority = i8;
 70
 71///
 72#[derive(Debug, Clone, PartialEq)]
 73pub enum Type {
 74    /// This value is not an acceptable 'type' attribute, it is only used
 75    /// internally to signal the absence of 'type'.
 76    None,
 77
 78    /// An error has occurred regarding processing of a previously sent
 79    /// presence stanza; if the presence stanza is of type "error", it MUST
 80    /// include an <error/> child element (refer to [XMPP‑CORE]).
 81    Error,
 82
 83    /// A request for an entity's current presence; SHOULD be generated only by
 84    /// a server on behalf of a user.
 85    Probe,
 86
 87    /// The sender wishes to subscribe to the recipient's presence.
 88    Subscribe,
 89
 90    /// The sender has allowed the recipient to receive their presence.
 91    Subscribed,
 92
 93    /// The sender is no longer available for communication.
 94    Unavailable,
 95
 96    /// The sender is unsubscribing from the receiver's presence.
 97    Unsubscribe,
 98
 99    /// The subscription request has been denied or a previously granted
100    /// subscription has been canceled.
101    Unsubscribed,
102}
103
104impl Default for Type {
105    fn default() -> Type {
106        Type::None
107    }
108}
109
110impl FromStr for Type {
111    type Err = Error;
112
113    fn from_str(s: &str) -> Result<Type, Error> {
114        Ok(match s {
115            "error" => Type::Error,
116            "probe" => Type::Probe,
117            "subscribe" => Type::Subscribe,
118            "subscribed" => Type::Subscribed,
119            "unavailable" => Type::Unavailable,
120            "unsubscribe" => Type::Unsubscribe,
121            "unsubscribed" => Type::Unsubscribed,
122
123            _ => {
124                return Err(Error::ParseError(
125                    "Invalid 'type' attribute on presence element.",
126                ));
127            }
128        })
129    }
130}
131
132impl IntoAttributeValue for Type {
133    fn into_attribute_value(self) -> Option<String> {
134        Some(
135            match self {
136                Type::None => return None,
137
138                Type::Error => "error",
139                Type::Probe => "probe",
140                Type::Subscribe => "subscribe",
141                Type::Subscribed => "subscribed",
142                Type::Unavailable => "unavailable",
143                Type::Unsubscribe => "unsubscribe",
144                Type::Unsubscribed => "unsubscribed",
145            }
146            .to_owned(),
147        )
148    }
149}
150
151/// The main structure representing the `<presence/>` stanza.
152#[derive(Debug, Clone)]
153pub struct Presence {
154    /// The sender of this presence.
155    pub from: Option<Jid>,
156
157    /// The recipient of this presence.
158    pub to: Option<Jid>,
159
160    /// The identifier, unique on this stream, of this stanza.
161    pub id: Option<String>,
162
163    /// The type of this presence stanza.
164    pub type_: Type,
165
166    /// The availability of the sender of this presence.
167    pub show: Option<Show>,
168
169    /// A localised list of statuses defined in this presence.
170    pub statuses: BTreeMap<Lang, Status>,
171
172    /// The sender’s resource priority, if negative it won’t receive messages
173    /// that haven’t been directed to it.
174    pub priority: Priority,
175
176    /// A list of payloads contained in this presence.
177    pub payloads: Vec<Element>,
178}
179
180impl Presence {
181    /// Create a new presence of this type.
182    pub fn new(type_: Type) -> Presence {
183        Presence {
184            from: None,
185            to: None,
186            id: None,
187            type_,
188            show: None,
189            statuses: BTreeMap::new(),
190            priority: 0i8,
191            payloads: vec![],
192        }
193    }
194
195    /// Set the emitter of this presence, this should only be useful for
196    /// servers and components, as clients can only send presences from their
197    /// own resource (which is implicit).
198    pub fn with_from(mut self, from: Option<Jid>) -> Presence {
199        self.from = from;
200        self
201    }
202
203    /// Set the recipient of this presence, this is only useful for directed
204    /// presences.
205    pub fn with_to(mut self, to: Option<Jid>) -> Presence {
206        self.to = to;
207        self
208    }
209
210    /// Set the identifier for this presence.
211    pub fn with_id(mut self, id: Option<String>) -> Presence {
212        self.id = id;
213        self
214    }
215
216    /// Set the availability information of this presence.
217    pub fn with_show(mut self, show: Show) -> Presence {
218        self.show = Some(show);
219        self
220    }
221
222    /// Set the priority of this presence.
223    pub fn with_priority(mut self, priority: i8) -> Presence {
224        self.priority = priority;
225        self
226    }
227
228    /// Set the payloads of this presence.
229    pub fn with_payloads(mut self, payloads: Vec<Element>) -> Presence {
230        self.payloads = payloads;
231        self
232    }
233
234    /// Set the availability information of this presence.
235    pub fn set_status<L, S>(&mut self, lang: L, status: S)
236    where L: Into<Lang>,
237          S: Into<Status>,
238    {
239        self.statuses.insert(lang.into(), status.into());
240    }
241
242    /// Add a payload to this presence.
243    pub fn add_payload<P: PresencePayload>(&mut self, payload: P) {
244        self.payloads.push(payload.into());
245    }
246}
247
248impl TryFrom<Element> for Presence {
249    type Error = Error;
250
251    fn try_from(root: Element) -> Result<Presence, Error> {
252        check_self!(root, "presence", DEFAULT_NS);
253        let mut show = None;
254        let mut priority = None;
255        let mut presence = Presence {
256            from: get_attr!(root, "from", Option),
257            to: get_attr!(root, "to", Option),
258            id: get_attr!(root, "id", Option),
259            type_: get_attr!(root, "type", Default),
260            show: None,
261            statuses: BTreeMap::new(),
262            priority: 0i8,
263            payloads: vec![],
264        };
265        for elem in root.children() {
266            if elem.is("show", ns::DEFAULT_NS) {
267                if show.is_some() {
268                    return Err(Error::ParseError(
269                        "More than one show element in a presence.",
270                    ));
271                }
272                check_no_attributes!(elem, "show");
273                check_no_children!(elem, "show");
274                show = Some(Show::from_str(elem.text().as_ref())?);
275            } else if elem.is("status", ns::DEFAULT_NS) {
276                check_no_unknown_attributes!(elem, "status", ["xml:lang"]);
277                check_no_children!(elem, "status");
278                let lang = get_attr!(elem, "xml:lang", Default);
279                if presence.statuses.insert(lang, elem.text()).is_some() {
280                    return Err(Error::ParseError(
281                        "Status element present twice for the same xml:lang.",
282                    ));
283                }
284            } else if elem.is("priority", ns::DEFAULT_NS) {
285                if priority.is_some() {
286                    return Err(Error::ParseError(
287                        "More than one priority element in a presence.",
288                    ));
289                }
290                check_no_attributes!(elem, "priority");
291                check_no_children!(elem, "priority");
292                priority = Some(Priority::from_str(elem.text().as_ref())?);
293            } else {
294                presence.payloads.push(elem.clone());
295            }
296        }
297        presence.show = show;
298        if let Some(priority) = priority {
299            presence.priority = priority;
300        }
301        Ok(presence)
302    }
303}
304
305impl From<Presence> for Element {
306    fn from(presence: Presence) -> Element {
307        Element::builder("presence")
308            .ns(ns::DEFAULT_NS)
309            .attr("from", presence.from)
310            .attr("to", presence.to)
311            .attr("id", presence.id)
312            .attr("type", presence.type_)
313            .append(presence.show)
314            .append(
315                presence
316                    .statuses
317                    .into_iter()
318                    .map(|(lang, status)| {
319                        Element::builder("status")
320                            .attr(
321                                "xml:lang",
322                                match lang.as_ref() {
323                                    "" => None,
324                                    lang => Some(lang),
325                                },
326                            )
327                            .append(status)
328                            .build()
329                    })
330                    .collect::<Vec<_>>(),
331            )
332            .append(if presence.priority == 0 {
333                None
334            } else {
335                Some(
336                    Element::builder("priority")
337                        .append(format!("{}", presence.priority))
338                        .build()
339                )
340            })
341            .append(presence.payloads)
342            .build()
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::util::compare_elements::NamespaceAwareCompare;
350
351    #[cfg(target_pointer_width = "32")]
352    #[test]
353    fn test_size() {
354        assert_size!(Show, 1);
355        assert_size!(Type, 1);
356        assert_size!(Presence, 120);
357    }
358
359    #[cfg(target_pointer_width = "64")]
360    #[test]
361    fn test_size() {
362        assert_size!(Show, 1);
363        assert_size!(Type, 1);
364        assert_size!(Presence, 240);
365    }
366
367    #[test]
368    fn test_simple() {
369        #[cfg(not(feature = "component"))]
370        let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
371        #[cfg(feature = "component")]
372        let elem: Element = "<presence xmlns='jabber:component:accept'/>"
373            .parse()
374            .unwrap();
375        let presence = Presence::try_from(elem).unwrap();
376        assert_eq!(presence.from, None);
377        assert_eq!(presence.to, None);
378        assert_eq!(presence.id, None);
379        assert_eq!(presence.type_, Type::None);
380        assert!(presence.payloads.is_empty());
381    }
382
383    #[test]
384    fn test_serialise() {
385        #[cfg(not(feature = "component"))]
386        let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>/>"
387            .parse()
388            .unwrap();
389        #[cfg(feature = "component")]
390        let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>/>"
391            .parse()
392            .unwrap();
393        let presence = Presence::new(Type::Unavailable);
394        let elem2 = presence.into();
395        assert!(elem.compare_to(&elem2));
396    }
397
398    #[test]
399    fn test_show() {
400        #[cfg(not(feature = "component"))]
401        let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>"
402            .parse()
403            .unwrap();
404        #[cfg(feature = "component")]
405        let elem: Element =
406            "<presence xmlns='jabber:component:accept'><show>chat</show></presence>"
407                .parse()
408                .unwrap();
409        let presence = Presence::try_from(elem).unwrap();
410        assert_eq!(presence.payloads.len(), 0);
411        assert_eq!(presence.show, Some(Show::Chat));
412    }
413
414    #[test]
415    fn test_empty_show_value() {
416        #[cfg(not(feature = "component"))]
417        let elem: Element = "<presence xmlns='jabber:client'/>"
418            .parse()
419            .unwrap();
420        #[cfg(feature = "component")]
421        let elem: Element = "<presence xmlns='jabber:component:accept'/>"
422            .parse()
423            .unwrap();
424        let presence = Presence::try_from(elem).unwrap();
425        assert_eq!(presence.show, None);
426    }
427
428    #[test]
429    fn test_missing_show_value() {
430        #[cfg(not(feature = "component"))]
431        let elem: Element = "<presence xmlns='jabber:client'><show/></presence>"
432            .parse()
433            .unwrap();
434        #[cfg(feature = "component")]
435        let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>"
436            .parse()
437            .unwrap();
438        let error = Presence::try_from(elem).unwrap_err();
439        let message = match error {
440            Error::ParseError(string) => string,
441            _ => panic!(),
442        };
443        assert_eq!(message, "Invalid value for show.");
444    }
445
446    #[test]
447    fn test_invalid_show() {
448        // "online" used to be a pretty common mistake.
449        #[cfg(not(feature = "component"))]
450        let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>"
451            .parse()
452            .unwrap();
453        #[cfg(feature = "component")]
454        let elem: Element =
455            "<presence xmlns='jabber:component:accept'><show>online</show></presence>"
456                .parse()
457                .unwrap();
458        let error = Presence::try_from(elem).unwrap_err();
459        let message = match error {
460            Error::ParseError(string) => string,
461            _ => panic!(),
462        };
463        assert_eq!(message, "Invalid value for show.");
464    }
465
466    #[test]
467    fn test_empty_status() {
468        #[cfg(not(feature = "component"))]
469        let elem: Element = "<presence xmlns='jabber:client'><status/></presence>"
470            .parse()
471            .unwrap();
472        #[cfg(feature = "component")]
473        let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>"
474            .parse()
475            .unwrap();
476        let presence = Presence::try_from(elem).unwrap();
477        assert_eq!(presence.payloads.len(), 0);
478        assert_eq!(presence.statuses.len(), 1);
479        assert_eq!(presence.statuses[""], "");
480    }
481
482    #[test]
483    fn test_status() {
484        #[cfg(not(feature = "component"))]
485        let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>"
486            .parse()
487            .unwrap();
488        #[cfg(feature = "component")]
489        let elem: Element =
490            "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>"
491                .parse()
492                .unwrap();
493        let presence = Presence::try_from(elem).unwrap();
494        assert_eq!(presence.payloads.len(), 0);
495        assert_eq!(presence.statuses.len(), 1);
496        assert_eq!(presence.statuses[""], "Here!");
497    }
498
499    #[test]
500    fn test_multiple_statuses() {
501        #[cfg(not(feature = "component"))]
502        let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
503        #[cfg(feature = "component")]
504        let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
505        let presence = Presence::try_from(elem).unwrap();
506        assert_eq!(presence.payloads.len(), 0);
507        assert_eq!(presence.statuses.len(), 2);
508        assert_eq!(presence.statuses[""], "Here!");
509        assert_eq!(presence.statuses["fr"], "LĂ !");
510    }
511
512    #[test]
513    fn test_invalid_multiple_statuses() {
514        #[cfg(not(feature = "component"))]
515        let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
516        #[cfg(feature = "component")]
517        let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
518        let error = Presence::try_from(elem).unwrap_err();
519        let message = match error {
520            Error::ParseError(string) => string,
521            _ => panic!(),
522        };
523        assert_eq!(
524            message,
525            "Status element present twice for the same xml:lang."
526        );
527    }
528
529    #[test]
530    fn test_priority() {
531        #[cfg(not(feature = "component"))]
532        let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>"
533            .parse()
534            .unwrap();
535        #[cfg(feature = "component")]
536        let elem: Element =
537            "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>"
538                .parse()
539                .unwrap();
540        let presence = Presence::try_from(elem).unwrap();
541        assert_eq!(presence.payloads.len(), 0);
542        assert_eq!(presence.priority, -1i8);
543    }
544
545    #[test]
546    fn test_invalid_priority() {
547        #[cfg(not(feature = "component"))]
548        let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>"
549            .parse()
550            .unwrap();
551        #[cfg(feature = "component")]
552        let elem: Element =
553            "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>"
554                .parse()
555                .unwrap();
556        let error = Presence::try_from(elem).unwrap_err();
557        match error {
558            Error::ParseIntError(_) => (),
559            _ => panic!(),
560        };
561    }
562
563    #[test]
564    fn test_unknown_child() {
565        #[cfg(not(feature = "component"))]
566        let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>"
567            .parse()
568            .unwrap();
569        #[cfg(feature = "component")]
570        let elem: Element =
571            "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>"
572                .parse()
573                .unwrap();
574        let presence = Presence::try_from(elem).unwrap();
575        let payload = &presence.payloads[0];
576        assert!(payload.is("test", "invalid"));
577    }
578
579    #[cfg(not(feature = "disable-validation"))]
580    #[test]
581    fn test_invalid_status_child() {
582        #[cfg(not(feature = "component"))]
583        let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>"
584            .parse()
585            .unwrap();
586        #[cfg(feature = "component")]
587        let elem: Element =
588            "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>"
589                .parse()
590                .unwrap();
591        let error = Presence::try_from(elem).unwrap_err();
592        let message = match error {
593            Error::ParseError(string) => string,
594            _ => panic!(),
595        };
596        assert_eq!(message, "Unknown child in status element.");
597    }
598
599    #[cfg(not(feature = "disable-validation"))]
600    #[test]
601    fn test_invalid_attribute() {
602        #[cfg(not(feature = "component"))]
603        let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>"
604            .parse()
605            .unwrap();
606        #[cfg(feature = "component")]
607        let elem: Element =
608            "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>"
609                .parse()
610                .unwrap();
611        let error = Presence::try_from(elem).unwrap_err();
612        let message = match error {
613            Error::ParseError(string) => string,
614            _ => panic!(),
615        };
616        assert_eq!(message, "Unknown attribute in status element.");
617    }
618
619    #[test]
620    fn test_serialise_status() {
621        let status = Status::from("Hello world!");
622        let mut presence = Presence::new(Type::Unavailable);
623        presence.statuses.insert(String::from(""), status);
624        let elem: Element = presence.into();
625        assert!(elem.is("presence", ns::DEFAULT_NS));
626        assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
627    }
628
629    #[test]
630    fn test_serialise_priority() {
631        let presence = Presence::new(Type::None)
632            .with_priority(42);
633        let elem: Element = presence.into();
634        assert!(elem.is("presence", ns::DEFAULT_NS));
635        let priority = elem.children().next().unwrap();
636        assert!(priority.is("priority", ns::DEFAULT_NS));
637        assert_eq!(priority.text(), "42");
638    }
639}