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;
 20use roster::Roster;
 21use disco::{DiscoInfoResult, DiscoInfoQuery};
 22use ibb::{Open as IbbOpen, Data as IbbData, Close as IbbClose};
 23use jingle::Jingle;
 24use ping::Ping;
 25use mam::{Query as MamQuery, Fin as MamFin, Prefs as MamPrefs};
 26
 27/// Lists every known payload of an `<iq type='get'/>`.
 28#[derive(Debug, Clone)]
 29pub enum IqGetPayload {
 30    Roster(Roster),
 31    DiscoInfo(DiscoInfoQuery),
 32    Ping(Ping),
 33    MamQuery(MamQuery),
 34    MamPrefs(MamPrefs),
 35
 36    Unknown(Element),
 37}
 38
 39/// Lists every known payload of an `<iq type='set'/>`.
 40#[derive(Debug, Clone)]
 41pub enum IqSetPayload {
 42    Roster(Roster),
 43    IbbOpen(IbbOpen),
 44    IbbData(IbbData),
 45    IbbClose(IbbClose),
 46    Jingle(Jingle),
 47    MamQuery(MamQuery),
 48    MamPrefs(MamPrefs),
 49
 50    Unknown(Element),
 51}
 52
 53/// Lists every known payload of an `<iq type='result'/>`.
 54#[derive(Debug, Clone)]
 55pub enum IqResultPayload {
 56    Roster(Roster),
 57    DiscoInfo(DiscoInfoResult),
 58    MamQuery(MamQuery),
 59    MamFin(MamFin),
 60    MamPrefs(MamPrefs),
 61
 62    Unknown(Element),
 63}
 64
 65impl TryFrom<Element> for IqGetPayload {
 66    type Err = Error;
 67
 68    fn try_from(elem: Element) -> Result<IqGetPayload, Error> {
 69        Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
 70            // RFC-6121
 71            ("query", ns::ROSTER) => IqGetPayload::Roster(Roster::try_from(elem)?),
 72
 73            // XEP-0030
 74            ("query", ns::DISCO_INFO) => IqGetPayload::DiscoInfo(DiscoInfoQuery::try_from(elem)?),
 75
 76            // XEP-0199
 77            ("ping", ns::PING) => IqGetPayload::Ping(Ping::try_from(elem)?),
 78
 79            // XEP-0313
 80            ("query", ns::MAM) => IqGetPayload::MamQuery(MamQuery::try_from(elem)?),
 81            ("prefs", ns::MAM) => IqGetPayload::MamPrefs(MamPrefs::try_from(elem)?),
 82
 83            _ => IqGetPayload::Unknown(elem),
 84        })
 85    }
 86}
 87
 88impl From<IqGetPayload> for Element {
 89    fn from(payload: IqGetPayload) -> Element {
 90        match payload {
 91            IqGetPayload::Roster(roster) => roster.into(),
 92            IqGetPayload::DiscoInfo(disco) => disco.into(),
 93            IqGetPayload::Ping(ping) => ping.into(),
 94            IqGetPayload::MamQuery(query) => query.into(),
 95            IqGetPayload::MamPrefs(prefs) => prefs.into(),
 96
 97            IqGetPayload::Unknown(elem) => elem,
 98        }
 99    }
100}
101
102impl TryFrom<Element> for IqSetPayload {
103    type Err = Error;
104
105    fn try_from(elem: Element) -> Result<IqSetPayload, Error> {
106        Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
107            // RFC-6121
108            ("query", ns::ROSTER) => IqSetPayload::Roster(Roster::try_from(elem)?),
109
110            // XEP-0047
111            ("open", ns::IBB) => IqSetPayload::IbbOpen(IbbOpen::try_from(elem)?),
112            ("data", ns::IBB) => IqSetPayload::IbbData(IbbData::try_from(elem)?),
113            ("close", ns::IBB) => IqSetPayload::IbbClose(IbbClose::try_from(elem)?),
114
115            // XEP-0166
116            ("jingle", ns::JINGLE) => IqSetPayload::Jingle(Jingle::try_from(elem)?),
117
118            // XEP-0313
119            ("query", ns::MAM) => IqSetPayload::MamQuery(MamQuery::try_from(elem)?),
120            ("prefs", ns::MAM) => IqSetPayload::MamPrefs(MamPrefs::try_from(elem)?),
121
122            _ => IqSetPayload::Unknown(elem),
123        })
124    }
125}
126
127impl From<IqSetPayload> for Element {
128    fn from(payload: IqSetPayload) -> Element {
129        match payload {
130            IqSetPayload::Roster(roster) => roster.into(),
131            IqSetPayload::IbbOpen(open) => open.into(),
132            IqSetPayload::IbbData(data) => data.into(),
133            IqSetPayload::IbbClose(close) => close.into(),
134            IqSetPayload::Jingle(jingle) => jingle.into(),
135            IqSetPayload::MamQuery(query) => query.into(),
136            IqSetPayload::MamPrefs(prefs) => prefs.into(),
137
138            IqSetPayload::Unknown(elem) => elem,
139        }
140    }
141}
142
143impl TryFrom<Element> for IqResultPayload {
144    type Err = Error;
145
146    fn try_from(elem: Element) -> Result<IqResultPayload, Error> {
147        Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
148            // RFC-6121
149            ("query", ns::ROSTER) => IqResultPayload::Roster(Roster::try_from(elem)?),
150
151            // XEP-0030
152            ("query", ns::DISCO_INFO) => IqResultPayload::DiscoInfo(DiscoInfoResult::try_from(elem)?),
153
154            // XEP-0313
155            ("query", ns::MAM) => IqResultPayload::MamQuery(MamQuery::try_from(elem)?),
156            ("fin", ns::MAM) => IqResultPayload::MamFin(MamFin::try_from(elem)?),
157            ("prefs", ns::MAM) => IqResultPayload::MamPrefs(MamPrefs::try_from(elem)?),
158
159            _ => IqResultPayload::Unknown(elem),
160        })
161    }
162}
163
164impl From<IqResultPayload> for Element {
165    fn from(payload: IqResultPayload) -> Element {
166        match payload {
167            IqResultPayload::Roster(roster) => roster.into(),
168            IqResultPayload::DiscoInfo(disco) => disco.into(),
169            IqResultPayload::MamQuery(query) => query.into(),
170            IqResultPayload::MamFin(fin) => fin.into(),
171            IqResultPayload::MamPrefs(prefs) => prefs.into(),
172
173            IqResultPayload::Unknown(elem) => elem,
174        }
175    }
176}
177
178#[derive(Debug, Clone)]
179pub enum IqType {
180    Get(Element),
181    Set(Element),
182    Result(Option<Element>),
183    Error(StanzaError),
184}
185
186impl<'a> IntoAttributeValue for &'a IqType {
187    fn into_attribute_value(self) -> Option<String> {
188        Some(match *self {
189            IqType::Get(_) => "get",
190            IqType::Set(_) => "set",
191            IqType::Result(_) => "result",
192            IqType::Error(_) => "error",
193        }.to_owned())
194    }
195}
196
197/// The main structure representing the `<iq/>` stanza.
198#[derive(Debug, Clone)]
199pub struct Iq {
200    pub from: Option<Jid>,
201    pub to: Option<Jid>,
202    pub id: Option<String>,
203    pub payload: IqType,
204}
205
206impl TryFrom<Element> for Iq {
207    type Err = Error;
208
209    fn try_from(root: Element) -> Result<Iq, Error> {
210        check_self!(root, "iq", ns::DEFAULT_NS);
211        let from = get_attr!(root, "from", optional);
212        let to = get_attr!(root, "to", optional);
213        let id = get_attr!(root, "id", optional);
214        let type_: String = get_attr!(root, "type", required);
215
216        let mut payload = None;
217        let mut error_payload = None;
218        for elem in root.children() {
219            if payload.is_some() {
220                return Err(Error::ParseError("Wrong number of children in iq element."));
221            }
222            if type_ == "error" {
223                if elem.is("error", ns::DEFAULT_NS) {
224                    if error_payload.is_some() {
225                        return Err(Error::ParseError("Wrong number of children in iq element."));
226                    }
227                    error_payload = Some(StanzaError::try_from(elem.clone())?);
228                } else if root.children().count() != 2 {
229                    return Err(Error::ParseError("Wrong number of children in iq element."));
230                }
231            } else {
232                payload = Some(elem.clone());
233            }
234        }
235
236        let type_ = if type_ == "get" {
237            if let Some(payload) = payload {
238                IqType::Get(payload)
239            } else {
240                return Err(Error::ParseError("Wrong number of children in iq element."));
241            }
242        } else if type_ == "set" {
243            if let Some(payload) = payload {
244                IqType::Set(payload)
245            } else {
246                return Err(Error::ParseError("Wrong number of children in iq element."));
247            }
248        } else if type_ == "result" {
249            if let Some(payload) = payload {
250                IqType::Result(Some(payload))
251            } else {
252                IqType::Result(None)
253            }
254        } else if type_ == "error" {
255            if let Some(payload) = error_payload {
256                IqType::Error(payload)
257            } else {
258                return Err(Error::ParseError("Wrong number of children in iq element."));
259            }
260        } else {
261            return Err(Error::ParseError("Unknown iq type."));
262        };
263
264        Ok(Iq {
265            from: from,
266            to: to,
267            id: id,
268            payload: type_,
269        })
270    }
271}
272
273impl From<Iq> for Element {
274    fn from(iq: Iq) -> Element {
275        let mut stanza = Element::builder("iq")
276                                 .ns(ns::DEFAULT_NS)
277                                 .attr("from", iq.from)
278                                 .attr("to", iq.to)
279                                 .attr("id", iq.id)
280                                 .attr("type", &iq.payload)
281                                 .build();
282        let elem = match iq.payload {
283            IqType::Get(elem)
284          | IqType::Set(elem)
285          | IqType::Result(Some(elem)) => elem,
286            IqType::Error(error) => error.into(),
287            IqType::Result(None) => return stanza,
288        };
289        stanza.append_child(elem);
290        stanza
291    }
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use stanza_error::{ErrorType, DefinedCondition};
298    use compare_elements::NamespaceAwareCompare;
299
300    #[test]
301    fn test_require_type() {
302        #[cfg(not(feature = "component"))]
303        let elem: Element = "<iq xmlns='jabber:client'/>".parse().unwrap();
304        #[cfg(feature = "component")]
305        let elem: Element = "<iq xmlns='jabber:component:accept'/>".parse().unwrap();
306        let error = Iq::try_from(elem).unwrap_err();
307        let message = match error {
308            Error::ParseError(string) => string,
309            _ => panic!(),
310        };
311        assert_eq!(message, "Required attribute 'type' missing.");
312    }
313
314    #[test]
315    fn test_get() {
316        #[cfg(not(feature = "component"))]
317        let elem: Element = "<iq xmlns='jabber:client' type='get'>
318            <foo xmlns='bar'/>
319        </iq>".parse().unwrap();
320        #[cfg(feature = "component")]
321        let elem: Element = "<iq xmlns='jabber:component:accept' type='get'>
322            <foo xmlns='bar'/>
323        </iq>".parse().unwrap();
324        let iq = Iq::try_from(elem).unwrap();
325        let query: Element = "<foo xmlns='bar'/>".parse().unwrap();
326        assert_eq!(iq.from, None);
327        assert_eq!(iq.to, None);
328        assert_eq!(iq.id, None);
329        assert!(match iq.payload {
330            IqType::Get(element) => element.compare_to(&query),
331            _ => false
332        });
333    }
334
335    #[test]
336    fn test_set() {
337        #[cfg(not(feature = "component"))]
338        let elem: Element = "<iq xmlns='jabber:client' type='set'>
339            <vCard xmlns='vcard-temp'/>
340        </iq>".parse().unwrap();
341        #[cfg(feature = "component")]
342        let elem: Element = "<iq xmlns='jabber:component:accept' type='set'>
343            <vCard xmlns='vcard-temp'/>
344        </iq>".parse().unwrap();
345        let iq = Iq::try_from(elem).unwrap();
346        let vcard: Element = "<vCard xmlns='vcard-temp'/>".parse().unwrap();
347        assert_eq!(iq.from, None);
348        assert_eq!(iq.to, None);
349        assert_eq!(iq.id, None);
350        assert!(match iq.payload {
351            IqType::Set(element) => element.compare_to(&vcard),
352            _ => false
353        });
354    }
355
356    #[test]
357    fn test_result_empty() {
358        #[cfg(not(feature = "component"))]
359        let elem: Element = "<iq xmlns='jabber:client' type='result'/>".parse().unwrap();
360        #[cfg(feature = "component")]
361        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'/>".parse().unwrap();
362        let iq = Iq::try_from(elem).unwrap();
363        assert_eq!(iq.from, None);
364        assert_eq!(iq.to, None);
365        assert_eq!(iq.id, None);
366        assert!(match iq.payload {
367            IqType::Result(None) => true,
368            _ => false,
369        });
370    }
371
372    #[test]
373    fn test_result() {
374        #[cfg(not(feature = "component"))]
375        let elem: Element = "<iq xmlns='jabber:client' type='result'>
376            <query xmlns='http://jabber.org/protocol/disco#items'/>
377        </iq>".parse().unwrap();
378        #[cfg(feature = "component")]
379        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'>
380            <query xmlns='http://jabber.org/protocol/disco#items'/>
381        </iq>".parse().unwrap();
382        let iq = Iq::try_from(elem).unwrap();
383        let query: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>".parse().unwrap();
384        assert_eq!(iq.from, None);
385        assert_eq!(iq.to, None);
386        assert_eq!(iq.id, None);
387        assert!(match iq.payload {
388            IqType::Result(Some(element)) => element.compare_to(&query),
389            _ => false,
390        });
391    }
392
393    #[test]
394    fn test_error() {
395        #[cfg(not(feature = "component"))]
396        let elem: Element = "<iq xmlns='jabber:client' type='error'>
397            <ping xmlns='urn:xmpp:ping'/>
398            <error type='cancel'>
399                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
400            </error>
401        </iq>".parse().unwrap();
402        #[cfg(feature = "component")]
403        let elem: Element = "<iq xmlns='jabber:component:accept' type='error'>
404            <ping xmlns='urn:xmpp:ping'/>
405            <error type='cancel'>
406                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
407            </error>
408        </iq>".parse().unwrap();
409        let iq = Iq::try_from(elem).unwrap();
410        assert_eq!(iq.from, None);
411        assert_eq!(iq.to, None);
412        assert_eq!(iq.id, None);
413        match iq.payload {
414            IqType::Error(error) => {
415                assert_eq!(error.type_, ErrorType::Cancel);
416                assert_eq!(error.by, None);
417                assert_eq!(error.defined_condition, DefinedCondition::ServiceUnavailable);
418                assert_eq!(error.texts.len(), 0);
419                assert_eq!(error.other, None);
420            },
421            _ => panic!(),
422        }
423    }
424
425    #[test]
426    fn test_children_invalid() {
427        #[cfg(not(feature = "component"))]
428        let elem: Element = "<iq xmlns='jabber:client' type='error'></iq>".parse().unwrap();
429        #[cfg(feature = "component")]
430        let elem: Element = "<iq xmlns='jabber:component:accept' type='error'></iq>".parse().unwrap();
431        let error = Iq::try_from(elem).unwrap_err();
432        let message = match error {
433            Error::ParseError(string) => string,
434            _ => panic!(),
435        };
436        assert_eq!(message, "Wrong number of children in iq element.");
437    }
438
439    #[test]
440    fn test_serialise() {
441        #[cfg(not(feature = "component"))]
442        let elem: Element = "<iq xmlns='jabber:client' type='result'/>".parse().unwrap();
443        #[cfg(feature = "component")]
444        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'/>".parse().unwrap();
445        let iq2 = Iq {
446            from: None,
447            to: None,
448            id: None,
449            payload: IqType::Result(None),
450        };
451        let elem2 = iq2.into();
452        assert_eq!(elem, elem2);
453    }
454
455    #[test]
456    fn test_disco() {
457        #[cfg(not(feature = "component"))]
458        let elem: Element = "<iq xmlns='jabber:client' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
459        #[cfg(feature = "component")]
460        let elem: Element = "<iq xmlns='jabber:component:accept' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
461        let iq = Iq::try_from(elem).unwrap();
462        let payload = match iq.payload {
463            IqType::Get(payload) => IqGetPayload::try_from(payload).unwrap(),
464            _ => panic!(),
465        };
466        assert!(match payload {
467            IqGetPayload::DiscoInfo(DiscoInfoQuery { .. }) => true,
468            _ => false,
469        });
470    }
471}