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