001/**
002 *
003 * Copyright 2014 Andriy Tsykholyas, 2015 Florian Schmaus
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 */
017package org.jivesoftware.smackx.hoxt.provider;
018
019import java.io.IOException;
020
021import org.jivesoftware.smack.packet.NamedElement;
022import org.jivesoftware.smack.provider.IQProvider;
023import org.jivesoftware.smack.util.StringUtils;
024
025import org.jivesoftware.smackx.hoxt.packet.AbstractHttpOverXmpp;
026import org.jivesoftware.smackx.shim.packet.HeadersExtension;
027import org.jivesoftware.smackx.shim.provider.HeadersProvider;
028
029import org.xmlpull.v1.XmlPullParser;
030import org.xmlpull.v1.XmlPullParserException;
031
032/**
033 * Abstract parent for Req and Resp stanza providers.
034 *
035 * @author Andriy Tsykholyas
036 * @see <a href="http://xmpp.org/extensions/xep-0332.html">XEP-0332: HTTP over XMPP transport</a>
037 */
038public abstract class AbstractHttpOverXmppProvider<H extends AbstractHttpOverXmpp> extends IQProvider<H> {
039
040    private static final String ELEMENT_DATA = "data";
041    private static final String ELEMENT_TEXT = "text";
042    private static final String ELEMENT_BASE_64 = "base64";
043    private static final String ELEMENT_CHUNKED_BASE_64 = "chunkedBase64";
044    private static final String ELEMENT_XML = "xml";
045    static final String ELEMENT_IBB = "ibb";
046    static final String ELEMENT_SIPUB = "sipub";
047    static final String ELEMENT_JINGLE = "jingle";
048
049    private static final String ATTRIBUTE_STREAM_ID = "streamId";
050    private static final String ATTRIBUTE_SID = "sid";
051    static final String ATTRIBUTE_VERSION = "version";
052
053    /**
054     * Parses HeadersExtension element if any.
055     *
056     * @param parser parser
057     * @return HeadersExtension or null if no headers
058     * @throws Exception
059     */
060    protected HeadersExtension parseHeaders(XmlPullParser parser) throws Exception {
061        HeadersExtension headersExtension = null;
062        /* We are either at start of headers, start of data or end of req/res */
063        if (parser.next() == XmlPullParser.START_TAG && parser.getName().equals(HeadersExtension.ELEMENT)) {
064            headersExtension = HeadersProvider.INSTANCE.parse(parser);
065            parser.next();
066        }
067
068        return headersExtension;
069    }
070
071    /**
072     * Parses Data element if any.
073     *
074     * @param parser parser
075     * @return Data or null if no data
076     *
077     * @throws XmlPullParserException
078     * @throws IOException
079     */
080    protected AbstractHttpOverXmpp.Data parseData(XmlPullParser parser) throws XmlPullParserException, IOException {
081        NamedElement child = null;
082        boolean done = false;
083        AbstractHttpOverXmpp.Data data = null;
084        /* We are either at start of data or end of req/res */
085        if (parser.getEventType() == XmlPullParser.START_TAG) {
086            while (!done) {
087                int eventType = parser.next();
088
089                if (eventType == XmlPullParser.START_TAG) {
090                    switch (parser.getName()) {
091                    case ELEMENT_TEXT:
092                        child = parseText(parser);
093                        break;
094                    case ELEMENT_BASE_64:
095                        child = parseBase64(parser);
096                        break;
097                    case ELEMENT_CHUNKED_BASE_64:
098                        child = parseChunkedBase64(parser);
099                        break;
100                    case ELEMENT_XML:
101                        child = parseXml(parser);
102                        break;
103                    case ELEMENT_IBB:
104                        child = parseIbb(parser);
105                        break;
106                    case ELEMENT_SIPUB:
107                        // TODO: sipub is allowed by xep-0332, but is not
108                        // implemented yet
109                        throw new UnsupportedOperationException("sipub is not supported yet");
110                    case ELEMENT_JINGLE:
111                        // TODO: jingle is allowed by xep-0332, but is not
112                        // implemented yet
113                        throw new UnsupportedOperationException("jingle is not supported yet");
114                    default:
115                        // other elements are not allowed
116                        throw new IllegalArgumentException("unsupported child tag: " + parser.getName());
117                    }
118                } else if (eventType == XmlPullParser.END_TAG) {
119                    if (parser.getName().equals(ELEMENT_DATA)) {
120                        done = true;
121                    }
122                }
123            }
124            data = new AbstractHttpOverXmpp.Data(child);
125        }
126        return data;
127    }
128
129    private static AbstractHttpOverXmpp.Text parseText(XmlPullParser parser) throws XmlPullParserException, IOException {
130        String text = null;
131        boolean done = false;
132
133        while (!done) {
134            int eventType = parser.next();
135
136            if (eventType == XmlPullParser.END_TAG) {
137                if (parser.getName().equals(ELEMENT_TEXT)) {
138                    done = true;
139                } else {
140                    throw new IllegalArgumentException("unexpected end tag of: " + parser.getName());
141                }
142            } else if (eventType == XmlPullParser.TEXT) {
143                text = parser.getText();
144            } else {
145                throw new IllegalArgumentException("unexpected eventType: " + eventType);
146            }
147        }
148
149        return new AbstractHttpOverXmpp.Text(text);
150    }
151
152    private static AbstractHttpOverXmpp.Xml parseXml(XmlPullParser parser)
153            throws XmlPullParserException, IOException {
154        StringBuilder builder = new StringBuilder();
155        boolean done = false;
156        boolean startClosed = true;
157
158        while (!done) {
159            int eventType = parser.next();
160
161            if ((eventType == XmlPullParser.END_TAG) && parser.getName().equals(ELEMENT_XML)) {
162                done = true;
163            } else { // just write everything else as text
164
165                if (eventType == XmlPullParser.START_TAG) {
166
167                    if (!startClosed) {
168                        builder.append('>');
169                    }
170
171                    builder.append('<');
172                    builder.append(parser.getName());
173                    appendXmlAttributes(parser, builder);
174                    startClosed = false;
175                } else if (eventType == XmlPullParser.END_TAG) {
176
177                    if (startClosed) {
178                        builder.append("</");
179                        builder.append(parser.getName());
180                        builder.append('>');
181                    } else {
182                        builder.append("/>");
183                        startClosed = true;
184                    }
185                } else if (eventType == XmlPullParser.TEXT) {
186
187                    if (!startClosed) {
188                        builder.append('>');
189                        startClosed = true;
190                    }
191                    builder.append(StringUtils.escapeForXmlText(parser.getText()));
192                } else {
193                    throw new IllegalArgumentException("unexpected eventType: " + eventType);
194                }
195            }
196        }
197
198        return new AbstractHttpOverXmpp.Xml(builder.toString());
199    }
200
201    private static void appendXmlAttributes(XmlPullParser parser, StringBuilder builder) {
202        // NOTE: for now we ignore namespaces
203        int count = parser.getAttributeCount();
204
205        if (count > 0) {
206
207            for (int i = 0; i < count; i++) {
208                builder.append(' ');
209                builder.append(parser.getAttributeName(i));
210                builder.append("=\"");
211                builder.append(StringUtils.escapeForXml(parser.getAttributeValue(i)));
212                builder.append('"');
213            }
214        }
215    }
216
217    private static AbstractHttpOverXmpp.Base64 parseBase64(XmlPullParser parser) throws XmlPullParserException,
218                    IOException {
219        String text = null;
220        boolean done = false;
221
222        while (!done) {
223            int eventType = parser.next();
224
225            if (eventType == XmlPullParser.END_TAG) {
226
227                if (parser.getName().equals(ELEMENT_BASE_64)) {
228                    done = true;
229                } else {
230                    throw new IllegalArgumentException("unexpected end tag of: " + parser.getName());
231                }
232            } else if (eventType == XmlPullParser.TEXT) {
233                text = parser.getText();
234            } else {
235                throw new IllegalArgumentException("unexpected eventType: " + eventType);
236            }
237        }
238
239        return new AbstractHttpOverXmpp.Base64(text);
240    }
241
242    private static AbstractHttpOverXmpp.ChunkedBase64 parseChunkedBase64(XmlPullParser parser)
243                    throws XmlPullParserException, IOException {
244        String streamId = parser.getAttributeValue("", ATTRIBUTE_STREAM_ID);
245        AbstractHttpOverXmpp.ChunkedBase64 child = new AbstractHttpOverXmpp.ChunkedBase64(streamId);
246        boolean done = false;
247
248        while (!done) {
249            int eventType = parser.next();
250
251            if (eventType == XmlPullParser.END_TAG) {
252                if (parser.getName().equals(ELEMENT_CHUNKED_BASE_64)) {
253                    done = true;
254                } else {
255                    throw new IllegalArgumentException("unexpected end tag: " + parser.getName());
256                }
257            } else {
258                throw new IllegalArgumentException("unexpected event type: " + eventType);
259            }
260        }
261        return child;
262    }
263
264    private static AbstractHttpOverXmpp.Ibb parseIbb(XmlPullParser parser) throws XmlPullParserException, IOException {
265        String sid = parser.getAttributeValue("", ATTRIBUTE_SID);
266        AbstractHttpOverXmpp.Ibb child = new AbstractHttpOverXmpp.Ibb(sid);
267        boolean done = false;
268
269        while (!done) {
270            int eventType = parser.next();
271
272            if (eventType == XmlPullParser.END_TAG) {
273                if (parser.getName().equals(ELEMENT_IBB)) {
274                    done = true;
275                } else {
276                    throw new IllegalArgumentException("unexpected end tag: " + parser.getName());
277                }
278            } else {
279                throw new IllegalArgumentException("unexpected event type: " + eventType);
280            }
281        }
282        return child;
283    }
284}