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::media_element::MediaElement;
8use crate::ns;
9use crate::Element;
10use xso::error::{Error, FromElementError};
11
12generate_element!(
13 /// Represents one of the possible values for a list- field.
14 Option_, "option", DATA_FORMS,
15 attributes: [
16 /// The optional label to be displayed to the user for this option.
17 label: Option<String> = "label"
18 ],
19 children: [
20 /// The value returned to the server when selecting this option.
21 value: Required<String> = ("value", DATA_FORMS) => String
22 ]
23);
24
25generate_attribute!(
26 /// The type of a [field](struct.Field.html) element.
27 FieldType, "type", {
28 /// This field can only take the values "0" or "false" for a false
29 /// value, and "1" or "true" for a true value.
30 Boolean => "boolean",
31
32 /// This field describes data, it must not be sent back to the
33 /// requester.
34 Fixed => "fixed",
35
36 /// This field is hidden, it should not be displayed to the user but
37 /// should be sent back to the requester.
38 Hidden => "hidden",
39
40 /// This field accepts one or more [JIDs](../../jid/struct.Jid.html).
41 /// A client may want to let the user autocomplete them based on their
42 /// contacts list for instance.
43 JidMulti => "jid-multi",
44
45 /// This field accepts one [JID](../../jid/struct.Jid.html). A client
46 /// may want to let the user autocomplete it based on their contacts
47 /// list for instance.
48 JidSingle => "jid-single",
49
50 /// This field accepts one or more values from the list provided as
51 /// [options](struct.Option_.html).
52 ListMulti => "list-multi",
53
54 /// This field accepts one value from the list provided as
55 /// [options](struct.Option_.html).
56 ListSingle => "list-single",
57
58 /// This field accepts one or more free form text lines.
59 TextMulti => "text-multi",
60
61 /// This field accepts one free form password, a client should hide it
62 /// in its user interface.
63 TextPrivate => "text-private",
64
65 /// This field accepts one free form text line.
66 TextSingle => "text-single",
67 }, Default = TextSingle
68);
69
70/// Represents a field in a [data form](struct.DataForm.html).
71#[derive(Debug, Clone, PartialEq)]
72pub struct Field {
73 /// The unique identifier for this field, in the form.
74 pub var: Option<String>,
75
76 /// The type of this field.
77 pub type_: FieldType,
78
79 /// The label to be possibly displayed to the user for this field.
80 pub label: Option<String>,
81
82 /// The form will be rejected if this field isn’t present.
83 pub required: bool,
84
85 /// The natural-language description of the field, intended for presentation in a user-agent
86 pub desc: Option<String>,
87
88 /// A list of allowed values.
89 pub options: Vec<Option_>,
90
91 /// The values provided for this field.
92 pub values: Vec<String>,
93
94 /// A list of media related to this field.
95 pub media: Vec<MediaElement>,
96}
97
98impl Field {
99 /// Create a new Field, of the given var and type.
100 pub fn new(var: &str, type_: FieldType) -> Field {
101 Field {
102 var: Some(String::from(var)),
103 type_,
104 label: None,
105 required: false,
106 desc: None,
107 options: Vec::new(),
108 media: Vec::new(),
109 values: Vec::new(),
110 }
111 }
112
113 /// Set only one value in this Field.
114 pub fn with_value(mut self, value: &str) -> Field {
115 self.values.push(String::from(value));
116 self
117 }
118
119 /// Create a text-single Field with the given var and unique value.
120 pub fn text_single(var: &str, value: &str) -> Field {
121 Field::new(var, FieldType::TextSingle).with_value(value)
122 }
123
124 fn is_list(&self) -> bool {
125 self.type_ == FieldType::ListSingle || self.type_ == FieldType::ListMulti
126 }
127
128 /// Return true if this field is a valid form type specifier as per
129 /// [XEP-0068](https://xmpp.org/extensions/xep-0068.html).
130 ///
131 /// This function requires knowledge of the form's type attribute as the
132 /// criteria differ slightly among form types.
133 pub fn is_form_type(&self, ty: &DataFormType) -> bool {
134 // 1. A field must have the var FORM_TYPE
135 if self.var.as_deref() != Some("FORM_TYPE") {
136 return false;
137 }
138
139 match ty {
140 // https://xmpp.org/extensions/xep-0068.html#usecases-incorrect
141 // > If the FORM_TYPE field is not hidden in a form with
142 // > type="form" or type="result", it MUST be ignored as a context
143 // > indicator.
144 DataFormType::Form | DataFormType::Result_ => self.type_ == FieldType::Hidden,
145
146 // https://xmpp.org/extensions/xep-0068.html#impl
147 // > Data forms with the type "submit" are free to omit any
148 // > explicit field type declaration (as per Data Forms (XEP-0004)
149 // > § 3.2), as the type is implied by the corresponding
150 // > "form"-type data form. As consequence, implementations MUST
151 // > treat a FORM_TYPE field without an explicit type attribute,
152 // > in data forms of type "submit", as the FORM_TYPE field with
153 // > the special meaning defined herein.
154 DataFormType::Submit => match self.type_ {
155 FieldType::Hidden => true,
156 FieldType::TextSingle => true,
157 _ => false,
158 },
159
160 // XEP-0068 does not explicitly mention cancel type forms.
161 // However, XEP-0004 states:
162 // > a data form of type "cancel" SHOULD NOT contain any <field/>
163 // > elements.
164 // thus we ignore those.
165 DataFormType::Cancel => false,
166 }
167 }
168}
169
170impl TryFrom<Element> for Field {
171 type Error = FromElementError;
172
173 fn try_from(elem: Element) -> Result<Field, FromElementError> {
174 check_self!(elem, "field", DATA_FORMS);
175 check_no_unknown_attributes!(elem, "field", ["label", "type", "var"]);
176 let mut field = Field {
177 var: get_attr!(elem, "var", Option),
178 type_: get_attr!(elem, "type", Default),
179 label: get_attr!(elem, "label", Option),
180 required: false,
181 desc: None,
182 options: vec![],
183 values: vec![],
184 media: vec![],
185 };
186
187 if field.type_ != FieldType::Fixed && field.var.is_none() {
188 return Err(Error::Other("Required attribute 'var' missing.").into());
189 }
190
191 for element in elem.children() {
192 if element.is("value", ns::DATA_FORMS) {
193 check_no_children!(element, "value");
194 check_no_attributes!(element, "value");
195 field.values.push(element.text());
196 } else if element.is("required", ns::DATA_FORMS) {
197 if field.required {
198 return Err(Error::Other("More than one required element.").into());
199 }
200 check_no_children!(element, "required");
201 check_no_attributes!(element, "required");
202 field.required = true;
203 } else if element.is("option", ns::DATA_FORMS) {
204 if !field.is_list() {
205 return Err(Error::Other("Option element found in non-list field.").into());
206 }
207 let option = Option_::try_from(element.clone())?;
208 field.options.push(option);
209 } else if element.is("media", ns::MEDIA_ELEMENT) {
210 let media_element = MediaElement::try_from(element.clone())?;
211 field.media.push(media_element);
212 } else if element.is("desc", ns::DATA_FORMS) {
213 check_no_children!(element, "desc");
214 check_no_attributes!(element, "desc");
215 field.desc = Some(element.text());
216 } else {
217 return Err(
218 Error::Other("Field child isn’t a value, option or media element.").into(),
219 );
220 }
221 }
222 Ok(field)
223 }
224}
225
226impl From<Field> for Element {
227 fn from(field: Field) -> Element {
228 Element::builder("field", ns::DATA_FORMS)
229 .attr("var", field.var)
230 .attr("type", field.type_)
231 .attr("label", field.label)
232 .append_all(if field.required {
233 Some(Element::builder("required", ns::DATA_FORMS))
234 } else {
235 None
236 })
237 .append_all(field.options.iter().cloned().map(Element::from))
238 .append_all(
239 field
240 .values
241 .into_iter()
242 .map(|value| Element::builder("value", ns::DATA_FORMS).append(value)),
243 )
244 .append_all(field.media.iter().cloned().map(Element::from))
245 .build()
246 }
247}
248
249generate_attribute!(
250 /// Represents the type of a [data form](struct.DataForm.html).
251 DataFormType, "type", {
252 /// This is a cancel request for a prior type="form" data form.
253 Cancel => "cancel",
254
255 /// This is a request for the recipient to fill this form and send it
256 /// back as type="submit".
257 Form => "form",
258
259 /// This is a result form, which contains what the requester asked for.
260 Result_ => "result",
261
262 /// This is a complete response to a form received before.
263 Submit => "submit",
264 }
265);
266
267/// This is a form to be sent to another entity for filling.
268#[derive(Debug, Clone, PartialEq)]
269pub struct DataForm {
270 /// The type of this form, telling the other party which action to execute.
271 pub type_: DataFormType,
272
273 /// An easy accessor for the FORM_TYPE of this form, see
274 /// [XEP-0068](https://xmpp.org/extensions/xep-0068.html) for more
275 /// information.
276 pub form_type: Option<String>,
277
278 /// The title of this form.
279 pub title: Option<String>,
280
281 /// The instructions given with this form.
282 pub instructions: Option<String>,
283
284 /// A list of fields comprising this form.
285 pub fields: Vec<Field>,
286}
287
288impl DataForm {
289 /// Create a new DataForm.
290 pub fn new(type_: DataFormType, form_type: &str, fields: Vec<Field>) -> DataForm {
291 DataForm {
292 type_,
293 form_type: Some(String::from(form_type)),
294 title: None,
295 instructions: None,
296 fields,
297 }
298 }
299}
300
301impl TryFrom<Element> for DataForm {
302 type Error = FromElementError;
303
304 fn try_from(elem: Element) -> Result<DataForm, FromElementError> {
305 check_self!(elem, "x", DATA_FORMS);
306 check_no_unknown_attributes!(elem, "x", ["type"]);
307 let type_ = get_attr!(elem, "type", Required);
308 let mut form = DataForm {
309 type_,
310 form_type: None,
311 title: None,
312 instructions: None,
313 fields: vec![],
314 };
315 for child in elem.children() {
316 if child.is("title", ns::DATA_FORMS) {
317 if form.title.is_some() {
318 return Err(Error::Other("More than one title in form element.").into());
319 }
320 check_no_children!(child, "title");
321 check_no_attributes!(child, "title");
322 form.title = Some(child.text());
323 } else if child.is("instructions", ns::DATA_FORMS) {
324 if form.instructions.is_some() {
325 return Err(Error::Other("More than one instructions in form element.").into());
326 }
327 check_no_children!(child, "instructions");
328 check_no_attributes!(child, "instructions");
329 form.instructions = Some(child.text());
330 } else if child.is("field", ns::DATA_FORMS) {
331 let field = Field::try_from(child.clone())?;
332 if field.is_form_type(&form.type_) {
333 let mut field = field;
334 if form.form_type.is_some() {
335 return Err(Error::Other("More than one FORM_TYPE in a data form.").into());
336 }
337 if field.values.len() != 1 {
338 return Err(Error::Other("Wrong number of values in FORM_TYPE.").into());
339 }
340 form.form_type = field.values.pop();
341 } else {
342 form.fields.push(field);
343 }
344 } else {
345 return Err(Error::Other("Unknown child in data form element.").into());
346 }
347 }
348 Ok(form)
349 }
350}
351
352impl From<DataForm> for Element {
353 fn from(form: DataForm) -> Element {
354 Element::builder("x", ns::DATA_FORMS)
355 .attr("type", form.type_)
356 .append_all(
357 form.title
358 .map(|title| Element::builder("title", ns::DATA_FORMS).append(title)),
359 )
360 .append_all(
361 form.instructions
362 .map(|text| Element::builder("instructions", ns::DATA_FORMS).append(text)),
363 )
364 .append_all(form.form_type.map(|form_type| {
365 Element::builder("field", ns::DATA_FORMS)
366 .attr("var", "FORM_TYPE")
367 .attr("type", "hidden")
368 .append(Element::builder("value", ns::DATA_FORMS).append(form_type))
369 }))
370 .append_all(form.fields.iter().cloned().map(Element::from))
371 .build()
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 #[cfg(target_pointer_width = "32")]
380 #[test]
381 fn test_size() {
382 assert_size!(Option_, 24);
383 assert_size!(FieldType, 1);
384 assert_size!(Field, 76);
385 assert_size!(DataFormType, 1);
386 assert_size!(DataForm, 52);
387 }
388
389 #[cfg(target_pointer_width = "64")]
390 #[test]
391 fn test_size() {
392 assert_size!(Option_, 48);
393 assert_size!(FieldType, 1);
394 assert_size!(Field, 152);
395 assert_size!(DataFormType, 1);
396 assert_size!(DataForm, 104);
397 }
398
399 #[test]
400 fn test_simple() {
401 let elem: Element = "<x xmlns='jabber:x:data' type='result'/>".parse().unwrap();
402 let form = DataForm::try_from(elem).unwrap();
403 assert_eq!(form.type_, DataFormType::Result_);
404 assert!(form.form_type.is_none());
405 assert!(form.fields.is_empty());
406 }
407
408 #[test]
409 fn test_missing_var() {
410 let elem: Element =
411 "<x xmlns='jabber:x:data' type='form'><field type='text-single' label='The name of your bot'/></x>"
412 .parse()
413 .unwrap();
414 let error = DataForm::try_from(elem).unwrap_err();
415 let message = match error {
416 FromElementError::Invalid(Error::Other(string)) => string,
417 _ => panic!(),
418 };
419 assert_eq!(message, "Required attribute 'var' missing.");
420 }
421
422 #[test]
423 fn test_fixed_field() {
424 let elem: Element =
425 "<x xmlns='jabber:x:data' type='form'><field type='fixed'><value>Section 1: Bot Info</value></field></x>"
426 .parse()
427 .unwrap();
428 let form = DataForm::try_from(elem).unwrap();
429 assert_eq!(form.type_, DataFormType::Form);
430 assert!(form.form_type.is_none());
431 assert_eq!(
432 form.fields,
433 vec![Field {
434 var: None,
435 type_: FieldType::Fixed,
436 label: None,
437 required: false,
438 desc: None,
439 options: vec![],
440 values: vec!["Section 1: Bot Info".to_string()],
441 media: vec![],
442 }]
443 );
444 }
445
446 #[test]
447 fn test_desc() {
448 let elem: Element =
449 "<x xmlns='jabber:x:data' type='form'><field type='jid-multi' label='People to invite' var='invitelist'><desc>Tell all your friends about your new bot!</desc></field></x>"
450 .parse()
451 .unwrap();
452 let form = DataForm::try_from(elem).unwrap();
453 assert_eq!(form.type_, DataFormType::Form);
454 assert!(form.form_type.is_none());
455 assert_eq!(
456 form.fields,
457 vec![Field {
458 var: Some("invitelist".to_string()),
459 type_: FieldType::JidMulti,
460 label: Some("People to invite".to_string()),
461 required: false,
462 desc: Some("Tell all your friends about your new bot!".to_string()),
463 options: vec![],
464 values: vec![],
465 media: vec![],
466 }]
467 );
468 }
469
470 #[test]
471 fn test_invalid() {
472 let elem: Element = "<x xmlns='jabber:x:data'/>".parse().unwrap();
473 let error = DataForm::try_from(elem).unwrap_err();
474 let message = match error {
475 FromElementError::Invalid(Error::Other(string)) => string,
476 _ => panic!(),
477 };
478 assert_eq!(message, "Required attribute 'type' missing.");
479
480 let elem: Element = "<x xmlns='jabber:x:data' type='coucou'/>".parse().unwrap();
481 let error = DataForm::try_from(elem).unwrap_err();
482 let message = match error {
483 FromElementError::Invalid(Error::TextParseError(string)) => string,
484 other => panic!("unexpected result: {:?}", other),
485 };
486 assert_eq!(message.to_string(), "Unknown value for 'type' attribute.");
487 }
488
489 #[test]
490 fn test_wrong_child() {
491 let elem: Element = "<x xmlns='jabber:x:data' type='cancel'><coucou/></x>"
492 .parse()
493 .unwrap();
494 let error = DataForm::try_from(elem).unwrap_err();
495 let message = match error {
496 FromElementError::Invalid(Error::Other(string)) => string,
497 _ => panic!(),
498 };
499 assert_eq!(message, "Unknown child in data form element.");
500 }
501
502 #[test]
503 fn option() {
504 let elem: Element =
505 "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value></option>"
506 .parse()
507 .unwrap();
508 let option = Option_::try_from(elem).unwrap();
509 assert_eq!(&option.label.unwrap(), "Coucou !");
510 assert_eq!(&option.value, "coucou");
511
512 let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'/>"
513 .parse()
514 .unwrap();
515 let error = Option_::try_from(elem).unwrap_err();
516 let message = match error {
517 FromElementError::Invalid(Error::Other(string)) => string,
518 _ => panic!(),
519 };
520 assert_eq!(message, "Missing child value in option element.");
521
522 let elem: Element = "<option xmlns='jabber:x:data' label='Coucou !'><value>coucou</value><value>error</value></option>".parse().unwrap();
523 let error = Option_::try_from(elem).unwrap_err();
524 let message = match error {
525 FromElementError::Invalid(Error::Other(string)) => string,
526 _ => panic!(),
527 };
528 assert_eq!(
529 message,
530 "Element option must not have more than one value child."
531 );
532 }
533
534 #[test]
535 fn test_ignore_form_type_field_if_field_type_mismatches_in_form_typed_forms() {
536 // https://xmpp.org/extensions/xep-0068.html#usecases-incorrect
537 // […] it MUST be ignored as a context indicator
538 let elem: Element = "<x xmlns='jabber:x:data' type='form'><field var='FORM_TYPE' type='text-single'><value>foo</value></field></x>".parse().unwrap();
539 match DataForm::try_from(elem) {
540 Ok(form) => {
541 match form.form_type {
542 None => (),
543 other => panic!("unexpected extracted form type: {:?}", other),
544 };
545 }
546 other => panic!("unexpected result: {:?}", other),
547 }
548 }
549
550 #[test]
551 fn test_ignore_form_type_field_if_field_type_mismatches_in_result_typed_forms() {
552 // https://xmpp.org/extensions/xep-0068.html#usecases-incorrect
553 // […] it MUST be ignored as a context indicator
554 let elem: Element = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='text-single'><value>foo</value></field></x>".parse().unwrap();
555 match DataForm::try_from(elem) {
556 Ok(form) => {
557 match form.form_type {
558 None => (),
559 other => panic!("unexpected extracted form type: {:?}", other),
560 };
561 }
562 other => panic!("unexpected result: {:?}", other),
563 }
564 }
565
566 #[test]
567 fn test_accept_form_type_field_without_type_attribute_in_submit_typed_forms() {
568 let elem: Element = "<x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE'><value>foo</value></field></x>".parse().unwrap();
569 match DataForm::try_from(elem) {
570 Ok(form) => {
571 match form.form_type {
572 Some(ty) => assert_eq!(ty, "foo"),
573 other => panic!("unexpected extracted form type: {:?}", other),
574 };
575 }
576 other => panic!("unexpected result: {:?}", other),
577 }
578 }
579
580 #[test]
581 fn test_accept_form_type_field_with_type_hidden_in_submit_typed_forms() {
582 let elem: Element = "<x xmlns='jabber:x:data' type='submit'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
583 match DataForm::try_from(elem) {
584 Ok(form) => {
585 match form.form_type {
586 Some(ty) => assert_eq!(ty, "foo"),
587 other => panic!("unexpected extracted form type: {:?}", other),
588 };
589 }
590 other => panic!("unexpected result: {:?}", other),
591 }
592 }
593
594 #[test]
595 fn test_accept_form_type_field_with_type_hidden_in_result_typed_forms() {
596 let elem: Element = "<x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
597 match DataForm::try_from(elem) {
598 Ok(form) => {
599 match form.form_type {
600 Some(ty) => assert_eq!(ty, "foo"),
601 other => panic!("unexpected extracted form type: {:?}", other),
602 };
603 }
604 other => panic!("unexpected result: {:?}", other),
605 }
606 }
607
608 #[test]
609 fn test_accept_form_type_field_with_type_hidden_in_form_typed_forms() {
610 let elem: Element = "<x xmlns='jabber:x:data' type='form'><field var='FORM_TYPE' type='hidden'><value>foo</value></field></x>".parse().unwrap();
611 match DataForm::try_from(elem) {
612 Ok(form) => {
613 match form.form_type {
614 Some(ty) => assert_eq!(ty, "foo"),
615 other => panic!("unexpected extracted form type: {:?}", other),
616 };
617 }
618 other => panic!("unexpected result: {:?}", other),
619 }
620 }
621}