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