iq.rs

  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}