presence.rs

  1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  2// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@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 try_from::TryFrom;
  9use std::str::FromStr;
 10use std::collections::BTreeMap;
 11
 12use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
 13
 14use jid::Jid;
 15
 16use error::Error;
 17
 18use ns;
 19
 20use stanza_error::StanzaError;
 21use muc::Muc;
 22use caps::Caps;
 23use delay::Delay;
 24use idle::Idle;
 25use ecaps2::ECaps2;
 26
 27#[derive(Debug, Clone, PartialEq)]
 28pub enum Show {
 29    None,
 30    Away,
 31    Chat,
 32    Dnd,
 33    Xa,
 34}
 35
 36impl Default for Show {
 37    fn default() -> Show {
 38        Show::None
 39    }
 40}
 41
 42impl FromStr for Show {
 43    type Err = Error;
 44
 45    fn from_str(s: &str) -> Result<Show, Error> {
 46        Ok(match s {
 47            "away" => Show::Away,
 48            "chat" => Show::Chat,
 49            "dnd" => Show::Dnd,
 50            "xa" => Show::Xa,
 51
 52            _ => return Err(Error::ParseError("Invalid value for show.")),
 53        })
 54    }
 55}
 56
 57impl IntoElements for Show {
 58    fn into_elements(self, emitter: &mut ElementEmitter) {
 59        if self == Show::None {
 60            return;
 61        }
 62        emitter.append_child(
 63            Element::builder("show")
 64                    .append(match self {
 65                         Show::None => unreachable!(),
 66                         Show::Away => Some("away"),
 67                         Show::Chat => Some("chat"),
 68                         Show::Dnd => Some("dnd"),
 69                         Show::Xa => Some("xa"),
 70                     })
 71                    .build())
 72    }
 73}
 74
 75pub type Lang = String;
 76pub type Status = String;
 77
 78pub type Priority = i8;
 79
 80/// Lists every known payload of a `<presence/>`.
 81#[derive(Debug, Clone)]
 82pub enum PresencePayload {
 83    StanzaError(StanzaError),
 84    Muc(Muc),
 85    Caps(Caps),
 86    Delay(Delay),
 87    Idle(Idle),
 88    ECaps2(ECaps2),
 89
 90    Unknown(Element),
 91}
 92
 93impl TryFrom<Element> for PresencePayload {
 94    type Err = Error;
 95
 96    fn try_from(elem: Element) -> Result<PresencePayload, Error> {
 97        Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
 98            ("error", ns::DEFAULT_NS) => PresencePayload::StanzaError(StanzaError::try_from(elem)?),
 99
100            // XEP-0045
101            ("x", ns::MUC) => PresencePayload::Muc(Muc::try_from(elem)?),
102
103            // XEP-0115
104            ("c", ns::CAPS) => PresencePayload::Caps(Caps::try_from(elem)?),
105
106            // XEP-0203
107            ("delay", ns::DELAY) => PresencePayload::Delay(Delay::try_from(elem)?),
108
109            // XEP-0319
110            ("idle", ns::IDLE) => PresencePayload::Idle(Idle::try_from(elem)?),
111
112            // XEP-0390
113            ("c", ns::ECAPS2) => PresencePayload::ECaps2(ECaps2::try_from(elem)?),
114
115            _ => PresencePayload::Unknown(elem),
116        })
117    }
118}
119
120impl From<PresencePayload> for Element {
121    fn from(payload: PresencePayload) -> Element {
122        match payload {
123            PresencePayload::StanzaError(stanza_error) => stanza_error.into(),
124            PresencePayload::Muc(muc) => muc.into(),
125            PresencePayload::Caps(caps) => caps.into(),
126            PresencePayload::Delay(delay) => delay.into(),
127            PresencePayload::Idle(idle) => idle.into(),
128            PresencePayload::ECaps2(ecaps2) => ecaps2.into(),
129
130            PresencePayload::Unknown(elem) => elem,
131        }
132    }
133}
134
135#[derive(Debug, Clone, PartialEq)]
136pub enum Type {
137    /// This value is not an acceptable 'type' attribute, it is only used
138    /// internally to signal the absence of 'type'.
139    None,
140    Error,
141    Probe,
142    Subscribe,
143    Subscribed,
144    Unavailable,
145    Unsubscribe,
146    Unsubscribed,
147}
148
149impl Default for Type {
150    fn default() -> Type {
151        Type::None
152    }
153}
154
155impl FromStr for Type {
156    type Err = Error;
157
158    fn from_str(s: &str) -> Result<Type, Error> {
159        Ok(match s {
160            "error" => Type::Error,
161            "probe" => Type::Probe,
162            "subscribe" => Type::Subscribe,
163            "subscribed" => Type::Subscribed,
164            "unavailable" => Type::Unavailable,
165            "unsubscribe" => Type::Unsubscribe,
166            "unsubscribed" => Type::Unsubscribed,
167
168            _ => return Err(Error::ParseError("Invalid 'type' attribute on presence element.")),
169        })
170    }
171}
172
173impl IntoAttributeValue for Type {
174    fn into_attribute_value(self) -> Option<String> {
175        Some(match self {
176            Type::None => return None,
177
178            Type::Error => "error",
179            Type::Probe => "probe",
180            Type::Subscribe => "subscribe",
181            Type::Subscribed => "subscribed",
182            Type::Unavailable => "unavailable",
183            Type::Unsubscribe => "unsubscribe",
184            Type::Unsubscribed => "unsubscribed",
185        }.to_owned())
186    }
187}
188
189/// The main structure representing the `<presence/>` stanza.
190#[derive(Debug, Clone)]
191pub struct Presence {
192    pub from: Option<Jid>,
193    pub to: Option<Jid>,
194    pub id: Option<String>,
195    pub type_: Type,
196    pub show: Show,
197    pub statuses: BTreeMap<Lang, Status>,
198    pub priority: Priority,
199    pub payloads: Vec<Element>,
200}
201
202impl Presence {
203    pub fn new(type_: Type) -> Presence {
204        Presence {
205            from: None,
206            to: None,
207            id: None,
208            type_: type_,
209            show: Show::None,
210            statuses: BTreeMap::new(),
211            priority: 0i8,
212            payloads: vec!(),
213        }
214    }
215
216    pub fn with_from(mut self, from: Option<Jid>) -> Presence {
217        self.from = from;
218        self
219    }
220
221    pub fn with_to(mut self, to: Option<Jid>) -> Presence {
222        self.to = to;
223        self
224    }
225
226    pub fn with_id(mut self, id: Option<String>) -> Presence {
227        self.id = id;
228        self
229    }
230
231    pub fn with_show(mut self, show: Show) -> Presence {
232        self.show = show;
233        self
234    }
235
236    pub fn with_priority(mut self, priority: i8) -> Presence {
237        self.priority = priority;
238        self
239    }
240
241    pub fn with_payloads(mut self, payloads: Vec<Element>) -> Presence {
242        self.payloads = payloads;
243        self
244    }
245}
246
247impl TryFrom<Element> for Presence {
248    type Err = Error;
249
250    fn try_from(root: Element) -> Result<Presence, Error> {
251        if !root.is("presence", ns::DEFAULT_NS) {
252            return Err(Error::ParseError("This is not a presence element."));
253        }
254        let mut show = None;
255        let mut priority = None;
256        let mut presence = Presence {
257            from: get_attr!(root, "from", optional),
258            to: get_attr!(root, "to", optional),
259            id: get_attr!(root, "id", optional),
260            type_: get_attr!(root, "type", default),
261            show: Show::None,
262            statuses: BTreeMap::new(),
263            priority: 0i8,
264            payloads: vec!(),
265        };
266        for elem in root.children() {
267            if elem.is("show", ns::DEFAULT_NS) {
268                if show.is_some() {
269                    return Err(Error::ParseError("More than one show element in a presence."));
270                }
271                for _ in elem.children() {
272                    return Err(Error::ParseError("Unknown child in show element."));
273                }
274                for _ in elem.attrs() {
275                    return Err(Error::ParseError("Unknown attribute in show element."));
276                }
277                show = Some(Show::from_str(elem.text().as_ref())?);
278            } else if elem.is("status", ns::DEFAULT_NS) {
279                for _ in elem.children() {
280                    return Err(Error::ParseError("Unknown child in status element."));
281                }
282                for (attr, _) in elem.attrs() {
283                    if attr != "xml:lang" {
284                        return Err(Error::ParseError("Unknown attribute in status element."));
285                    }
286                }
287                let lang = get_attr!(elem, "xml:lang", default);
288                if presence.statuses.insert(lang, elem.text()).is_some() {
289                    return Err(Error::ParseError("Status element present twice for the same xml:lang."));
290                }
291            } else if elem.is("priority", ns::DEFAULT_NS) {
292                if priority.is_some() {
293                    return Err(Error::ParseError("More than one priority element in a presence."));
294                }
295                for _ in elem.children() {
296                    return Err(Error::ParseError("Unknown child in priority element."));
297                }
298                for _ in elem.attrs() {
299                    return Err(Error::ParseError("Unknown attribute in priority element."));
300                }
301                priority = Some(Priority::from_str(elem.text().as_ref())?);
302            } else {
303                presence.payloads.push(elem.clone());
304            }
305        }
306        if let Some(show) = show {
307            presence.show = show;
308        }
309        if let Some(priority) = priority {
310            presence.priority = priority;
311        }
312        Ok(presence)
313    }
314}
315
316impl From<Presence> for Element {
317    fn from(presence: Presence) -> Element {
318        Element::builder("presence")
319                .ns(ns::DEFAULT_NS)
320                .attr("from", presence.from)
321                .attr("to", presence.to)
322                .attr("id", presence.id)
323                .attr("type", presence.type_)
324                .append(presence.show)
325                .append(presence.statuses.into_iter().map(|(lang, status)| {
326                     Element::builder("status")
327                             .attr("xml:lang", match lang.as_ref() {
328                                  "" => None,
329                                  lang => Some(lang),
330                              })
331                             .append(status)
332                             .build()
333                 }).collect::<Vec<_>>())
334                .append(if presence.priority == 0 { None } else { Some(format!("{}", presence.priority)) })
335                .append(presence.payloads)
336                .build()
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use compare_elements::NamespaceAwareCompare;
344
345    #[test]
346    fn test_simple() {
347        #[cfg(not(feature = "component"))]
348        let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
349        #[cfg(feature = "component")]
350        let elem: Element = "<presence xmlns='jabber:component:accept'/>".parse().unwrap();
351        let presence = Presence::try_from(elem).unwrap();
352        assert_eq!(presence.from, None);
353        assert_eq!(presence.to, None);
354        assert_eq!(presence.id, None);
355        assert_eq!(presence.type_, Type::None);
356        assert!(presence.payloads.is_empty());
357    }
358
359    #[test]
360    fn test_serialise() {
361        #[cfg(not(feature = "component"))]
362        let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>/>".parse().unwrap();
363        #[cfg(feature = "component")]
364        let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>/>".parse().unwrap();
365        let presence = Presence::new(Type::Unavailable);
366        let elem2 = presence.into();
367        assert!(elem.compare_to(&elem2));
368    }
369
370    #[test]
371    fn test_show() {
372        #[cfg(not(feature = "component"))]
373        let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>".parse().unwrap();
374        #[cfg(feature = "component")]
375        let elem: Element = "<presence xmlns='jabber:component:accept'><show>chat</show></presence>".parse().unwrap();
376        let presence = Presence::try_from(elem).unwrap();
377        assert_eq!(presence.payloads.len(), 0);
378        assert_eq!(presence.show, Show::Chat);
379    }
380
381    #[test]
382    fn test_missing_show_value() {
383        #[cfg(not(feature = "component"))]
384        let elem: Element = "<presence xmlns='jabber:client'><show/></presence>".parse().unwrap();
385        #[cfg(feature = "component")]
386        let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>".parse().unwrap();
387        let error = Presence::try_from(elem).unwrap_err();
388        let message = match error {
389            Error::ParseError(string) => string,
390            _ => panic!(),
391        };
392        assert_eq!(message, "Invalid value for show.");
393    }
394
395    #[test]
396    fn test_invalid_show() {
397        // "online" used to be a pretty common mistake.
398        #[cfg(not(feature = "component"))]
399        let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>".parse().unwrap();
400        #[cfg(feature = "component")]
401        let elem: Element = "<presence xmlns='jabber:component:accept'><show>online</show></presence>".parse().unwrap();
402        let error = Presence::try_from(elem).unwrap_err();
403        let message = match error {
404            Error::ParseError(string) => string,
405            _ => panic!(),
406        };
407        assert_eq!(message, "Invalid value for show.");
408    }
409
410    #[test]
411    fn test_empty_status() {
412        #[cfg(not(feature = "component"))]
413        let elem: Element = "<presence xmlns='jabber:client'><status/></presence>".parse().unwrap();
414        #[cfg(feature = "component")]
415        let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>".parse().unwrap();
416        let presence = Presence::try_from(elem).unwrap();
417        assert_eq!(presence.payloads.len(), 0);
418        assert_eq!(presence.statuses.len(), 1);
419        assert_eq!(presence.statuses[""], "");
420    }
421
422    #[test]
423    fn test_status() {
424        #[cfg(not(feature = "component"))]
425        let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>".parse().unwrap();
426        #[cfg(feature = "component")]
427        let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>".parse().unwrap();
428        let presence = Presence::try_from(elem).unwrap();
429        assert_eq!(presence.payloads.len(), 0);
430        assert_eq!(presence.statuses.len(), 1);
431        assert_eq!(presence.statuses[""], "Here!");
432    }
433
434    #[test]
435    fn test_multiple_statuses() {
436        #[cfg(not(feature = "component"))]
437        let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
438        #[cfg(feature = "component")]
439        let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
440        let presence = Presence::try_from(elem).unwrap();
441        assert_eq!(presence.payloads.len(), 0);
442        assert_eq!(presence.statuses.len(), 2);
443        assert_eq!(presence.statuses[""], "Here!");
444        assert_eq!(presence.statuses["fr"], "LĂ !");
445    }
446
447    #[test]
448    fn test_invalid_multiple_statuses() {
449        #[cfg(not(feature = "component"))]
450        let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
451        #[cfg(feature = "component")]
452        let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
453        let error = Presence::try_from(elem).unwrap_err();
454        let message = match error {
455            Error::ParseError(string) => string,
456            _ => panic!(),
457        };
458        assert_eq!(message, "Status element present twice for the same xml:lang.");
459    }
460
461    #[test]
462    fn test_priority() {
463        #[cfg(not(feature = "component"))]
464        let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>".parse().unwrap();
465        #[cfg(feature = "component")]
466        let elem: Element = "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>".parse().unwrap();
467        let presence = Presence::try_from(elem).unwrap();
468        assert_eq!(presence.payloads.len(), 0);
469        assert_eq!(presence.priority, -1i8);
470    }
471
472    #[test]
473    fn test_invalid_priority() {
474        #[cfg(not(feature = "component"))]
475        let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>".parse().unwrap();
476        #[cfg(feature = "component")]
477        let elem: Element = "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>".parse().unwrap();
478        let error = Presence::try_from(elem).unwrap_err();
479        match error {
480            Error::ParseIntError(_) => (),
481            _ => panic!(),
482        };
483    }
484
485    #[test]
486    fn test_unknown_child() {
487        #[cfg(not(feature = "component"))]
488        let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>".parse().unwrap();
489        #[cfg(feature = "component")]
490        let elem: Element = "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>".parse().unwrap();
491        let presence = Presence::try_from(elem).unwrap();
492        let payload = &presence.payloads[0];
493        assert!(payload.is("test", "invalid"));
494    }
495
496    #[test]
497    fn test_invalid_status_child() {
498        #[cfg(not(feature = "component"))]
499        let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>".parse().unwrap();
500        #[cfg(feature = "component")]
501        let elem: Element = "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>".parse().unwrap();
502        let error = Presence::try_from(elem).unwrap_err();
503        let message = match error {
504            Error::ParseError(string) => string,
505            _ => panic!(),
506        };
507        assert_eq!(message, "Unknown child in status element.");
508    }
509
510    #[test]
511    fn test_invalid_attribute() {
512        #[cfg(not(feature = "component"))]
513        let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>".parse().unwrap();
514        #[cfg(feature = "component")]
515        let elem: Element = "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>".parse().unwrap();
516        let error = Presence::try_from(elem).unwrap_err();
517        let message = match error {
518            Error::ParseError(string) => string,
519            _ => panic!(),
520        };
521        assert_eq!(message, "Unknown attribute in status element.");
522    }
523
524    #[test]
525    fn test_serialise_status() {
526        let status = Status::from("Hello world!");
527        let mut presence = Presence::new(Type::Unavailable);
528        presence.statuses.insert(String::from(""), status);
529        let elem: Element = presence.into();
530        assert!(elem.is("presence", ns::DEFAULT_NS));
531        assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
532    }
533}