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