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