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::util::error::Error;
  9use crate::ns;
 10use crate::stanza_error::StanzaError;
 11use jid::Jid;
 12use minidom::Element;
 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)]
 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: 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 `<iq/>` stanza containing a result.
 94    pub fn from_result<S: Into<String>>(id: S, payload: Option<impl IqResultPayload>) -> Iq {
 95        Iq {
 96            from: None,
 97            to: None,
 98            id: id.into(),
 99            payload: IqType::Result(payload.map(Into::into)),
100        }
101    }
102
103    /// Creates an `<iq/>` stanza containing an error.
104    pub fn from_error<S: Into<String>>(id: S, payload: StanzaError) -> Iq {
105        Iq {
106            from: None,
107            to: None,
108            id: id.into(),
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 = id;
128        self
129    }
130}
131
132impl TryFrom<Element> for Iq {
133    type Error = 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", Option);
138        let to = get_attr!(root, "to", Option);
139        let id = get_attr!(root, "id", Required);
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,
192            to,
193            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::util::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, 112);
229        assert_size!(Iq, 204);
230    }
231
232    #[cfg(target_pointer_width = "64")]
233    #[test]
234    fn test_size() {
235        assert_size!(IqType, 224);
236        assert_size!(Iq, 408);
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 'id' missing.");
251
252        #[cfg(not(feature = "component"))]
253        let elem: Element = "<iq xmlns='jabber:client' id='coucou'/>".parse().unwrap();
254        #[cfg(feature = "component")]
255        let elem: Element = "<iq xmlns='jabber:component:accept' id='coucou'/>".parse().unwrap();
256        let error = Iq::try_from(elem).unwrap_err();
257        let message = match error {
258            Error::ParseError(string) => string,
259            _ => panic!(),
260        };
261        assert_eq!(message, "Required attribute 'type' missing.");
262    }
263
264    #[test]
265    fn test_get() {
266        #[cfg(not(feature = "component"))]
267        let elem: Element = "<iq xmlns='jabber:client' type='get' id='foo'>
268            <foo xmlns='bar'/>
269        </iq>"
270            .parse()
271            .unwrap();
272        #[cfg(feature = "component")]
273        let elem: Element = "<iq xmlns='jabber:component:accept' type='get' id='foo'>
274            <foo xmlns='bar'/>
275        </iq>"
276            .parse()
277            .unwrap();
278        let iq = Iq::try_from(elem).unwrap();
279        let query: Element = "<foo xmlns='bar'/>".parse().unwrap();
280        assert_eq!(iq.from, None);
281        assert_eq!(iq.to, None);
282        assert_eq!(&iq.id, "foo");
283        assert!(match iq.payload {
284            IqType::Get(element) => element.compare_to(&query),
285            _ => false,
286        });
287    }
288
289    #[test]
290    fn test_set() {
291        #[cfg(not(feature = "component"))]
292        let elem: Element = "<iq xmlns='jabber:client' type='set' id='vcard'>
293            <vCard xmlns='vcard-temp'/>
294        </iq>"
295            .parse()
296            .unwrap();
297        #[cfg(feature = "component")]
298        let elem: Element = "<iq xmlns='jabber:component:accept' type='set' id='vcard'>
299            <vCard xmlns='vcard-temp'/>
300        </iq>"
301            .parse()
302            .unwrap();
303        let iq = Iq::try_from(elem).unwrap();
304        let vcard: Element = "<vCard xmlns='vcard-temp'/>".parse().unwrap();
305        assert_eq!(iq.from, None);
306        assert_eq!(iq.to, None);
307        assert_eq!(&iq.id, "vcard");
308        assert!(match iq.payload {
309            IqType::Set(element) => element.compare_to(&vcard),
310            _ => false,
311        });
312    }
313
314    #[test]
315    fn test_result_empty() {
316        #[cfg(not(feature = "component"))]
317        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>".parse().unwrap();
318        #[cfg(feature = "component")]
319        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
320            .parse()
321            .unwrap();
322        let iq = Iq::try_from(elem).unwrap();
323        assert_eq!(iq.from, None);
324        assert_eq!(iq.to, None);
325        assert_eq!(&iq.id, "res");
326        assert!(match iq.payload {
327            IqType::Result(None) => true,
328            _ => false,
329        });
330    }
331
332    #[test]
333    fn test_result() {
334        #[cfg(not(feature = "component"))]
335        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'>
336            <query xmlns='http://jabber.org/protocol/disco#items'/>
337        </iq>"
338            .parse()
339            .unwrap();
340        #[cfg(feature = "component")]
341        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'>
342            <query xmlns='http://jabber.org/protocol/disco#items'/>
343        </iq>"
344            .parse()
345            .unwrap();
346        let iq = Iq::try_from(elem).unwrap();
347        let query: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>"
348            .parse()
349            .unwrap();
350        assert_eq!(iq.from, None);
351        assert_eq!(iq.to, None);
352        assert_eq!(&iq.id, "res");
353        assert!(match iq.payload {
354            IqType::Result(Some(element)) => element.compare_to(&query),
355            _ => false,
356        });
357    }
358
359    #[test]
360    fn test_error() {
361        #[cfg(not(feature = "component"))]
362        let elem: Element = "<iq xmlns='jabber:client' type='error' id='err1'>
363            <ping xmlns='urn:xmpp:ping'/>
364            <error type='cancel'>
365                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
366            </error>
367        </iq>"
368            .parse()
369            .unwrap();
370        #[cfg(feature = "component")]
371        let elem: Element = "<iq xmlns='jabber:component:accept' type='error' id='err1'>
372            <ping xmlns='urn:xmpp:ping'/>
373            <error type='cancel'>
374                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
375            </error>
376        </iq>"
377            .parse()
378            .unwrap();
379        let iq = Iq::try_from(elem).unwrap();
380        assert_eq!(iq.from, None);
381        assert_eq!(iq.to, None);
382        assert_eq!(iq.id, "err1");
383        match iq.payload {
384            IqType::Error(error) => {
385                assert_eq!(error.type_, ErrorType::Cancel);
386                assert_eq!(error.by, None);
387                assert_eq!(
388                    error.defined_condition,
389                    DefinedCondition::ServiceUnavailable
390                );
391                assert_eq!(error.texts.len(), 0);
392                assert_eq!(error.other, None);
393            }
394            _ => panic!(),
395        }
396    }
397
398    #[test]
399    fn test_children_invalid() {
400        #[cfg(not(feature = "component"))]
401        let elem: Element = "<iq xmlns='jabber:client' type='error' id='error'/>"
402            .parse()
403            .unwrap();
404        #[cfg(feature = "component")]
405        let elem: Element = "<iq xmlns='jabber:component:accept' type='error' id='error'/>"
406            .parse()
407            .unwrap();
408        let error = Iq::try_from(elem).unwrap_err();
409        let message = match error {
410            Error::ParseError(string) => string,
411            _ => panic!(),
412        };
413        assert_eq!(message, "Wrong number of children in iq element.");
414    }
415
416    #[test]
417    fn test_serialise() {
418        #[cfg(not(feature = "component"))]
419        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>".parse().unwrap();
420        #[cfg(feature = "component")]
421        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
422            .parse()
423            .unwrap();
424        let iq2 = Iq {
425            from: None,
426            to: None,
427            id: String::from("res"),
428            payload: IqType::Result(None),
429        };
430        let elem2 = iq2.into();
431        assert_eq!(elem, elem2);
432    }
433
434    #[test]
435    fn test_disco() {
436        #[cfg(not(feature = "component"))]
437        let elem: Element = "<iq xmlns='jabber:client' type='get' id='disco'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
438        #[cfg(feature = "component")]
439        let elem: Element = "<iq xmlns='jabber:component:accept' type='get' id='disco'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
440        let iq = Iq::try_from(elem).unwrap();
441        let disco_info = match iq.payload {
442            IqType::Get(payload) => DiscoInfoQuery::try_from(payload).unwrap(),
443            _ => panic!(),
444        };
445        assert!(disco_info.node.is_none());
446    }
447}