bind.rs

  1// Copyright (c) 2018 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
  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 crate::iq::{IqResultPayload, IqSetPayload};
  8use crate::ns;
  9use crate::Element;
 10use jid::{FullJid, Jid};
 11use std::str::FromStr;
 12use xso::error::{Error, FromElementError};
 13
 14/// The request for resource binding, which is the process by which a client
 15/// can obtain a full JID and start exchanging on the XMPP network.
 16///
 17/// See <https://xmpp.org/rfcs/rfc6120.html#bind>
 18#[derive(Debug, Clone, PartialEq)]
 19pub struct BindQuery {
 20    /// Requests this resource, the server may associate another one though.
 21    ///
 22    /// If this is None, we request no particular resource, and a random one
 23    /// will be affected by the server.
 24    resource: Option<String>,
 25}
 26
 27impl BindQuery {
 28    /// Creates a resource binding request.
 29    pub fn new(resource: Option<String>) -> BindQuery {
 30        BindQuery { resource }
 31    }
 32}
 33
 34impl IqSetPayload for BindQuery {}
 35
 36impl TryFrom<Element> for BindQuery {
 37    type Error = FromElementError;
 38
 39    fn try_from(elem: Element) -> Result<BindQuery, FromElementError> {
 40        check_self!(elem, "bind", BIND);
 41        check_no_attributes!(elem, "bind");
 42
 43        let mut resource = None;
 44        for child in elem.children() {
 45            if resource.is_some() {
 46                return Err(Error::Other("Bind can only have one child.").into());
 47            }
 48            if child.is("resource", ns::BIND) {
 49                check_no_attributes!(child, "resource");
 50                check_no_children!(child, "resource");
 51                resource = Some(child.text());
 52            } else {
 53                return Err(Error::Other("Unknown element in bind request.").into());
 54            }
 55        }
 56
 57        Ok(BindQuery { resource })
 58    }
 59}
 60
 61impl From<BindQuery> for Element {
 62    fn from(bind: BindQuery) -> Element {
 63        Element::builder("bind", ns::BIND)
 64            .append_all(
 65                bind.resource
 66                    .map(|resource| Element::builder("resource", ns::BIND).append(resource)),
 67            )
 68            .build()
 69    }
 70}
 71
 72/// The response for resource binding, containing the client’s full JID.
 73///
 74/// See <https://xmpp.org/rfcs/rfc6120.html#bind>
 75#[derive(Debug, Clone, PartialEq)]
 76pub struct BindResponse {
 77    /// The full JID returned by the server for this client.
 78    jid: FullJid,
 79}
 80
 81impl IqResultPayload for BindResponse {}
 82
 83impl From<BindResponse> for FullJid {
 84    fn from(bind: BindResponse) -> FullJid {
 85        bind.jid
 86    }
 87}
 88
 89impl From<BindResponse> for Jid {
 90    fn from(bind: BindResponse) -> Jid {
 91        Jid::from(bind.jid)
 92    }
 93}
 94
 95impl TryFrom<Element> for BindResponse {
 96    type Error = FromElementError;
 97
 98    fn try_from(elem: Element) -> Result<BindResponse, FromElementError> {
 99        check_self!(elem, "bind", BIND);
100        check_no_attributes!(elem, "bind");
101
102        let mut jid = None;
103        for child in elem.children() {
104            if jid.is_some() {
105                return Err(Error::Other("Bind can only have one child.").into());
106            }
107            if child.is("jid", ns::BIND) {
108                check_no_attributes!(child, "jid");
109                check_no_children!(child, "jid");
110                jid = Some(FullJid::from_str(&child.text()).map_err(Error::text_parse_error)?);
111            } else {
112                return Err(Error::Other("Unknown element in bind response.").into());
113            }
114        }
115
116        Ok(BindResponse {
117            jid: match jid {
118                None => {
119                    return Err(Error::Other("Bind response must contain a jid element.").into())
120                }
121                Some(jid) => jid,
122            },
123        })
124    }
125}
126
127impl From<BindResponse> for Element {
128    fn from(bind: BindResponse) -> Element {
129        Element::builder("bind", ns::BIND)
130            .append(Element::builder("jid", ns::BIND).append(bind.jid))
131            .build()
132    }
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[cfg(target_pointer_width = "32")]
140    #[test]
141    fn test_size() {
142        assert_size!(BindQuery, 12);
143        assert_size!(BindResponse, 16);
144    }
145
146    #[cfg(target_pointer_width = "64")]
147    #[test]
148    fn test_size() {
149        assert_size!(BindQuery, 24);
150        assert_size!(BindResponse, 32);
151    }
152
153    #[test]
154    fn test_simple() {
155        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
156            .parse()
157            .unwrap();
158        let bind = BindQuery::try_from(elem).unwrap();
159        assert_eq!(bind.resource, None);
160
161        let elem: Element =
162            "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>Hello™</resource></bind>"
163                .parse()
164                .unwrap();
165        let bind = BindQuery::try_from(elem).unwrap();
166        // FIXME: “™” should be resourceprep’d into “TM” here…
167        //assert_eq!(bind.resource.unwrap(), "HelloTM");
168        assert_eq!(bind.resource.unwrap(), "Hello™");
169
170        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>coucou@linkmauve.fr/Hello™</jid></bind>"
171            .parse()
172            .unwrap();
173        let bind = BindResponse::try_from(elem).unwrap();
174        assert_eq!(
175            bind.jid,
176            FullJid::new("coucou@linkmauve.fr/HelloTM").unwrap()
177        );
178    }
179
180    #[cfg(not(feature = "disable-validation"))]
181    #[test]
182    fn test_invalid_resource() {
183        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource attr='coucou'>resource</resource></bind>"
184            .parse()
185            .unwrap();
186        let error = BindQuery::try_from(elem).unwrap_err();
187        let message = match error {
188            FromElementError::Invalid(Error::Other(string)) => string,
189            _ => panic!(),
190        };
191        assert_eq!(message, "Unknown attribute in resource element.");
192
193        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource><hello-world/>resource</resource></bind>"
194            .parse()
195            .unwrap();
196        let error = BindQuery::try_from(elem).unwrap_err();
197        let message = match error {
198            FromElementError::Invalid(Error::Other(string)) => string,
199            _ => panic!(),
200        };
201        assert_eq!(message, "Unknown child in resource element.");
202    }
203}