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