Skip to main content

xmpp_parsers/
stream_features.rs

1// Copyright (c) 2024 xmpp-rs contributors
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use minidom::Element;
8use xso::{AsXml, FromXml};
9
10use crate::bind::BindFeature;
11use crate::ns;
12use crate::sasl2::Authentication;
13use crate::sasl_cb::SaslChannelBinding;
14use crate::starttls::StartTls;
15use crate::stream_limits::Limits;
16
17/// Wraps `<stream:features/>`, usually the very first nonza of a
18/// XMPP stream. Indicates which features are supported.
19#[derive(FromXml, AsXml, PartialEq, Debug, Default, Clone)]
20#[xml(namespace = ns::STREAM, name = "features")]
21pub struct StreamFeatures {
22    /// StartTLS is supported, and may be mandatory.
23    #[xml(child(default))]
24    pub starttls: Option<StartTls>,
25
26    /// Bind is supported.
27    #[xml(child(default))]
28    pub bind: Option<BindFeature>,
29
30    /// List of supported SASL mechanisms
31    #[xml(child(default))]
32    pub sasl_mechanisms: SaslMechanisms,
33
34    /// Limits advertised by the server.
35    #[xml(child(default))]
36    pub limits: Option<Limits>,
37
38    /// Extensible SASL Profile, a newer authentication method than the one from the RFC.
39    #[xml(child(default))]
40    pub sasl2: Option<Authentication>,
41
42    /// SASL Channel-Binding Type Capability.
43    #[xml(child(default))]
44    pub sasl_cb: Option<SaslChannelBinding>,
45
46    /// Stream management feature
47    #[xml(child(default))]
48    pub stream_management: Option<crate::sm::StreamManagement>,
49
50    /// Stream management feature
51    #[xml(flag(namespace = ns::REGISTER_FEATURE, name = "register"))]
52    pub in_band_registration: bool,
53
54    /// Other stream features advertised
55    ///
56    /// If some features you use end up here, you may want to contribute
57    /// a typed equivalent to the xmpp-parsers project!
58    #[xml(element(n = ..))]
59    pub others: Vec<Element>,
60}
61
62/// List of supported SASL mechanisms
63#[derive(FromXml, AsXml, PartialEq, Debug, Clone, Default)]
64#[xml(namespace = ns::SASL, name = "mechanisms")]
65pub struct SaslMechanisms {
66    /// List of information elements describing this SASL mechanism.
67    #[xml(extract(n = .., name = "mechanism", fields(text(type_ = String))))]
68    pub mechanisms: Vec<String>,
69}
70
71impl StreamFeatures {
72    /// Can initiate TLS session with this server?
73    pub fn can_starttls(&self) -> bool {
74        self.starttls.is_some()
75    }
76
77    /// Does server support user resource binding?
78    pub fn can_bind(&self) -> bool {
79        self.bind.is_some()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use minidom::Element;
87
88    #[cfg(target_pointer_width = "32")]
89    #[test]
90    fn test_size() {
91        assert_size!(SaslMechanisms, 12);
92        assert_size!(StreamFeatures, 92);
93    }
94
95    #[cfg(target_pointer_width = "64")]
96    #[test]
97    fn test_size() {
98        assert_size!(SaslMechanisms, 24);
99        assert_size!(StreamFeatures, 168);
100    }
101
102    #[test]
103    fn test_sasl_mechanisms() {
104        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
105            <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
106                <mechanism>PLAIN</mechanism>
107                <mechanism>SCRAM-SHA-1</mechanism>
108                <mechanism>SCRAM-SHA-1-PLUS</mechanism>
109            </mechanisms>
110        </stream:features>"
111            .parse()
112            .unwrap();
113
114        let features = StreamFeatures::try_from(elem).unwrap();
115        assert_eq!(
116            features.sasl_mechanisms.mechanisms,
117            ["PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS"]
118        );
119    }
120
121    #[test]
122    fn test_required_starttls() {
123        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
124                                 <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
125                                     <required/>
126                                 </starttls>
127                             </stream:features>"
128            .parse()
129            .unwrap();
130
131        let features = StreamFeatures::try_from(elem).unwrap();
132
133        assert_eq!(features.can_bind(), false);
134        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
135        assert_eq!(features.can_starttls(), true);
136        assert_eq!(features.starttls.unwrap().required, true);
137    }
138
139    #[test]
140    fn test_deprecated_compression() {
141        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
142                                 <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
143                                 <compression xmlns='http://jabber.org/features/compress'>
144                                     <method>zlib</method>
145                                     <method>lzw</method>
146                                 </compression>
147                             </stream:features>"
148            .parse()
149            .unwrap();
150
151        let features = StreamFeatures::try_from(elem).unwrap();
152
153        assert_eq!(features.can_bind(), true);
154        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
155        assert_eq!(features.can_starttls(), false);
156        assert_eq!(features.others.len(), 1);
157
158        let compression = &features.others[0];
159        assert!(compression.is("compression", "http://jabber.org/features/compress"));
160        let mut children = compression.children();
161
162        let child = children.next().expect("zlib not found");
163        assert_eq!(child.name(), "method");
164        let mut texts = child.texts();
165        assert_eq!(texts.next().unwrap(), "zlib");
166        assert_eq!(texts.next(), None);
167
168        let child = children.next().expect("lzw not found");
169        assert_eq!(child.name(), "method");
170        let mut texts = child.texts();
171        assert_eq!(texts.next().unwrap(), "lzw");
172        assert_eq!(texts.next(), None);
173    }
174
175    #[test]
176    fn test_empty_features() {
177        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
178            .parse()
179            .unwrap();
180
181        let features = StreamFeatures::try_from(elem).unwrap();
182
183        assert_eq!(features.can_bind(), false);
184        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
185        assert_eq!(features.can_starttls(), false);
186    }
187}