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}