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