xmpp_parsers/
iq.rs

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