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