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