1// Copyright (c) 2017 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::data_forms::DataForm;
8use crate::util::error::Error;
9use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
10use crate::ns;
11use minidom::Element;
12use std::collections::HashMap;
13use std::convert::TryFrom;
14
15/// Query for registering against a service.
16#[derive(Debug, Clone)]
17pub struct Query {
18 /// Deprecated fixed list of possible fields to fill before the user can
19 /// register.
20 pub fields: HashMap<String, String>,
21
22 /// Whether this account is already registered.
23 pub registered: bool,
24
25 /// Whether to remove this account.
26 pub remove: bool,
27
28 /// A data form the user must fill before being allowed to register.
29 pub form: Option<DataForm>,
30 // Not yet implemented.
31 //pub oob: Option<Oob>,
32}
33
34impl IqGetPayload for Query {}
35impl IqSetPayload for Query {}
36impl IqResultPayload for Query {}
37
38impl TryFrom<Element> for Query {
39 type Error = Error;
40
41 fn try_from(elem: Element) -> Result<Query, Error> {
42 check_self!(elem, "query", REGISTER, "IBR query");
43 let mut query = Query {
44 registered: false,
45 fields: HashMap::new(),
46 remove: false,
47 form: None,
48 };
49 for child in elem.children() {
50 let namespace = child.ns().unwrap();
51 if namespace == ns::REGISTER {
52 let name = child.name();
53 let fields = vec![
54 "address",
55 "city",
56 "date",
57 "email",
58 "first",
59 "instructions",
60 "key",
61 "last",
62 "misc",
63 "name",
64 "nick",
65 "password",
66 "phone",
67 "state",
68 "text",
69 "url",
70 "username",
71 "zip",
72 ];
73 if fields.binary_search(&name).is_ok() {
74 query.fields.insert(name.to_owned(), child.text());
75 } else if name == "registered" {
76 query.registered = true;
77 } else if name == "remove" {
78 query.remove = true;
79 } else {
80 return Err(Error::ParseError("Wrong field in ibr element."));
81 }
82 } else if child.is("x", ns::DATA_FORMS) {
83 query.form = Some(DataForm::try_from(child.clone())?);
84 } else {
85 return Err(Error::ParseError("Unknown child in ibr element."));
86 }
87 }
88 Ok(query)
89 }
90}
91
92impl From<Query> for Element {
93 fn from(query: Query) -> Element {
94 Element::builder("query")
95 .ns(ns::REGISTER)
96 .append(if query.registered {
97 Some(Element::builder("registered").ns(ns::REGISTER))
98 } else {
99 None
100 })
101 .append(
102 query
103 .fields
104 .into_iter()
105 .map(|(name, value)| Element::builder(name).ns(ns::REGISTER).append(value))
106 .collect::<Vec<_>>(),
107 )
108 .append(if query.remove {
109 Some(Element::builder("remove").ns(ns::REGISTER))
110 } else {
111 None
112 })
113 .append(query.form)
114 .build()
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::util::compare_elements::NamespaceAwareCompare;
122
123 // TODO: These size tests are sensible to the size of HashMap, which recently grew of two
124 // pointers and is thus different on stable and nightly. Let’s wait for this issue before
125 // attempting a fix:
126 // https://github.com/rust-lang/hashbrown/issues/69
127
128 #[cfg(target_pointer_width = "32")]
129 #[test]
130 #[ignore]
131 fn test_size() {
132 assert_size!(Query, 88);
133 }
134
135 #[cfg(target_pointer_width = "64")]
136 #[test]
137 #[ignore]
138 fn test_size() {
139 assert_size!(Query, 152);
140 }
141
142 #[test]
143 fn test_simple() {
144 let elem: Element = "<query xmlns='jabber:iq:register'/>".parse().unwrap();
145 Query::try_from(elem).unwrap();
146 }
147
148 #[test]
149 fn test_ex2() {
150 let elem: Element = r#"
151<query xmlns='jabber:iq:register'>
152 <instructions>
153 Choose a username and password for use with this service.
154 Please also provide your email address.
155 </instructions>
156 <username/>
157 <password/>
158 <email/>
159</query>
160"#
161 .parse()
162 .unwrap();
163 let query = Query::try_from(elem).unwrap();
164 assert_eq!(query.registered, false);
165 assert_eq!(query.fields["instructions"], "\n Choose a username and password for use with this service.\n Please also provide your email address.\n ");
166 assert_eq!(query.fields["username"], "");
167 assert_eq!(query.fields["password"], "");
168 assert_eq!(query.fields["email"], "");
169 assert_eq!(query.fields.contains_key("name"), false);
170
171 // FIXME: HashMap doesn’t keep the order right.
172 //let elem2 = query.into();
173 //assert_eq!(elem, elem2);
174 }
175
176 #[test]
177 fn test_ex9() {
178 let elem: Element = r#"
179<query xmlns='jabber:iq:register'>
180 <instructions>
181 Use the enclosed form to register. If your Jabber client does not
182 support Data Forms, visit http://www.shakespeare.lit/contests.php
183 </instructions>
184 <x xmlns='jabber:x:data' type='form'>
185 <title>Contest Registration</title>
186 <instructions>
187 Please provide the following information
188 to sign up for our special contests!
189 </instructions>
190 <field type='hidden' var='FORM_TYPE'>
191 <value>jabber:iq:register</value>
192 </field>
193 <field label='Given Name' var='first'>
194 <required/>
195 </field>
196 <field label='Family Name' var='last'>
197 <required/>
198 </field>
199 <field label='Email Address' var='email'>
200 <required/>
201 </field>
202 <field type='list-single' label='Gender' var='x-gender'>
203 <option label='Male'><value>M</value></option>
204 <option label='Female'><value>F</value></option>
205 </field>
206 </x>
207</query>
208"#
209 .parse()
210 .unwrap();
211 let elem1 = elem.clone();
212 let query = Query::try_from(elem).unwrap();
213 assert_eq!(query.registered, false);
214 assert!(!query.fields["instructions"].is_empty());
215 let form = query.form.clone().unwrap();
216 assert!(!form.instructions.unwrap().is_empty());
217 let elem2 = query.into();
218 assert!(elem1.compare_to(&elem2));
219 }
220
221 #[test]
222 fn test_ex10() {
223 let elem: Element = r#"
224<query xmlns='jabber:iq:register'>
225 <x xmlns='jabber:x:data' type='submit'>
226 <field type='hidden' var='FORM_TYPE'>
227 <value>jabber:iq:register</value>
228 </field>
229 <field label='Given Name' var='first'>
230 <value>Juliet</value>
231 </field>
232 <field label='Family Name' var='last'>
233 <value>Capulet</value>
234 </field>
235 <field label='Email Address' var='email'>
236 <value>juliet@capulet.com</value>
237 </field>
238 <field type='list-single' label='Gender' var='x-gender'>
239 <value>F</value>
240 </field>
241 </x>
242</query>
243"#
244 .parse()
245 .unwrap();
246 let elem1 = elem.clone();
247 let query = Query::try_from(elem).unwrap();
248 assert_eq!(query.registered, false);
249 for _ in &query.fields {
250 panic!();
251 }
252 let elem2 = query.into();
253 assert!(elem1.compare_to(&elem2));
254 }
255}