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::util::error::Error;
 10use crate::Element;
 11use jid::{FullJid, Jid};
 12use std::str::FromStr;
 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 = Error;
 38
 39    fn try_from(elem: Element) -> Result<BindQuery, Error> {
 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::ParseError("Bind can only have one child."));
 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::ParseError("Unknown element in bind request."));
 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 = Error;
 97
 98    fn try_from(elem: Element) -> Result<BindResponse, Error> {
 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::ParseError("Bind can only have one child."));
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())?);
111            } else {
112                return Err(Error::ParseError("Unknown element in bind response."));
113            }
114        }
115
116        Ok(BindResponse {
117            jid: match jid {
118                None => {
119                    return Err(Error::ParseError(
120                        "Bind response must contain a jid element.",
121                    ))
122                }
123                Some(jid) => jid,
124            },
125        })
126    }
127}
128
129impl From<BindResponse> for Element {
130    fn from(bind: BindResponse) -> Element {
131        Element::builder("bind", ns::BIND)
132            .append(Element::builder("jid", ns::BIND).append(bind.jid))
133            .build()
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[cfg(target_pointer_width = "32")]
142    #[test]
143    fn test_size() {
144        assert_size!(BindQuery, 12);
145        assert_size!(BindResponse, 16);
146    }
147
148    #[cfg(target_pointer_width = "64")]
149    #[test]
150    fn test_size() {
151        assert_size!(BindQuery, 24);
152        assert_size!(BindResponse, 32);
153    }
154
155    #[test]
156    fn test_simple() {
157        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>"
158            .parse()
159            .unwrap();
160        let bind = BindQuery::try_from(elem).unwrap();
161        assert_eq!(bind.resource, None);
162
163        let elem: Element =
164            "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>Hello™</resource></bind>"
165                .parse()
166                .unwrap();
167        let bind = BindQuery::try_from(elem).unwrap();
168        // FIXME: “™” should be resourceprep’d into “TM” here…
169        //assert_eq!(bind.resource.unwrap(), "HelloTM");
170        assert_eq!(bind.resource.unwrap(), "Hello™");
171
172        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>coucou@linkmauve.fr/Hello™</jid></bind>"
173            .parse()
174            .unwrap();
175        let bind = BindResponse::try_from(elem).unwrap();
176        assert_eq!(
177            bind.jid,
178            FullJid::new("coucou@linkmauve.fr/HelloTM").unwrap()
179        );
180    }
181
182    #[cfg(not(feature = "disable-validation"))]
183    #[test]
184    fn test_invalid_resource() {
185        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource attr='coucou'>resource</resource></bind>"
186            .parse()
187            .unwrap();
188        let error = BindQuery::try_from(elem).unwrap_err();
189        let message = match error {
190            Error::ParseError(string) => string,
191            _ => panic!(),
192        };
193        assert_eq!(message, "Unknown attribute in resource element.");
194
195        let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource><hello-world/>resource</resource></bind>"
196            .parse()
197            .unwrap();
198        let error = BindQuery::try_from(elem).unwrap_err();
199        let message = match error {
200            Error::ParseError(string) => string,
201            _ => panic!(),
202        };
203        assert_eq!(message, "Unknown child in resource element.");
204    }
205}