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::convert::TryFrom;
8
9use minidom::Element;
10
11use error::Error;
12
13use ns;
14
15#[derive(Debug, Clone)]
16pub struct URI {
17 pub type_: String,
18 pub uri: String,
19}
20
21#[derive(Debug, Clone)]
22pub struct MediaElement {
23 pub width: Option<usize>,
24 pub height: Option<usize>,
25 pub uris: Vec<URI>,
26}
27
28impl TryFrom<Element> for MediaElement {
29 type Error = Error;
30
31 fn try_from(elem: Element) -> Result<MediaElement, Error> {
32 if !elem.is("media", ns::MEDIA_ELEMENT) {
33 return Err(Error::ParseError("This is not a media element."));
34 }
35
36 let mut media = MediaElement {
37 width: get_attr!(elem, "width", optional),
38 height: get_attr!(elem, "height", optional),
39 uris: vec!(),
40 };
41 for uri in elem.children() {
42 if uri.is("uri", ns::MEDIA_ELEMENT) {
43 let type_ = get_attr!(uri, "type", required);
44 let text = uri.text().trim().to_owned();
45 if text == "" {
46 return Err(Error::ParseError("URI missing in uri."));
47 }
48 media.uris.push(URI { type_: type_, uri: text });
49 } else {
50 return Err(Error::ParseError("Unknown child in media element."));
51 }
52 }
53 Ok(media)
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use data_forms::DataForm;
61 use std::error::Error as StdError;
62
63 #[test]
64 fn test_simple() {
65 let elem: Element = "<media xmlns='urn:xmpp:media-element'/>".parse().unwrap();
66 let media = MediaElement::try_from(elem).unwrap();
67 assert!(media.width.is_none());
68 assert!(media.height.is_none());
69 assert!(media.uris.is_empty());
70 }
71
72 #[test]
73 fn test_width_height() {
74 let elem: Element = "<media xmlns='urn:xmpp:media-element' width='32' height='32'/>".parse().unwrap();
75 let media = MediaElement::try_from(elem).unwrap();
76 assert_eq!(media.width.unwrap(), 32);
77 assert_eq!(media.height.unwrap(), 32);
78 }
79
80 #[test]
81 fn test_uri() {
82 let elem: Element = "<media xmlns='urn:xmpp:media-element'><uri type='text/html'>https://example.org/</uri></media>".parse().unwrap();
83 let media = MediaElement::try_from(elem).unwrap();
84 assert_eq!(media.uris.len(), 1);
85 assert_eq!(media.uris[0].type_, "text/html");
86 assert_eq!(media.uris[0].uri, "https://example.org/");
87 }
88
89 #[test]
90 fn test_invalid_width_height() {
91 let elem: Element = "<media xmlns='urn:xmpp:media-element' width=''/>".parse().unwrap();
92 let error = MediaElement::try_from(elem).unwrap_err();
93 let error = match error {
94 Error::ParseIntError(error) => error,
95 _ => panic!(),
96 };
97 assert_eq!(error.description(), "cannot parse integer from empty string");
98
99 let elem: Element = "<media xmlns='urn:xmpp:media-element' width='coucou'/>".parse().unwrap();
100 let error = MediaElement::try_from(elem).unwrap_err();
101 let error = match error {
102 Error::ParseIntError(error) => error,
103 _ => panic!(),
104 };
105 assert_eq!(error.description(), "invalid digit found in string");
106
107 let elem: Element = "<media xmlns='urn:xmpp:media-element' height=''/>".parse().unwrap();
108 let error = MediaElement::try_from(elem).unwrap_err();
109 let error = match error {
110 Error::ParseIntError(error) => error,
111 _ => panic!(),
112 };
113 assert_eq!(error.description(), "cannot parse integer from empty string");
114
115 let elem: Element = "<media xmlns='urn:xmpp:media-element' height='-10'/>".parse().unwrap();
116 let error = MediaElement::try_from(elem).unwrap_err();
117 let error = match error {
118 Error::ParseIntError(error) => error,
119 _ => panic!(),
120 };
121 assert_eq!(error.description(), "invalid digit found in string");
122 }
123
124 #[test]
125 fn test_unknown_child() {
126 let elem: Element = "<media xmlns='urn:xmpp:media-element'><coucou/></media>".parse().unwrap();
127 let error = MediaElement::try_from(elem).unwrap_err();
128 let message = match error {
129 Error::ParseError(string) => string,
130 _ => panic!(),
131 };
132 assert_eq!(message, "Unknown child in media element.");
133 }
134
135 #[test]
136 fn test_bad_uri() {
137 let elem: Element = "<media xmlns='urn:xmpp:media-element'><uri>https://example.org/</uri></media>".parse().unwrap();
138 let error = MediaElement::try_from(elem).unwrap_err();
139 let message = match error {
140 Error::ParseError(string) => string,
141 _ => panic!(),
142 };
143 assert_eq!(message, "Required attribute 'type' missing.");
144
145 let elem: Element = "<media xmlns='urn:xmpp:media-element'><uri type='text/html'/></media>".parse().unwrap();
146 let error = MediaElement::try_from(elem).unwrap_err();
147 let message = match error {
148 Error::ParseError(string) => string,
149 _ => panic!(),
150 };
151 assert_eq!(message, "URI missing in uri.");
152 }
153
154 #[test]
155 fn test_xep_ex1() {
156 let elem: Element = r#"
157<media xmlns='urn:xmpp:media-element'>
158 <uri type='audio/x-wav'>
159 http://victim.example.com/challenges/speech.wav?F3A6292C
160 </uri>
161 <uri type='audio/ogg; codecs=speex'>
162 cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org
163 </uri>
164 <uri type='audio/mpeg'>
165 http://victim.example.com/challenges/speech.mp3?F3A6292C
166 </uri>
167</media>"#.parse().unwrap();
168 let media = MediaElement::try_from(elem).unwrap();
169 assert!(media.width.is_none());
170 assert!(media.height.is_none());
171 assert_eq!(media.uris.len(), 3);
172 assert_eq!(media.uris[0].type_, "audio/x-wav");
173 assert_eq!(media.uris[0].uri, "http://victim.example.com/challenges/speech.wav?F3A6292C");
174 assert_eq!(media.uris[1].type_, "audio/ogg; codecs=speex");
175 assert_eq!(media.uris[1].uri, "cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org");
176 assert_eq!(media.uris[2].type_, "audio/mpeg");
177 assert_eq!(media.uris[2].uri, "http://victim.example.com/challenges/speech.mp3?F3A6292C");
178 }
179
180 #[test]
181 fn test_xep_ex2() {
182 let elem: Element = r#"
183<x xmlns='jabber:x:data' type='form'>
184 [ ... ]
185 <field var='ocr'>
186 <media xmlns='urn:xmpp:media-element'
187 height='80'
188 width='290'>
189 <uri type='image/jpeg'>
190 http://www.victim.com/challenges/ocr.jpeg?F3A6292C
191 </uri>
192 <uri type='image/jpeg'>
193 cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org
194 </uri>
195 </media>
196 </field>
197 [ ... ]
198</x>"#.parse().unwrap();
199 let form = DataForm::try_from(elem).unwrap();
200 assert_eq!(form.fields.len(), 1);
201 assert_eq!(form.fields[0].var, "ocr");
202 assert_eq!(form.fields[0].media[0].width, Some(290));
203 assert_eq!(form.fields[0].media[0].height, Some(80));
204 assert_eq!(form.fields[0].media[0].uris[0].type_, "image/jpeg");
205 assert_eq!(form.fields[0].media[0].uris[0].uri, "http://www.victim.com/challenges/ocr.jpeg?F3A6292C");
206 assert_eq!(form.fields[0].media[0].uris[1].type_, "image/jpeg");
207 assert_eq!(form.fields[0].media[0].uris[1].uri, "cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org");
208 }
209}