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