xmpp_parsers/
iq.rs

1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2// Copyright (c) 2017 Maxime “pep” Buquet <pep@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 crate::ns;
9use crate::stanza_error::StanzaError;
10use jid::Jid;
11use minidom::Element;
12use xso::{AsXml, FromXml};
13
14/// Should be implemented on every known payload of an `<iq type='get'/>`.
15pub trait IqGetPayload: TryFrom<Element> + Into<Element> {}
16
17/// Should be implemented on every known payload of an `<iq type='set'/>`.
18pub trait IqSetPayload: TryFrom<Element> + Into<Element> {}
19
20/// Should be implemented on every known payload of an `<iq type='result'/>`.
21pub trait IqResultPayload: TryFrom<Element> + Into<Element> {}
22
23/// Metadata of an IQ stanza.
24pub struct IqHeader {
25    /// The sender JID.
26    pub from: Option<Jid>,
27
28    /// The reciepient JID.
29    pub to: Option<Jid>,
30
31    /// The stanza's ID.
32    pub id: String,
33}
34
35impl IqHeader {
36    /// Combine a header with [`IqPayload`] to create a full [`Iq`] stanza.
37    pub fn assemble(self, data: IqPayload) -> Iq {
38        data.assemble(self)
39    }
40}
41
42/// Payload of an IQ request stanza.
43pub enum IqRequestPayload {
44    /// Payload of a type='get' stanza.
45    Get(Element),
46
47    /// Payload of a type='set' stanza.
48    Set(Element),
49}
50
51/// Payload of an IQ stanza, by type.
52pub enum IqPayload {
53    /// Payload of a type='get' stanza.
54    Get(Element),
55
56    /// Payload of a type='set' stanza.
57    Set(Element),
58
59    /// Payload of a type='result' stanza.
60    Result(Option<Element>),
61
62    /// The error carride in a type='error' stanza.
63    Error(StanzaError),
64}
65
66impl IqPayload {
67    /// Combine the data with an [`IqHeader`] to create a full [`Iq`] stanza.
68    pub fn assemble(self, IqHeader { from, to, id }: IqHeader) -> Iq {
69        match self {
70            Self::Get(payload) => Iq::Get {
71                from,
72                to,
73                id,
74                payload,
75            },
76            Self::Set(payload) => Iq::Set {
77                from,
78                to,
79                id,
80                payload,
81            },
82            Self::Result(payload) => Iq::Result {
83                from,
84                to,
85                id,
86                payload,
87            },
88            Self::Error(error) => Iq::Error {
89                from,
90                to,
91                id,
92                payload: None,
93                error,
94            },
95        }
96    }
97}
98
99/// The main structure representing the `<iq/>` stanza.
100#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
101#[xml(namespace = ns::DEFAULT_NS, name = "iq", attribute = "type", exhaustive)]
102pub enum Iq {
103    /// An `<iq type='get'/>` stanza.
104    #[xml(value = "get")]
105    Get {
106        /// The JID emitting this stanza.
107        #[xml(attribute(default))]
108        from: Option<Jid>,
109
110        /// The recipient of this stanza.
111        #[xml(attribute(default))]
112        to: Option<Jid>,
113
114        /// The @id attribute of this stanza, which is required in order to match a
115        /// request with its result/error.
116        #[xml(attribute)]
117        id: String,
118
119        /// The payload content of this stanza.
120        #[xml(element(n = 1))]
121        payload: Element,
122    },
123
124    /// An `<iq type='set'/>` stanza.
125    #[xml(value = "set")]
126    Set {
127        /// The JID emitting this stanza.
128        #[xml(attribute(default))]
129        from: Option<Jid>,
130
131        /// The recipient of this stanza.
132        #[xml(attribute(default))]
133        to: Option<Jid>,
134
135        /// The @id attribute of this stanza, which is required in order to match a
136        /// request with its result/error.
137        #[xml(attribute)]
138        id: String,
139
140        /// The payload content of this stanza.
141        #[xml(element(n = 1))]
142        payload: Element,
143    },
144
145    /// An `<iq type='result'/>` stanza.
146    #[xml(value = "result")]
147    Result {
148        /// The JID emitting this stanza.
149        #[xml(attribute(default))]
150        from: Option<Jid>,
151
152        /// The recipient of this stanza.
153        #[xml(attribute(default))]
154        to: Option<Jid>,
155
156        /// The @id attribute of this stanza, which is required in order to match a
157        /// request with its result/error.
158        #[xml(attribute)]
159        id: String,
160
161        /// The payload content of this stanza.
162        #[xml(element(n = 1, default))]
163        payload: Option<Element>,
164    },
165
166    /// An `<iq type='error'/>` stanza.
167    #[xml(value = "error")]
168    Error {
169        /// The JID emitting this stanza.
170        #[xml(attribute(default))]
171        from: Option<Jid>,
172
173        /// The recipient of this stanza.
174        #[xml(attribute(default))]
175        to: Option<Jid>,
176
177        /// The @id attribute of this stanza, which is required in order to match a
178        /// request with its result/error.
179        #[xml(attribute)]
180        id: String,
181
182        /// The error carried by this stanza.
183        #[xml(child)]
184        error: StanzaError,
185
186        /// The optional payload content which caused the error.
187        ///
188        /// As per
189        /// [RFC 6120 § 8.3.1](https://datatracker.ietf.org/doc/html/rfc6120#section-8.3.1),
190        /// the emitter of an error stanza MAY include the original XML which
191        /// caused the error. However, recipients MUST NOT rely on this.
192        #[xml(element(n = 1, default))]
193        payload: Option<Element>,
194    },
195}
196
197impl Iq {
198    /// Assemble a new Iq stanza from an [`IqHeader`] and the given
199    /// [`IqPayload`].
200    pub fn assemble(header: IqHeader, data: IqPayload) -> Self {
201        data.assemble(header)
202    }
203
204    /// Creates an `<iq/>` stanza containing a get request.
205    pub fn from_get<S: Into<String>>(id: S, payload: impl IqGetPayload) -> Iq {
206        Iq::Get {
207            from: None,
208            to: None,
209            id: id.into(),
210            payload: payload.into(),
211        }
212    }
213
214    /// Creates an `<iq/>` stanza containing a set request.
215    pub fn from_set<S: Into<String>>(id: S, payload: impl IqSetPayload) -> Iq {
216        Iq::Set {
217            from: None,
218            to: None,
219            id: id.into(),
220            payload: payload.into(),
221        }
222    }
223
224    /// Creates an empty `<iq type="result"/>` stanza.
225    pub fn empty_result<S: Into<String>>(to: Jid, id: S) -> Iq {
226        Iq::Result {
227            from: None,
228            to: Some(to),
229            id: id.into(),
230            payload: None,
231        }
232    }
233
234    /// Creates an `<iq/>` stanza containing a result.
235    pub fn from_result<S: Into<String>>(id: S, payload: Option<impl IqResultPayload>) -> Iq {
236        Iq::Result {
237            from: None,
238            to: None,
239            id: id.into(),
240            payload: payload.map(Into::into),
241        }
242    }
243
244    /// Creates an `<iq/>` stanza containing an error.
245    pub fn from_error<S: Into<String>>(id: S, payload: StanzaError) -> Iq {
246        Iq::Error {
247            from: None,
248            to: None,
249            id: id.into(),
250            error: payload,
251            payload: None,
252        }
253    }
254
255    /// Sets the recipient of this stanza.
256    pub fn with_to(mut self, to: Jid) -> Iq {
257        *self.to_mut() = Some(to);
258        self
259    }
260
261    /// Sets the emitter of this stanza.
262    pub fn with_from(mut self, from: Jid) -> Iq {
263        *self.from_mut() = Some(from);
264        self
265    }
266
267    /// Sets the id of this stanza, in order to later match its response.
268    pub fn with_id(mut self, id: String) -> Iq {
269        *self.id_mut() = id;
270        self
271    }
272
273    /// Access the sender address.
274    pub fn from(&self) -> Option<&Jid> {
275        match self {
276            Self::Get { from, .. }
277            | Self::Set { from, .. }
278            | Self::Result { from, .. }
279            | Self::Error { from, .. } => from.as_ref(),
280        }
281    }
282
283    /// Access the sender address, mutably.
284    pub fn from_mut(&mut self) -> &mut Option<Jid> {
285        match self {
286            Self::Get { ref mut from, .. }
287            | Self::Set { ref mut from, .. }
288            | Self::Result { ref mut from, .. }
289            | Self::Error { ref mut from, .. } => from,
290        }
291    }
292
293    /// Access the recipient address.
294    pub fn to(&self) -> Option<&Jid> {
295        match self {
296            Self::Get { to, .. }
297            | Self::Set { to, .. }
298            | Self::Result { to, .. }
299            | Self::Error { to, .. } => to.as_ref(),
300        }
301    }
302
303    /// Access the recipient address, mutably.
304    pub fn to_mut(&mut self) -> &mut Option<Jid> {
305        match self {
306            Self::Get { ref mut to, .. }
307            | Self::Set { ref mut to, .. }
308            | Self::Result { ref mut to, .. }
309            | Self::Error { ref mut to, .. } => to,
310        }
311    }
312
313    /// Access the id.
314    pub fn id(&self) -> &str {
315        match self {
316            Self::Get { id, .. }
317            | Self::Set { id, .. }
318            | Self::Result { id, .. }
319            | Self::Error { id, .. } => id.as_str(),
320        }
321    }
322
323    /// Access the id mutably.
324    pub fn id_mut(&mut self) -> &mut String {
325        match self {
326            Self::Get { ref mut id, .. }
327            | Self::Set { ref mut id, .. }
328            | Self::Result { ref mut id, .. }
329            | Self::Error { ref mut id, .. } => id,
330        }
331    }
332
333    /// Split the IQ stanza in its metadata and data.
334    ///
335    /// Note that this discards the optional original error-inducing
336    /// [`payload`][`Self::Error::payload`] of the
337    /// [`Iq::Error`][`Self::Error`] variant.
338    pub fn split(self) -> (IqHeader, IqPayload) {
339        match self {
340            Self::Get {
341                from,
342                to,
343                id,
344                payload,
345            } => (IqHeader { from, to, id }, IqPayload::Get(payload)),
346            Self::Set {
347                from,
348                to,
349                id,
350                payload,
351            } => (IqHeader { from, to, id }, IqPayload::Set(payload)),
352            Self::Result {
353                from,
354                to,
355                id,
356                payload,
357            } => (IqHeader { from, to, id }, IqPayload::Result(payload)),
358            Self::Error {
359                from,
360                to,
361                id,
362                error,
363                payload: _,
364            } => (IqHeader { from, to, id }, IqPayload::Error(error)),
365        }
366    }
367
368    /// Return the [`IqHeader`] of this stanza, discarding the payload.
369    pub fn into_header(self) -> IqHeader {
370        self.split().0
371    }
372
373    /// Return the [`IqPayload`] of this stanza, discarding the header.
374    ///
375    /// Note that this also discards the optional original error-inducing
376    /// [`payload`][`Self::Error::payload`] of the
377    /// [`Iq::Error`][`Self::Error`] variant.
378    pub fn into_payload(self) -> IqPayload {
379        self.split().1
380    }
381}
382
383#[cfg(test)]
384mod tests {
385    use super::*;
386    use crate::disco::DiscoInfoQuery;
387    use crate::stanza_error::{DefinedCondition, ErrorType};
388    use xso::error::{Error, FromElementError};
389
390    #[cfg(target_pointer_width = "32")]
391    #[test]
392    fn test_size() {
393        assert_size!(IqHeader, 44);
394        assert_size!(IqPayload, 108);
395        assert_size!(Iq, 212);
396    }
397
398    #[cfg(target_pointer_width = "64")]
399    #[test]
400    fn test_size() {
401        assert_size!(IqHeader, 88);
402        assert_size!(IqPayload, 216);
403        assert_size!(Iq, 424);
404    }
405
406    #[test]
407    fn test_require_type() {
408        #[cfg(not(feature = "component"))]
409        let elem: Element = "<iq xmlns='jabber:client'/>".parse().unwrap();
410        #[cfg(feature = "component")]
411        let elem: Element = "<iq xmlns='jabber:component:accept'/>".parse().unwrap();
412        let error = Iq::try_from(elem).unwrap_err();
413        let message = match error {
414            FromElementError::Invalid(Error::Other(string)) => string,
415            _ => panic!(),
416        };
417        assert_eq!(message, "Missing discriminator attribute.");
418    }
419
420    #[test]
421    fn test_require_id() {
422        for type_ in ["get", "set", "result", "error"] {
423            #[cfg(not(feature = "component"))]
424            let elem: Element = format!("<iq xmlns='jabber:client' type='{}'/>", type_)
425                .parse()
426                .unwrap();
427            #[cfg(feature = "component")]
428            let elem: Element = format!("<iq xmlns='jabber:component:accept' type='{}'/>", type_)
429                .parse()
430                .unwrap();
431            let error = Iq::try_from(elem).unwrap_err();
432            let message = match error {
433                FromElementError::Invalid(Error::Other(string)) => string,
434                _ => panic!(),
435            };
436            // Slicing here, because the rest of the error message is specific
437            // about the enum variant.
438            assert_eq!(&message[..33], "Required attribute field 'id' on ");
439        }
440    }
441
442    #[test]
443    fn test_get() {
444        #[cfg(not(feature = "component"))]
445        let elem: Element = "<iq xmlns='jabber:client' type='get' id='foo'>
446            <foo xmlns='bar'/>
447        </iq>"
448            .parse()
449            .unwrap();
450        #[cfg(feature = "component")]
451        let elem: Element = "<iq xmlns='jabber:component:accept' type='get' id='foo'>
452            <foo xmlns='bar'/>
453        </iq>"
454            .parse()
455            .unwrap();
456        let iq = Iq::try_from(elem).unwrap();
457        let query: Element = "<foo xmlns='bar'/>".parse().unwrap();
458        assert_eq!(iq.from(), None);
459        assert_eq!(iq.to(), None);
460        assert_eq!(iq.id(), "foo");
461        assert!(match iq {
462            Iq::Get { payload, .. } => payload == query,
463            _ => false,
464        });
465    }
466
467    #[test]
468    fn test_set() {
469        #[cfg(not(feature = "component"))]
470        let elem: Element = "<iq xmlns='jabber:client' type='set' id='vcard'>
471            <vCard xmlns='vcard-temp'/>
472        </iq>"
473            .parse()
474            .unwrap();
475        #[cfg(feature = "component")]
476        let elem: Element = "<iq xmlns='jabber:component:accept' type='set' id='vcard'>
477            <vCard xmlns='vcard-temp'/>
478        </iq>"
479            .parse()
480            .unwrap();
481        let iq = Iq::try_from(elem).unwrap();
482        let vcard: Element = "<vCard xmlns='vcard-temp'/>".parse().unwrap();
483        assert_eq!(iq.from(), None);
484        assert_eq!(iq.to(), None);
485        assert_eq!(iq.id(), "vcard");
486        assert!(match iq {
487            Iq::Set { payload, .. } => payload == vcard,
488            _ => false,
489        });
490    }
491
492    #[test]
493    fn test_result_empty() {
494        #[cfg(not(feature = "component"))]
495        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>"
496            .parse()
497            .unwrap();
498        #[cfg(feature = "component")]
499        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
500            .parse()
501            .unwrap();
502        let iq = Iq::try_from(elem).unwrap();
503        assert_eq!(iq.from(), None);
504        assert_eq!(iq.to(), None);
505        assert_eq!(iq.id(), "res");
506        assert!(match iq {
507            Iq::Result { payload: None, .. } => true,
508            _ => false,
509        });
510    }
511
512    #[test]
513    fn test_result() {
514        #[cfg(not(feature = "component"))]
515        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'>
516            <query xmlns='http://jabber.org/protocol/disco#items'/>
517        </iq>"
518            .parse()
519            .unwrap();
520        #[cfg(feature = "component")]
521        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'>
522            <query xmlns='http://jabber.org/protocol/disco#items'/>
523        </iq>"
524            .parse()
525            .unwrap();
526        let iq = Iq::try_from(elem).unwrap();
527        let query: Element = "<query xmlns='http://jabber.org/protocol/disco#items'/>"
528            .parse()
529            .unwrap();
530        assert_eq!(iq.from(), None);
531        assert_eq!(iq.to(), None);
532        assert_eq!(iq.id(), "res");
533        assert!(match iq {
534            Iq::Result {
535                payload: Some(element),
536                ..
537            } => element == query,
538            _ => false,
539        });
540    }
541
542    #[test]
543    fn test_error() {
544        #[cfg(not(feature = "component"))]
545        let elem: Element = "<iq xmlns='jabber:client' type='error' id='err1'>
546            <ping xmlns='urn:xmpp:ping'/>
547            <error type='cancel'>
548                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
549            </error>
550        </iq>"
551            .parse()
552            .unwrap();
553        #[cfg(feature = "component")]
554        let elem: Element = "<iq xmlns='jabber:component:accept' type='error' id='err1'>
555            <ping xmlns='urn:xmpp:ping'/>
556            <error type='cancel'>
557                <service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
558            </error>
559        </iq>"
560            .parse()
561            .unwrap();
562        let iq = Iq::try_from(elem).unwrap();
563        assert_eq!(iq.from(), None);
564        assert_eq!(iq.to(), None);
565        assert_eq!(iq.id(), "err1");
566        match iq {
567            Iq::Error { error, .. } => {
568                assert_eq!(error.type_, ErrorType::Cancel);
569                assert_eq!(error.by, None);
570                assert_eq!(
571                    error.defined_condition,
572                    DefinedCondition::ServiceUnavailable
573                );
574                assert_eq!(error.texts.len(), 0);
575                assert_eq!(error.other, None);
576            }
577            _ => panic!(),
578        }
579    }
580
581    #[test]
582    fn test_children_invalid() {
583        #[cfg(not(feature = "component"))]
584        let elem: Element = "<iq xmlns='jabber:client' type='error' id='error'/>"
585            .parse()
586            .unwrap();
587        #[cfg(feature = "component")]
588        let elem: Element = "<iq xmlns='jabber:component:accept' type='error' id='error'/>"
589            .parse()
590            .unwrap();
591        let error = Iq::try_from(elem).unwrap_err();
592        let message = match error {
593            FromElementError::Invalid(Error::Other(string)) => string,
594            _ => panic!(),
595        };
596        assert_eq!(message, "Missing child field 'error' in Iq::Error element.");
597    }
598
599    #[test]
600    fn test_serialise() {
601        #[cfg(not(feature = "component"))]
602        let elem: Element = "<iq xmlns='jabber:client' type='result' id='res'/>"
603            .parse()
604            .unwrap();
605        #[cfg(feature = "component")]
606        let elem: Element = "<iq xmlns='jabber:component:accept' type='result' id='res'/>"
607            .parse()
608            .unwrap();
609        let iq2 = Iq::Result {
610            from: None,
611            to: None,
612            id: String::from("res"),
613            payload: None,
614        };
615        let elem2 = iq2.into();
616        assert_eq!(elem, elem2);
617    }
618
619    #[test]
620    fn test_disco() {
621        #[cfg(not(feature = "component"))]
622        let elem: Element = "<iq xmlns='jabber:client' type='get' id='disco'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
623        #[cfg(feature = "component")]
624        let elem: Element = "<iq xmlns='jabber:component:accept' type='get' id='disco'><query xmlns='http://jabber.org/protocol/disco#info'/></iq>".parse().unwrap();
625        let iq = Iq::try_from(elem).unwrap();
626        let disco_info = match iq {
627            Iq::Get { payload, .. } => DiscoInfoQuery::try_from(payload).unwrap(),
628            _ => panic!(),
629        };
630        assert!(disco_info.node.is_none());
631    }
632}