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