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