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_require_type() {
231        #[cfg(not(feature = "component"))]
232        let elem: Element = "<iq xmlns='jabber:client'/>".parse().unwrap();
233        #[cfg(feature = "component")]
234        let elem: Element = "<iq xmlns='jabber:component:accept'/>".parse().unwrap();
235        let error = Iq::try_from(elem).unwrap_err();
236        let message = match error {
237            Error::ParseError(string) => string,
238            _ => panic!(),
239        };
240        assert_eq!(message, "Required attribute 'type' missing.");
241    }
242
243    #[test]
244    fn test_get() {
245        #[cfg(not(feature = "component"))]
246        let elem: Element = "<iq xmlns='jabber:client' type='get'>
247            <foo xmlns='bar'/>
248        </iq>".parse().unwrap();
249        #[cfg(feature = "component")]
250        let elem: Element = "<iq xmlns='jabber:component:accept' type='get'>
251            <foo xmlns='bar'/>
252        </iq>".parse().unwrap();
253        let iq = Iq::try_from(elem).unwrap();
254        let query: Element = "<foo xmlns='bar'/>".parse().unwrap();
255        assert_eq!(iq.from, None);
256        assert_eq!(iq.to, None);
257        assert_eq!(iq.id, None);
258        assert!(match iq.payload {
259            IqType::Get(element) => element.compare_to(&query),
260            _ => false
261        });
262    }
263
264    #[test]
265    fn test_set() {
266        #[cfg(not(feature = "component"))]
267        let elem: Element = "<iq xmlns='jabber:client' type='set'>
268            <vCard xmlns='vcard-temp'/>
269        </iq>".parse().unwrap();
270        #[cfg(feature = "component")]
271        let elem: Element = "<iq xmlns='jabber:component:accept' type='set'>
272            <vCard xmlns='vcard-temp'/>
273        </iq>".parse().unwrap();
274        let iq = Iq::try_from(elem).unwrap();
275        let vcard: Element = "<vCard xmlns='vcard-temp'/>".parse().unwrap();
276        assert_eq!(iq.from, None);
277        assert_eq!(iq.to, None);
278        assert_eq!(iq.id, None);
279        assert!(match iq.payload {
280            IqType::Set(element) => element.compare_to(&vcard),
281            _ => false
282        });
283    }
284
285    #[test]
286    fn test_result_empty() {
287        #[cfg(not(feature = "component"))]
288        let elem: Element = "<iq xmlns='jabber:client' type='result'/>".parse().unwrap();
289        #[cfg(feature = "component")]
290        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'/>".parse().unwrap();
291        let iq = Iq::try_from(elem).unwrap();
292        assert_eq!(iq.from, None);
293        assert_eq!(iq.to, None);
294        assert_eq!(iq.id, None);
295        assert!(match iq.payload {
296            IqType::Result(None) => true,
297            _ => false,
298        });
299    }
300
301    #[test]
302    fn test_result() {
303        #[cfg(not(feature = "component"))]
304        let elem: Element = "<iq xmlns='jabber:client' type='result'>
305            <query xmlns='http://jabber.org/protocol/disco#items'/>
306        </iq>".parse().unwrap();
307        #[cfg(feature = "component")]
308        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'>
309            <query xmlns='http://jabber.org/protocol/disco#items'/>
310        </iq>".parse().unwrap();
311        let iq = Iq::try_from(elem).unwrap();
312        let query: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>".parse().unwrap();
313        assert_eq!(iq.from, None);
314        assert_eq!(iq.to, None);
315        assert_eq!(iq.id, None);
316        assert!(match iq.payload {
317            IqType::Result(Some(element)) => element.compare_to(&query),
318            _ => false,
319        });
320    }
321
322    #[test]
323    fn test_error() {
324        #[cfg(not(feature = "component"))]
325        let elem: Element = "<iq xmlns='jabber:client' type='error'>
326            <ping xmlns='urn:xmpp:ping'/>
327            <error type='cancel'>
328                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
329            </error>
330        </iq>".parse().unwrap();
331        #[cfg(feature = "component")]
332        let elem: Element = "<iq xmlns='jabber:component:accept' type='error'>
333            <ping xmlns='urn:xmpp:ping'/>
334            <error type='cancel'>
335                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
336            </error>
337        </iq>".parse().unwrap();
338        let iq = Iq::try_from(elem).unwrap();
339        assert_eq!(iq.from, None);
340        assert_eq!(iq.to, None);
341        assert_eq!(iq.id, None);
342        match iq.payload {
343            IqType::Error(error) => {
344                assert_eq!(error.type_, ErrorType::Cancel);
345                assert_eq!(error.by, None);
346                assert_eq!(error.defined_condition, DefinedCondition::ServiceUnavailable);
347                assert_eq!(error.texts.len(), 0);
348                assert_eq!(error.other, None);
349            },
350            _ => panic!(),
351        }
352    }
353
354    #[test]
355    fn test_children_invalid() {
356        #[cfg(not(feature = "component"))]
357        let elem: Element = "<iq xmlns='jabber:client' type='error'></iq>".parse().unwrap();
358        #[cfg(feature = "component")]
359        let elem: Element = "<iq xmlns='jabber:component:accept' type='error'></iq>".parse().unwrap();
360        let error = Iq::try_from(elem).unwrap_err();
361        let message = match error {
362            Error::ParseError(string) => string,
363            _ => panic!(),
364        };
365        assert_eq!(message, "Wrong number of children in iq element.");
366    }
367
368    #[test]
369    fn test_serialise() {
370        #[cfg(not(feature = "component"))]
371        let elem: Element = "<iq xmlns='jabber:client' type='result'/>".parse().unwrap();
372        #[cfg(feature = "component")]
373        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'/>".parse().unwrap();
374        let iq2 = Iq {
375            from: None,
376            to: None,
377            id: None,
378            payload: IqType::Result(None),
379        };
380        let elem2 = iq2.into();
381        assert_eq!(elem, elem2);
382    }
383
384    #[test]
385    fn test_disco() {
386        #[cfg(not(feature = "component"))]
387        let elem: Element = "<iq xmlns='jabber:client' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
388        #[cfg(feature = "component")]
389        let elem: Element = "<iq xmlns='jabber:component:accept' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
390        let iq = Iq::try_from(elem).unwrap();
391        let disco_info = match iq.payload {
392            IqType::Get(payload) => DiscoInfoQuery::try_from(payload).unwrap(),
393            _ => panic!(),
394        };
395        assert!(disco_info.node.is_none());
396    }
397}