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;
8
9use minidom::Element;
10
11use error::Error;
12use ns;
13
14use data_forms::{DataForm, DataFormType};
15
16#[derive(Debug, Clone, PartialEq)]
17pub struct Feature {
18 pub var: String,
19}
20
21#[derive(Debug, Clone)]
22pub struct Identity {
23 pub category: String, // TODO: use an enum here.
24 pub type_: String, // TODO: use an enum here.
25 pub xml_lang: String,
26 pub name: Option<String>,
27}
28
29#[derive(Debug, Clone)]
30pub struct Disco {
31 pub node: Option<String>,
32 pub identities: Vec<Identity>,
33 pub features: Vec<Feature>,
34 pub extensions: Vec<DataForm>,
35}
36
37pub fn parse_disco(root: &Element) -> Result<Disco, Error> {
38 if !root.is("query", ns::DISCO_INFO) {
39 return Err(Error::ParseError("This is not a disco#info element."));
40 }
41
42 let mut identities: Vec<Identity> = vec!();
43 let mut features: Vec<Feature> = vec!();
44 let mut extensions: Vec<DataForm> = vec!();
45
46 let node = root.attr("node")
47 .and_then(|node| node.parse().ok());
48
49 for child in root.children() {
50 if child.is("feature", ns::DISCO_INFO) {
51 let feature = child.attr("var")
52 .ok_or(Error::ParseError("Feature must have a 'var' attribute."))?;
53 features.push(Feature {
54 var: feature.to_owned(),
55 });
56 } else if child.is("identity", ns::DISCO_INFO) {
57 let category = child.attr("category")
58 .ok_or(Error::ParseError("Identity must have a 'category' attribute."))?;
59 if category == "" {
60 return Err(Error::ParseError("Identity must have a non-empty 'category' attribute."))
61 }
62
63 let type_ = child.attr("type")
64 .ok_or(Error::ParseError("Identity must have a 'type' attribute."))?;
65 if type_ == "" {
66 return Err(Error::ParseError("Identity must have a non-empty 'type' attribute."))
67 }
68
69 let xml_lang = child.attr("xml:lang").unwrap_or("");
70 let name = child.attr("name")
71 .and_then(|name| name.parse().ok());
72 identities.push(Identity {
73 category: category.to_owned(),
74 type_: type_.to_owned(),
75 xml_lang: xml_lang.to_owned(),
76 name: name,
77 });
78 } else if child.is("x", ns::DATA_FORMS) {
79 let data_form = DataForm::try_from(child)?;
80 match data_form.type_ {
81 DataFormType::Result_ => (),
82 _ => return Err(Error::ParseError("Data form must have a 'result' type in disco#info.")),
83 }
84 match data_form.form_type {
85 Some(_) => extensions.push(data_form),
86 None => return Err(Error::ParseError("Data form found without a FORM_TYPE.")),
87 }
88 } else {
89 return Err(Error::ParseError("Unknown element in disco#info."));
90 }
91 }
92
93 /*
94 // TODO: encode these restrictions only for result disco#info, not get ones.
95 if identities.is_empty() {
96 return Err(Error::ParseError("There must be at least one identity in disco#info."));
97 }
98 if features.is_empty() {
99 return Err(Error::ParseError("There must be at least one feature in disco#info."));
100 }
101 if !features.contains(&Feature { var: ns::DISCO_INFO.to_owned() }) {
102 return Err(Error::ParseError("disco#info feature not present in disco#info."));
103 }
104 */
105
106 Ok(Disco {
107 node: node,
108 identities: identities,
109 features: features,
110 extensions: extensions
111 })
112}
113
114pub fn serialise_disco(disco: &Disco) -> Element {
115 let mut root = Element::builder("query")
116 .ns(ns::DISCO_INFO)
117 .attr("node", disco.node.clone())
118 .build();
119 for identity in &disco.identities {
120 let identity_element = Element::builder("identity")
121 .ns(ns::DISCO_INFO)
122 .attr("category", identity.category.clone())
123 .attr("type", identity.type_.clone())
124 .attr("xml:lang", identity.xml_lang.clone())
125 .attr("name", identity.name.clone())
126 .build();
127 root.append_child(identity_element);
128 }
129 for feature in &disco.features {
130 let feature_element = Element::builder("feature")
131 .ns(ns::DISCO_INFO)
132 .attr("var", feature.var.clone())
133 .build();
134 root.append_child(feature_element);
135 }
136 for _ in &disco.extensions {
137 panic!("Not yet implemented!");
138 }
139 root
140}
141
142#[cfg(test)]
143mod tests {
144 use minidom::Element;
145 use error::Error;
146 use disco;
147
148 #[test]
149 fn test_simple() {
150 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/><feature var='http://jabber.org/protocol/disco#info'/></query>".parse().unwrap();
151 let query = disco::parse_disco(&elem).unwrap();
152 assert!(query.node.is_none());
153 assert_eq!(query.identities.len(), 1);
154 assert_eq!(query.features.len(), 1);
155 assert!(query.extensions.is_empty());
156 }
157
158 #[test]
159 fn test_invalid() {
160 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><coucou/></query>".parse().unwrap();
161 let error = disco::parse_disco(&elem).unwrap_err();
162 let message = match error {
163 Error::ParseError(string) => string,
164 _ => panic!(),
165 };
166 assert_eq!(message, "Unknown element in disco#info.");
167 }
168
169 #[test]
170 fn test_invalid_identity() {
171 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity/></query>".parse().unwrap();
172 let error = disco::parse_disco(&elem).unwrap_err();
173 let message = match error {
174 Error::ParseError(string) => string,
175 _ => panic!(),
176 };
177 assert_eq!(message, "Identity must have a 'category' attribute.");
178
179 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category=''/></query>".parse().unwrap();
180 let error = disco::parse_disco(&elem).unwrap_err();
181 let message = match error {
182 Error::ParseError(string) => string,
183 _ => panic!(),
184 };
185 assert_eq!(message, "Identity must have a non-empty 'category' attribute.");
186
187 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou'/></query>".parse().unwrap();
188 let error = disco::parse_disco(&elem).unwrap_err();
189 let message = match error {
190 Error::ParseError(string) => string,
191 _ => panic!(),
192 };
193 assert_eq!(message, "Identity must have a 'type' attribute.");
194
195 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='coucou' type=''/></query>".parse().unwrap();
196 let error = disco::parse_disco(&elem).unwrap_err();
197 let message = match error {
198 Error::ParseError(string) => string,
199 _ => panic!(),
200 };
201 assert_eq!(message, "Identity must have a non-empty 'type' attribute.");
202 }
203
204 #[test]
205 fn test_invalid_feature() {
206 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><feature/></query>".parse().unwrap();
207 let error = disco::parse_disco(&elem).unwrap_err();
208 let message = match error {
209 Error::ParseError(string) => string,
210 _ => panic!(),
211 };
212 assert_eq!(message, "Feature must have a 'var' attribute.");
213 }
214
215 #[test]
216 #[ignore]
217 fn test_invalid_result() {
218 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'/>".parse().unwrap();
219 let error = disco::parse_disco(&elem).unwrap_err();
220 let message = match error {
221 Error::ParseError(string) => string,
222 _ => panic!(),
223 };
224 assert_eq!(message, "There must be at least one identity in disco#info.");
225
226 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/></query>".parse().unwrap();
227 let error = disco::parse_disco(&elem).unwrap_err();
228 let message = match error {
229 Error::ParseError(string) => string,
230 _ => panic!(),
231 };
232 assert_eq!(message, "There must be at least one feature in disco#info.");
233
234 let elem: Element = "<query xmlns='http://jabber.org/protocol/disco#info'><identity category='client' type='pc'/><feature var='http://jabber.org/protocol/disco#items'/></query>".parse().unwrap();
235 let error = disco::parse_disco(&elem).unwrap_err();
236 let message = match error {
237 Error::ParseError(string) => string,
238 _ => panic!(),
239 };
240 assert_eq!(message, "disco#info feature not present in disco#info.");
241 }
242}