diff --git a/src/jingle_ft.rs b/src/jingle_ft.rs new file mode 100644 index 00000000..6e3e2803 --- /dev/null +++ b/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, + pub hashes: Vec, +} + +#[derive(Debug, Clone)] +pub struct File { + pub date: Option, + pub media_type: Option, + pub name: Option, + pub size: Option, + pub range: Option, + pub hashes: Vec, +} + +#[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 { + if !root.is("description", ns::JINGLE_FT) { + return Err(Error::ParseError("This is not a JingleFT description element.")); + } + if root.children().collect::>().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#" + + + text/plain + test.txt + 2015-07-26T21:46:00 + 6144 + w0mcJylzCn+AfvuGdqkty2+KP48= + + +"#.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#" + + + w0mcJylzCn+AfvuGdqkty2+KP48= + + +"#.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="); + } +} diff --git a/src/lib.rs b/src/lib.rs index d26a5c9a..614737dd 100644 --- a/src/lib.rs +++ b/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; diff --git a/src/ns.rs b/src/ns.rs index d3d21068..5dfb3f5d 100644 --- a/src/ns.rs +++ b/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";