/** * * Copyright 2003-2007 Jive Software, 2015 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smack.packet; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.logging.Logger; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; /** * Represents an XMPP error sub-packet. Typically, a server responds to a request that has * problems by sending the stanza(/packet) back and including an error packet. Each error has a type, * error condition as well as as an optional text explanation. Typical errors are:

* * * * * * * * * * * * * * * * * * * * * * * * * * *
XMPP Errors
XMPP Error ConditionTypeRFC 6120 Section
bad-requestMODIFY8.3.3.1
conflictCANCEL8.3.3.2
feature-not-implementedCANCEL8.3.3.3
forbiddenAUTH8.3.3.4
goneCANCEL8.3.3.5
internal-server-errorWAIT8.3.3.6
item-not-foundCANCEL8.3.3.7
jid-malformedMODIFY8.3.3.8
not-acceptableMODIFY8.3.3.9
not-allowedCANCEL8.3.3.10
not-authorizedAUTH8.3.3.11
policy-violationMODIFY8.3.3.12
recipient-unavailableWAIT8.3.3.13
redirectMODIFY8.3.3.14
registration-requiredAUTH8.3.3.15
remote-server-not-foundCANCEL8.3.3.16
remote-server-timeoutWAIT8.3.3.17
resource-constraintWAIT8.3.3.18
service-unavailableCANCEL8.3.3.19
subscription-requiredAUTH8.3.3.20
undefined-conditionMODIFY8.3.3.21
unexpected-requestWAIT8.3.3.22
* * @author Matt Tucker * @see RFC 6120 - 8.3.2 Syntax: The Syntax of XMPP error stanzas */ // TODO Rename this class to StanzaError (RFC 6120 ยง 8.3) in Smack 4.3, as this is what this class actually is. SMACK-769 // TODO Use StanzaErrorTextElement here. public class XMPPError extends AbstractError { public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas"; public static final String ERROR = "error"; private static final Logger LOGGER = Logger.getLogger(XMPPError.class.getName()); static final Map CONDITION_TO_TYPE = new HashMap(); static { CONDITION_TO_TYPE.put(Condition.bad_request, Type.MODIFY); CONDITION_TO_TYPE.put(Condition.conflict, Type.CANCEL); CONDITION_TO_TYPE.put(Condition.feature_not_implemented, Type.CANCEL); CONDITION_TO_TYPE.put(Condition.forbidden, Type.AUTH); CONDITION_TO_TYPE.put(Condition.gone, Type.CANCEL); CONDITION_TO_TYPE.put(Condition.internal_server_error, Type.CANCEL); CONDITION_TO_TYPE.put(Condition.item_not_found, Type.CANCEL); CONDITION_TO_TYPE.put(Condition.jid_malformed, Type.MODIFY); CONDITION_TO_TYPE.put(Condition.not_acceptable, Type.MODIFY); CONDITION_TO_TYPE.put(Condition.not_allowed, Type.CANCEL); CONDITION_TO_TYPE.put(Condition.not_authorized, Type.AUTH); CONDITION_TO_TYPE.put(Condition.policy_violation, Type.MODIFY); CONDITION_TO_TYPE.put(Condition.recipient_unavailable, Type.WAIT); CONDITION_TO_TYPE.put(Condition.redirect, Type.MODIFY); CONDITION_TO_TYPE.put(Condition.registration_required, Type.AUTH); CONDITION_TO_TYPE.put(Condition.remote_server_not_found, Type.CANCEL); CONDITION_TO_TYPE.put(Condition.remote_server_timeout, Type.WAIT); CONDITION_TO_TYPE.put(Condition.resource_constraint, Type.WAIT); CONDITION_TO_TYPE.put(Condition.service_unavailable, Type.CANCEL); CONDITION_TO_TYPE.put(Condition.subscription_required, Type.AUTH); CONDITION_TO_TYPE.put(Condition.undefined_condition, Type.MODIFY); CONDITION_TO_TYPE.put(Condition.unexpected_request, Type.WAIT); } private final Condition condition; private final String conditionText; private final String errorGenerator; private final Type type; private final Stanza stanza; // TODO: Deprecated constructors // deprecate in 4.3 /** * Create a new XMPPError. * * @param condition error condition. * @deprecated use {@link Builder} instead. */ @Deprecated public XMPPError(Condition condition) { this(condition, null, null, null, null, null, null); } /** * Create a new XMPPError. * * @param condition error condition. * @param applicationSpecificCondition application specific condition. * @deprecated use {@link Builder} instead. */ @Deprecated public XMPPError(Condition condition, ExtensionElement applicationSpecificCondition) { this(condition, null, null, null, null, Arrays.asList(applicationSpecificCondition), null); } /** * Creates a new error with the specified type, condition and message. * This constructor is used when the condition is not recognized automatically by XMPPError * i.e. there is not a defined instance of ErrorCondition or it does not apply the default * specification. * * @param type the error type. * @param condition the error condition. * @param conditionText * @param errorGenerator * @param descriptiveTexts * @param extensions list of stanza(/packet) extensions * @deprecated use {@link Builder} instead. */ @Deprecated public XMPPError(Condition condition, String conditionText, String errorGenerator, Type type, Map descriptiveTexts, List extensions) { this(condition, conditionText, errorGenerator, type, descriptiveTexts, extensions, null); } /** * Creates a new error with the specified type, condition and message. * This constructor is used when the condition is not recognized automatically by XMPPError * i.e. there is not a defined instance of ErrorCondition or it does not apply the default * specification. * * @param type the error type. * @param condition the error condition. * @param conditionText * @param errorGenerator * @param descriptiveTexts * @param extensions list of stanza(/packet) extensions * @param stanza the stanza carrying this XMPP error. */ public XMPPError(Condition condition, String conditionText, String errorGenerator, Type type, Map descriptiveTexts, List extensions, Stanza stanza) { super(descriptiveTexts, NAMESPACE, extensions); this.condition = Objects.requireNonNull(condition, "condition must not be null"); this.stanza = stanza; // Some implementations may send the condition as non-empty element containing the empty string, that is // , in this case the parser may calls this constructor with the empty string // as conditionText, therefore reset it to null if it's the empty string if (StringUtils.isNullOrEmpty(conditionText)) { conditionText = null; } if (conditionText != null) { switch (condition) { case gone: case redirect: break; default: throw new IllegalArgumentException( "Condition text can only be set with condtion types 'gone' and 'redirect', not " + condition); } } this.conditionText = conditionText; this.errorGenerator = errorGenerator; if (type == null) { Type determinedType = CONDITION_TO_TYPE.get(condition); if (determinedType == null) { LOGGER.warning("Could not determine type for condition: " + condition); determinedType = Type.CANCEL; } this.type = determinedType; } else { this.type = type; } } /** * Returns the error condition. * * @return the error condition. */ public Condition getCondition() { return condition; } /** * Returns the error type. * * @return the error type. */ public Type getType() { return type; } public String getErrorGenerator() { return errorGenerator; } public String getConditionText() { return conditionText; } /** * Get the stanza carrying the XMPP error. * * @return the stanza carrying the XMPP error. * @since 4.2 */ public Stanza getStanza() { return stanza; } @Override public String toString() { StringBuilder sb = new StringBuilder("XMPPError: "); sb.append(condition.toString()).append(" - ").append(type.toString()); if (errorGenerator != null) { sb.append(". Generated by ").append(errorGenerator); } return sb.toString(); } /** * Returns the error as XML. * * @return the error as XML. */ public XmlStringBuilder toXML() { XmlStringBuilder xml = new XmlStringBuilder(); xml.halfOpenElement(ERROR); xml.attribute("type", type.toString()); xml.optAttribute("by", errorGenerator); xml.rightAngleBracket(); xml.halfOpenElement(condition.toString()); xml.xmlnsAttribute(NAMESPACE); if (conditionText != null) { xml.rightAngleBracket(); xml.escape(conditionText); xml.closeElement(condition.toString()); } else { xml.closeEmptyElement(); } addDescriptiveTextsAndExtensions(xml); xml.closeElement(ERROR); return xml; } public static XMPPError.Builder from(Condition condition, String descriptiveText) { XMPPError.Builder builder = getBuilder().setCondition(condition); if (descriptiveText != null) { Map descriptiveTexts = new HashMap<>(); descriptiveTexts.put("en", descriptiveText); builder.setDescriptiveTexts(descriptiveTexts); } return builder; } public static Builder getBuilder() { return new Builder(); } public static Builder getBuilder(Condition condition) { return getBuilder().setCondition(condition); } public static Builder getBuilder(XMPPError xmppError) { return getBuilder().copyFrom(xmppError); } public static final class Builder extends AbstractError.Builder { private Condition condition; private String conditionText; private String errorGenerator; private Type type; private Stanza stanza; private Builder() { } public Builder setCondition(Condition condition) { this.condition = condition; return this; } public Builder setType(Type type) { this.type = type; return this; } public Builder setConditionText(String conditionText) { this.conditionText = conditionText; return this; } public Builder setErrorGenerator(String errorGenerator) { this.errorGenerator = errorGenerator; return this; } public Builder setStanza(Stanza stanza) { this.stanza = stanza; return this; } public Builder copyFrom(XMPPError xmppError) { setCondition(xmppError.getCondition()); setType(xmppError.getType()); setConditionText(xmppError.getConditionText()); setErrorGenerator(xmppError.getErrorGenerator()); setStanza(xmppError.getStanza()); setDescriptiveTexts(xmppError.descriptiveTexts); setTextNamespace(xmppError.textNamespace); setExtensions(xmppError.extensions); return this; } public XMPPError build() { return new XMPPError(condition, conditionText, errorGenerator, type, descriptiveTexts, extensions, stanza); } @Override protected Builder getThis() { return this; } } /** * A class to represent the type of the Error. The types are: * *

*/ public static enum Type { WAIT, CANCEL, MODIFY, AUTH, CONTINUE; @Override public String toString() { return name().toLowerCase(Locale.US); } public static Type fromString(String string) { string = string.toUpperCase(Locale.US); return Type.valueOf(string); } } public enum Condition { bad_request, conflict, feature_not_implemented, forbidden, gone, internal_server_error, item_not_found, jid_malformed, not_acceptable, not_allowed, not_authorized, policy_violation, recipient_unavailable, redirect, registration_required, remote_server_not_found, remote_server_timeout, resource_constraint, service_unavailable, subscription_required, undefined_condition, unexpected_request; @Override public String toString() { return this.name().replace('_', '-'); } public static Condition fromString(String string) { // Backwards compatibility for older implementations still using RFC 3920. RFC 6120 // changed 'xml-not-well-formed' to 'not-well-formed'. if ("xml-not-well-formed".equals(string)) { string = "not-well-formed"; } string = string.replace('-', '_'); Condition condition = null; try { condition = Condition.valueOf(string); } catch (Exception e) { throw new IllegalStateException("Could not transform string '" + string + "' to XMPPErrorCondition", e); } return condition; } } }