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