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}