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}