roster.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 try_from::TryFrom;
  8use std::str::FromStr;
  9
 10use minidom::{Element, IntoAttributeValue};
 11use jid::Jid;
 12
 13use error::Error;
 14use ns;
 15
 16generate_elem_id!(Group, "group", ns::ROSTER);
 17
 18generate_attribute!(Subscription, "subscription", {
 19    None => "none",
 20    From => "from",
 21    To => "to",
 22    Both => "both",
 23    Remove => "remove",
 24}, Default = None);
 25
 26/// Contact from the user’s contact list.
 27#[derive(Debug, Clone, PartialEq)]
 28pub struct Item {
 29    /// JID of this contact.
 30    pub jid: Jid,
 31
 32    /// Name of this contact.
 33    pub name: Option<String>,
 34
 35    /// Subscription status of this contact.
 36    pub subscription: Subscription,
 37
 38    /// Groups this contact is part of.
 39    pub groups: Vec<Group>,
 40}
 41
 42impl TryFrom<Element> for Item {
 43    type Err = Error;
 44
 45    fn try_from(elem: Element) -> Result<Item, Error> {
 46        if !elem.is("item", ns::ROSTER) {
 47            return Err(Error::ParseError("This is not a roster item element."));
 48        }
 49
 50        let mut item = Item {
 51            jid: get_attr!(elem, "jid", required),
 52            name: get_attr!(elem, "name", optional).and_then(|name| if name == "" { None } else { Some(name) }),
 53            subscription: get_attr!(elem, "subscription", default),
 54            groups: vec!(),
 55        };
 56        for child in elem.children() {
 57            if !child.is("group", ns::ROSTER) {
 58                return Err(Error::ParseError("Unknown element in roster item element."));
 59            }
 60            for _ in child.children() {
 61                return Err(Error::ParseError("Roster item group can’t have children."));
 62            }
 63            for _ in child.attrs() {
 64                return Err(Error::ParseError("Roster item group can’t have attributes."));
 65            }
 66            let group = Group(child.text());
 67            item.groups.push(group);
 68        }
 69        Ok(item)
 70    }
 71}
 72
 73impl From<Item> for Element {
 74    fn from(item: Item) -> Element {
 75        Element::builder("item")
 76                .ns(ns::ROSTER)
 77                .attr("jid", item.jid)
 78                .attr("name", item.name)
 79                .attr("subscription", item.subscription)
 80                .append(item.groups)
 81                .build()
 82    }
 83}
 84
 85/// The contact list of the user.
 86#[derive(Debug, Clone)]
 87pub struct Roster {
 88    /// Version of the contact list.
 89    ///
 90    /// This is an opaque string that should only be sent back to the server on
 91    /// a new connection, if this client is storing the contact list between
 92    /// connections.
 93    pub ver: Option<String>,
 94
 95    /// List of the contacts of the user.
 96    pub items: Vec<Item>,
 97}
 98
 99impl TryFrom<Element> for Roster {
100    type Err = Error;
101
102    fn try_from(elem: Element) -> Result<Roster, Error> {
103        if !elem.is("query", ns::ROSTER) {
104            return Err(Error::ParseError("This is not a roster element."));
105        }
106        for (attr, _) in elem.attrs() {
107            if attr != "ver" {
108                return Err(Error::ParseError("Unknown attribute in roster element."));
109            }
110        }
111
112        let mut roster = Roster {
113            ver: get_attr!(elem, "ver", optional),
114            items: vec!(),
115        };
116        for child in elem.children() {
117            if !child.is("item", ns::ROSTER) {
118                return Err(Error::ParseError("Unknown element in roster element."));
119            }
120            let item = Item::try_from(child.clone())?;
121            roster.items.push(item);
122        }
123        Ok(roster)
124    }
125}
126
127impl From<Roster> for Element {
128    fn from(roster: Roster) -> Element {
129        Element::builder("query")
130                .ns(ns::ROSTER)
131                .attr("ver", roster.ver)
132                .append(roster.items)
133                .build()
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140    use compare_elements::NamespaceAwareCompare;
141
142    #[test]
143    fn test_get() {
144        let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
145        let roster = Roster::try_from(elem).unwrap();
146        assert!(roster.ver.is_none());
147        assert!(roster.items.is_empty());
148    }
149
150    #[test]
151    fn test_result() {
152        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
153        let roster = Roster::try_from(elem).unwrap();
154        assert_eq!(roster.ver, Some(String::from("ver7")));
155        assert_eq!(roster.items.len(), 2);
156
157        let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
158        let roster2 = Roster::try_from(elem2).unwrap();
159        assert_eq!(roster.items, roster2.items);
160
161        let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>".parse().unwrap();
162        let roster = Roster::try_from(elem).unwrap();
163        assert_eq!(roster.ver, Some(String::from("ver9")));
164        assert!(roster.items.is_empty());
165
166        let elem: Element = r#"
167<query xmlns='jabber:iq:roster' ver='ver11'>
168  <item jid='romeo@example.net'
169        name='Romeo'
170        subscription='both'>
171    <group>Friends</group>
172  </item>
173  <item jid='mercutio@example.com'
174        name='Mercutio'
175        subscription='from'/>
176  <item jid='benvolio@example.net'
177        name='Benvolio'
178        subscription='both'/>
179</query>
180"#.parse().unwrap();
181        let roster = Roster::try_from(elem).unwrap();
182        assert_eq!(roster.ver, Some(String::from("ver11")));
183        assert_eq!(roster.items.len(), 3);
184        assert_eq!(roster.items[0].jid, Jid::from_str("romeo@example.net").unwrap());
185        assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
186        assert_eq!(roster.items[0].subscription, Subscription::Both);
187        assert_eq!(roster.items[0].groups, vec!(Group::from_str("Friends").unwrap()));
188    }
189
190    #[test]
191    fn test_multiple_groups() {
192        let elem: Element = r#"
193<query xmlns='jabber:iq:roster'>
194  <item jid='test@example.org'>
195    <group>A</group>
196    <group>B</group>
197  </item>
198</query>
199"#.parse().unwrap();
200        let elem1 = elem.clone();
201        let roster = Roster::try_from(elem).unwrap();
202        assert!(roster.ver.is_none());
203        assert_eq!(roster.items.len(), 1);
204        assert_eq!(roster.items[0].jid, Jid::from_str("test@example.org").unwrap());
205        assert_eq!(roster.items[0].name, None);
206        assert_eq!(roster.items[0].groups.len(), 2);
207        assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
208        assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
209        let elem2 = roster.into();
210        assert!(elem1.compare_to(&elem2));
211    }
212
213    #[test]
214    fn test_set() {
215        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>".parse().unwrap();
216        let roster = Roster::try_from(elem).unwrap();
217        assert!(roster.ver.is_none());
218        assert_eq!(roster.items.len(), 1);
219
220        let elem: Element = r#"
221<query xmlns='jabber:iq:roster'>
222  <item jid='nurse@example.com'
223        name='Nurse'>
224    <group>Servants</group>
225  </item>
226</query>
227"#.parse().unwrap();
228        let roster = Roster::try_from(elem).unwrap();
229        assert!(roster.ver.is_none());
230        assert_eq!(roster.items.len(), 1);
231        assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
232        assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
233        assert_eq!(roster.items[0].groups.len(), 1);
234        assert_eq!(roster.items[0].groups[0], Group::from_str("Servants").unwrap());
235
236        let elem: Element = r#"
237<query xmlns='jabber:iq:roster'>
238  <item jid='nurse@example.com'
239        subscription='remove'/>
240</query>
241"#.parse().unwrap();
242        let roster = Roster::try_from(elem).unwrap();
243        assert!(roster.ver.is_none());
244        assert_eq!(roster.items.len(), 1);
245        assert_eq!(roster.items[0].jid, Jid::from_str("nurse@example.com").unwrap());
246        assert!(roster.items[0].name.is_none());
247        assert!(roster.items[0].groups.is_empty());
248        assert_eq!(roster.items[0].subscription, Subscription::Remove);
249    }
250
251    #[test]
252    fn test_invalid() {
253        let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>".parse().unwrap();
254        let error = Roster::try_from(elem).unwrap_err();
255        let message = match error {
256            Error::ParseError(string) => string,
257            _ => panic!(),
258        };
259        assert_eq!(message, "Unknown element in roster element.");
260
261        let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>".parse().unwrap();
262        let error = Roster::try_from(elem).unwrap_err();
263        let message = match error {
264            Error::ParseError(string) => string,
265            _ => panic!(),
266        };
267        assert_eq!(message, "Unknown attribute in roster element.");
268    }
269
270    #[test]
271    fn test_invalid_item() {
272        let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>".parse().unwrap();
273        let error = Roster::try_from(elem).unwrap_err();
274        let message = match error {
275            Error::ParseError(string) => string,
276            _ => panic!(),
277        };
278        assert_eq!(message, "Required attribute 'jid' missing.");
279
280        /*
281        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
282        let error = Roster::try_from(elem).unwrap_err();
283        let error = match error {
284            Error::JidParseError(error) => error,
285            _ => panic!(),
286        };
287        assert_eq!(error.description(), "Invalid JID, I guess?");
288        */
289
290        let elem: Element = "<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>".parse().unwrap();
291        let error = Roster::try_from(elem).unwrap_err();
292        let message = match error {
293            Error::ParseError(string) => string,
294            _ => panic!(),
295        };
296        assert_eq!(message, "Unknown element in roster item element.");
297    }
298}