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