1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use try_from::TryFrom;
8use std::str::FromStr;
9use std::collections::BTreeMap;
10
11use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
12
13use error::Error;
14use jid::Jid;
15use ns;
16
17generate_attribute!(ErrorType, "type", {
18 Auth => "auth",
19 Cancel => "cancel",
20 Continue => "continue",
21 Modify => "modify",
22 Wait => "wait",
23});
24
25#[derive(Debug, Clone, PartialEq)]
26pub enum DefinedCondition {
27 BadRequest,
28 Conflict,
29 FeatureNotImplemented,
30 Forbidden,
31 Gone,
32 InternalServerError,
33 ItemNotFound,
34 JidMalformed,
35 NotAcceptable,
36 NotAllowed,
37 NotAuthorized,
38 PolicyViolation,
39 RecipientUnavailable,
40 Redirect,
41 RegistrationRequired,
42 RemoteServerNotFound,
43 RemoteServerTimeout,
44 ResourceConstraint,
45 ServiceUnavailable,
46 SubscriptionRequired,
47 UndefinedCondition,
48 UnexpectedRequest,
49}
50
51impl FromStr for DefinedCondition {
52 type Err = Error;
53
54 fn from_str(s: &str) -> Result<DefinedCondition, Error> {
55 Ok(match s {
56 "bad-request" => DefinedCondition::BadRequest,
57 "conflict" => DefinedCondition::Conflict,
58 "feature-not-implemented" => DefinedCondition::FeatureNotImplemented,
59 "forbidden" => DefinedCondition::Forbidden,
60 "gone" => DefinedCondition::Gone,
61 "internal-server-error" => DefinedCondition::InternalServerError,
62 "item-not-found" => DefinedCondition::ItemNotFound,
63 "jid-malformed" => DefinedCondition::JidMalformed,
64 "not-acceptable" => DefinedCondition::NotAcceptable,
65 "not-allowed" => DefinedCondition::NotAllowed,
66 "not-authorized" => DefinedCondition::NotAuthorized,
67 "policy-violation" => DefinedCondition::PolicyViolation,
68 "recipient-unavailable" => DefinedCondition::RecipientUnavailable,
69 "redirect" => DefinedCondition::Redirect,
70 "registration-required" => DefinedCondition::RegistrationRequired,
71 "remote-server-not-found" => DefinedCondition::RemoteServerNotFound,
72 "remote-server-timeout" => DefinedCondition::RemoteServerTimeout,
73 "resource-constraint" => DefinedCondition::ResourceConstraint,
74 "service-unavailable" => DefinedCondition::ServiceUnavailable,
75 "subscription-required" => DefinedCondition::SubscriptionRequired,
76 "undefined-condition" => DefinedCondition::UndefinedCondition,
77 "unexpected-request" => DefinedCondition::UnexpectedRequest,
78
79 _ => return Err(Error::ParseError("Unknown defined-condition.")),
80 })
81 }
82}
83
84impl IntoElements for DefinedCondition {
85 fn into_elements(self, emitter: &mut ElementEmitter) {
86 emitter.append_child(Element::builder(match self {
87 DefinedCondition::BadRequest => "bad-request",
88 DefinedCondition::Conflict => "conflict",
89 DefinedCondition::FeatureNotImplemented => "feature-not-implemented",
90 DefinedCondition::Forbidden => "forbidden",
91 DefinedCondition::Gone => "gone",
92 DefinedCondition::InternalServerError => "internal-server-error",
93 DefinedCondition::ItemNotFound => "item-not-found",
94 DefinedCondition::JidMalformed => "jid-malformed",
95 DefinedCondition::NotAcceptable => "not-acceptable",
96 DefinedCondition::NotAllowed => "not-allowed",
97 DefinedCondition::NotAuthorized => "not-authorized",
98 DefinedCondition::PolicyViolation => "policy-violation",
99 DefinedCondition::RecipientUnavailable => "recipient-unavailable",
100 DefinedCondition::Redirect => "redirect",
101 DefinedCondition::RegistrationRequired => "registration-required",
102 DefinedCondition::RemoteServerNotFound => "remote-server-not-found",
103 DefinedCondition::RemoteServerTimeout => "remote-server-timeout",
104 DefinedCondition::ResourceConstraint => "resource-constraint",
105 DefinedCondition::ServiceUnavailable => "service-unavailable",
106 DefinedCondition::SubscriptionRequired => "subscription-required",
107 DefinedCondition::UndefinedCondition => "undefined-condition",
108 DefinedCondition::UnexpectedRequest => "unexpected-request",
109 }).ns(ns::XMPP_STANZAS).build());
110 }
111}
112
113pub type Lang = String;
114
115#[derive(Debug, Clone)]
116pub struct StanzaError {
117 pub type_: ErrorType,
118 pub by: Option<Jid>,
119 pub defined_condition: DefinedCondition,
120 pub texts: BTreeMap<Lang, String>,
121 pub other: Option<Element>,
122}
123
124impl TryFrom<Element> for StanzaError {
125 type Err = Error;
126
127 fn try_from(elem: Element) -> Result<StanzaError, Error> {
128 if !elem.is("error", ns::DEFAULT_NS) {
129 return Err(Error::ParseError("This is not an error element."));
130 }
131
132 let type_ = get_attr!(elem, "type", required);
133 let by = get_attr!(elem, "by", optional);
134 let mut defined_condition = None;
135 let mut texts = BTreeMap::new();
136 let mut other = None;
137
138 for child in elem.children() {
139 let child_ns = child.ns();
140 if child.is("text", ns::XMPP_STANZAS) {
141 for _ in child.children() {
142 return Err(Error::ParseError("Unknown element in error text."));
143 }
144 let lang = get_attr!(elem, "xml:lang", default);
145 if texts.insert(lang, child.text()).is_some() {
146 return Err(Error::ParseError("Text element present twice for the same xml:lang."));
147 }
148 } else if child_ns.as_ref().map(|ns| ns.as_str()) == Some(ns::XMPP_STANZAS) {
149 if defined_condition.is_some() {
150 return Err(Error::ParseError("Error must not have more than one defined-condition."));
151 }
152 for _ in child.children() {
153 return Err(Error::ParseError("Unknown element in defined-condition."));
154 }
155 let condition = DefinedCondition::from_str(child.name())?;
156 defined_condition = Some(condition);
157 } else {
158 if other.is_some() {
159 return Err(Error::ParseError("Error must not have more than one other element."));
160 }
161 other = Some(child.clone());
162 }
163 }
164 let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
165
166 Ok(StanzaError {
167 type_: type_,
168 by: by,
169 defined_condition: defined_condition,
170 texts: texts,
171 other: other,
172 })
173 }
174}
175
176impl From<StanzaError> for Element {
177 fn from(err: StanzaError) -> Element {
178 let mut root = Element::builder("error")
179 .ns(ns::DEFAULT_NS)
180 .attr("type", err.type_)
181 .attr("by", err.by)
182 .append(err.defined_condition)
183 .build();
184 for (lang, text) in err.texts {
185 let elem = Element::builder("text")
186 .ns(ns::XMPP_STANZAS)
187 .attr("xml:lang", lang)
188 .append(text)
189 .build();
190 root.append_child(elem);
191 }
192 if let Some(other) = err.other {
193 root.append_child(other);
194 }
195 root
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_simple() {
205 #[cfg(not(feature = "component"))]
206 let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
207 #[cfg(feature = "component")]
208 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
209 let error = StanzaError::try_from(elem).unwrap();
210 assert_eq!(error.type_, ErrorType::Cancel);
211 assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
212 }
213
214 #[test]
215 fn test_invalid_type() {
216 #[cfg(not(feature = "component"))]
217 let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
218 #[cfg(feature = "component")]
219 let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
220 let error = StanzaError::try_from(elem).unwrap_err();
221 let message = match error {
222 Error::ParseError(string) => string,
223 _ => panic!(),
224 };
225 assert_eq!(message, "Required attribute 'type' missing.");
226
227 #[cfg(not(feature = "component"))]
228 let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
229 #[cfg(feature = "component")]
230 let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>".parse().unwrap();
231 let error = StanzaError::try_from(elem).unwrap_err();
232 let message = match error {
233 Error::ParseError(string) => string,
234 _ => panic!(),
235 };
236 assert_eq!(message, "Unknown value for 'type' attribute.");
237 }
238
239 #[test]
240 fn test_invalid_condition() {
241 #[cfg(not(feature = "component"))]
242 let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
243 #[cfg(feature = "component")]
244 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>".parse().unwrap();
245 let error = StanzaError::try_from(elem).unwrap_err();
246 let message = match error {
247 Error::ParseError(string) => string,
248 _ => panic!(),
249 };
250 assert_eq!(message, "Error must have a defined-condition.");
251 }
252}