001/**
002 *
003 * Copyright 2003-2005 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smack.packet;
019
020import java.util.List;
021import java.util.Map;
022
023import org.jivesoftware.smack.util.StringUtils;
024import org.jivesoftware.smack.util.XmlStringBuilder;
025
026/**
027 * Represents a stream error packet. Stream errors are unrecoverable errors where the server
028 * will close the underlying TCP connection after the stream error was sent to the client.
029 * These is the list of stream errors as defined in the XMPP spec:
030 *
031 * <table border=1>
032 *      <caption>Stream Errors</caption>
033 *      <tr><td><b>Code</b></td><td><b>Description</b></td></tr>
034 *      <tr><td> bad-format </td><td> the entity has sent XML that cannot be processed </td></tr>
035 *      <tr><td> unsupported-encoding </td><td>  the entity has sent a namespace prefix that is
036 *          unsupported </td></tr>
037 *      <tr><td> bad-namespace-prefix </td><td> Remote Server Timeout </td></tr>
038 *      <tr><td> conflict </td><td> the server is closing the active stream for this entity
039 *          because a new stream has been initiated that conflicts with the existing
040 *          stream. </td></tr>
041 *      <tr><td> connection-timeout </td><td> the entity has not generated any traffic over
042 *          the stream for some period of time. </td></tr>
043 *      <tr><td> host-gone </td><td> the value of the 'to' attribute provided by the initiating
044 *          entity in the stream header corresponds to a hostname that is no longer hosted by
045 *          the server. </td></tr>
046 *      <tr><td> host-unknown </td><td> the value of the 'to' attribute provided by the
047 *          initiating entity in the stream header does not correspond to a hostname that is
048 *          hosted by the server. </td></tr>
049 *      <tr><td> improper-addressing </td><td> a stanza sent between two servers lacks a 'to'
050 *          or 'from' attribute </td></tr>
051 *      <tr><td> internal-server-error </td><td> the server has experienced a
052 *          misconfiguration. </td></tr>
053 *      <tr><td> invalid-from </td><td> the JID or hostname provided in a 'from' address does
054 *          not match an authorized JID. </td></tr>
055 *      <tr><td> invalid-namespace </td><td> the streams namespace name is invalid. </td></tr>
056 *      <tr><td> invalid-xml </td><td> the entity has sent invalid XML over the stream. </td></tr>
057 *      <tr><td> not-authorized </td><td> the entity has attempted to send data before the
058 *          stream has been authenticated </td></tr>
059 *      <tr><td> policy-violation </td><td> the entity has violated some local service
060 *          policy. </td></tr>
061 *      <tr><td> remote-connection-failed </td><td> Rthe server is unable to properly connect
062 *          to a remote entity. </td></tr>
063 *      <tr><td> resource-constraint </td><td> Rthe server lacks the system resources necessary
064 *          to service the stream. </td></tr>
065 *      <tr><td> restricted-xml </td><td> the entity has attempted to send restricted XML
066 *          features. </td></tr>
067 *      <tr><td> see-other-host </td><td>  the server will not provide service to the initiating
068 *          entity but is redirecting traffic to another host. </td></tr>
069 *      <tr><td> system-shutdown </td><td> the server is being shut down and all active streams
070 *          are being closed. </td></tr>
071 *      <tr><td> undefined-condition </td><td> the error condition is not one of those defined
072 *          by the other conditions in this list. </td></tr>
073 *      <tr><td> unsupported-encoding </td><td> the initiating entity has encoded the stream in
074 *          an encoding that is not supported. </td></tr>
075 *      <tr><td> unsupported-stanza-type </td><td> the initiating entity has sent a first-level
076 *          child of the stream that is not supported. </td></tr>
077 *      <tr><td> unsupported-version </td><td> the value of the 'version' attribute provided by
078 *          the initiating entity in the stream header specifies a version of XMPP that is not
079 *          supported. </td></tr>
080 *      <tr><td> not-well-formed </td><td> the initiating entity has sent XML that is not
081 *          well-formed. </td></tr>
082 * </table>
083 * <p>
084 * Stream error syntax:
085 * <pre>
086 * {@code
087 * <stream:error>
088 *   <defined-condition xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>
089 *   [<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'
090 *      xml:lang='langcode'>
091 *   OPTIONAL descriptive text
092 *   </text>]
093 *   [OPTIONAL application-specific condition element]
094 * </stream:error>
095 * }
096 * </pre>
097 *
098 * @author Gaston Dombiak
099 */
100public class StreamError extends AbstractError implements Nonza {
101
102    public static final String ELEMENT = "stream:error";
103    public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-streams";
104
105    private final Condition condition;
106    private final String conditionText;
107
108    public StreamError(Condition condition, String conditionText, Map<String, String> descriptiveTexts, List<ExtensionElement> extensions) {
109        super(descriptiveTexts, extensions);
110        // Some implementations may send the condition as non-empty element containing the empty string, that is
111        // <condition xmlns='foo'></condition>, in this case the parser may calls this constructor with the empty string
112        // as conditionText, therefore reset it to null if it's the empty string
113        if (StringUtils.isNullOrEmpty(conditionText)) {
114            conditionText = null;
115        }
116        if (conditionText != null) {
117            switch (condition) {
118            case see_other_host:
119                break;
120            default:
121                throw new IllegalArgumentException("The given condition '" + condition
122                                + "' can not contain a conditionText");
123            }
124        }
125        this.condition = condition;
126        this.conditionText = conditionText;
127    }
128
129    public Condition getCondition() {
130        return condition;
131    }
132
133    public String getConditionText() {
134        return conditionText;
135    }
136
137    @Override
138    public String toString() {
139        return toXML().toString();
140    }
141
142    @Override
143    public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
144        XmlStringBuilder xml = new XmlStringBuilder();
145        xml.openElement(ELEMENT);
146        xml.halfOpenElement(condition.toString()).xmlnsAttribute(NAMESPACE).closeEmptyElement();
147        addDescriptiveTextsAndExtensions(xml);
148        xml.closeElement(ELEMENT);
149        return xml;
150    }
151
152    /**
153     * The defined stream error conditions, see RFC 6120 ยง 4.9.3.
154     *
155     */
156    public enum Condition {
157        bad_format,
158        bad_namespace_prefix,
159        conflict,
160        connection_timeout,
161        host_gone,
162        host_unknown,
163        improper_addressing,
164        internal_server_error,
165        invalid_from,
166        invalid_namespace,
167        invalid_xml,
168        not_authorized,
169        not_well_formed,
170        policy_violation,
171        remote_connection_failed,
172        reset,
173        resource_constraint,
174        restricted_xml,
175        see_other_host,
176        system_shutdown,
177        undefined_condition,
178        unsupported_encoding,
179        unsupported_feature,
180        unsupported_stanza_type,
181        unsupported_version;
182
183        @Override
184        public String toString() {
185            return this.name().replace('_', '-');
186        }
187
188        public static Condition fromString(String string) {
189            // Backwards compatibility for older implementations still using RFC 3920. RFC 6120
190            // changed 'xml-not-well-formed' to 'not-well-formed'.
191            if ("xml-not-well-formed".equals(string)) {
192                string = "not-well-formed";
193            }
194            string = string.replace('-', '_');
195            Condition condition = null;
196            try {
197                condition = Condition.valueOf(string);
198            } catch (Exception e) {
199                throw new IllegalStateException("Could not transform string '" + string
200                                + "' to XMPPErrorCondition", e);
201            }
202            return condition;
203        }
204    }
205
206    @Override
207    public String getNamespace() {
208        return NAMESPACE;
209    }
210
211    @Override
212    public String getElementName() {
213        return ELEMENT;
214    }
215}