1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8use try_from::TryFrom;
9
10use minidom::Element;
11use minidom::IntoAttributeValue;
12
13use jid::Jid;
14
15use error::Error;
16
17use ns;
18
19use stanza_error::StanzaError;
20
21/// Should be implemented on every known payload of an `<iq type='get'/>`.
22pub trait IqGetPayload: TryFrom<Element> + Into<Element> {}
23
24/// Should be implemented on every known payload of an `<iq type='set'/>`.
25pub trait IqSetPayload: TryFrom<Element> + Into<Element> {}
26
27/// Should be implemented on every known payload of an `<iq type='result'/>`.
28pub trait IqResultPayload: TryFrom<Element> + Into<Element> {}
29
30#[derive(Debug, Clone)]
31pub enum IqType {
32 Get(Element),
33 Set(Element),
34 Result(Option<Element>),
35 Error(StanzaError),
36}
37
38impl<'a> IntoAttributeValue for &'a IqType {
39 fn into_attribute_value(self) -> Option<String> {
40 Some(match *self {
41 IqType::Get(_) => "get",
42 IqType::Set(_) => "set",
43 IqType::Result(_) => "result",
44 IqType::Error(_) => "error",
45 }.to_owned())
46 }
47}
48
49/// The main structure representing the `<iq/>` stanza.
50#[derive(Debug, Clone)]
51pub struct Iq {
52 pub from: Option<Jid>,
53 pub to: Option<Jid>,
54 pub id: Option<String>,
55 pub payload: IqType,
56}
57
58impl Iq {
59 pub fn from_get(payload: impl IqGetPayload) -> Iq {
60 Iq {
61 from: None,
62 to: None,
63 id: None,
64 payload: IqType::Get(payload.into()),
65 }
66 }
67
68 pub fn from_set(payload: impl IqSetPayload) -> Iq {
69 Iq {
70 from: None,
71 to: None,
72 id: None,
73 payload: IqType::Set(payload.into()),
74 }
75 }
76
77 pub fn from_result(payload: Option<impl IqResultPayload>) -> Iq {
78 Iq {
79 from: None,
80 to: None,
81 id: None,
82 payload: IqType::Result(payload.map(|payload| payload.into())),
83 }
84 }
85
86 pub fn from_error(payload: StanzaError) -> Iq {
87 Iq {
88 from: None,
89 to: None,
90 id: None,
91 payload: IqType::Error(payload),
92 }
93 }
94
95 pub fn with_to(mut self, to: Jid) -> Iq {
96 self.to = Some(to);
97 self
98 }
99
100 pub fn with_from(mut self, from: Jid) -> Iq {
101 self.from = Some(from);
102 self
103 }
104
105 pub fn with_id(mut self, id: String) -> Iq {
106 self.id = Some(id);
107 self
108 }
109}
110
111impl TryFrom<Element> for Iq {
112 type Err = Error;
113
114 fn try_from(root: Element) -> Result<Iq, Error> {
115 check_self!(root, "iq", DEFAULT_NS);
116 let from = get_attr!(root, "from", optional);
117 let to = get_attr!(root, "to", optional);
118 let id = get_attr!(root, "id", optional);
119 let type_: String = get_attr!(root, "type", required);
120
121 let mut payload = None;
122 let mut error_payload = None;
123 for elem in root.children() {
124 if payload.is_some() {
125 return Err(Error::ParseError("Wrong number of children in iq element."));
126 }
127 if type_ == "error" {
128 if elem.is("error", ns::DEFAULT_NS) {
129 if error_payload.is_some() {
130 return Err(Error::ParseError("Wrong number of children in iq element."));
131 }
132 error_payload = Some(StanzaError::try_from(elem.clone())?);
133 } else if root.children().count() != 2 {
134 return Err(Error::ParseError("Wrong number of children in iq element."));
135 }
136 } else {
137 payload = Some(elem.clone());
138 }
139 }
140
141 let type_ = if type_ == "get" {
142 if let Some(payload) = payload {
143 IqType::Get(payload)
144 } else {
145 return Err(Error::ParseError("Wrong number of children in iq element."));
146 }
147 } else if type_ == "set" {
148 if let Some(payload) = payload {
149 IqType::Set(payload)
150 } else {
151 return Err(Error::ParseError("Wrong number of children in iq element."));
152 }
153 } else if type_ == "result" {
154 if let Some(payload) = payload {
155 IqType::Result(Some(payload))
156 } else {
157 IqType::Result(None)
158 }
159 } else if type_ == "error" {
160 if let Some(payload) = error_payload {
161 IqType::Error(payload)
162 } else {
163 return Err(Error::ParseError("Wrong number of children in iq element."));
164 }
165 } else {
166 return Err(Error::ParseError("Unknown iq type."));
167 };
168
169 Ok(Iq {
170 from: from,
171 to: to,
172 id: id,
173 payload: type_,
174 })
175 }
176}
177
178impl From<Iq> for Element {
179 fn from(iq: Iq) -> Element {
180 let mut stanza = Element::builder("iq")
181 .ns(ns::DEFAULT_NS)
182 .attr("from", iq.from)
183 .attr("to", iq.to)
184 .attr("id", iq.id)
185 .attr("type", &iq.payload)
186 .build();
187 let elem = match iq.payload {
188 IqType::Get(elem)
189 | IqType::Set(elem)
190 | IqType::Result(Some(elem)) => elem,
191 IqType::Error(error) => error.into(),
192 IqType::Result(None) => return stanza,
193 };
194 stanza.append_child(elem);
195 stanza
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use stanza_error::{ErrorType, DefinedCondition};
203 use compare_elements::NamespaceAwareCompare;
204 use disco::DiscoInfoQuery;
205
206 #[test]
207 fn test_require_type() {
208 #[cfg(not(feature = "component"))]
209 let elem: Element = "<iq xmlns='jabber:client'/>".parse().unwrap();
210 #[cfg(feature = "component")]
211 let elem: Element = "<iq xmlns='jabber:component:accept'/>".parse().unwrap();
212 let error = Iq::try_from(elem).unwrap_err();
213 let message = match error {
214 Error::ParseError(string) => string,
215 _ => panic!(),
216 };
217 assert_eq!(message, "Required attribute 'type' missing.");
218 }
219
220 #[test]
221 fn test_get() {
222 #[cfg(not(feature = "component"))]
223 let elem: Element = "<iq xmlns='jabber:client' type='get'>
224 <foo xmlns='bar'/>
225 </iq>".parse().unwrap();
226 #[cfg(feature = "component")]
227 let elem: Element = "<iq xmlns='jabber:component:accept' type='get'>
228 <foo xmlns='bar'/>
229 </iq>".parse().unwrap();
230 let iq = Iq::try_from(elem).unwrap();
231 let query: Element = "<foo xmlns='bar'/>".parse().unwrap();
232 assert_eq!(iq.from, None);
233 assert_eq!(iq.to, None);
234 assert_eq!(iq.id, None);
235 assert!(match iq.payload {
236 IqType::Get(element) => element.compare_to(&query),
237 _ => false
238 });
239 }
240
241 #[test]
242 fn test_set() {
243 #[cfg(not(feature = "component"))]
244 let elem: Element = "<iq xmlns='jabber:client' type='set'>
245 <vCard xmlns='vcard-temp'/>
246 </iq>".parse().unwrap();
247 #[cfg(feature = "component")]
248 let elem: Element = "<iq xmlns='jabber:component:accept' type='set'>
249 <vCard xmlns='vcard-temp'/>
250 </iq>".parse().unwrap();
251 let iq = Iq::try_from(elem).unwrap();
252 let vcard: Element = "<vCard xmlns='vcard-temp'/>".parse().unwrap();
253 assert_eq!(iq.from, None);
254 assert_eq!(iq.to, None);
255 assert_eq!(iq.id, None);
256 assert!(match iq.payload {
257 IqType::Set(element) => element.compare_to(&vcard),
258 _ => false
259 });
260 }
261
262 #[test]
263 fn test_result_empty() {
264 #[cfg(not(feature = "component"))]
265 let elem: Element = "<iq xmlns='jabber:client' type='result'/>".parse().unwrap();
266 #[cfg(feature = "component")]
267 let elem: Element = "<iq xmlns='jabber:component:accept' type='result'/>".parse().unwrap();
268 let iq = Iq::try_from(elem).unwrap();
269 assert_eq!(iq.from, None);
270 assert_eq!(iq.to, None);
271 assert_eq!(iq.id, None);
272 assert!(match iq.payload {
273 IqType::Result(None) => true,
274 _ => false,
275 });
276 }
277
278 #[test]
279 fn test_result() {
280 #[cfg(not(feature = "component"))]
281 let elem: Element = "<iq xmlns='jabber:client' type='result'>
282 <query xmlns='http://jabber.org/protocol/disco#items'/>
283 </iq>".parse().unwrap();
284 #[cfg(feature = "component")]
285 let elem: Element = "<iq xmlns='jabber:component:accept' type='result'>
286 <query xmlns='http://jabber.org/protocol/disco#items'/>
287 </iq>".parse().unwrap();
288 let iq = Iq::try_from(elem).unwrap();
289 let query: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>".parse().unwrap();
290 assert_eq!(iq.from, None);
291 assert_eq!(iq.to, None);
292 assert_eq!(iq.id, None);
293 assert!(match iq.payload {
294 IqType::Result(Some(element)) => element.compare_to(&query),
295 _ => false,
296 });
297 }
298
299 #[test]
300 fn test_error() {
301 #[cfg(not(feature = "component"))]
302 let elem: Element = "<iq xmlns='jabber:client' type='error'>
303 <ping xmlns='urn:xmpp:ping'/>
304 <error type='cancel'>
305 <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
306 </error>
307 </iq>".parse().unwrap();
308 #[cfg(feature = "component")]
309 let elem: Element = "<iq xmlns='jabber:component:accept' type='error'>
310 <ping xmlns='urn:xmpp:ping'/>
311 <error type='cancel'>
312 <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
313 </error>
314 </iq>".parse().unwrap();
315 let iq = Iq::try_from(elem).unwrap();
316 assert_eq!(iq.from, None);
317 assert_eq!(iq.to, None);
318 assert_eq!(iq.id, None);
319 match iq.payload {
320 IqType::Error(error) => {
321 assert_eq!(error.type_, ErrorType::Cancel);
322 assert_eq!(error.by, None);
323 assert_eq!(error.defined_condition, DefinedCondition::ServiceUnavailable);
324 assert_eq!(error.texts.len(), 0);
325 assert_eq!(error.other, None);
326 },
327 _ => panic!(),
328 }
329 }
330
331 #[test]
332 fn test_children_invalid() {
333 #[cfg(not(feature = "component"))]
334 let elem: Element = "<iq xmlns='jabber:client' type='error'></iq>".parse().unwrap();
335 #[cfg(feature = "component")]
336 let elem: Element = "<iq xmlns='jabber:component:accept' type='error'></iq>".parse().unwrap();
337 let error = Iq::try_from(elem).unwrap_err();
338 let message = match error {
339 Error::ParseError(string) => string,
340 _ => panic!(),
341 };
342 assert_eq!(message, "Wrong number of children in iq element.");
343 }
344
345 #[test]
346 fn test_serialise() {
347 #[cfg(not(feature = "component"))]
348 let elem: Element = "<iq xmlns='jabber:client' type='result'/>".parse().unwrap();
349 #[cfg(feature = "component")]
350 let elem: Element = "<iq xmlns='jabber:component:accept' type='result'/>".parse().unwrap();
351 let iq2 = Iq {
352 from: None,
353 to: None,
354 id: None,
355 payload: IqType::Result(None),
356 };
357 let elem2 = iq2.into();
358 assert_eq!(elem, elem2);
359 }
360
361 #[test]
362 fn test_disco() {
363 #[cfg(not(feature = "component"))]
364 let elem: Element = "<iq xmlns='jabber:client' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
365 #[cfg(feature = "component")]
366 let elem: Element = "<iq xmlns='jabber:component:accept' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
367 let iq = Iq::try_from(elem).unwrap();
368 let disco_info = match iq.payload {
369 IqType::Get(payload) => DiscoInfoQuery::try_from(payload).unwrap(),
370 _ => panic!(),
371 };
372 assert!(disco_info.node.is_none());
373 }
374}