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