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 #[cfg(target_pointer_width = "32")]
124 #[test]
125 fn test_size() {
126 assert_size!(Query, 96);
127 }
128
129 #[cfg(target_pointer_width = "64")]
130 #[test]
131 fn test_size() {
132 assert_size!(Query, 168);
133 }
134
135 #[test]
136 fn test_simple() {
137 let elem: Element = "<query xmlns='jabber:iq:register'/>".parse().unwrap();
138 Query::try_from(elem).unwrap();
139 }
140
141 #[test]
142 fn test_ex2() {
143 let elem: Element = r#"
144<query xmlns='jabber:iq:register'>
145 <instructions>
146 Choose a username and password for use with this service.
147 Please also provide your email address.
148 </instructions>
149 <username/>
150 <password/>
151 <email/>
152</query>
153"#
154 .parse()
155 .unwrap();
156 let query = Query::try_from(elem).unwrap();
157 assert_eq!(query.registered, false);
158 assert_eq!(query.fields["instructions"], "\n Choose a username and password for use with this service.\n Please also provide your email address.\n ");
159 assert_eq!(query.fields["username"], "");
160 assert_eq!(query.fields["password"], "");
161 assert_eq!(query.fields["email"], "");
162 assert_eq!(query.fields.contains_key("name"), false);
163
164 // FIXME: HashMap doesn’t keep the order right.
165 //let elem2 = query.into();
166 //assert_eq!(elem, elem2);
167 }
168
169 #[test]
170 fn test_ex9() {
171 let elem: Element = r#"
172<query xmlns='jabber:iq:register'>
173 <instructions>
174 Use the enclosed form to register. If your Jabber client does not
175 support Data Forms, visit http://www.shakespeare.lit/contests.php
176 </instructions>
177 <x xmlns='jabber:x:data' type='form'>
178 <title>Contest Registration</title>
179 <instructions>
180 Please provide the following information
181 to sign up for our special contests!
182 </instructions>
183 <field type='hidden' var='FORM_TYPE'>
184 <value>jabber:iq:register</value>
185 </field>
186 <field label='Given Name' var='first'>
187 <required/>
188 </field>
189 <field label='Family Name' var='last'>
190 <required/>
191 </field>
192 <field label='Email Address' var='email'>
193 <required/>
194 </field>
195 <field type='list-single' label='Gender' var='x-gender'>
196 <option label='Male'><value>M</value></option>
197 <option label='Female'><value>F</value></option>
198 </field>
199 </x>
200</query>
201"#
202 .parse()
203 .unwrap();
204 let elem1 = elem.clone();
205 let query = Query::try_from(elem).unwrap();
206 assert_eq!(query.registered, false);
207 assert!(!query.fields["instructions"].is_empty());
208 let form = query.form.clone().unwrap();
209 assert!(!form.instructions.unwrap().is_empty());
210 let elem2 = query.into();
211 assert!(elem1.compare_to(&elem2));
212 }
213
214 #[test]
215 fn test_ex10() {
216 let elem: Element = r#"
217<query xmlns='jabber:iq:register'>
218 <x xmlns='jabber:x:data' type='submit'>
219 <field type='hidden' var='FORM_TYPE'>
220 <value>jabber:iq:register</value>
221 </field>
222 <field label='Given Name' var='first'>
223 <value>Juliet</value>
224 </field>
225 <field label='Family Name' var='last'>
226 <value>Capulet</value>
227 </field>
228 <field label='Email Address' var='email'>
229 <value>juliet@capulet.com</value>
230 </field>
231 <field type='list-single' label='Gender' var='x-gender'>
232 <value>F</value>
233 </field>
234 </x>
235</query>
236"#
237 .parse()
238 .unwrap();
239 let elem1 = elem.clone();
240 let query = Query::try_from(elem).unwrap();
241 assert_eq!(query.registered, false);
242 for _ in &query.fields {
243 panic!();
244 }
245 let elem2 = query.into();
246 assert!(elem1.compare_to(&elem2));
247 }
248}