commit 9cf1521775d2eda1df49c63bfe1485777efde945 Author: Emmanuel Gil Peyrot Date: Tue Apr 18 20:44:36 2017 +0100 Hello world! diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..a9d37c56 --- /dev/null +++ b/.hgignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..10c55dd5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "xmpp-parsers" +version = "0.1.0" +authors = ["Emmanuel Gil Peyrot "] + +[dependencies] +minidom = "0.1.1" diff --git a/src/data_forms.rs b/src/data_forms.rs new file mode 100644 index 00000000..f4eb70c8 --- /dev/null +++ b/src/data_forms.rs @@ -0,0 +1,148 @@ +extern crate minidom; + +use std::str::FromStr; + +use minidom::Element; + +use error::Error; +use ns::{DATA_FORMS_NS, MEDIA_ELEMENT_NS}; + +use media_element::{MediaElement, parse_media_element}; + +#[derive(Debug)] +pub struct Field { + pub var: String, + pub type_: String, // TODO: use an enum here. + pub label: Option, + pub values: Vec, + pub media: Vec, +} + +#[derive(Debug, PartialEq)] +pub enum DataFormType { + Cancel, + Form, + Result_, + Submit, +} + +impl FromStr for DataFormType { + type Err = Error; + + fn from_str(s: &str) -> Result { + if s == "cancel" { + Ok(DataFormType::Cancel) + } else if s == "form" { + Ok(DataFormType::Form) + } else if s == "result" { + Ok(DataFormType::Result_) + } else if s == "submit" { + Ok(DataFormType::Submit) + } else { + Err(Error::ParseError("Unknown data form type.")) + } + } +} + +#[derive(Debug)] +pub struct DataForm { + pub type_: DataFormType, + pub form_type: Option, + pub fields: Vec, +} + +pub fn parse_data_form(root: &Element) -> Result { + assert!(root.is("x", DATA_FORMS_NS)); + let type_: DataFormType = match root.attr("type") { + Some(type_) => type_.parse()?, + None => return Err(Error::ParseError("Type attribute on data form is mandatory.")), + }; + let mut fields = vec!(); + let mut form_type = None; + for field in root.children() { + if field.is("field", DATA_FORMS_NS) { + let var = field.attr("var").ok_or(Error::ParseError("Field must have a 'var' attribute."))?; + let field_type = field.attr("type").unwrap_or("text-single"); + let label = field.attr("label").and_then(|label| label.parse().ok()); + let mut values = vec!(); + let mut media = vec!(); + for element in field.children() { + if element.is("value", DATA_FORMS_NS) { + values.push(element.text()); + } else if element.is("media", MEDIA_ELEMENT_NS) { + match parse_media_element(element) { + Ok(media_element) => media.push(media_element), + Err(_) => (), // TODO: is it really nice to swallow this error? + } + } else { + return Err(Error::ParseError("Field child isn’t a value or media element.")); + } + } + if var == "FORM_TYPE" && field_type == "hidden" { + if form_type != None { + return Err(Error::ParseError("More than one FORM_TYPE in a data form.")); + } + if values.len() != 1 { + return Err(Error::ParseError("Wrong number of values in FORM_TYPE.")); + } + form_type = Some(values[0].clone()); + } + fields.push(Field { + var: var.to_owned(), + type_: field_type.to_owned(), + label: label, + values: values, + media: media, + }); + } else { + return Err(Error::ParseError("Unknown field type in data form.")); + } + } + Ok(DataForm { type_: type_, form_type: form_type, fields: fields }) +} + +#[cfg(test)] +mod tests { + use minidom::Element; + use error::Error; + use data_forms; + + #[test] + fn test_simple() { + let elem: Element = "".parse().unwrap(); + let form = data_forms::parse_data_form(&elem).unwrap(); + assert_eq!(form.type_, data_forms::DataFormType::Result_); + assert!(form.form_type.is_none()); + assert!(form.fields.is_empty()); + } + + #[test] + fn test_invalid() { + let elem: Element = "".parse().unwrap(); + let error = data_forms::parse_data_form(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Type attribute on data form is mandatory."); + + let elem: Element = "".parse().unwrap(); + let error = data_forms::parse_data_form(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Unknown data form type."); + } + + #[test] + fn test_wrong_child() { + let elem: Element = "".parse().unwrap(); + let error = data_forms::parse_data_form(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Unknown field type in data form."); + } +} diff --git a/src/disco.rs b/src/disco.rs new file mode 100644 index 00000000..7b2232ec --- /dev/null +++ b/src/disco.rs @@ -0,0 +1,193 @@ +extern crate minidom; + +use minidom::Element; + +use error::Error; +use ns::{DISCO_INFO_NS, DATA_FORMS_NS}; + +use data_forms::{DataForm, DataFormType, parse_data_form}; + +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct Feature { + pub var: String, +} + +#[derive(Debug)] +pub struct Identity { + pub category: String, // TODO: use an enum here. + pub type_: String, // TODO: use an enum here. + pub xml_lang: String, + pub name: Option, +} + +#[derive(Debug)] +pub struct Disco { + pub node: Option, + pub identities: Vec, + pub features: Vec, + pub extensions: Vec, +} + +pub fn parse_disco(root: &Element) -> Result { + assert!(root.is("query", DISCO_INFO_NS)); + let mut identities: Vec = vec!(); + let mut features: Vec = vec!(); + let mut extensions: Vec = vec!(); + + let node = root.attr("node") + .and_then(|node| node.parse().ok()); + + for child in root.children() { + if child.is("feature", DISCO_INFO_NS) { + let feature = child.attr("var") + .ok_or(Error::ParseError("Feature must have a 'var' attribute."))?; + features.push(Feature { + var: feature.to_owned(), + }); + } else if child.is("identity", DISCO_INFO_NS) { + let category = child.attr("category") + .ok_or(Error::ParseError("Identity must have a 'category' attribute."))?; + if category == "" { + return Err(Error::ParseError("Identity must have a non-empty 'category' attribute.")) + } + + let type_ = child.attr("type") + .ok_or(Error::ParseError("Identity must have a 'type' attribute."))?; + if type_ == "" { + return Err(Error::ParseError("Identity must have a non-empty 'type' attribute.")) + } + + // TODO: this must check for the namespace of the attribute, but minidom doesn’t support that yet, see issue #2. + let xml_lang = child.attr("lang").unwrap_or(""); + let name = child.attr("name") + .and_then(|node| node.parse().ok()); + identities.push(Identity { + category: category.to_owned(), + type_: type_.to_owned(), + xml_lang: xml_lang.to_owned(), + name: name, + }); + } else if child.is("x", DATA_FORMS_NS) { + let data_form = parse_data_form(child)?; + match data_form.type_ { + DataFormType::Result_ => (), + _ => return Err(Error::ParseError("Data form must have a 'result' type in disco#info.")), + } + match data_form.form_type { + Some(_) => extensions.push(data_form), + None => return Err(Error::ParseError("Data form found without a FORM_TYPE.")), + } + } else { + return Err(Error::ParseError("Unknown element in disco#info.")); + } + } + + if identities.is_empty() { + return Err(Error::ParseError("There must be at least one identity in disco#info.")); + } + if features.is_empty() { + return Err(Error::ParseError("There must be at least one feature in disco#info.")); + } + if !features.contains(&Feature { var: DISCO_INFO_NS.to_owned() }) { + return Err(Error::ParseError("disco#info feature not present in disco#info.")); + } + + return Ok(Disco { + node: node, + identities: identities, + features: features, + extensions: extensions + }); +} + +#[cfg(test)] +mod tests { + use minidom::Element; + use error::Error; + use disco; + + #[test] + fn test_simple() { + let elem: Element = "".parse().unwrap(); + let query = disco::parse_disco(&elem).unwrap(); + assert!(query.node.is_none()); + assert_eq!(query.identities.len(), 1); + assert_eq!(query.features.len(), 1); + assert!(query.extensions.is_empty()); + } + + #[test] + fn test_invalid() { + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Unknown element in disco#info."); + + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "There must be at least one identity in disco#info."); + + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Identity must have a 'category' attribute."); + + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Identity must have a non-empty 'category' attribute."); + + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Identity must have a 'type' attribute."); + + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Identity must have a non-empty 'type' attribute."); + + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Feature must have a 'var' attribute."); + + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "There must be at least one feature in disco#info."); + + let elem: Element = "".parse().unwrap(); + let error = disco::parse_disco(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "disco#info feature not present in disco#info."); + } +} diff --git a/src/ecaps2.rs b/src/ecaps2.rs new file mode 100644 index 00000000..50b94128 --- /dev/null +++ b/src/ecaps2.rs @@ -0,0 +1,330 @@ +extern crate minidom; + +use minidom::Element; + +use error::Error; + +use disco::{Feature, Identity, Disco, parse_disco}; +use data_forms::DataForm; + +fn compute_item(field: String) -> Vec { + let mut bytes = field.as_bytes().to_vec(); + bytes.push(0x1f); + bytes +} + +fn compute_items Vec>(things: Vec, separator: u8, encode: F) -> Vec { + let mut string: Vec = vec!(); + let mut accumulator: Vec> = vec!(); + for thing in things { + let bytes = encode(thing); + accumulator.push(bytes); + } + // This works using the expected i;octet collation. + accumulator.sort(); + for mut bytes in accumulator { + string.append(&mut bytes); + } + string.push(separator); + string +} + +fn compute_features(features: Vec) -> Vec { + compute_items(features, 0x1c, |feature| compute_item(feature.var)) +} + +fn compute_identities(identities: Vec) -> Vec { + compute_items(identities, 0x1c, |identity| { + let mut bytes = compute_item(identity.category); + bytes.append(&mut compute_item(identity.type_)); + bytes.append(&mut compute_item(identity.xml_lang)); + bytes.append(&mut compute_item(identity.name.unwrap_or(String::new()))); + bytes.push(0x1e); + bytes + }) +} + +fn compute_extensions(extensions: Vec) -> Vec { + compute_items(extensions, 0x1c, |extension| { + compute_items(extension.fields, 0x1d, |field| { + let mut bytes = compute_item(field.var); + bytes.append(&mut compute_items(field.values, 0x1e, + |value| compute_item(value))); + bytes + }) + }) +} + +fn compute_disco(disco: Disco) -> Vec { + let features_string = compute_features(disco.features); + let identities_string = compute_identities(disco.identities); + let extensions_string = compute_extensions(disco.extensions); + + let mut final_string = vec!(); + final_string.extend(features_string); + final_string.extend(identities_string); + final_string.extend(extensions_string); + final_string +} + +pub fn convert_element(root: &Element) -> Result, Error> { + let disco = parse_disco(root)?; + let final_string = compute_disco(disco); + Ok(final_string) +} + +#[cfg(test)] +mod tests { + use minidom::Element; + use ecaps2; + + #[test] + fn test_simple() { + let elem: Element = "".parse().unwrap(); + let ecaps2 = ecaps2::convert_element(&elem).unwrap(); + assert_eq!(ecaps2.len(), 54); + } + + #[test] + fn test_xep_ex1() { + let elem: Element = r#" + + + + + + + + + + + + + + + + + + + + +"#.parse().unwrap(); + let expected = vec![104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, + 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, + 108, 47, 98, 121, 116, 101, 115, 116, 114, 101, 97, 109, 115, 31, + 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, + 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 99, 104, + 97, 116, 115, 116, 97, 116, 101, 115, 31, 104, 116, 116, 112, 58, + 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, + 111, 116, 111, 99, 111, 108, 47, 100, 105, 115, 99, 111, 35, 105, + 110, 102, 111, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, + 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, + 108, 47, 100, 105, 115, 99, 111, 35, 105, 116, 101, 109, 115, 31, + 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, + 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 105, 98, + 98, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, + 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, + 114, 111, 115, 116, 101, 114, 120, 31, 104, 116, 116, 112, 58, 47, + 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, + 111, 116, 111, 99, 111, 108, 47, 115, 105, 31, 104, 116, 116, 112, + 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, + 114, 111, 116, 111, 99, 111, 108, 47, 115, 105, 47, 112, 114, 111, + 102, 105, 108, 101, 47, 102, 105, 108, 101, 45, 116, 114, 97, 110, + 115, 102, 101, 114, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, + 58, 108, 97, 115, 116, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, + 58, 112, 114, 105, 118, 97, 99, 121, 31, 106, 97, 98, 98, 101, 114, + 58, 105, 113, 58, 114, 111, 115, 116, 101, 114, 31, 106, 97, 98, + 98, 101, 114, 58, 105, 113, 58, 116, 105, 109, 101, 31, 106, 97, + 98, 98, 101, 114, 58, 105, 113, 58, 118, 101, 114, 115, 105, 111, + 110, 31, 106, 97, 98, 98, 101, 114, 58, 120, 58, 111, 111, 98, 31, + 117, 114, 110, 58, 120, 109, 112, 112, 58, 112, 105, 110, 103, 31, + 117, 114, 110, 58, 120, 109, 112, 112, 58, 114, 101, 99, 101, 105, + 112, 116, 115, 31, 117, 114, 110, 58, 120, 109, 112, 112, 58, 116, + 105, 109, 101, 31, 28, 99, 108, 105, 101, 110, 116, 31, 109, 111, + 98, 105, 108, 101, 31, 31, 66, 111, 109, 98, 117, 115, 77, 111, + 100, 31, 30, 28, 28]; + let ecaps2 = ecaps2::convert_element(&elem).unwrap(); + assert_eq!(ecaps2.len(), 0x1d9); + assert_eq!(ecaps2, expected); + + /* + let sha_256 = hash(ecaps2, "sha-256"); + assert_eq!(sha_256, "kzBZbkqJ3ADrj7v08reD1qcWUwNGHaidNUgD7nHpiw8="); + let sha3_256 = hash(ecaps2, "sha3-256"); + assert_eq!(sha3_256, "79mdYAfU9rEdTOcWDO7UEAt6E56SUzk/g6TnqUeuD9Q="); + */ + } + + #[test] + fn test_xep_ex2() { + let elem: Element = r#" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + urn:xmpp:dataforms:softwareinfo + + + Tkabber + + + 0.11.1-svn-20111216-mod (Tcl/Tk 8.6b2) + + + Windows + + + XP + + + +"#.parse().unwrap(); + let expected = vec![103, 97, 109, 101, 115, 58, 98, 111, 97, 114, 100, + 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, + 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 97, + 99, 116, 105, 118, 105, 116, 121, 31, 104, 116, 116, 112, 58, 47, + 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, + 111, 116, 111, 99, 111, 108, 47, 97, 99, 116, 105, 118, 105, 116, + 121, 43, 110, 111, 116, 105, 102, 121, 31, 104, 116, 116, 112, 58, + 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, + 111, 116, 111, 99, 111, 108, 47, 98, 121, 116, 101, 115, 116, 114, + 101, 97, 109, 115, 31, 104, 116, 116, 112, 58,47, 47, 106, 97, 98, + 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, + 111, 108, 47, 99, 104, 97, 116, 115, 116, 97, 116, 101, 115, 31, + 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, + 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 99, 111, + 109, 109, 97, 110, 100, 115, 31,104,116, 116, 112, 58, 47, 47, 106, + 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, + 111, 99, 111, 108, 47, 100, 105, 115, 99, 111, 35, 105, 110, 102, + 111, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, + 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, + 100, 105, 115, 99, 111, 35, 105, 116, 101, 109, 115, 31, 104, 116, + 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, + 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 101, 118, 105, 108, + 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, + 111, 114, 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 102, + 101, 97, 116, 117, 114, 101, 45, 110, 101, 103, 31, 104, 116, 116, + 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, + 112, 114, 111, 116, 111, 99, 111, 108, 47, 103, 101, 111, 108, 111, + 99, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, + 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, 99,111, 108, 47, + 103, 101, 111, 108, 111, 99, 43, 110, 111, 116, 105, 102, 121, 31, + 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, + 114, 103,47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 105, 98, + 98, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, + 46, 111, 114, 103, 47, 112, 114, 111,116, 111, 99, 111, 108, 47, + 105, 113, 105, 98, 98, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, + 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116,111, + 99, 111, 108, 47, 109, 111, 111, 100, 31, 104, 116, 116, 112, 58, + 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, + 111, 116, 111, 99, 111,108, 47, 109, 111, 111, 100, 43, 110, 111, + 116, 105, 102, 121, 31, 104, 116, 116, 112, 58, 47, 47, 106, 97, + 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, 111, 116, 111, + 99, 111, 108, 47, 114, 111, 115, 116, 101, 114, 120, 31, 104, 116, + 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, + 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 115, 105, 31, 104, + 116, 116, 112, 58, 47, 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, + 103, 47, 112, 114, 111, 116, 111, 99, 111, 108, 47, 115, 105, 47, + 112, 114, 111, 102, 105, 108, 101, 47, 102, 105, 108, 101, 45, 116, + 114, 97, 110, 115, 102, 101, 114, 31, 104, 116, 116, 112, 58, 47, + 47, 106, 97, 98, 98, 101, 114, 46, 111, 114, 103, 47, 112, 114, + 111, 116, 111, 99, 111, 108, 47, 116, 117, 110, 101, 31, 104, 116, + 116, 112, 58, 47, 47, 119, 119, 119, 46, 102, 97, 99, 101, 98, 111, + 111, 107, 46, 99, 111, 109, 47, 120, 109, 112, 112, 47, 109, 101, + 115, 115, 97, 103, 101, 115, 31, 104, 116, 116, 112, 58, 47, 47, + 119, 119, 119, 46, 120, 109, 112, 112, 46, 111, 114, 103, 47, 101, + 120, 116, 101, 110, 115, 105, 111, 110, 115, 47, 120, 101, 112, 45, + 48, 48, 56, 52, 46, 104, 116, 109, 108, 35, 110, 115, 45, 109, 101, + 116, 97, 100, 97, 116, 97, 43, 110, 111, 116, 105, 102, 121, 31, + 106, 97, 98, 98, 101, 114,58, 105,113, 58, 97, 118, 97, 116, 97, + 114, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 98, 114, 111, + 119, 115, 101, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, + 100, 116, 99, 112, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, + 102, 105, 108, 101, 120, 102, 101, 114, 31, 106, 97, 98, 98, 101, + 114, 58, 105, 113, 58, 105, 98, 98, 31, 106, 97, 98, 98, 101, 114, + 58, 105, 113, 58, 105, 110, 98, 97, 110, 100, 31, 106, 97, 98, 98, + 101, 114, 58, 105, 113, 58, 106, 105, 100, 108, 105, 110, 107, 31, + 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 108, 97, 115, 116, 31, + 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 111, 111, 98, 31, 106, + 97,98, 98, 101, 114, 58, 105, 113, 58, 112, 114, 105, 118, 97, 99, + 121, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, 114, 111, + 115, 116, 101, 114,31, 106, 97, 98, 98, 101, 114, 58, 105, 113, 58, + 116, 105, 109, 101, 31, 106, 97, 98, 98, 101, 114, 58, 105, 113, + 58, 118, 101, 114, 115, 105, 111, 110, 31, 106, 97, 98, 98, 101, + 114, 58, 120, 58, 100, 97, 116, 97, 31, 106, 97, 98, 98, 101, 114, + 58, 120, 58, 101, 118, 101, 110, 116, 31, 106, 97, 98, 98, 101, + 114, 58, 120, 58, 111, 111, 98, 31, 117, 114, 110, 58, 120, 109, + 112, 112, 58, 97, 118, 97, 116, 97, 114, 58, 109, 101, 116, 97, + 100, 97, 116, 97, 43, 110, 111, 116, 105, 102, 121,31, 117, 114, + 110, 58, 120, 109, 112, 112, 58, 112, 105, 110, 103, 31, 117, 114, + 110, 58, 120, 109, 112, 112, 58, 114, 101, 99, 101, 105, 112, 116, + 115, 31, 117, 114, 110, 58, 120, 109, 112, 112, 58, 116, 105, 109, + 101, 31, 28, 99, 108, 105, 101, 110, 116, 31, 112, 99, 31, 101, + 110, 31, 84, 107, 97, 98, 98, 101, 114,31, 30, 99, 108, 105, 101, + 110, 116, 31, 112, 99, 31, 114, 117, 31, 208, 162, 208, 186, 208, + 176, 208, 177, 208, 177, 208, 181, 209, 128, 31, 30, 28, 70, 79, + 82, 77, 95, 84, 89, 80, 69, 31, 117, 114, 110, 58, 120, 109, 112, + 112, 58, 100, 97, 116, 97, 102, 111, 114, 109, 115, 58, 115, 111, + 102, 116, 119, 97, 114, 101,105, 110, 102, 111, 31, 30, 111, 115, + 31, 87, 105, 110, 100, 111, 119, 115, 31, 30, 111, 115, 95, 118, + 101, 114, 115, 105, 111, 110, 31, 88, 80, 31, 30, 115, 111, 102, + 116, 119, 97, 114, 101, 31, 84, 107, 97, 98, 98, 101, 114, 31, 30, + 115, 111, 102, 116, 119, 97, 114, 101, 95, 118, 101, 114, 115, 105, + 111, 110, 31, 48, 46, 49, 49, 46, 49, 45, 115, 118, 110, 45, 50, + 48, 49, 49, 49, 50, 49, 54, 45, 109, 111, 100, 32, 40, 84, 99, 108, + 47, 84, 107, 32, 56, 46,54, 98, 50, 41, 31, 30, 29, 28]; + let ecaps2 = ecaps2::convert_element(&elem).unwrap(); + assert_eq!(ecaps2.len(), 0x543); + assert_eq!(ecaps2, expected); + + /* + let sha_256 = hash(ecaps2, "sha-256"); + assert_eq!(sha_256, "u79ZroNJbdSWhdSp311mddz44oHHPsEBntQ5b1jqBSY="); + let sha3_256 = hash(ecaps2, "sha3-256"); + assert_eq!(sha3_256, "XpUJzLAc93258sMECZ3FJpebkzuyNXDzRNwQog8eycg="); + */ + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..bd7d2d9c --- /dev/null +++ b/src/error.rs @@ -0,0 +1,23 @@ +use std::convert::From; +use std::io; + +use minidom; + +#[derive(Debug)] +pub enum Error { + IoError(io::Error), + XMLError(minidom::Error), + ParseError(&'static str), +} + +impl From for Error { + fn from(err: io::Error) -> Error { + Error::IoError(err) + } +} + +impl From for Error { + fn from(err: minidom::Error) -> Error { + Error::XMLError(err) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..a6aa1901 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,9 @@ +extern crate minidom; + +pub mod error; +pub mod ns; + +pub mod disco; +pub mod data_forms; +pub mod media_element; +pub mod ecaps2; diff --git a/src/media_element.rs b/src/media_element.rs new file mode 100644 index 00000000..1fc84507 --- /dev/null +++ b/src/media_element.rs @@ -0,0 +1,177 @@ +use minidom::Element; + +use error::Error; + +const MEDIA_ELEMENT_NS: &'static str = "urn:xmpp:media-element"; + +#[derive(Debug)] +pub struct URI { + pub type_: String, + pub uri: String, +} + +#[derive(Debug)] +pub struct MediaElement { + pub width: Option, + pub height: Option, + pub uris: Vec, +} + +pub fn parse_media_element(root: &Element) -> Result { + assert!(root.is("media", MEDIA_ELEMENT_NS)); + let width = root.attr("width").and_then(|width| width.parse().ok()); + let height = root.attr("height").and_then(|height| height.parse().ok()); + let mut uris = vec!(); + for uri in root.children() { + if uri.is("uri", MEDIA_ELEMENT_NS) { + let type_ = uri.attr("type").ok_or(Error::ParseError("Attribute type on uri is mandatory."))?; + let text = uri.text().trim().to_owned(); + if text == "" { + return Err(Error::ParseError("URI missing in uri.")); + } + uris.push(URI { type_: type_.to_owned(), uri: text }); + } else { + return Err(Error::ParseError("Unknown child in media element.")); + } + } + Ok(MediaElement { width: width, height: height, uris: uris }) +} + +#[cfg(test)] +mod tests { + use minidom::Element; + use error::Error; + use media_element; + use data_forms; + + #[test] + fn test_simple() { + let elem: Element = "".parse().unwrap(); + let media = media_element::parse_media_element(&elem).unwrap(); + assert!(media.width.is_none()); + assert!(media.height.is_none()); + assert!(media.uris.is_empty()); + } + + #[test] + fn test_width_height() { + let elem: Element = "".parse().unwrap(); + let media = media_element::parse_media_element(&elem).unwrap(); + assert_eq!(media.width.unwrap(), 32); + assert_eq!(media.height.unwrap(), 32); + } + + #[test] + fn test_uri() { + let elem: Element = "https://example.org/".parse().unwrap(); + let media = media_element::parse_media_element(&elem).unwrap(); + assert_eq!(media.uris.len(), 1); + assert_eq!(media.uris[0].type_, "text/html"); + assert_eq!(media.uris[0].uri, "https://example.org/"); + } + + #[test] + fn test_invalid_width_height() { + let elem: Element = "".parse().unwrap(); + let media = media_element::parse_media_element(&elem).unwrap(); + assert!(media.width.is_none()); + + let elem: Element = "".parse().unwrap(); + let media = media_element::parse_media_element(&elem).unwrap(); + assert!(media.width.is_none()); + + let elem: Element = "".parse().unwrap(); + let media = media_element::parse_media_element(&elem).unwrap(); + assert!(media.height.is_none()); + + let elem: Element = "".parse().unwrap(); + let media = media_element::parse_media_element(&elem).unwrap(); + assert!(media.height.is_none()); + } + + #[test] + fn test_unknown_child() { + let elem: Element = "".parse().unwrap(); + let error = media_element::parse_media_element(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Unknown child in media element."); + } + + #[test] + fn test_bad_uri() { + let elem: Element = "https://example.org/".parse().unwrap(); + let error = media_element::parse_media_element(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "Attribute type on uri is mandatory."); + + let elem: Element = "".parse().unwrap(); + let error = media_element::parse_media_element(&elem).unwrap_err(); + let message = match error { + Error::ParseError(string) => string, + _ => panic!(), + }; + assert_eq!(message, "URI missing in uri."); + } + + #[test] + fn test_xep_ex1() { + let elem: Element = r#" + + + http://victim.example.com/challenges/speech.wav?F3A6292C + + + cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org + + + http://victim.example.com/challenges/speech.mp3?F3A6292C + +"#.parse().unwrap(); + let media = media_element::parse_media_element(&elem).unwrap(); + assert!(media.width.is_none()); + assert!(media.height.is_none()); + assert_eq!(media.uris.len(), 3); + assert_eq!(media.uris[0].type_, "audio/x-wav"); + assert_eq!(media.uris[0].uri, "http://victim.example.com/challenges/speech.wav?F3A6292C"); + assert_eq!(media.uris[1].type_, "audio/ogg; codecs=speex"); + assert_eq!(media.uris[1].uri, "cid:sha1+a15a505e360702b79c75a5f67773072ed392f52a@bob.xmpp.org"); + assert_eq!(media.uris[2].type_, "audio/mpeg"); + assert_eq!(media.uris[2].uri, "http://victim.example.com/challenges/speech.mp3?F3A6292C"); + } + + #[test] + fn test_xep_ex2() { + let elem: Element = r#" + + [ ... ] + + + + http://www.victim.com/challenges/ocr.jpeg?F3A6292C + + + cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org + + + + [ ... ] +"#.parse().unwrap(); + let form = data_forms::parse_data_form(&elem).unwrap(); + assert_eq!(form.fields.len(), 1); + assert_eq!(form.fields[0].var, "ocr"); + assert_eq!(form.fields[0].media[0].width, Some(290)); + assert_eq!(form.fields[0].media[0].height, Some(80)); + assert_eq!(form.fields[0].media[0].uris[0].type_, "image/jpeg"); + assert_eq!(form.fields[0].media[0].uris[0].uri, "http://www.victim.com/challenges/ocr.jpeg?F3A6292C"); + assert_eq!(form.fields[0].media[0].uris[1].type_, "image/jpeg"); + assert_eq!(form.fields[0].media[0].uris[1].uri, "cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org"); + } +} diff --git a/src/ns.rs b/src/ns.rs new file mode 100644 index 00000000..36bd127c --- /dev/null +++ b/src/ns.rs @@ -0,0 +1,3 @@ +pub const DISCO_INFO_NS: &'static str = "http://jabber.org/protocol/disco#info"; +pub const DATA_FORMS_NS: &'static str = "jabber:x:data"; +pub const MEDIA_ELEMENT_NS: &'static str = "urn:xmpp:media-element";