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