xmpp_parsers/
data_forms.rs

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