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