diff --git a/src/ibr.rs b/src/ibr.rs new file mode 100644 index 0000000..183d371 --- /dev/null +++ b/src/ibr.rs @@ -0,0 +1,201 @@ +// Copyright (c) 2017 Emmanuel Gil Peyrot +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +use std::collections::HashMap; +use std::convert::TryFrom; + +use minidom::{Element, IntoElements, ElementEmitter}; + +use error::Error; + +use data_forms::DataForm; + +use ns; + +#[derive(Debug, Clone)] +pub struct Query { + pub fields: HashMap, + pub registered: bool, + pub remove: bool, + pub form: Option, + // Not yet implemented. + //pub oob: Option, +} + +impl TryFrom for Query { + type Error = Error; + + fn try_from(elem: Element) -> Result { + if !elem.is("query", ns::REGISTER) { + return Err(Error::ParseError("This is not an ibr element.")); + } + let mut query = Query { + registered: false, + fields: HashMap::new(), + remove: false, + form: None, + }; + for child in elem.children() { + let namespace = child.ns().unwrap(); + if namespace == ns::REGISTER { + let name = child.name(); + let fields = vec!["address", "city", "date", "email", "first", "instructions", + "key", "last", "misc", "name", "nick", "password", "phone", + "state", "text", "url", "username", "zip"]; + if fields.binary_search(&name).is_ok() { + query.fields.insert(name.to_owned(), child.text()); + } else if name == "registered" { + query.registered = true; + } else if name == "remove" { + query.remove = true; + } else { + return Err(Error::ParseError("Wrong field in ibr element.")); + } + } else if child.is("x", ns::DATA_FORMS) { + query.form = Some(DataForm::try_from(child.clone())?); + } else { + return Err(Error::ParseError("Unknown child in ibr element.")); + } + } + Ok(query) + } +} + +impl Into for Query { + fn into(self) -> Element { + Element::builder("query") + .ns(ns::REGISTER) + .append(if self.registered { Some(Element::builder("registered").ns(ns::REGISTER)) } else { None }) + .append(self.fields.iter().map(|(name, value)| { + Element::builder(name.clone()).ns(ns::REGISTER).append(value.clone()) + }).collect::>()) + .append(if self.remove { Some(Element::builder("remove").ns(ns::REGISTER)) } else { None }) + .append(self.form) + .build() + } +} + +impl IntoElements for Query { + fn into_elements(self, emitter: &mut ElementEmitter) { + emitter.append_child(self.into()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_simple() { + let elem: Element = "".parse().unwrap(); + Query::try_from(elem).unwrap(); + } + + #[test] + fn test_ex2() { + let elem: Element = r#" + + + Choose a username and password for use with this service. + Please also provide your email address. + + + + + +"#.parse().unwrap(); + let query = Query::try_from(elem).unwrap(); + assert_eq!(query.registered, false); + assert_eq!(query.fields["instructions"], "\n Choose a username and password for use with this service.\n Please also provide your email address.\n "); + assert_eq!(query.fields["username"], ""); + assert_eq!(query.fields["password"], ""); + assert_eq!(query.fields["email"], ""); + assert_eq!(query.fields.contains_key("name"), false); + + // FIXME: HashMap doesn’t keep the order right. + //let elem2 = query.into(); + //assert_eq!(elem, elem2); + } + + #[test] + fn test_ex9() { + let elem: Element = r#" + + + Use the enclosed form to register. If your Jabber client does not + support Data Forms, visit http://www.shakespeare.lit/contests.php + + + Contest Registration + + Please provide the following information + to sign up for our special contests! + + + jabber:iq:register + + + + + + + + + + + + + + + + +"#.parse().unwrap(); + let elem1 = elem.clone(); + let query = Query::try_from(elem).unwrap(); + assert_eq!(query.registered, false); + assert!(!query.fields["instructions"].is_empty()); + let form = query.form.clone().unwrap(); + assert!(!form.instructions.unwrap().is_empty()); + assert!(form.fields.binary_search_by(|field| field.var.cmp(&String::from("first"))).is_ok()); + assert!(form.fields.binary_search_by(|field| field.var.cmp(&String::from("x-gender"))).is_ok()); + assert!(form.fields.binary_search_by(|field| field.var.cmp(&String::from("coucou"))).is_err()); + let elem2 = query.into(); + assert_eq!(elem1, elem2); + } + + #[test] + fn test_ex10() { + let elem: Element = r#" + + + + jabber:iq:register + + + Juliet + + + Capulet + + + juliet@capulet.com + + + F + + + +"#.parse().unwrap(); + let elem1 = elem.clone(); + let query = Query::try_from(elem).unwrap(); + assert_eq!(query.registered, false); + for _ in &query.fields { + panic!(); + } + let elem2 = query.into(); + assert_eq!(elem1, elem2); + } +} diff --git a/src/lib.rs b/src/lib.rs index 4fc420b..00b2592 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -144,6 +144,9 @@ pub mod rsm; /// XEP-0060: Publish-Subscribe pub mod pubsub; +/// XEP-0077: In-Band Registration +pub mod ibr; + /// XEP-0085: Chat State Notifications pub mod chatstates; diff --git a/src/ns.rs b/src/ns.rs index af76dd9..fbc0048 100644 --- a/src/ns.rs +++ b/src/ns.rs @@ -39,6 +39,9 @@ pub const PUBSUB_EVENT: &str = "http://jabber.org/protocol/pubsub#event"; /// XEP-0060: Publish-Subscribe pub const PUBSUB_OWNER: &str = "http://jabber.org/protocol/pubsub#owner"; +/// XEP-0077: In-Band Registration +pub const REGISTER: &str = "jabber:iq:register"; + /// XEP-0085: Chat State Notifications pub const CHATSTATES: &str = "http://jabber.org/protocol/chatstates";