2017-05-01 00:24:45 +00:00
|
|
|
// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
|
|
|
//
|
|
|
|
// 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/.
|
|
|
|
|
2017-07-20 19:03:15 +00:00
|
|
|
use try_from::TryFrom;
|
2017-05-01 00:24:45 +00:00
|
|
|
use std::str::FromStr;
|
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
2017-05-25 00:14:36 +00:00
|
|
|
use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
|
2017-05-01 00:24:45 +00:00
|
|
|
|
|
|
|
use error::Error;
|
|
|
|
use jid::Jid;
|
|
|
|
use ns;
|
|
|
|
|
2017-06-13 23:50:57 +00:00
|
|
|
generate_attribute!(ErrorType, "type", {
|
|
|
|
Auth => "auth",
|
|
|
|
Cancel => "cancel",
|
|
|
|
Continue => "continue",
|
|
|
|
Modify => "modify",
|
|
|
|
Wait => "wait",
|
|
|
|
});
|
2017-05-01 00:24:45 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
|
|
pub enum DefinedCondition {
|
|
|
|
BadRequest,
|
|
|
|
Conflict,
|
|
|
|
FeatureNotImplemented,
|
|
|
|
Forbidden,
|
|
|
|
Gone,
|
|
|
|
InternalServerError,
|
|
|
|
ItemNotFound,
|
|
|
|
JidMalformed,
|
|
|
|
NotAcceptable,
|
|
|
|
NotAllowed,
|
|
|
|
NotAuthorized,
|
|
|
|
PolicyViolation,
|
|
|
|
RecipientUnavailable,
|
|
|
|
Redirect,
|
|
|
|
RegistrationRequired,
|
|
|
|
RemoteServerNotFound,
|
|
|
|
RemoteServerTimeout,
|
|
|
|
ResourceConstraint,
|
|
|
|
ServiceUnavailable,
|
|
|
|
SubscriptionRequired,
|
|
|
|
UndefinedCondition,
|
|
|
|
UnexpectedRequest,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FromStr for DefinedCondition {
|
|
|
|
type Err = Error;
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<DefinedCondition, Error> {
|
|
|
|
Ok(match s {
|
|
|
|
"bad-request" => DefinedCondition::BadRequest,
|
|
|
|
"conflict" => DefinedCondition::Conflict,
|
|
|
|
"feature-not-implemented" => DefinedCondition::FeatureNotImplemented,
|
|
|
|
"forbidden" => DefinedCondition::Forbidden,
|
|
|
|
"gone" => DefinedCondition::Gone,
|
|
|
|
"internal-server-error" => DefinedCondition::InternalServerError,
|
|
|
|
"item-not-found" => DefinedCondition::ItemNotFound,
|
|
|
|
"jid-malformed" => DefinedCondition::JidMalformed,
|
|
|
|
"not-acceptable" => DefinedCondition::NotAcceptable,
|
|
|
|
"not-allowed" => DefinedCondition::NotAllowed,
|
|
|
|
"not-authorized" => DefinedCondition::NotAuthorized,
|
|
|
|
"policy-violation" => DefinedCondition::PolicyViolation,
|
|
|
|
"recipient-unavailable" => DefinedCondition::RecipientUnavailable,
|
|
|
|
"redirect" => DefinedCondition::Redirect,
|
|
|
|
"registration-required" => DefinedCondition::RegistrationRequired,
|
|
|
|
"remote-server-not-found" => DefinedCondition::RemoteServerNotFound,
|
|
|
|
"remote-server-timeout" => DefinedCondition::RemoteServerTimeout,
|
|
|
|
"resource-constraint" => DefinedCondition::ResourceConstraint,
|
|
|
|
"service-unavailable" => DefinedCondition::ServiceUnavailable,
|
|
|
|
"subscription-required" => DefinedCondition::SubscriptionRequired,
|
|
|
|
"undefined-condition" => DefinedCondition::UndefinedCondition,
|
|
|
|
"unexpected-request" => DefinedCondition::UnexpectedRequest,
|
|
|
|
|
|
|
|
_ => return Err(Error::ParseError("Unknown defined-condition.")),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-25 00:14:36 +00:00
|
|
|
impl IntoElements for DefinedCondition {
|
|
|
|
fn into_elements(self, emitter: &mut ElementEmitter) {
|
|
|
|
emitter.append_child(Element::builder(match self {
|
2017-05-01 00:24:45 +00:00
|
|
|
DefinedCondition::BadRequest => "bad-request",
|
|
|
|
DefinedCondition::Conflict => "conflict",
|
|
|
|
DefinedCondition::FeatureNotImplemented => "feature-not-implemented",
|
|
|
|
DefinedCondition::Forbidden => "forbidden",
|
|
|
|
DefinedCondition::Gone => "gone",
|
|
|
|
DefinedCondition::InternalServerError => "internal-server-error",
|
|
|
|
DefinedCondition::ItemNotFound => "item-not-found",
|
|
|
|
DefinedCondition::JidMalformed => "jid-malformed",
|
|
|
|
DefinedCondition::NotAcceptable => "not-acceptable",
|
|
|
|
DefinedCondition::NotAllowed => "not-allowed",
|
|
|
|
DefinedCondition::NotAuthorized => "not-authorized",
|
|
|
|
DefinedCondition::PolicyViolation => "policy-violation",
|
|
|
|
DefinedCondition::RecipientUnavailable => "recipient-unavailable",
|
|
|
|
DefinedCondition::Redirect => "redirect",
|
|
|
|
DefinedCondition::RegistrationRequired => "registration-required",
|
|
|
|
DefinedCondition::RemoteServerNotFound => "remote-server-not-found",
|
|
|
|
DefinedCondition::RemoteServerTimeout => "remote-server-timeout",
|
|
|
|
DefinedCondition::ResourceConstraint => "resource-constraint",
|
|
|
|
DefinedCondition::ServiceUnavailable => "service-unavailable",
|
|
|
|
DefinedCondition::SubscriptionRequired => "subscription-required",
|
|
|
|
DefinedCondition::UndefinedCondition => "undefined-condition",
|
|
|
|
DefinedCondition::UnexpectedRequest => "unexpected-request",
|
2017-05-25 00:14:36 +00:00
|
|
|
}).ns(ns::XMPP_STANZAS).build());
|
2017-05-01 00:24:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type Lang = String;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
pub struct StanzaError {
|
|
|
|
pub type_: ErrorType,
|
|
|
|
pub by: Option<Jid>,
|
|
|
|
pub defined_condition: DefinedCondition,
|
|
|
|
pub texts: BTreeMap<Lang, String>,
|
|
|
|
pub other: Option<Element>,
|
|
|
|
}
|
|
|
|
|
2017-05-23 22:31:33 +00:00
|
|
|
impl TryFrom<Element> for StanzaError {
|
2017-07-20 19:03:15 +00:00
|
|
|
type Err = Error;
|
2017-05-01 00:24:45 +00:00
|
|
|
|
2017-05-23 22:31:33 +00:00
|
|
|
fn try_from(elem: Element) -> Result<StanzaError, Error> {
|
2017-07-29 05:49:02 +00:00
|
|
|
if !elem.is("error", ns::DEFAULT_NS) {
|
2017-05-06 20:13:53 +00:00
|
|
|
return Err(Error::ParseError("This is not an error element."));
|
|
|
|
}
|
2017-05-01 00:24:45 +00:00
|
|
|
|
2017-05-23 00:02:23 +00:00
|
|
|
let type_ = get_attr!(elem, "type", required);
|
|
|
|
let by = get_attr!(elem, "by", optional);
|
2017-05-06 20:13:53 +00:00
|
|
|
let mut defined_condition = None;
|
|
|
|
let mut texts = BTreeMap::new();
|
|
|
|
let mut other = None;
|
|
|
|
|
|
|
|
for child in elem.children() {
|
2017-08-18 23:04:18 +00:00
|
|
|
let child_ns = child.ns();
|
2017-05-06 20:13:53 +00:00
|
|
|
if child.is("text", ns::XMPP_STANZAS) {
|
|
|
|
for _ in child.children() {
|
|
|
|
return Err(Error::ParseError("Unknown element in error text."));
|
|
|
|
}
|
2017-05-23 00:02:23 +00:00
|
|
|
let lang = get_attr!(elem, "xml:lang", default);
|
2017-05-07 14:23:06 +00:00
|
|
|
if texts.insert(lang, child.text()).is_some() {
|
2017-05-06 20:13:53 +00:00
|
|
|
return Err(Error::ParseError("Text element present twice for the same xml:lang."));
|
|
|
|
}
|
2017-08-18 23:04:18 +00:00
|
|
|
} else if child_ns.as_ref().map(|ns| ns.as_str()) == Some(ns::XMPP_STANZAS) {
|
2017-05-06 20:13:53 +00:00
|
|
|
if defined_condition.is_some() {
|
|
|
|
return Err(Error::ParseError("Error must not have more than one defined-condition."));
|
|
|
|
}
|
|
|
|
for _ in child.children() {
|
|
|
|
return Err(Error::ParseError("Unknown element in defined-condition."));
|
|
|
|
}
|
|
|
|
let condition = DefinedCondition::from_str(child.name())?;
|
|
|
|
defined_condition = Some(condition);
|
|
|
|
} else {
|
|
|
|
if other.is_some() {
|
|
|
|
return Err(Error::ParseError("Error must not have more than one other element."));
|
|
|
|
}
|
|
|
|
other = Some(child.clone());
|
2017-05-01 00:24:45 +00:00
|
|
|
}
|
|
|
|
}
|
2017-05-24 22:56:35 +00:00
|
|
|
let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
|
2017-05-01 00:24:45 +00:00
|
|
|
|
2017-05-06 20:13:53 +00:00
|
|
|
Ok(StanzaError {
|
|
|
|
type_: type_,
|
|
|
|
by: by,
|
|
|
|
defined_condition: defined_condition,
|
|
|
|
texts: texts,
|
|
|
|
other: other,
|
|
|
|
})
|
|
|
|
}
|
2017-05-01 00:24:45 +00:00
|
|
|
}
|
|
|
|
|
2017-07-20 19:36:13 +00:00
|
|
|
impl From<StanzaError> for Element {
|
|
|
|
fn from(err: StanzaError) -> Element {
|
2017-05-06 20:13:53 +00:00
|
|
|
let mut root = Element::builder("error")
|
2017-07-29 05:49:02 +00:00
|
|
|
.ns(ns::DEFAULT_NS)
|
2017-07-20 19:36:13 +00:00
|
|
|
.attr("type", err.type_)
|
2017-07-29 05:28:20 +00:00
|
|
|
.attr("by", err.by)
|
2017-07-20 19:36:13 +00:00
|
|
|
.append(err.defined_condition)
|
2017-05-06 20:13:53 +00:00
|
|
|
.build();
|
2017-07-20 19:36:13 +00:00
|
|
|
for (lang, text) in err.texts {
|
2017-05-06 20:13:53 +00:00
|
|
|
let elem = Element::builder("text")
|
|
|
|
.ns(ns::XMPP_STANZAS)
|
|
|
|
.attr("xml:lang", lang)
|
|
|
|
.append(text)
|
|
|
|
.build();
|
|
|
|
root.append_child(elem);
|
|
|
|
}
|
2017-07-20 19:36:13 +00:00
|
|
|
if let Some(other) = err.other {
|
2017-05-24 22:56:35 +00:00
|
|
|
root.append_child(other);
|
2017-05-06 20:13:53 +00:00
|
|
|
}
|
|
|
|
root
|
2017-05-01 00:24:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2017-05-06 20:13:53 +00:00
|
|
|
use super::*;
|
2017-05-01 00:24:45 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_simple() {
|
2017-07-29 05:49:02 +00:00
|
|
|
#[cfg(not(feature = "component"))]
|
2017-05-01 00:24:45 +00:00
|
|
|
let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
|
2017-07-29 05:49:02 +00:00
|
|
|
#[cfg(feature = "component")]
|
|
|
|
let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
|
2017-05-23 22:31:33 +00:00
|
|
|
let error = StanzaError::try_from(elem).unwrap();
|
2017-05-06 20:13:53 +00:00
|
|
|
assert_eq!(error.type_, ErrorType::Cancel);
|
|
|
|
assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
|
2017-05-01 00:24:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invalid_type() {
|
2017-07-29 05:49:02 +00:00
|
|
|
#[cfg(not(feature = "component"))]
|
2017-05-01 00:24:45 +00:00
|
|
|
let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
|
2017-07-29 05:49:02 +00:00
|
|
|
#[cfg(feature = "component")]
|
|
|
|
let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
|
2017-05-23 22:31:33 +00:00
|
|
|
let error = StanzaError::try_from(elem).unwrap_err();
|
2017-05-01 00:24:45 +00:00
|
|
|
let message = match error {
|
|
|
|
Error::ParseError(string) => string,
|
|
|
|
_ => panic!(),
|
|
|
|
};
|
2017-05-23 00:02:23 +00:00
|
|
|
assert_eq!(message, "Required attribute 'type' missing.");
|
2017-05-01 00:24:45 +00:00
|
|
|
|
2017-07-29 05:49:02 +00:00
|
|
|
#[cfg(not(feature = "component"))]
|
2017-05-01 00:24:45 +00:00
|
|
|
let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
|
2017-07-29 05:49:02 +00:00
|
|
|
#[cfg(feature = "component")]
|
|
|
|
let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>".parse().unwrap();
|
2017-05-23 22:31:33 +00:00
|
|
|
let error = StanzaError::try_from(elem).unwrap_err();
|
2017-05-01 00:24:45 +00:00
|
|
|
let message = match error {
|
|
|
|
Error::ParseError(string) => string,
|
|
|
|
_ => panic!(),
|
|
|
|
};
|
2017-06-13 23:50:57 +00:00
|
|
|
assert_eq!(message, "Unknown value for 'type' attribute.");
|
2017-05-01 00:24:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_invalid_condition() {
|
2017-07-29 05:49:02 +00:00
|
|
|
#[cfg(not(feature = "component"))]
|
2017-05-01 00:24:45 +00:00
|
|
|
let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
|
2017-07-29 05:49:02 +00:00
|
|
|
#[cfg(feature = "component")]
|
|
|
|
let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>".parse().unwrap();
|
2017-05-23 22:31:33 +00:00
|
|
|
let error = StanzaError::try_from(elem).unwrap_err();
|
2017-05-01 00:24:45 +00:00
|
|
|
let message = match error {
|
|
|
|
Error::ParseError(string) => string,
|
|
|
|
_ => panic!(),
|
|
|
|
};
|
|
|
|
assert_eq!(message, "Error must have a defined-condition.");
|
|
|
|
}
|
|
|
|
}
|