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