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::JABBER_CLIENT) => 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#[derive(Debug, Clone)]
190pub struct Presence {
191    pub from: Option<Jid>,
192    pub to: Option<Jid>,
193    pub id: Option<String>,
194    pub type_: Type,
195    pub show: Show,
196    pub statuses: BTreeMap<Lang, Status>,
197    pub priority: Priority,
198    pub payloads: Vec<Element>,
199}
200
201impl Presence {
202    pub fn new(type_: Type) -> Presence {
203        Presence {
204            from: None,
205            to: None,
206            id: None,
207            type_: type_,
208            show: Show::None,
209            statuses: BTreeMap::new(),
210            priority: 0i8,
211            payloads: vec!(),
212        }
213    }
214
215    pub fn with_from(mut self, from: Option<Jid>) -> Presence {
216        self.from = from;
217        self
218    }
219
220    pub fn with_to(mut self, to: Option<Jid>) -> Presence {
221        self.to = to;
222        self
223    }
224
225    pub fn with_id(mut self, id: Option<String>) -> Presence {
226        self.id = id;
227        self
228    }
229
230    pub fn with_show(mut self, show: Show) -> Presence {
231        self.show = show;
232        self
233    }
234
235    pub fn with_priority(mut self, priority: i8) -> Presence {
236        self.priority = priority;
237        self
238    }
239
240    pub fn with_payloads(mut self, payloads: Vec<Element>) -> Presence {
241        self.payloads = payloads;
242        self
243    }
244}
245
246impl TryFrom<Element> for Presence {
247    type Err = Error;
248
249    fn try_from(root: Element) -> Result<Presence, Error> {
250        if !root.is("presence", ns::JABBER_CLIENT) {
251            return Err(Error::ParseError("This is not a presence element."));
252        }
253        let mut show = None;
254        let mut priority = None;
255        let mut presence = Presence {
256            from: get_attr!(root, "from", optional),
257            to: get_attr!(root, "to", optional),
258            id: get_attr!(root, "id", optional),
259            type_: get_attr!(root, "type", default),
260            show: 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::JABBER_CLIENT) {
267                if show.is_some() {
268                    return Err(Error::ParseError("More than one show element in a presence."));
269                }
270                for _ in elem.children() {
271                    return Err(Error::ParseError("Unknown child in show element."));
272                }
273                for _ in elem.attrs() {
274                    return Err(Error::ParseError("Unknown attribute in show element."));
275                }
276                show = Some(Show::from_str(elem.text().as_ref())?);
277            } else if elem.is("status", ns::JABBER_CLIENT) {
278                for _ in elem.children() {
279                    return Err(Error::ParseError("Unknown child in status element."));
280                }
281                for (attr, _) in elem.attrs() {
282                    if attr != "xml:lang" {
283                        return Err(Error::ParseError("Unknown attribute in status element."));
284                    }
285                }
286                let lang = get_attr!(elem, "xml:lang", default);
287                if presence.statuses.insert(lang, elem.text()).is_some() {
288                    return Err(Error::ParseError("Status element present twice for the same xml:lang."));
289                }
290            } else if elem.is("priority", ns::JABBER_CLIENT) {
291                if priority.is_some() {
292                    return Err(Error::ParseError("More than one priority element in a presence."));
293                }
294                for _ in elem.children() {
295                    return Err(Error::ParseError("Unknown child in priority element."));
296                }
297                for _ in elem.attrs() {
298                    return Err(Error::ParseError("Unknown attribute in priority element."));
299                }
300                priority = Some(Priority::from_str(elem.text().as_ref())?);
301            } else {
302                presence.payloads.push(elem.clone());
303            }
304        }
305        if let Some(show) = show {
306            presence.show = show;
307        }
308        if let Some(priority) = priority {
309            presence.priority = priority;
310        }
311        Ok(presence)
312    }
313}
314
315impl From<Presence> for Element {
316    fn from(presence: Presence) -> Element {
317        Element::builder("presence")
318                .ns(ns::JABBER_CLIENT)
319                .attr("from", presence.from.and_then(|value| Some(String::from(value))))
320                .attr("to", presence.to.and_then(|value| Some(String::from(value))))
321                .attr("id", presence.id)
322                .attr("type", presence.type_)
323                .append(presence.show)
324                .append(presence.statuses.iter().map(|(lang, status)| {
325                     Element::builder("status")
326                             .attr("xml:lang", match lang.as_ref() {
327                                  "" => None,
328                                  lang => Some(lang),
329                              })
330                             .append(status)
331                             .build()
332                 }).collect::<Vec<_>>())
333                .append(if presence.priority == 0 { None } else { Some(format!("{}", presence.priority)) })
334                .append(presence.payloads)
335                .build()
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn test_simple() {
345        let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
346        let presence = Presence::try_from(elem).unwrap();
347        assert_eq!(presence.from, None);
348        assert_eq!(presence.to, None);
349        assert_eq!(presence.id, None);
350        assert_eq!(presence.type_, Type::None);
351        assert!(presence.payloads.is_empty());
352    }
353
354    #[test]
355    fn test_serialise() {
356        let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>".parse().unwrap();
357        let presence = Presence::new(Type::Unavailable);
358        let elem2 = presence.into();
359        assert_eq!(elem, elem2);
360    }
361
362    #[test]
363    fn test_show() {
364        let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>".parse().unwrap();
365        let presence = Presence::try_from(elem).unwrap();
366        assert_eq!(presence.payloads.len(), 0);
367        assert_eq!(presence.show, Show::Chat);
368    }
369
370    #[test]
371    fn test_missing_show_value() {
372        // "online" used to be a pretty common mistake.
373        let elem: Element = "<presence xmlns='jabber:client'><show/></presence>".parse().unwrap();
374        let error = Presence::try_from(elem).unwrap_err();
375        let message = match error {
376            Error::ParseError(string) => string,
377            _ => panic!(),
378        };
379        assert_eq!(message, "Invalid value for show.");
380    }
381
382    #[test]
383    fn test_invalid_show() {
384        // "online" used to be a pretty common mistake.
385        let elem: Element = "<presence xmlns='jabber:client'><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        let elem: Element = "<presence xmlns='jabber:client'><status/></presence>".parse().unwrap();
397        let presence = Presence::try_from(elem).unwrap();
398        assert_eq!(presence.payloads.len(), 0);
399        assert_eq!(presence.statuses.len(), 1);
400        assert_eq!(presence.statuses[""], "");
401    }
402
403    #[test]
404    fn test_status() {
405        let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>".parse().unwrap();
406        let presence = Presence::try_from(elem).unwrap();
407        assert_eq!(presence.payloads.len(), 0);
408        assert_eq!(presence.statuses.len(), 1);
409        assert_eq!(presence.statuses[""], "Here!");
410    }
411
412    #[test]
413    fn test_multiple_statuses() {
414        let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
415        let presence = Presence::try_from(elem).unwrap();
416        assert_eq!(presence.payloads.len(), 0);
417        assert_eq!(presence.statuses.len(), 2);
418        assert_eq!(presence.statuses[""], "Here!");
419        assert_eq!(presence.statuses["fr"], "LĂ !");
420    }
421
422    #[test]
423    fn test_invalid_multiple_statuses() {
424        let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
425        let error = Presence::try_from(elem).unwrap_err();
426        let message = match error {
427            Error::ParseError(string) => string,
428            _ => panic!(),
429        };
430        assert_eq!(message, "Status element present twice for the same xml:lang.");
431    }
432
433    #[test]
434    fn test_priority() {
435        let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>".parse().unwrap();
436        let presence = Presence::try_from(elem).unwrap();
437        assert_eq!(presence.payloads.len(), 0);
438        assert_eq!(presence.priority, -1i8);
439    }
440
441    #[test]
442    fn test_invalid_priority() {
443        let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>".parse().unwrap();
444        let error = Presence::try_from(elem).unwrap_err();
445        match error {
446            Error::ParseIntError(_) => (),
447            _ => panic!(),
448        };
449    }
450
451    #[test]
452    fn test_unknown_child() {
453        let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>".parse().unwrap();
454        let presence = Presence::try_from(elem).unwrap();
455        let payload = &presence.payloads[0];
456        assert!(payload.is("test", "invalid"));
457    }
458
459    #[test]
460    fn test_invalid_status_child() {
461        let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>".parse().unwrap();
462        let error = Presence::try_from(elem).unwrap_err();
463        let message = match error {
464            Error::ParseError(string) => string,
465            _ => panic!(),
466        };
467        assert_eq!(message, "Unknown child in status element.");
468    }
469
470    #[test]
471    fn test_invalid_attribute() {
472        let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>".parse().unwrap();
473        let error = Presence::try_from(elem).unwrap_err();
474        let message = match error {
475            Error::ParseError(string) => string,
476            _ => panic!(),
477        };
478        assert_eq!(message, "Unknown attribute in status element.");
479    }
480
481    #[test]
482    fn test_serialise_status() {
483        let status = Status::from("Hello world!");
484        let mut presence = Presence::new(Type::Unavailable);
485        presence.statuses.insert(String::from(""), status);
486        let elem: Element = presence.into();
487        assert!(elem.is("presence", ns::JABBER_CLIENT));
488        assert!(elem.children().next().unwrap().is("status", ns::JABBER_CLIENT));
489    }
490}