xmpp_parsers/
media_element.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::{text::EmptyAsError, AsXml, FromXml};
8
9use crate::ns;
10
11/// Represents an URI used in a media element.
12#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
13#[xml(namespace = ns::MEDIA_ELEMENT, name = "uri")]
14pub struct Uri {
15    /// The MIME type of the URI referenced.
16    ///
17    /// See the [IANA MIME Media Types Registry][1] for a list of
18    /// registered types, but unregistered or yet-to-be-registered are
19    /// accepted too.
20    ///
21    /// [1]: <https://www.iana.org/assignments/media-types/media-types.xhtml>
22    #[xml(attribute(name = "type"))]
23    pub type_: String,
24
25    /// The actual URI contained.
26    #[xml(text(codec = EmptyAsError))]
27    pub uri: String,
28}
29
30/// References a media element, to be used in [data
31/// forms](../data_forms/index.html).
32#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
33#[xml(namespace = ns::MEDIA_ELEMENT, name = "media")]
34pub struct MediaElement {
35    /// The recommended display width in pixels.
36    #[xml(attribute(default))]
37    pub width: Option<usize>,
38
39    /// The recommended display height in pixels.
40    #[xml(attribute(default))]
41    pub height: Option<usize>,
42
43    /// A list of URIs referencing this media.
44    #[xml(child(n = ..))]
45    pub uris: Vec<Uri>,
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use crate::data_forms::DataForm;
52    use minidom::Element;
53    use xso::error::{Error, FromElementError};
54
55    #[cfg(target_pointer_width = "32")]
56    #[test]
57    fn test_size() {
58        assert_size!(Uri, 24);
59        assert_size!(MediaElement, 28);
60    }
61
62    #[cfg(target_pointer_width = "64")]
63    #[test]
64    fn test_size() {
65        assert_size!(Uri, 48);
66        assert_size!(MediaElement, 56);
67    }
68
69    #[test]
70    fn test_simple() {
71        let elem: Element = "<media xmlns='urn:xmpp:media-element'/>".parse().unwrap();
72        let media = MediaElement::try_from(elem).unwrap();
73        assert!(media.width.is_none());
74        assert!(media.height.is_none());
75        assert!(media.uris.is_empty());
76    }
77
78    #[test]
79    fn test_width_height() {
80        let elem: Element = "<media xmlns='urn:xmpp:media-element' width='32' height='32'/>"
81            .parse()
82            .unwrap();
83        let media = MediaElement::try_from(elem).unwrap();
84        assert_eq!(media.width.unwrap(), 32);
85        assert_eq!(media.height.unwrap(), 32);
86    }
87
88    #[test]
89    fn test_uri() {
90        let elem: Element = "<media xmlns='urn:xmpp:media-element'><uri type='text/html'>https://example.org/</uri></media>".parse().unwrap();
91        let media = MediaElement::try_from(elem).unwrap();
92        assert_eq!(media.uris.len(), 1);
93        assert_eq!(media.uris[0].type_, "text/html");
94        assert_eq!(media.uris[0].uri, "https://example.org/");
95    }
96
97    #[test]
98    fn test_invalid_width_height() {
99        let elem: Element = "<media xmlns='urn:xmpp:media-element' width=''/>"
100            .parse()
101            .unwrap();
102        let error = MediaElement::try_from(elem).unwrap_err();
103        let error = match error {
104            FromElementError::Invalid(Error::TextParseError(error))
105                if error.is::<core::num::ParseIntError>() =>
106            {
107                error
108            }
109            _ => panic!(),
110        };
111        assert_eq!(error.to_string(), "cannot parse integer from empty string");
112
113        let elem: Element = "<media xmlns='urn:xmpp:media-element' width='coucou'/>"
114            .parse()
115            .unwrap();
116        let error = MediaElement::try_from(elem).unwrap_err();
117        let error = match error {
118            FromElementError::Invalid(Error::TextParseError(error))
119                if error.is::<core::num::ParseIntError>() =>
120            {
121                error
122            }
123            _ => panic!(),
124        };
125        assert_eq!(error.to_string(), "invalid digit found in string");
126
127        let elem: Element = "<media xmlns='urn:xmpp:media-element' height=''/>"
128            .parse()
129            .unwrap();
130        let error = MediaElement::try_from(elem).unwrap_err();
131        let error = match error {
132            FromElementError::Invalid(Error::TextParseError(error))
133                if error.is::<core::num::ParseIntError>() =>
134            {
135                error
136            }
137            _ => panic!(),
138        };
139        assert_eq!(error.to_string(), "cannot parse integer from empty string");
140
141        let elem: Element = "<media xmlns='urn:xmpp:media-element' height='-10'/>"
142            .parse()
143            .unwrap();
144        let error = MediaElement::try_from(elem).unwrap_err();
145        let error = match error {
146            FromElementError::Invalid(Error::TextParseError(error))
147                if error.is::<core::num::ParseIntError>() =>
148            {
149                error
150            }
151            _ => panic!(),
152        };
153        assert_eq!(error.to_string(), "invalid digit found in string");
154    }
155
156    #[test]
157    #[cfg_attr(feature = "disable-validation", should_panic = "Result::unwrap_err")]
158    fn test_unknown_child() {
159        let elem: Element = "<media xmlns='urn:xmpp:media-element'><coucou/></media>"
160            .parse()
161            .unwrap();
162        let error = MediaElement::try_from(elem).unwrap_err();
163        let message = match error {
164            FromElementError::Invalid(Error::Other(string)) => string,
165            _ => panic!(),
166        };
167        assert_eq!(message, "Unknown child in MediaElement element.");
168    }
169
170    #[test]
171    fn test_bad_uri() {
172        let elem: Element =
173            "<media xmlns='urn:xmpp:media-element'><uri>https://example.org/</uri></media>"
174                .parse()
175                .unwrap();
176        let error = MediaElement::try_from(elem).unwrap_err();
177        let message = match error {
178            FromElementError::Invalid(Error::Other(string)) => string,
179            _ => panic!(),
180        };
181        assert_eq!(
182            message,
183            "Required attribute field 'type_' on Uri element missing."
184        );
185
186        let elem: Element = "<media xmlns='urn:xmpp:media-element'><uri type='text/html'/></media>"
187            .parse()
188            .unwrap();
189        let error = MediaElement::try_from(elem).unwrap_err();
190        let message = match error {
191            FromElementError::Invalid(Error::Other(string)) => string,
192            _ => panic!(),
193        };
194        assert_eq!(message, "Empty text node.");
195    }
196
197    #[test]
198    fn test_xep_ex1() {
199        let elem: Element = r#"<media xmlns='urn:xmpp:media-element'>
200  <uri type='audio/x-wav'>http://victim.example.com/challenges/speech.wav?F3A6292C</uri>
201  <uri type='audio/ogg; codecs=speex'>cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org</uri>
202  <uri type='audio/mpeg'>http://victim.example.com/challenges/speech.mp3?F3A6292C</uri>
203</media>"#
204            .parse()
205            .unwrap();
206        let media = MediaElement::try_from(elem).unwrap();
207        assert!(media.width.is_none());
208        assert!(media.height.is_none());
209        assert_eq!(media.uris.len(), 3);
210        assert_eq!(media.uris[0].type_, "audio/x-wav");
211        assert_eq!(
212            media.uris[0].uri,
213            "http://victim.example.com/challenges/speech.wav?F3A6292C"
214        );
215        assert_eq!(media.uris[1].type_, "audio/ogg; codecs=speex");
216        assert_eq!(
217            media.uris[1].uri,
218            "cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org"
219        );
220        assert_eq!(media.uris[2].type_, "audio/mpeg");
221        assert_eq!(
222            media.uris[2].uri,
223            "http://victim.example.com/challenges/speech.mp3?F3A6292C"
224        );
225    }
226
227    #[test]
228    fn test_xep_ex2() {
229        let elem: Element = r#"<x xmlns='jabber:x:data' type='form'>
230  [ ... ]
231  <field var='ocr'>
232    <media xmlns='urn:xmpp:media-element'
233           height='80'
234           width='290'>
235      <uri type='image/jpeg'>http://www.victim.com/challenges/ocr.jpeg?F3A6292C</uri>
236      <uri type='image/jpeg'>cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org</uri>
237    </media>
238  </field>
239  [ ... ]
240</x>"#
241            .parse()
242            .unwrap();
243        let form = DataForm::try_from(elem).unwrap();
244        assert_eq!(form.fields.len(), 1);
245        assert_eq!(form.fields[0].var.as_deref(), Some("ocr"));
246        assert_eq!(form.fields[0].media[0].width, Some(290));
247        assert_eq!(form.fields[0].media[0].height, Some(80));
248        assert_eq!(form.fields[0].media[0].uris[0].type_, "image/jpeg");
249        assert_eq!(
250            form.fields[0].media[0].uris[0].uri,
251            "http://www.victim.com/challenges/ocr.jpeg?F3A6292C"
252        );
253        assert_eq!(form.fields[0].media[0].uris[1].type_, "image/jpeg");
254        assert_eq!(
255            form.fields[0].media[0].uris[1].uri,
256            "cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org"
257        );
258    }
259}