stanza_error.rs

  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}