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 crate::util::error::Error;
 11use crate::Element;
 12use jid::Jid;
 13use minidom::IntoAttributeValue;
 14use std::convert::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, PartialEq)]
 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, PartialEq)]
 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: 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<S: Into<String>>(id: S, payload: impl IqGetPayload) -> Iq {
 75        Iq {
 76            from: None,
 77            to: None,
 78            id: id.into(),
 79            payload: IqType::Get(payload.into()),
 80        }
 81    }
 82
 83    /// Creates an `<iq/>` stanza containing a set request.
 84    pub fn from_set<S: Into<String>>(id: S, payload: impl IqSetPayload) -> Iq {
 85        Iq {
 86            from: None,
 87            to: None,
 88            id: id.into(),
 89            payload: IqType::Set(payload.into()),
 90        }
 91    }
 92
 93    /// Creates an empty `<iq type="result"/>` stanza.
 94    pub fn empty_result<S: Into<String>>(to: Jid, id: S) -> Iq {
 95        Iq {
 96            from: None,
 97            to: Some(to),
 98            id: id.into(),
 99            payload: IqType::Result(None),
100        }
101    }
102
103    /// Creates an `<iq/>` stanza containing a result.
104    pub fn from_result<S: Into<String>>(id: S, payload: Option<impl IqResultPayload>) -> Iq {
105        Iq {
106            from: None,
107            to: None,
108            id: id.into(),
109            payload: IqType::Result(payload.map(Into::into)),
110        }
111    }
112
113    /// Creates an `<iq/>` stanza containing an error.
114    pub fn from_error<S: Into<String>>(id: S, payload: StanzaError) -> Iq {
115        Iq {
116            from: None,
117            to: None,
118            id: id.into(),
119            payload: IqType::Error(payload),
120        }
121    }
122
123    /// Sets the recipient of this stanza.
124    pub fn with_to(mut self, to: Jid) -> Iq {
125        self.to = Some(to);
126        self
127    }
128
129    /// Sets the emitter of this stanza.
130    pub fn with_from(mut self, from: Jid) -> Iq {
131        self.from = Some(from);
132        self
133    }
134
135    /// Sets the id of this stanza, in order to later match its response.
136    pub fn with_id(mut self, id: String) -> Iq {
137        self.id = id;
138        self
139    }
140}
141
142impl TryFrom<Element> for Iq {
143    type Error = Error;
144
145    fn try_from(root: Element) -> Result<Iq, Error> {
146        check_self!(root, "iq", DEFAULT_NS);
147        let from = get_attr!(root, "from", Option);
148        let to = get_attr!(root, "to", Option);
149        let id = get_attr!(root, "id", Required);
150        let type_: String = get_attr!(root, "type", Required);
151
152        let mut payload = None;
153        let mut error_payload = None;
154        for elem in root.children() {
155            if payload.is_some() {
156                return Err(Error::ParseError("Wrong number of children in iq element."));
157            }
158            if type_ == "error" {
159                if elem.is("error", ns::DEFAULT_NS) {
160                    if error_payload.is_some() {
161                        return Err(Error::ParseError("Wrong number of children in iq element."));
162                    }
163                    error_payload = Some(StanzaError::try_from(elem.clone())?);
164                } else if root.children().count() != 2 {
165                    return Err(Error::ParseError("Wrong number of children in iq element."));
166                }
167            } else {
168                payload = Some(elem.clone());
169            }
170        }
171
172        let type_ = if type_ == "get" {
173            if let Some(payload) = payload {
174                IqType::Get(payload)
175            } else {
176                return Err(Error::ParseError("Wrong number of children in iq element."));
177            }
178        } else if type_ == "set" {
179            if let Some(payload) = payload {
180                IqType::Set(payload)
181            } else {
182                return Err(Error::ParseError("Wrong number of children in iq element."));
183            }
184        } else if type_ == "result" {
185            if let Some(payload) = payload {
186                IqType::Result(Some(payload))
187            } else {
188                IqType::Result(None)
189            }
190        } else if type_ == "error" {
191            if let Some(payload) = error_payload {
192                IqType::Error(payload)
193            } else {
194                return Err(Error::ParseError("Wrong number of children in iq element."));
195            }
196        } else {
197            return Err(Error::ParseError("Unknown iq type."));
198        };
199
200        Ok(Iq {
201            from,
202            to,
203            id,
204            payload: type_,
205        })
206    }
207}
208
209impl From<Iq> for Element {
210    fn from(iq: Iq) -> Element {
211        let mut stanza = Element::builder("iq", ns::DEFAULT_NS)
212            .attr("from", iq.from)
213            .attr("to", iq.to)
214            .attr("id", iq.id)
215            .attr("type", &iq.payload)
216            .build();
217        let elem = match iq.payload {
218            IqType::Get(elem) | IqType::Set(elem) | IqType::Result(Some(elem)) => elem,
219            IqType::Error(error) => error.into(),
220            IqType::Result(None) => return stanza,
221        };
222        stanza.append_child(elem);
223        stanza
224    }
225}
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230    use crate::disco::DiscoInfoQuery;
231    use crate::stanza_error::{DefinedCondition, ErrorType};
232
233    #[cfg(target_pointer_width = "32")]
234    #[test]
235    fn test_size() {
236        assert_size!(IqType, 120);
237        assert_size!(Iq, 212);
238    }
239
240    #[cfg(target_pointer_width = "64")]
241    #[test]
242    fn test_size() {
243        assert_size!(IqType, 232);
244        assert_size!(Iq, 416);
245    }
246
247    #[test]
248    fn test_require_type() {
249        #[cfg(not(feature = "component"))]
250        let elem: Element = "<iq xmlns='jabber:client'/>".parse().unwrap();
251        #[cfg(feature = "component")]
252        let elem: Element = "<iq xmlns='jabber:component:accept'/>".parse().unwrap();
253        let error = Iq::try_from(elem).unwrap_err();
254        let message = match error {
255            Error::ParseError(string) => string,
256            _ => panic!(),
257        };
258        assert_eq!(message, "Required attribute 'id' missing.");
259
260        #[cfg(not(feature = "component"))]
261        let elem: Element = "<iq xmlns='jabber:client' id='coucou'/>".parse().unwrap();
262        #[cfg(feature = "component")]
263        let elem: Element = "<iq xmlns='jabber:component:accept' id='coucou'/>"
264            .parse()
265            .unwrap();
266        let error = Iq::try_from(elem).unwrap_err();
267        let message = match error {
268            Error::ParseError(string) => string,
269            _ => panic!(),
270        };
271        assert_eq!(message, "Required attribute 'type' missing.");
272    }
273
274    #[test]
275    fn test_get() {
276        #[cfg(not(feature = "component"))]
277        let elem: Element = "<iq xmlns='jabber:client' type='get' id='foo'>
278            <foo xmlns='bar'/>
279        </iq>"
280            .parse()
281            .unwrap();
282        #[cfg(feature = "component")]
283        let elem: Element = "<iq xmlns='jabber:component:accept' type='get' id='foo'>
284            <foo xmlns='bar'/>
285        </iq>"
286            .parse()
287            .unwrap();
288        let iq = Iq::try_from(elem).unwrap();
289        let query: Element = "<foo xmlns='bar'/>".parse().unwrap();
290        assert_eq!(iq.from, None);
291        assert_eq!(iq.to, None);
292        assert_eq!(&iq.id, "foo");
293        assert!(match iq.payload {
294            IqType::Get(element) => element == query,
295            _ => false,
296        });
297    }
298
299    #[test]
300    fn test_set() {
301        #[cfg(not(feature = "component"))]
302        let elem: Element = "<iq xmlns='jabber:client' type='set' id='vcard'>
303            <vCard xmlns='vcard-temp'/>
304        </iq>"
305            .parse()
306            .unwrap();
307        #[cfg(feature = "component")]
308        let elem: Element = "<iq xmlns='jabber:component:accept' type='set' id='vcard'>
309            <vCard xmlns='vcard-temp'/>
310        </iq>"
311            .parse()
312            .unwrap();
313        let iq = Iq::try_from(elem).unwrap();
314        let vcard: Element = "<vCard xmlns='vcard-temp'/>".parse().unwrap();
315        assert_eq!(iq.from, None);
316        assert_eq!(iq.to, None);
317        assert_eq!(&iq.id, "vcard");
318        assert!(match iq.payload {
319            IqType::Set(element) => element == vcard,
320            _ => false,
321        });
322    }
323
324    #[test]
325    fn test_result_empty() {
326        #[cfg(not(feature = "component"))]
327        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>"
328            .parse()
329            .unwrap();
330        #[cfg(feature = "component")]
331        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
332            .parse()
333            .unwrap();
334        let iq = Iq::try_from(elem).unwrap();
335        assert_eq!(iq.from, None);
336        assert_eq!(iq.to, None);
337        assert_eq!(&iq.id, "res");
338        assert!(match iq.payload {
339            IqType::Result(None) => true,
340            _ => false,
341        });
342    }
343
344    #[test]
345    fn test_result() {
346        #[cfg(not(feature = "component"))]
347        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'>
348            <query xmlns='http://jabber.org/protocol/disco#items'/>
349        </iq>"
350            .parse()
351            .unwrap();
352        #[cfg(feature = "component")]
353        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'>
354            <query xmlns='http://jabber.org/protocol/disco#items'/>
355        </iq>"
356            .parse()
357            .unwrap();
358        let iq = Iq::try_from(elem).unwrap();
359        let query: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>"
360            .parse()
361            .unwrap();
362        assert_eq!(iq.from, None);
363        assert_eq!(iq.to, None);
364        assert_eq!(&iq.id, "res");
365        assert!(match iq.payload {
366            IqType::Result(Some(element)) => element == query,
367            _ => false,
368        });
369    }
370
371    #[test]
372    fn test_error() {
373        #[cfg(not(feature = "component"))]
374        let elem: Element = "<iq xmlns='jabber:client' type='error' id='err1'>
375            <ping xmlns='urn:xmpp:ping'/>
376            <error type='cancel'>
377                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
378            </error>
379        </iq>"
380            .parse()
381            .unwrap();
382        #[cfg(feature = "component")]
383        let elem: Element = "<iq xmlns='jabber:component:accept' type='error' id='err1'>
384            <ping xmlns='urn:xmpp:ping'/>
385            <error type='cancel'>
386                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
387            </error>
388        </iq>"
389            .parse()
390            .unwrap();
391        let iq = Iq::try_from(elem).unwrap();
392        assert_eq!(iq.from, None);
393        assert_eq!(iq.to, None);
394        assert_eq!(iq.id, "err1");
395        match iq.payload {
396            IqType::Error(error) => {
397                assert_eq!(error.type_, ErrorType::Cancel);
398                assert_eq!(error.by, None);
399                assert_eq!(
400                    error.defined_condition,
401                    DefinedCondition::ServiceUnavailable
402                );
403                assert_eq!(error.texts.len(), 0);
404                assert_eq!(error.other, None);
405            }
406            _ => panic!(),
407        }
408    }
409
410    #[test]
411    fn test_children_invalid() {
412        #[cfg(not(feature = "component"))]
413        let elem: Element = "<iq xmlns='jabber:client' type='error' id='error'/>"
414            .parse()
415            .unwrap();
416        #[cfg(feature = "component")]
417        let elem: Element = "<iq xmlns='jabber:component:accept' type='error' id='error'/>"
418            .parse()
419            .unwrap();
420        let error = Iq::try_from(elem).unwrap_err();
421        let message = match error {
422            Error::ParseError(string) => string,
423            _ => panic!(),
424        };
425        assert_eq!(message, "Wrong number of children in iq element.");
426    }
427
428    #[test]
429    fn test_serialise() {
430        #[cfg(not(feature = "component"))]
431        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>"
432            .parse()
433            .unwrap();
434        #[cfg(feature = "component")]
435        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
436            .parse()
437            .unwrap();
438        let iq2 = Iq {
439            from: None,
440            to: None,
441            id: String::from("res"),
442            payload: IqType::Result(None),
443        };
444        let elem2 = iq2.into();
445        assert_eq!(elem, elem2);
446    }
447
448    #[test]
449    fn test_disco() {
450        #[cfg(not(feature = "component"))]
451        let elem: Element = "<iq xmlns='jabber:client' type='get' id='disco'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
452        #[cfg(feature = "component")]
453        let elem: Element = "<iq xmlns='jabber:component:accept' type='get' id='disco'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
454        let iq = Iq::try_from(elem).unwrap();
455        let disco_info = match iq.payload {
456            IqType::Get(payload) => DiscoInfoQuery::try_from(payload).unwrap(),
457            _ => panic!(),
458        };
459        assert!(disco_info.node.is_none());
460    }
461}