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 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}