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