1/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * Copyright 2009 Jive Software.
7 *
8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package org.jivesoftware.smack;
22
23import java.io.StringReader;
24
25import org.jivesoftware.smack.util.PacketParserUtils;
26import org.jivesoftware.smack.sasl.SASLMechanism.Challenge;
27import org.jivesoftware.smack.sasl.SASLMechanism.Failure;
28import org.jivesoftware.smack.sasl.SASLMechanism.Success;
29import org.xmlpull.v1.XmlPullParserFactory;
30import org.xmlpull.v1.XmlPullParser;
31
32import com.kenai.jbosh.AbstractBody;
33import com.kenai.jbosh.BOSHClientResponseListener;
34import com.kenai.jbosh.BOSHMessageEvent;
35import com.kenai.jbosh.BodyQName;
36import com.kenai.jbosh.ComposableBody;
37
38/**
39 * Listens for XML traffic from the BOSH connection manager and parses it into
40 * packet objects.
41 *
42 * @author Guenther Niess
43 */
44public class BOSHPacketReader implements BOSHClientResponseListener {
45
46    private BOSHConnection connection;
47
48    /**
49     * Create a packet reader which listen on a BOSHConnection for received
50     * HTTP responses, parse the packets and notifies the connection.
51     *
52     * @param connection the corresponding connection for the received packets.
53     */
54    public BOSHPacketReader(BOSHConnection connection) {
55        this.connection = connection;
56    }
57
58    /**
59     * Parse the received packets and notify the corresponding connection.
60     *
61     * @param event the BOSH client response which includes the received packet.
62     */
63    public void responseReceived(BOSHMessageEvent event) {
64        AbstractBody body = event.getBody();
65        if (body != null) {
66            try {
67                if (connection.sessionID == null) {
68                    connection.sessionID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "sid"));
69                }
70                if (connection.authID == null) {
71                    connection.authID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "authid"));
72                }
73                final XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
74                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES,
75                        true);
76                parser.setInput(new StringReader(body.toXML()));
77                int eventType = parser.getEventType();
78                do {
79                    eventType = parser.next();
80                    if (eventType == XmlPullParser.START_TAG) {
81                        if (parser.getName().equals("body")) {
82                            // ignore the container root element
83                        } else if (parser.getName().equals("message")) {
84                            connection.processPacket(PacketParserUtils.parseMessage(parser));
85                        } else if (parser.getName().equals("iq")) {
86                            connection.processPacket(PacketParserUtils.parseIQ(parser, connection));
87                        } else if (parser.getName().equals("presence")) {
88                            connection.processPacket(PacketParserUtils.parsePresence(parser));
89                        } else if (parser.getName().equals("challenge")) {
90                            // The server is challenging the SASL authentication
91                            // made by the client
92                            final String challengeData = parser.nextText();
93                            connection.getSASLAuthentication()
94                                    .challengeReceived(challengeData);
95                            connection.processPacket(new Challenge(
96                                    challengeData));
97                        } else if (parser.getName().equals("success")) {
98                            connection.send(ComposableBody.builder()
99                                    .setNamespaceDefinition("xmpp", BOSHConnection.XMPP_BOSH_NS)
100                                    .setAttribute(
101                                            BodyQName.createWithPrefix(BOSHConnection.XMPP_BOSH_NS, "restart", "xmpp"),
102                                            "true")
103                                    .setAttribute(
104                                            BodyQName.create(BOSHConnection.BOSH_URI, "to"),
105                                            connection.getServiceName())
106                                    .build());
107                            connection.getSASLAuthentication().authenticated();
108                            connection.processPacket(new Success(parser.nextText()));
109                        } else if (parser.getName().equals("features")) {
110                            parseFeatures(parser);
111                        } else if (parser.getName().equals("failure")) {
112                            if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) {
113                                final Failure failure = PacketParserUtils.parseSASLFailure(parser);
114                                connection.getSASLAuthentication().authenticationFailed();
115                                connection.processPacket(failure);
116                            }
117                        } else if (parser.getName().equals("error")) {
118                            throw new XMPPException(PacketParserUtils.parseStreamError(parser));
119                        }
120                    }
121                } while (eventType != XmlPullParser.END_DOCUMENT);
122            }
123            catch (Exception e) {
124                if (connection.isConnected()) {
125                    connection.notifyConnectionError(e);
126                }
127            }
128        }
129    }
130
131    /**
132     * Parse and setup the XML stream features.
133     *
134     * @param parser the XML parser, positioned at the start of a message packet.
135     * @throws Exception if an exception occurs while parsing the packet.
136     */
137    private void parseFeatures(XmlPullParser parser) throws Exception {
138        boolean done = false;
139        while (!done) {
140            int eventType = parser.next();
141
142            if (eventType == XmlPullParser.START_TAG) {
143                if (parser.getName().equals("mechanisms")) {
144                    // The server is reporting available SASL mechanisms. Store
145                    // this information
146                    // which will be used later while logging (i.e.
147                    // authenticating) into
148                    // the server
149                    connection.getSASLAuthentication().setAvailableSASLMethods(
150                            PacketParserUtils.parseMechanisms(parser));
151                } else if (parser.getName().equals("bind")) {
152                    // The server requires the client to bind a resource to the
153                    // stream
154                    connection.getSASLAuthentication().bindingRequired();
155                } else if (parser.getName().equals("session")) {
156                    // The server supports sessions
157                    connection.getSASLAuthentication().sessionsSupported();
158                } else if (parser.getName().equals("register")) {
159                    connection.getAccountManager().setSupportsAccountCreation(
160                            true);
161                }
162            } else if (eventType == XmlPullParser.END_TAG) {
163                if (parser.getName().equals("features")) {
164                    done = true;
165                }
166            }
167        }
168    }
169}
170