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