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        if !root.is("iq", ns::DEFAULT_NS) {
211            return Err(Error::ParseError("This is not an iq element."));
212        }
213        let from = get_attr!(root, "from", optional);
214        let to = get_attr!(root, "to", optional);
215        let id = get_attr!(root, "id", optional);
216        let type_: String = get_attr!(root, "type", required);
217
218        let mut payload = None;
219        let mut error_payload = None;
220        for elem in root.children() {
221            if payload.is_some() {
222                return Err(Error::ParseError("Wrong number of children in iq element."));
223            }
224            if type_ == "error" {
225                if elem.is("error", ns::DEFAULT_NS) {
226                    if error_payload.is_some() {
227                        return Err(Error::ParseError("Wrong number of children in iq element."));
228                    }
229                    error_payload = Some(StanzaError::try_from(elem.clone())?);
230                } else if root.children().count() != 2 {
231                    return Err(Error::ParseError("Wrong number of children in iq element."));
232                }
233            } else {
234                payload = Some(elem.clone());
235            }
236        }
237
238        let type_ = if type_ == "get" {
239            if let Some(payload) = payload {
240                IqType::Get(payload)
241            } else {
242                return Err(Error::ParseError("Wrong number of children in iq element."));
243            }
244        } else if type_ == "set" {
245            if let Some(payload) = payload {
246                IqType::Set(payload)
247            } else {
248                return Err(Error::ParseError("Wrong number of children in iq element."));
249            }
250        } else if type_ == "result" {
251            if let Some(payload) = payload {
252                IqType::Result(Some(payload))
253            } else {
254                IqType::Result(None)
255            }
256        } else if type_ == "error" {
257            if let Some(payload) = error_payload {
258                IqType::Error(payload)
259            } else {
260                return Err(Error::ParseError("Wrong number of children in iq element."));
261            }
262        } else {
263            return Err(Error::ParseError("Unknown iq type."));
264        };
265
266        Ok(Iq {
267            from: from,
268            to: to,
269            id: id,
270            payload: type_,
271        })
272    }
273}
274
275impl From<Iq> for Element {
276    fn from(iq: Iq) -> Element {
277        let mut stanza = Element::builder("iq")
278                                 .ns(ns::DEFAULT_NS)
279                                 .attr("from", iq.from)
280                                 .attr("to", iq.to)
281                                 .attr("id", iq.id)
282                                 .attr("type", &iq.payload)
283                                 .build();
284        let elem = match iq.payload {
285            IqType::Get(elem)
286          | IqType::Set(elem)
287          | IqType::Result(Some(elem)) => elem,
288            IqType::Error(error) => error.into(),
289            IqType::Result(None) => return stanza,
290        };
291        stanza.append_child(elem);
292        stanza
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299    use stanza_error::{ErrorType, DefinedCondition};
300    use compare_elements::NamespaceAwareCompare;
301
302    #[test]
303    fn test_require_type() {
304        #[cfg(not(feature = "component"))]
305        let elem: Element = "<iq xmlns='jabber:client'/>".parse().unwrap();
306        #[cfg(feature = "component")]
307        let elem: Element = "<iq xmlns='jabber:component:accept'/>".parse().unwrap();
308        let error = Iq::try_from(elem).unwrap_err();
309        let message = match error {
310            Error::ParseError(string) => string,
311            _ => panic!(),
312        };
313        assert_eq!(message, "Required attribute 'type' missing.");
314    }
315
316    #[test]
317    fn test_get() {
318        #[cfg(not(feature = "component"))]
319        let elem: Element = "<iq xmlns='jabber:client' type='get'>
320            <foo xmlns='bar'/>
321        </iq>".parse().unwrap();
322        #[cfg(feature = "component")]
323        let elem: Element = "<iq xmlns='jabber:component:accept' type='get'>
324            <foo xmlns='bar'/>
325        </iq>".parse().unwrap();
326        let iq = Iq::try_from(elem).unwrap();
327        let query: Element = "<foo xmlns='bar'/>".parse().unwrap();
328        assert_eq!(iq.from, None);
329        assert_eq!(iq.to, None);
330        assert_eq!(iq.id, None);
331        assert!(match iq.payload {
332            IqType::Get(element) => element.compare_to(&query),
333            _ => false
334        });
335    }
336
337    #[test]
338    fn test_set() {
339        #[cfg(not(feature = "component"))]
340        let elem: Element = "<iq xmlns='jabber:client' type='set'>
341            <vCard xmlns='vcard-temp'/>
342        </iq>".parse().unwrap();
343        #[cfg(feature = "component")]
344        let elem: Element = "<iq xmlns='jabber:component:accept' type='set'>
345            <vCard xmlns='vcard-temp'/>
346        </iq>".parse().unwrap();
347        let iq = Iq::try_from(elem).unwrap();
348        let vcard: Element = "<vCard xmlns='vcard-temp'/>".parse().unwrap();
349        assert_eq!(iq.from, None);
350        assert_eq!(iq.to, None);
351        assert_eq!(iq.id, None);
352        assert!(match iq.payload {
353            IqType::Set(element) => element.compare_to(&vcard),
354            _ => false
355        });
356    }
357
358    #[test]
359    fn test_result_empty() {
360        #[cfg(not(feature = "component"))]
361        let elem: Element = "<iq xmlns='jabber:client' type='result'/>".parse().unwrap();
362        #[cfg(feature = "component")]
363        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'/>".parse().unwrap();
364        let iq = Iq::try_from(elem).unwrap();
365        assert_eq!(iq.from, None);
366        assert_eq!(iq.to, None);
367        assert_eq!(iq.id, None);
368        assert!(match iq.payload {
369            IqType::Result(None) => true,
370            _ => false,
371        });
372    }
373
374    #[test]
375    fn test_result() {
376        #[cfg(not(feature = "component"))]
377        let elem: Element = "<iq xmlns='jabber:client' type='result'>
378            <query xmlns='http://jabber.org/protocol/disco#items'/>
379        </iq>".parse().unwrap();
380        #[cfg(feature = "component")]
381        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'>
382            <query xmlns='http://jabber.org/protocol/disco#items'/>
383        </iq>".parse().unwrap();
384        let iq = Iq::try_from(elem).unwrap();
385        let query: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>".parse().unwrap();
386        assert_eq!(iq.from, None);
387        assert_eq!(iq.to, None);
388        assert_eq!(iq.id, None);
389        assert!(match iq.payload {
390            IqType::Result(Some(element)) => element.compare_to(&query),
391            _ => false,
392        });
393    }
394
395    #[test]
396    fn test_error() {
397        #[cfg(not(feature = "component"))]
398        let elem: Element = "<iq xmlns='jabber:client' type='error'>
399            <ping xmlns='urn:xmpp:ping'/>
400            <error type='cancel'>
401                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
402            </error>
403        </iq>".parse().unwrap();
404        #[cfg(feature = "component")]
405        let elem: Element = "<iq xmlns='jabber:component:accept' type='error'>
406            <ping xmlns='urn:xmpp:ping'/>
407            <error type='cancel'>
408                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
409            </error>
410        </iq>".parse().unwrap();
411        let iq = Iq::try_from(elem).unwrap();
412        assert_eq!(iq.from, None);
413        assert_eq!(iq.to, None);
414        assert_eq!(iq.id, None);
415        match iq.payload {
416            IqType::Error(error) => {
417                assert_eq!(error.type_, ErrorType::Cancel);
418                assert_eq!(error.by, None);
419                assert_eq!(error.defined_condition, DefinedCondition::ServiceUnavailable);
420                assert_eq!(error.texts.len(), 0);
421                assert_eq!(error.other, None);
422            },
423            _ => panic!(),
424        }
425    }
426
427    #[test]
428    fn test_children_invalid() {
429        #[cfg(not(feature = "component"))]
430        let elem: Element = "<iq xmlns='jabber:client' type='error'></iq>".parse().unwrap();
431        #[cfg(feature = "component")]
432        let elem: Element = "<iq xmlns='jabber:component:accept' type='error'></iq>".parse().unwrap();
433        let error = Iq::try_from(elem).unwrap_err();
434        let message = match error {
435            Error::ParseError(string) => string,
436            _ => panic!(),
437        };
438        assert_eq!(message, "Wrong number of children in iq element.");
439    }
440
441    #[test]
442    fn test_serialise() {
443        #[cfg(not(feature = "component"))]
444        let elem: Element = "<iq xmlns='jabber:client' type='result'/>".parse().unwrap();
445        #[cfg(feature = "component")]
446        let elem: Element = "<iq xmlns='jabber:component:accept' type='result'/>".parse().unwrap();
447        let iq2 = Iq {
448            from: None,
449            to: None,
450            id: None,
451            payload: IqType::Result(None),
452        };
453        let elem2 = iq2.into();
454        assert_eq!(elem, elem2);
455    }
456
457    #[test]
458    fn test_disco() {
459        #[cfg(not(feature = "component"))]
460        let elem: Element = "<iq xmlns='jabber:client' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
461        #[cfg(feature = "component")]
462        let elem: Element = "<iq xmlns='jabber:component:accept' type='get'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
463        let iq = Iq::try_from(elem).unwrap();
464        let payload = match iq.payload {
465            IqType::Get(payload) => IqGetPayload::try_from(payload).unwrap(),
466            _ => panic!(),
467        };
468        assert!(match payload {
469            IqGetPayload::DiscoInfo(DiscoInfoQuery { .. }) => true,
470            _ => false,
471        });
472    }
473}