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 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}