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    /// Other stream features advertised
 51    ///
 52    /// If some features you use end up here, you may want to contribute
 53    /// a typed equivalent to the xmpp-parsers project!
 54    #[xml(element(n = ..))]
 55    pub others: Vec<Element>,
 56}
 57
 58/// List of supported SASL mechanisms
 59#[derive(FromXml, AsXml, PartialEq, Debug, Clone, Default)]
 60#[xml(namespace = ns::SASL, name = "mechanisms")]
 61pub struct SaslMechanisms {
 62    /// List of information elements describing this SASL mechanism.
 63    #[xml(extract(n = .., name = "mechanism", fields(text(type_ = String))))]
 64    pub mechanisms: Vec<String>,
 65}
 66
 67impl StreamFeatures {
 68    /// Can initiate TLS session with this server?
 69    pub fn can_starttls(&self) -> bool {
 70        self.starttls.is_some()
 71    }
 72
 73    /// Does server support user resource binding?
 74    pub fn can_bind(&self) -> bool {
 75        self.bind.is_some()
 76    }
 77}
 78
 79#[cfg(test)]
 80mod tests {
 81    use super::*;
 82    use minidom::Element;
 83
 84    #[cfg(target_pointer_width = "32")]
 85    #[test]
 86    fn test_size() {
 87        assert_size!(SaslMechanisms, 12);
 88        assert_size!(StreamFeatures, 92);
 89    }
 90
 91    #[cfg(target_pointer_width = "64")]
 92    #[test]
 93    fn test_size() {
 94        assert_size!(SaslMechanisms, 24);
 95        assert_size!(StreamFeatures, 168);
 96    }
 97
 98    #[test]
 99    fn test_sasl_mechanisms() {
100        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
101            <mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
102                <mechanism>PLAIN</mechanism>
103                <mechanism>SCRAM-SHA-1</mechanism>
104                <mechanism>SCRAM-SHA-1-PLUS</mechanism>
105            </mechanisms>
106        </stream:features>"
107            .parse()
108            .unwrap();
109
110        let features = StreamFeatures::try_from(elem).unwrap();
111        assert_eq!(
112            features.sasl_mechanisms.mechanisms,
113            ["PLAIN", "SCRAM-SHA-1", "SCRAM-SHA-1-PLUS"]
114        );
115    }
116
117    #[test]
118    fn test_required_starttls() {
119        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
120                                 <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'>
121                                     <required/>
122                                 </starttls>
123                             </stream:features>"
124            .parse()
125            .unwrap();
126
127        let features = StreamFeatures::try_from(elem).unwrap();
128
129        assert_eq!(features.can_bind(), false);
130        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
131        assert_eq!(features.can_starttls(), true);
132        assert_eq!(features.starttls.unwrap().required, true);
133    }
134
135    #[test]
136    fn test_deprecated_compression() {
137        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
138                                 <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
139                                 <compression xmlns='http://jabber.org/features/compress'>
140                                     <method>zlib</method>
141                                     <method>lzw</method>
142                                 </compression>
143                             </stream:features>"
144            .parse()
145            .unwrap();
146
147        let features = StreamFeatures::try_from(elem).unwrap();
148
149        assert_eq!(features.can_bind(), true);
150        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
151        assert_eq!(features.can_starttls(), false);
152        assert_eq!(features.others.len(), 1);
153
154        let compression = &features.others[0];
155        assert!(compression.is("compression", "http://jabber.org/features/compress"));
156        let mut children = compression.children();
157
158        let child = children.next().expect("zlib not found");
159        assert_eq!(child.name(), "method");
160        let mut texts = child.texts();
161        assert_eq!(texts.next().unwrap(), "zlib");
162        assert_eq!(texts.next(), None);
163
164        let child = children.next().expect("lzw not found");
165        assert_eq!(child.name(), "method");
166        let mut texts = child.texts();
167        assert_eq!(texts.next().unwrap(), "lzw");
168        assert_eq!(texts.next(), None);
169    }
170
171    #[test]
172    fn test_empty_features() {
173        let elem: Element = "<stream:features xmlns:stream='http://etherx.jabber.org/streams'/>"
174            .parse()
175            .unwrap();
176
177        let features = StreamFeatures::try_from(elem).unwrap();
178
179        assert_eq!(features.can_bind(), false);
180        assert_eq!(features.sasl_mechanisms.mechanisms.len(), 0);
181        assert_eq!(features.can_starttls(), false);
182    }
183}