Add a JingleFT parser.

Emmanuel Gil Peyrot created

Change summary

src/jingle_ft.rs | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++
src/lib.rs       |   3 
src/ns.rs        |   5 +
3 files changed, 183 insertions(+)

Detailed changes

src/jingle_ft.rs 🔗

@@ -0,0 +1,175 @@
+extern crate minidom;
+
+use hashes::{Hash, parse_hash};
+
+use minidom::Element;
+
+use error::Error;
+use ns;
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct Range {
+    pub offset: u64,
+    pub length: Option<u64>,
+    pub hashes: Vec<Hash>,
+}
+
+#[derive(Debug, Clone)]
+pub struct File {
+    pub date: Option<String>,
+    pub media_type: Option<String>,
+    pub name: Option<String>,
+    pub size: Option<String>,
+    pub range: Option<Range>,
+    pub hashes: Vec<Hash>,
+}
+
+#[derive(Debug, Clone)]
+pub struct Description {
+    pub file: File,
+}
+
+#[derive(Debug, Clone)]
+pub struct Creator {
+    pub creator: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct Checksum {
+    pub name: String,
+    pub creator: Creator,
+    pub file: File,
+}
+
+pub fn parse_jingle_ft(root: &Element) -> Result<Description, Error> {
+    if !root.is("description", ns::JINGLE_FT) {
+        return Err(Error::ParseError("This is not a JingleFT description element."));
+    }
+    if root.children().collect::<Vec<_>>().len() != 1 {
+        return Err(Error::ParseError("JingleFT description element must have exactly one child."));
+    }
+
+    let mut date = None;
+    let mut media_type = None;
+    let mut name = None;
+    let mut size = None;
+    let mut range = None;
+    let mut hashes = vec!();
+    for description_payload in root.children() {
+        if !description_payload.is("file", ns::JINGLE_FT) {
+            return Err(Error::ParseError("Unknown element in JingleFT description."));
+        }
+        for file_payload in description_payload.children() {
+            if file_payload.is("date", ns::JINGLE_FT) {
+                if date.is_some() {
+                    return Err(Error::ParseError("File must not have more than one date."));
+                }
+                date = Some(file_payload.text());
+            } else if file_payload.is("media-type", ns::JINGLE_FT) {
+                if media_type.is_some() {
+                    return Err(Error::ParseError("File must not have more than one media-type."));
+                }
+                media_type = Some(file_payload.text());
+            } else if file_payload.is("name", ns::JINGLE_FT) {
+                if name.is_some() {
+                    return Err(Error::ParseError("File must not have more than one name."));
+                }
+                name = Some(file_payload.text());
+            } else if file_payload.is("size", ns::JINGLE_FT) {
+                if size.is_some() {
+                    return Err(Error::ParseError("File must not have more than one size."));
+                }
+                size = Some(file_payload.text());
+            } else if file_payload.is("range", ns::JINGLE_FT) {
+                if range.is_some() {
+                    return Err(Error::ParseError("File must not have more than one range."));
+                }
+                let offset = file_payload.attr("offset").unwrap_or("0").parse()?;
+                let length = match file_payload.attr("length") {
+                    Some(length) => Some(length.parse()?),
+                    None => None,
+                };
+                let mut range_hashes = vec!();
+                for hash_element in file_payload.children() {
+                    if !hash_element.is("hash", ns::HASHES) {
+                        return Err(Error::ParseError("Unknown element in JingleFT range."));
+                    }
+                    range_hashes.push(parse_hash(hash_element)?);
+                }
+                range = Some(Range {
+                    offset: offset,
+                    length: length,
+                    hashes: range_hashes,
+                });
+            } else if file_payload.is("hash", ns::HASHES) {
+                hashes.push(parse_hash(file_payload)?);
+            } else {
+                return Err(Error::ParseError("Unknown element in JingleFT file."));
+            }
+        }
+    }
+
+    Ok(Description {
+        file: File {
+            date: date,
+            media_type: media_type,
+            name: name,
+            size: size,
+            range: range,
+            hashes: hashes,
+        },
+    })
+}
+
+#[cfg(test)]
+mod tests {
+    use minidom::Element;
+    use error::Error;
+    use jingle_ft;
+
+    #[test]
+    fn test_description() {
+        let elem: Element = r#"
+<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
+  <file>
+    <media-type>text/plain</media-type>
+    <name>test.txt</name>
+    <date>2015-07-26T21:46:00</date>
+    <size>6144</size>
+    <hash xmlns='urn:xmpp:hashes:2'
+          algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash>
+  </file>
+</description>
+"#.parse().unwrap();
+
+        let desc = jingle_ft::parse_jingle_ft(&elem).unwrap();
+        assert_eq!(desc.file.media_type, Some(String::from("text/plain")));
+        assert_eq!(desc.file.name, Some(String::from("test.txt")));
+        assert_eq!(desc.file.date, Some(String::from("2015-07-26T21:46:00")));
+        assert_eq!(desc.file.size, Some(String::from("6144")));
+        assert_eq!(desc.file.range, None);
+        assert_eq!(desc.file.hashes[0].algo, "sha-1");
+        assert_eq!(desc.file.hashes[0].hash, "w0mcJylzCn+AfvuGdqkty2+KP48=");
+    }
+
+    #[test]
+    fn test_request() {
+        let elem: Element = r#"
+<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
+  <file>
+    <hash xmlns='urn:xmpp:hashes:2'
+          algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash>
+  </file>
+</description>
+"#.parse().unwrap();
+
+        let desc = jingle_ft::parse_jingle_ft(&elem).unwrap();
+        assert_eq!(desc.file.media_type, None);
+        assert_eq!(desc.file.name, None);
+        assert_eq!(desc.file.date, None);
+        assert_eq!(desc.file.size, None);
+        assert_eq!(desc.file.range, None);
+        assert_eq!(desc.file.hashes[0].algo, "sha-1");
+        assert_eq!(desc.file.hashes[0].hash, "w0mcJylzCn+AfvuGdqkty2+KP48=");
+    }
+}

src/lib.rs 🔗

@@ -49,6 +49,9 @@ pub mod media_element;
 /// XEP-0224: Attention
 pub mod attention;
 
+/// XEP-0234: Jingle File Transfer
+pub mod jingle_ft;
+
 /// XEP-0300: Use of Cryptographic Hash Functions in XMPP
 pub mod hashes;
 

src/ns.rs 🔗

@@ -31,6 +31,11 @@ pub const MEDIA_ELEMENT: &'static str = "urn:xmpp:media-element";
 /// XEP-0224: Attention
 pub const ATTENTION: &'static str = "urn:xmpp:attention:0";
 
+/// XEP-0234: Jingle File Transfer
+pub const JINGLE_FT: &'static str = "urn:xmpp:jingle:apps:file-transfer:5";
+/// XEP-0234: Jingle File Transfer
+pub const JINGLE_FT_ERROR: &'static str = "urn:xmpp:jingle:apps:file-transfer:errors:0";
+
 /// XEP-0300: Use of Cryptographic Hash Functions in XMPP
 pub const HASHES: &'static str = "urn:xmpp:hashes:2";