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