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