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