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