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::collections::BTreeMap;
9
10use minidom::Element;
11
12use presence::PresencePayload;
13use error::Error;
14use jid::Jid;
15use ns;
16
17generate_attribute!(
18 /// The type of the error.
19 ErrorType, "type", {
20 /// Retry after providing credentials.
21 Auth => "auth",
22
23 /// Do not retry (the error cannot be remedied).
24 Cancel => "cancel",
25
26 /// Proceed (the condition was only a warning).
27 Continue => "continue",
28
29 /// Retry after changing the data sent.
30 Modify => "modify",
31
32 /// Retry after waiting (the error is temporary).
33 Wait => "wait",
34 }
35);
36
37generate_element_enum!(
38 /// List of valid error conditions.
39 DefinedCondition, "condition", XMPP_STANZAS, {
40 /// The sender has sent a stanza containing XML that does not conform
41 /// to the appropriate schema or that cannot be processed (e.g., an IQ
42 /// stanza that includes an unrecognized value of the 'type' attribute,
43 /// or an element that is qualified by a recognized namespace but that
44 /// violates the defined syntax for the element); the associated error
45 /// type SHOULD be "modify".
46 BadRequest => "bad-request",
47
48 /// Access cannot be granted because an existing resource exists with
49 /// the same name or address; the associated error type SHOULD be
50 /// "cancel".
51 Conflict => "conflict",
52
53 /// The feature represented in the XML stanza is not implemented by the
54 /// intended recipient or an intermediate server and therefore the
55 /// stanza cannot be processed (e.g., the entity understands the
56 /// namespace but does not recognize the element name); the associated
57 /// error type SHOULD be "cancel" or "modify".
58 FeatureNotImplemented => "feature-not-implemented",
59
60 /// The requesting entity does not possess the necessary permissions to
61 /// perform an action that only certain authorized roles or individuals
62 /// are allowed to complete (i.e., it typically relates to
63 /// authorization rather than authentication); the associated error
64 /// type SHOULD be "auth".
65 Forbidden => "forbidden",
66
67 /// The recipient or server can no longer be contacted at this address,
68 /// typically on a permanent basis (as opposed to the <redirect/> error
69 /// condition, which is used for temporary addressing failures); the
70 /// associated error type SHOULD be "cancel" and the error stanza
71 /// SHOULD include a new address (if available) as the XML character
72 /// data of the <gone/> element (which MUST be a Uniform Resource
73 /// Identifier [URI] or Internationalized Resource Identifier [IRI] at
74 /// which the entity can be contacted, typically an XMPP IRI as
75 /// specified in [XMPP‑URI]).
76 Gone => "gone",
77
78 /// The server has experienced a misconfiguration or other internal
79 /// error that prevents it from processing the stanza; the associated
80 /// error type SHOULD be "cancel".
81 InternalServerError => "internal-server-error",
82
83 /// The addressed JID or item requested cannot be found; the associated
84 /// error type SHOULD be "cancel".
85 ItemNotFound => "item-not-found",
86
87 /// The sending entity has provided (e.g., during resource binding) or
88 /// communicated (e.g., in the 'to' address of a stanza) an XMPP
89 /// address or aspect thereof that violates the rules defined in
90 /// [XMPP‑ADDR]; the associated error type SHOULD be "modify".
91 JidMalformed => "jid-malformed",
92
93 /// The recipient or server understands the request but cannot process
94 /// it because the request does not meet criteria defined by the
95 /// recipient or server (e.g., a request to subscribe to information
96 /// that does not simultaneously include configuration parameters
97 /// needed by the recipient); the associated error type SHOULD be
98 /// "modify".
99 NotAcceptable => "not-acceptable",
100
101 /// The recipient or server does not allow any entity to perform the
102 /// action (e.g., sending to entities at a blacklisted domain); the
103 /// associated error type SHOULD be "cancel".
104 NotAllowed => "not-allowed",
105
106 /// The sender needs to provide credentials before being allowed to
107 /// perform the action, or has provided improper credentials (the name
108 /// "not-authorized", which was borrowed from the "401 Unauthorized"
109 /// error of [HTTP], might lead the reader to think that this condition
110 /// relates to authorization, but instead it is typically used in
111 /// relation to authentication); the associated error type SHOULD be
112 /// "auth".
113 NotAuthorized => "not-authorized",
114
115 /// The entity has violated some local service policy (e.g., a message
116 /// contains words that are prohibited by the service) and the server
117 /// MAY choose to specify the policy in the <text/> element or in an
118 /// application-specific condition element; the associated error type
119 /// SHOULD be "modify" or "wait" depending on the policy being
120 /// violated.
121 PolicyViolation => "policy-violation",
122
123 /// The intended recipient is temporarily unavailable, undergoing
124 /// maintenance, etc.; the associated error type SHOULD be "wait".
125 RecipientUnavailable => "recipient-unavailable",
126
127 /// The recipient or server is redirecting requests for this
128 /// information to another entity, typically in a temporary fashion (as
129 /// opposed to the <gone/> error condition, which is used for permanent
130 /// addressing failures); the associated error type SHOULD be "modify"
131 /// and the error stanza SHOULD contain the alternate address in the
132 /// XML character data of the <redirect/> element (which MUST be a URI
133 /// or IRI with which the sender can communicate, typically an XMPP IRI
134 /// as specified in [XMPP‑URI]).
135 Redirect => "redirect",
136
137 /// The requesting entity is not authorized to access the requested
138 /// service because prior registration is necessary (examples of prior
139 /// registration include members-only rooms in XMPP multi-user chat
140 /// [XEP‑0045] and gateways to non-XMPP instant messaging services,
141 /// which traditionally required registration in order to use the
142 /// gateway [XEP‑0100]); the associated error type SHOULD be "auth".
143 RegistrationRequired => "registration-required",
144
145 /// A remote server or service specified as part or all of the JID of
146 /// the intended recipient does not exist or cannot be resolved (e.g.,
147 /// there is no _xmpp-server._tcp DNS SRV record, the A or AAAA
148 /// fallback resolution fails, or A/AAAA lookups succeed but there is
149 /// no response on the IANA-registered port 5269); the associated error
150 /// type SHOULD be "cancel".
151 RemoteServerNotFound => "remote-server-not-found",
152
153 /// A remote server or service specified as part or all of the JID of
154 /// the intended recipient (or needed to fulfill a request) was
155 /// resolved but communications could not be established within a
156 /// reasonable amount of time (e.g., an XML stream cannot be
157 /// established at the resolved IP address and port, or an XML stream
158 /// can be established but stream negotiation fails because of problems
159 /// with TLS, SASL, Server Dialback, etc.); the associated error type
160 /// SHOULD be "wait" (unless the error is of a more permanent nature,
161 /// e.g., the remote server is found but it cannot be authenticated or
162 /// it violates security policies).
163 RemoteServerTimeout => "remote-server-timeout",
164
165 /// The server or recipient is busy or lacks the system resources
166 /// necessary to service the request; the associated error type SHOULD
167 /// be "wait".
168 ResourceConstraint => "resource-constraint",
169
170 /// The server or recipient does not currently provide the requested
171 /// service; the associated error type SHOULD be "cancel".
172 ServiceUnavailable => "service-unavailable",
173
174 /// The requesting entity is not authorized to access the requested
175 /// service because a prior subscription is necessary (examples of
176 /// prior subscription include authorization to receive presence
177 /// information as defined in [XMPP‑IM] and opt-in data feeds for XMPP
178 /// publish-subscribe as defined in [XEP‑0060]); the associated error
179 /// type SHOULD be "auth".
180 SubscriptionRequired => "subscription-required",
181
182 /// The error condition is not one of those defined by the other
183 /// conditions in this list; any error type can be associated with this
184 /// condition, and it SHOULD NOT be used except in conjunction with an
185 /// application-specific condition.
186 UndefinedCondition => "undefined-condition",
187
188 /// The recipient or server understood the request but was not
189 /// expecting it at this time (e.g., the request was out of order); the
190 /// associated error type SHOULD be "wait" or "modify".
191 UnexpectedRequest => "unexpected-request",
192 }
193);
194
195type Lang = String;
196
197/// The representation of a stanza error.
198#[derive(Debug, Clone)]
199pub struct StanzaError {
200 /// The type of this error.
201 pub type_: ErrorType,
202
203 /// The JID of the entity who set this error.
204 pub by: Option<Jid>,
205
206 /// One of the defined conditions for this error to happen.
207 pub defined_condition: DefinedCondition,
208
209 /// Human-readable description of this error.
210 pub texts: BTreeMap<Lang, String>,
211
212 /// A protocol-specific extension for this error.
213 pub other: Option<Element>,
214}
215
216impl PresencePayload for StanzaError {}
217
218impl TryFrom<Element> for StanzaError {
219 type Err = Error;
220
221 fn try_from(elem: Element) -> Result<StanzaError, Error> {
222 check_self!(elem, "error", DEFAULT_NS);
223
224 let type_ = get_attr!(elem, "type", required);
225 let by = get_attr!(elem, "by", optional);
226 let mut defined_condition = None;
227 let mut texts = BTreeMap::new();
228 let mut other = None;
229
230 for child in elem.children() {
231 if child.is("text", ns::XMPP_STANZAS) {
232 check_no_children!(child, "text");
233 let lang = get_attr!(elem, "xml:lang", default);
234 if texts.insert(lang, child.text()).is_some() {
235 return Err(Error::ParseError("Text element present twice for the same xml:lang."));
236 }
237 } else if child.has_ns(ns::XMPP_STANZAS) {
238 if defined_condition.is_some() {
239 return Err(Error::ParseError("Error must not have more than one defined-condition."));
240 }
241 check_no_children!(child, "defined-condition");
242 let condition = DefinedCondition::try_from(child.clone())?;
243 defined_condition = Some(condition);
244 } else {
245 if other.is_some() {
246 return Err(Error::ParseError("Error must not have more than one other element."));
247 }
248 other = Some(child.clone());
249 }
250 }
251 let defined_condition = defined_condition.ok_or(Error::ParseError("Error must have a defined-condition."))?;
252
253 Ok(StanzaError {
254 type_: type_,
255 by: by,
256 defined_condition: defined_condition,
257 texts: texts,
258 other: other,
259 })
260 }
261}
262
263impl From<StanzaError> for Element {
264 fn from(err: StanzaError) -> Element {
265 let mut root = Element::builder("error")
266 .ns(ns::DEFAULT_NS)
267 .attr("type", err.type_)
268 .attr("by", err.by)
269 .append(err.defined_condition)
270 .build();
271 for (lang, text) in err.texts {
272 let elem = Element::builder("text")
273 .ns(ns::XMPP_STANZAS)
274 .attr("xml:lang", lang)
275 .append(text)
276 .build();
277 root.append_child(elem);
278 }
279 if let Some(other) = err.other {
280 root.append_child(other);
281 }
282 root
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_simple() {
292 #[cfg(not(feature = "component"))]
293 let elem: Element = "<error xmlns='jabber:client' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
294 #[cfg(feature = "component")]
295 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'><undefined-condition xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/></error>".parse().unwrap();
296 let error = StanzaError::try_from(elem).unwrap();
297 assert_eq!(error.type_, ErrorType::Cancel);
298 assert_eq!(error.defined_condition, DefinedCondition::UndefinedCondition);
299 }
300
301 #[test]
302 fn test_invalid_type() {
303 #[cfg(not(feature = "component"))]
304 let elem: Element = "<error xmlns='jabber:client'/>".parse().unwrap();
305 #[cfg(feature = "component")]
306 let elem: Element = "<error xmlns='jabber:component:accept'/>".parse().unwrap();
307 let error = StanzaError::try_from(elem).unwrap_err();
308 let message = match error {
309 Error::ParseError(string) => string,
310 _ => panic!(),
311 };
312 assert_eq!(message, "Required attribute 'type' missing.");
313
314 #[cfg(not(feature = "component"))]
315 let elem: Element = "<error xmlns='jabber:client' type='coucou'/>".parse().unwrap();
316 #[cfg(feature = "component")]
317 let elem: Element = "<error xmlns='jabber:component:accept' type='coucou'/>".parse().unwrap();
318 let error = StanzaError::try_from(elem).unwrap_err();
319 let message = match error {
320 Error::ParseError(string) => string,
321 _ => panic!(),
322 };
323 assert_eq!(message, "Unknown value for 'type' attribute.");
324 }
325
326 #[test]
327 fn test_invalid_condition() {
328 #[cfg(not(feature = "component"))]
329 let elem: Element = "<error xmlns='jabber:client' type='cancel'/>".parse().unwrap();
330 #[cfg(feature = "component")]
331 let elem: Element = "<error xmlns='jabber:component:accept' type='cancel'/>".parse().unwrap();
332 let error = StanzaError::try_from(elem).unwrap_err();
333 let message = match error {
334 Error::ParseError(string) => string,
335 _ => panic!(),
336 };
337 assert_eq!(message, "Error must have a defined-condition.");
338 }
339}