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 unrelying 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:<p>
030 *
031 * <table border=1>
032 *      <tr><td><b>Code</b></td><td><b>Description</b></td></tr>
033 *      <tr><td> bad-format </td><td> the entity has sent XML that cannot be processed </td></tr>
034 *      <tr><td> unsupported-encoding </td><td>  the entity has sent a namespace prefix that is
035 *          unsupported </td></tr>
036 *      <tr><td> bad-namespace-prefix </td><td> Remote Server Timeout </td></tr>
037 *      <tr><td> conflict </td><td> the server is closing the active stream for this entity
038 *          because a new stream has been initiated that conflicts with the existing
039 *          stream. </td></tr>
040 *      <tr><td> connection-timeout </td><td> the entity has not generated any traffic over
041 *          the stream for some period of time. </td></tr>
042 *      <tr><td> host-gone </td><td> the value of the 'to' attribute provided by the initiating
043 *          entity in the stream header corresponds to a hostname that is no longer hosted by
044 *          the server. </td></tr>
045 *      <tr><td> host-unknown </td><td> the value of the 'to' attribute provided by the
046 *          initiating entity in the stream header does not correspond to a hostname that is
047 *          hosted by the server. </td></tr>
048 *      <tr><td> improper-addressing </td><td> a stanza sent between two servers lacks a 'to'
049 *          or 'from' attribute </td></tr>
050 *      <tr><td> internal-server-error </td><td> the server has experienced a
051 *          misconfiguration. </td></tr>
052 *      <tr><td> invalid-from </td><td> the JID or hostname provided in a 'from' address does
053 *          not match an authorized JID. </td></tr>
054 *      <tr><td> invalid-namespace </td><td> the streams namespace name is invalid. </td></tr>
055 *      <tr><td> invalid-xml </td><td> the entity has sent invalid XML over the stream. </td></tr>
056 *      <tr><td> not-authorized </td><td> the entity has attempted to send data before the
057 *          stream has been authenticated </td></tr>
058 *      <tr><td> policy-violation </td><td> the entity has violated some local service
059 *          policy. </td></tr>
060 *      <tr><td> remote-connection-failed </td><td> Rthe server is unable to properly connect
061 *          to a remote entity. </td></tr>
062 *      <tr><td> resource-constraint </td><td> Rthe server lacks the system resources necessary
063 *          to service the stream. </td></tr>
064 *      <tr><td> restricted-xml </td><td> the entity has attempted to send restricted XML
065 *          features. </td></tr>
066 *      <tr><td> see-other-host </td><td>  the server will not provide service to the initiating
067 *          entity but is redirecting traffic to another host. </td></tr>
068 *      <tr><td> system-shutdown </td><td> the server is being shut down and all active streams
069 *          are being closed. </td></tr>
070 *      <tr><td> undefined-condition </td><td> the error condition is not one of those defined
071 *          by the other conditions in this list. </td></tr>
072 *      <tr><td> unsupported-encoding </td><td> the initiating entity has encoded the stream in
073 *          an encoding that is not supported. </td></tr>
074 *      <tr><td> unsupported-stanza-type </td><td> the initiating entity has sent a first-level
075 *          child of the stream that is not supported. </td></tr>
076 *      <tr><td> unsupported-version </td><td> the value of the 'version' attribute provided by
077 *          the initiating entity in the stream header specifies a version of XMPP that is not
078 *          supported. </td></tr>
079 *      <tr><td> not-well-formed </td><td> the initiating entity has sent XML that is not
080 *          well-formed. </td></tr>
081 * </table>
082 * <p>
083 * Stream error syntax:
084 * <pre>
085 * {@code
086 * <stream:error>
087 *   <defined-condition xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>
088 *   [<text xmlns='urn:ietf:params:xml:ns:xmpp-streams'
089 *      xml:lang='langcode'>
090 *   OPTIONAL descriptive text
091 *   </text>]
092 *   [OPTIONAL application-specific condition element]
093 * </stream:error>
094 * }
095 * </pre>
096 *
097 * @author Gaston Dombiak
098 */
099public class StreamError extends AbstractError implements PlainStreamElement {
100
101    public static final String ELEMENT = "stream:error";
102    public static final String NAMESPACE = "urn:ietf:params:xml:ns:xmpp-streams";
103
104    private final Condition condition;
105    private final String conditionText;
106
107    public StreamError(Condition condition, String conditionText, Map<String, String> descriptiveTexts, List<ExtensionElement> extensions) {
108        super(descriptiveTexts, extensions);
109        // Some implementations may send the condition as non-empty element containing the empty string, that is
110        // <condition xmlns='foo'></condition>, in this case the parser may calls this constructor with the empty string
111        // as conditionText, therefore reset it to null if it's the empty string
112        if (StringUtils.isNullOrEmpty(conditionText)) {
113            conditionText = null;
114        }
115        if (conditionText != null) {
116            switch (condition) {
117            case see_other_host:
118                break;
119            default:
120                throw new IllegalArgumentException("The given condition '" + condition
121                                + "' can not contain a conditionText");
122            }
123        }
124        this.condition = condition;
125        this.conditionText = conditionText;
126    }
127
128    public Condition getCondition() {
129        return condition;
130    }
131
132    public String getConditionText() {
133        return conditionText;
134    }
135
136    @Override
137    public String toString() {
138        return toXML().toString();
139    }
140
141    @Override
142    public XmlStringBuilder toXML() {
143        XmlStringBuilder xml = new XmlStringBuilder();
144        xml.openElement(ELEMENT);
145        xml.halfOpenElement(condition.toString()).xmlnsAttribute(NAMESPACE).closeEmptyElement();
146        addDescriptiveTextsAndExtensions(xml);
147        xml.closeElement(ELEMENT);
148        return xml;
149    }
150
151    /**
152     * The defined stream error conditions, see RFC 6120 ยง 4.9.3
153     *
154     */
155    public enum Condition {
156        bad_format,
157        bad_namespace_prefix,
158        conflict,
159        connection_timeout,
160        host_gone,
161        host_unknown,
162        improper_addressing,
163        internal_server_error,
164        invalid_from,
165        invalid_namespace,
166        invalid_xml,
167        not_authorized,
168        not_well_formed,
169        policy_violation,
170        remote_connection_failed,
171        reset,
172        resource_constraint,
173        restricted_xml,
174        see_other_host,
175        system_shutdown,
176        undeficed_condition,
177        unsupported_encoding,
178        unsupported_feature,
179        unsupported_stanza_type,
180        unsupported_version;
181
182        @Override
183        public String toString() {
184            return this.name().replace('_', '-');
185        }
186
187        public static Condition fromString(String string) {
188            string = string.replace('-', '_');
189            Condition condition = null;
190            try {
191                condition = Condition.valueOf(string);
192            } catch (Exception e) {
193                throw new IllegalStateException("Could not transform string '" + string
194                                + "' to XMPPErrorCondition", e);
195            }
196            return condition;
197        }
198    }
199}