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