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        check_self!(root, "presence", DEFAULT_NS);
252        let mut show = None;
253        let mut priority = None;
254        let mut presence = Presence {
255            from: get_attr!(root, "from", optional),
256            to: get_attr!(root, "to", optional),
257            id: get_attr!(root, "id", optional),
258            type_: get_attr!(root, "type", default),
259            show: Show::None,
260            statuses: BTreeMap::new(),
261            priority: 0i8,
262            payloads: vec!(),
263        };
264        for elem in root.children() {
265            if elem.is("show", ns::DEFAULT_NS) {
266                if show.is_some() {
267                    return Err(Error::ParseError("More than one show element in a presence."));
268                }
269                check_no_attributes!(elem, "show");
270                check_no_children!(elem, "show");
271                show = Some(Show::from_str(elem.text().as_ref())?);
272            } else if elem.is("status", ns::DEFAULT_NS) {
273                check_no_unknown_attributes!(elem, "status", ["xml:lang"]);
274                check_no_children!(elem, "status");
275                let lang = get_attr!(elem, "xml:lang", default);
276                if presence.statuses.insert(lang, elem.text()).is_some() {
277                    return Err(Error::ParseError("Status element present twice for the same xml:lang."));
278                }
279            } else if elem.is("priority", ns::DEFAULT_NS) {
280                if priority.is_some() {
281                    return Err(Error::ParseError("More than one priority element in a presence."));
282                }
283                check_no_attributes!(elem, "priority");
284                check_no_children!(elem, "priority");
285                priority = Some(Priority::from_str(elem.text().as_ref())?);
286            } else {
287                presence.payloads.push(elem.clone());
288            }
289        }
290        if let Some(show) = show {
291            presence.show = show;
292        }
293        if let Some(priority) = priority {
294            presence.priority = priority;
295        }
296        Ok(presence)
297    }
298}
299
300impl From<Presence> for Element {
301    fn from(presence: Presence) -> Element {
302        Element::builder("presence")
303                .ns(ns::DEFAULT_NS)
304                .attr("from", presence.from)
305                .attr("to", presence.to)
306                .attr("id", presence.id)
307                .attr("type", presence.type_)
308                .append(presence.show)
309                .append(presence.statuses.into_iter().map(|(lang, status)| {
310                     Element::builder("status")
311                             .attr("xml:lang", match lang.as_ref() {
312                                  "" => None,
313                                  lang => Some(lang),
314                              })
315                             .append(status)
316                             .build()
317                 }).collect::<Vec<_>>())
318                .append(if presence.priority == 0 { None } else { Some(format!("{}", presence.priority)) })
319                .append(presence.payloads)
320                .build()
321    }
322}
323
324#[cfg(test)]
325mod tests {
326    use super::*;
327    use compare_elements::NamespaceAwareCompare;
328
329    #[test]
330    fn test_simple() {
331        #[cfg(not(feature = "component"))]
332        let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
333        #[cfg(feature = "component")]
334        let elem: Element = "<presence xmlns='jabber:component:accept'/>".parse().unwrap();
335        let presence = Presence::try_from(elem).unwrap();
336        assert_eq!(presence.from, None);
337        assert_eq!(presence.to, None);
338        assert_eq!(presence.id, None);
339        assert_eq!(presence.type_, Type::None);
340        assert!(presence.payloads.is_empty());
341    }
342
343    #[test]
344    fn test_serialise() {
345        #[cfg(not(feature = "component"))]
346        let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>/>".parse().unwrap();
347        #[cfg(feature = "component")]
348        let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>/>".parse().unwrap();
349        let presence = Presence::new(Type::Unavailable);
350        let elem2 = presence.into();
351        assert!(elem.compare_to(&elem2));
352    }
353
354    #[test]
355    fn test_show() {
356        #[cfg(not(feature = "component"))]
357        let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>".parse().unwrap();
358        #[cfg(feature = "component")]
359        let elem: Element = "<presence xmlns='jabber:component:accept'><show>chat</show></presence>".parse().unwrap();
360        let presence = Presence::try_from(elem).unwrap();
361        assert_eq!(presence.payloads.len(), 0);
362        assert_eq!(presence.show, Show::Chat);
363    }
364
365    #[test]
366    fn test_missing_show_value() {
367        #[cfg(not(feature = "component"))]
368        let elem: Element = "<presence xmlns='jabber:client'><show/></presence>".parse().unwrap();
369        #[cfg(feature = "component")]
370        let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>".parse().unwrap();
371        let error = Presence::try_from(elem).unwrap_err();
372        let message = match error {
373            Error::ParseError(string) => string,
374            _ => panic!(),
375        };
376        assert_eq!(message, "Invalid value for show.");
377    }
378
379    #[test]
380    fn test_invalid_show() {
381        // "online" used to be a pretty common mistake.
382        #[cfg(not(feature = "component"))]
383        let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>".parse().unwrap();
384        #[cfg(feature = "component")]
385        let elem: Element = "<presence xmlns='jabber:component:accept'><show>online</show></presence>".parse().unwrap();
386        let error = Presence::try_from(elem).unwrap_err();
387        let message = match error {
388            Error::ParseError(string) => string,
389            _ => panic!(),
390        };
391        assert_eq!(message, "Invalid value for show.");
392    }
393
394    #[test]
395    fn test_empty_status() {
396        #[cfg(not(feature = "component"))]
397        let elem: Element = "<presence xmlns='jabber:client'><status/></presence>".parse().unwrap();
398        #[cfg(feature = "component")]
399        let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>".parse().unwrap();
400        let presence = Presence::try_from(elem).unwrap();
401        assert_eq!(presence.payloads.len(), 0);
402        assert_eq!(presence.statuses.len(), 1);
403        assert_eq!(presence.statuses[""], "");
404    }
405
406    #[test]
407    fn test_status() {
408        #[cfg(not(feature = "component"))]
409        let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>".parse().unwrap();
410        #[cfg(feature = "component")]
411        let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>".parse().unwrap();
412        let presence = Presence::try_from(elem).unwrap();
413        assert_eq!(presence.payloads.len(), 0);
414        assert_eq!(presence.statuses.len(), 1);
415        assert_eq!(presence.statuses[""], "Here!");
416    }
417
418    #[test]
419    fn test_multiple_statuses() {
420        #[cfg(not(feature = "component"))]
421        let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
422        #[cfg(feature = "component")]
423        let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
424        let presence = Presence::try_from(elem).unwrap();
425        assert_eq!(presence.payloads.len(), 0);
426        assert_eq!(presence.statuses.len(), 2);
427        assert_eq!(presence.statuses[""], "Here!");
428        assert_eq!(presence.statuses["fr"], "LĂ !");
429    }
430
431    #[test]
432    fn test_invalid_multiple_statuses() {
433        #[cfg(not(feature = "component"))]
434        let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
435        #[cfg(feature = "component")]
436        let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
437        let error = Presence::try_from(elem).unwrap_err();
438        let message = match error {
439            Error::ParseError(string) => string,
440            _ => panic!(),
441        };
442        assert_eq!(message, "Status element present twice for the same xml:lang.");
443    }
444
445    #[test]
446    fn test_priority() {
447        #[cfg(not(feature = "component"))]
448        let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>".parse().unwrap();
449        #[cfg(feature = "component")]
450        let elem: Element = "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>".parse().unwrap();
451        let presence = Presence::try_from(elem).unwrap();
452        assert_eq!(presence.payloads.len(), 0);
453        assert_eq!(presence.priority, -1i8);
454    }
455
456    #[test]
457    fn test_invalid_priority() {
458        #[cfg(not(feature = "component"))]
459        let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>".parse().unwrap();
460        #[cfg(feature = "component")]
461        let elem: Element = "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>".parse().unwrap();
462        let error = Presence::try_from(elem).unwrap_err();
463        match error {
464            Error::ParseIntError(_) => (),
465            _ => panic!(),
466        };
467    }
468
469    #[test]
470    fn test_unknown_child() {
471        #[cfg(not(feature = "component"))]
472        let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>".parse().unwrap();
473        #[cfg(feature = "component")]
474        let elem: Element = "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>".parse().unwrap();
475        let presence = Presence::try_from(elem).unwrap();
476        let payload = &presence.payloads[0];
477        assert!(payload.is("test", "invalid"));
478    }
479
480    #[test]
481    fn test_invalid_status_child() {
482        #[cfg(not(feature = "component"))]
483        let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>".parse().unwrap();
484        #[cfg(feature = "component")]
485        let elem: Element = "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>".parse().unwrap();
486        let error = Presence::try_from(elem).unwrap_err();
487        let message = match error {
488            Error::ParseError(string) => string,
489            _ => panic!(),
490        };
491        assert_eq!(message, "Unknown child in status element.");
492    }
493
494    #[test]
495    fn test_invalid_attribute() {
496        #[cfg(not(feature = "component"))]
497        let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>".parse().unwrap();
498        #[cfg(feature = "component")]
499        let elem: Element = "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>".parse().unwrap();
500        let error = Presence::try_from(elem).unwrap_err();
501        let message = match error {
502            Error::ParseError(string) => string,
503            _ => panic!(),
504        };
505        assert_eq!(message, "Unknown attribute in status element.");
506    }
507
508    #[test]
509    fn test_serialise_status() {
510        let status = Status::from("Hello world!");
511        let mut presence = Presence::new(Type::Unavailable);
512        presence.statuses.insert(String::from(""), status);
513        let elem: Element = presence.into();
514        assert!(elem.is("presence", ns::DEFAULT_NS));
515        assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
516    }
517}