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