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}