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