001/**
002 *
003 * Copyright 2009 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.bosh;
019
020import java.io.StringReader;
021
022import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
023import org.jivesoftware.smack.sasl.SASLMechanism.SASLFailure;
024import org.jivesoftware.smack.sasl.SASLMechanism.Success;
025import org.jivesoftware.smack.util.PacketParserUtils;
026import org.jivesoftware.smack.XMPPException.StreamErrorException;
027import org.xmlpull.v1.XmlPullParserFactory;
028import org.xmlpull.v1.XmlPullParser;
029import org.igniterealtime.jbosh.AbstractBody;
030import org.igniterealtime.jbosh.BOSHClientResponseListener;
031import org.igniterealtime.jbosh.BOSHMessageEvent;
032import org.igniterealtime.jbosh.BodyQName;
033import org.igniterealtime.jbosh.ComposableBody;
034
035/**
036 * Listens for XML traffic from the BOSH connection manager and parses it into
037 * packet objects.
038 * 
039 * @author Guenther Niess
040 */
041public class BOSHPacketReader implements BOSHClientResponseListener {
042
043    private XMPPBOSHConnection connection;
044
045    /**
046     * Create a packet reader which listen on a BOSHConnection for received
047     * HTTP responses, parse the packets and notifies the connection.
048     * 
049     * @param connection the corresponding connection for the received packets.
050     */
051    public BOSHPacketReader(XMPPBOSHConnection connection) {
052        this.connection = connection;
053    }
054
055    /**
056     * Parse the received packets and notify the corresponding connection.
057     * 
058     * @param event the BOSH client response which includes the received packet.
059     */
060    public void responseReceived(BOSHMessageEvent event) {
061        AbstractBody body = event.getBody();
062        if (body != null) {
063            try {
064                if (connection.sessionID == null) {
065                    connection.sessionID = body.getAttribute(BodyQName.create(XMPPBOSHConnection.BOSH_URI, "sid"));
066                }
067                if (connection.authID == null) {
068                    connection.authID = body.getAttribute(BodyQName.create(XMPPBOSHConnection.BOSH_URI, "authid"));
069                }
070                final XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
071                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
072                        true);
073                parser.setInput(new StringReader(body.toXML()));
074                int eventType = parser.getEventType();
075                do {
076                    eventType = parser.next();
077                    if (eventType == XmlPullParser.START_TAG) {
078                        if (parser.getName().equals("body")) {
079                            // ignore the container root element
080                        } else if (parser.getName().equals("message")) {
081                            connection.processPacket(PacketParserUtils.parseMessage(parser));
082                        } else if (parser.getName().equals("iq")) {
083                            connection.processPacket(PacketParserUtils.parseIQ(parser, connection));
084                        } else if (parser.getName().equals("presence")) {
085                            connection.processPacket(PacketParserUtils.parsePresence(parser));
086                        } else if (parser.getName().equals("challenge")) {
087                            // The server is challenging the SASL authentication
088                            // made by the client
089                            final String challengeData = parser.nextText();
090                            connection.getSASLAuthentication()
091                                    .challengeReceived(challengeData);
092                            connection.processPacket(new Challenge(
093                                    challengeData));
094                        } else if (parser.getName().equals("success")) {
095                            connection.send(ComposableBody.builder()
096                                    .setNamespaceDefinition("xmpp", XMPPBOSHConnection.XMPP_BOSH_NS)
097                                    .setAttribute(
098                                            BodyQName.createWithPrefix(XMPPBOSHConnection.XMPP_BOSH_NS, "restart", "xmpp"),
099                                            "true")
100                                    .setAttribute(
101                                            BodyQName.create(XMPPBOSHConnection.BOSH_URI, "to"),
102                                            connection.getServiceName())
103                                    .build());
104                            connection.getSASLAuthentication().authenticated();
105                            connection.processPacket(new Success(parser.nextText()));
106                        } else if (parser.getName().equals("features")) {
107                            parseFeatures(parser);
108                        } else if (parser.getName().equals("failure")) {
109                            if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
110                                final SASLFailure failure = PacketParserUtils.parseSASLFailure(parser);
111                                connection.getSASLAuthentication().authenticationFailed(failure);
112                                connection.processPacket(failure);
113                            }
114                        } else if (parser.getName().equals("error")) {
115                            throw new StreamErrorException(PacketParserUtils.parseStreamError(parser));
116                        }
117                    }
118                } while (eventType != XmlPullParser.END_DOCUMENT);
119            }
120            catch (Exception e) {
121                if (connection.isConnected()) {
122                    connection.notifyConnectionError(e);
123                }
124            }
125        }
126    }
127
128    /**
129     * Parse and setup the XML stream features.
130     * 
131     * @param parser the XML parser, positioned at the start of a message packet.
132     * @throws Exception if an exception occurs while parsing the packet.
133     */
134    private void parseFeatures(XmlPullParser parser) throws Exception {
135        boolean done = false;
136        while (!done) {
137            int eventType = parser.next();
138
139            if (eventType == XmlPullParser.START_TAG) {
140                if (parser.getName().equals("mechanisms")) {
141                    // The server is reporting available SASL mechanisms. Store
142                    // this information
143                    // which will be used later while logging (i.e.
144                    // authenticating) into
145                    // the server
146                    connection.getSASLAuthentication().setAvailableSASLMethods(
147                            PacketParserUtils.parseMechanisms(parser));
148                } else if (parser.getName().equals("bind")) {
149                    // The server requires the client to bind a resource to the
150                    // stream
151                    connection.serverRequiresBinding();
152                } else if (parser.getName().equals("session")) {
153                    // The server supports sessions
154                    connection.serverSupportsSession();
155                } else if (parser.getName().equals("register")) {
156                    connection.serverSupportsAccountCreation();
157                }
158            } else if (eventType == XmlPullParser.END_TAG) {
159                if (parser.getName().equals("features")) {
160                    done = true;
161                }
162            }
163        }
164    }
165}