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