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