/** * * Copyright 2014 Andriy Tsykholyas * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx.hoxt.provider; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.provider.IQProvider; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.hoxt.packet.AbstractHttpOverXmpp; import org.jivesoftware.smackx.shim.packet.HeadersExtension; import org.jivesoftware.smackx.shim.provider.HeadersProvider; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; /** * Abstract parent for Req and Resp stanza(/packet) providers. * * @author Andriy Tsykholyas * @see XEP-0332: HTTP over XMPP transport */ public abstract class AbstractHttpOverXmppProvider extends IQProvider { private static final String ELEMENT_DATA = "data"; private static final String ELEMENT_TEXT = "text"; private static final String ELEMENT_BASE_64 = "base64"; private static final String ELEMENT_CHUNKED_BASE_64 = "chunkedBase64"; private static final String ELEMENT_XML = "xml"; static final String ELEMENT_IBB = "ibb"; static final String ELEMENT_SIPUB = "sipub"; static final String ELEMENT_JINGLE = "jingle"; private static final String ATTRIBUTE_STREAM_ID = "streamId"; private static final String ATTRIBUTE_SID = "sid"; static final String ATTRIBUTE_VERSION = "version"; /** * Parses Headers and Data elements. * * @param parser parser * @param elementName name of concrete implementation of this element * @param body parent Body element * @throws IOException * @throws XmlPullParserException * @throws SmackException */ protected void parseHeadersAndData(XmlPullParser parser, String elementName, AbstractHttpOverXmpp body) throws XmlPullParserException, IOException, SmackException { boolean done = false; while (!done) { int eventType = parser.next(); if (eventType == XmlPullParser.START_TAG) { if (parser.getName().equals(HeadersExtension.ELEMENT)) { HeadersExtension headersExtension = HeadersProvider.INSTANCE.parse(parser); body.setHeaders(headersExtension); } else if (parser.getName().endsWith(ELEMENT_DATA)) { AbstractHttpOverXmpp.Data data = parseData(parser); body.setData(data); } else { throw new IllegalArgumentException("unexpected tag:" + parser.getName() + "'"); } } else if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals(elementName)) { done = true; } } } } private AbstractHttpOverXmpp.Data parseData(XmlPullParser parser) throws XmlPullParserException, IOException { NamedElement child = null; boolean done = false; while (!done) { int eventType = parser.next(); if (eventType == XmlPullParser.START_TAG) { if (parser.getName().equals(ELEMENT_TEXT)) { child = parseText(parser); } else if (parser.getName().equals(ELEMENT_BASE_64)) { child = parseBase64(parser); } else if (parser.getName().equals(ELEMENT_CHUNKED_BASE_64)) { child = parseChunkedBase64(parser); } else if (parser.getName().equals(ELEMENT_XML)) { child = parseXml(parser); } else if (parser.getName().equals(ELEMENT_IBB)) { child = parseIbb(parser); } else if (parser.getName().equals(ELEMENT_SIPUB)) { // TODO: sipub is allowed by xep-0332, but is not implemented yet throw new UnsupportedOperationException("sipub is not supported yet"); } else if (parser.getName().equals(ELEMENT_JINGLE)) { // TODO: jingle is allowed by xep-0332, but is not implemented yet throw new UnsupportedOperationException("jingle is not supported yet"); } else { // other elements are not allowed throw new IllegalArgumentException("unsupported child tag: " + parser.getName()); } } else if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals(ELEMENT_DATA)) { done = true; } } } AbstractHttpOverXmpp.Data data = new AbstractHttpOverXmpp.Data(child); return data; } private AbstractHttpOverXmpp.Text parseText(XmlPullParser parser) throws XmlPullParserException, IOException { String text = null; boolean done = false; while (!done) { int eventType = parser.next(); if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals(ELEMENT_TEXT)) { done = true; } else { throw new IllegalArgumentException("unexpected end tag of: " + parser.getName()); } } else if (eventType == XmlPullParser.TEXT) { text = parser.getText(); } else { throw new IllegalArgumentException("unexpected eventType: " + eventType); } } return new AbstractHttpOverXmpp.Text(text); } private AbstractHttpOverXmpp.Xml parseXml(XmlPullParser parser) throws XmlPullParserException, IOException { StringBuilder builder = new StringBuilder(); boolean done = false; boolean startClosed = true; while (!done) { int eventType = parser.next(); if ((eventType == XmlPullParser.END_TAG) && parser.getName().equals(ELEMENT_XML)) { done = true; } else { // just write everything else as text if (eventType == XmlPullParser.START_TAG) { if (!startClosed) { builder.append('>'); } builder.append('<'); builder.append(parser.getName()); appendXmlAttributes(parser, builder); startClosed = false; } else if (eventType == XmlPullParser.END_TAG) { if (startClosed) { builder.append("'); } else { builder.append("/>"); startClosed = true; } } else if (eventType == XmlPullParser.TEXT) { if (!startClosed) { builder.append('>'); startClosed = true; } builder.append(StringUtils.escapeForXML(parser.getText())); } else { throw new IllegalArgumentException("unexpected eventType: " + eventType); } } } return new AbstractHttpOverXmpp.Xml(builder.toString()); } private void appendXmlAttributes(XmlPullParser parser, StringBuilder builder) { // NOTE: for now we ignore namespaces int count = parser.getAttributeCount(); if (count > 0) { for (int i = 0; i < count; i++) { builder.append(' '); builder.append(parser.getAttributeName(i)); builder.append("=\""); builder.append(StringUtils.escapeForXML(parser.getAttributeValue(i))); builder.append('"'); } } } private AbstractHttpOverXmpp.Base64 parseBase64(XmlPullParser parser) throws XmlPullParserException, IOException { String text = null; boolean done = false; while (!done) { int eventType = parser.next(); if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals(ELEMENT_BASE_64)) { done = true; } else { throw new IllegalArgumentException("unexpected end tag of: " + parser.getName()); } } else if (eventType == XmlPullParser.TEXT) { text = parser.getText(); } else { throw new IllegalArgumentException("unexpected eventType: " + eventType); } } return new AbstractHttpOverXmpp.Base64(text); } private AbstractHttpOverXmpp.ChunkedBase64 parseChunkedBase64(XmlPullParser parser) throws XmlPullParserException, IOException { String streamId = parser.getAttributeValue("", ATTRIBUTE_STREAM_ID); AbstractHttpOverXmpp.ChunkedBase64 child = new AbstractHttpOverXmpp.ChunkedBase64(streamId); boolean done = false; while (!done) { int eventType = parser.next(); if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals(ELEMENT_CHUNKED_BASE_64)) { done = true; } else { throw new IllegalArgumentException("unexpected end tag: " + parser.getName()); } } else { throw new IllegalArgumentException("unexpected event type: " + eventType); } } return child; } private AbstractHttpOverXmpp.Ibb parseIbb(XmlPullParser parser) throws XmlPullParserException, IOException { String sid = parser.getAttributeValue("", ATTRIBUTE_SID); AbstractHttpOverXmpp.Ibb child = new AbstractHttpOverXmpp.Ibb(sid); boolean done = false; while (!done) { int eventType = parser.next(); if (eventType == XmlPullParser.END_TAG) { if (parser.getName().equals(ELEMENT_IBB)) { done = true; } else { throw new IllegalArgumentException("unexpected end tag: " + parser.getName()); } } else { throw new IllegalArgumentException("unexpected event type: " + eventType); } } return child; } }