001/** 002 * 003 * Copyright 2003-2007 Jive Software 2020-2021 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 */ 017 018package org.jivesoftware.smackx.xdata.provider; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.List; 024import java.util.logging.Logger; 025 026import javax.xml.namespace.QName; 027 028import org.jivesoftware.smack.packet.XmlEnvironment; 029import org.jivesoftware.smack.parsing.SmackParsingException; 030import org.jivesoftware.smack.provider.ExtensionElementProvider; 031import org.jivesoftware.smack.roster.packet.RosterPacket; 032import org.jivesoftware.smack.roster.provider.RosterPacketProvider; 033import org.jivesoftware.smack.xml.XmlPullParser; 034import org.jivesoftware.smack.xml.XmlPullParserException; 035 036import org.jivesoftware.smackx.formtypes.FormFieldRegistry; 037import org.jivesoftware.smackx.xdata.AbstractMultiFormField; 038import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField; 039import org.jivesoftware.smackx.xdata.BooleanFormField; 040import org.jivesoftware.smackx.xdata.FormField; 041import org.jivesoftware.smackx.xdata.FormFieldChildElement; 042import org.jivesoftware.smackx.xdata.FormFieldWithOptions; 043import org.jivesoftware.smackx.xdata.JidMultiFormField; 044import org.jivesoftware.smackx.xdata.JidSingleFormField; 045import org.jivesoftware.smackx.xdata.ListMultiFormField; 046import org.jivesoftware.smackx.xdata.ListSingleFormField; 047import org.jivesoftware.smackx.xdata.TextSingleFormField; 048import org.jivesoftware.smackx.xdata.packet.DataForm; 049import org.jivesoftware.smackx.xdatalayout.packet.DataLayout; 050import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider; 051 052import org.jxmpp.jid.Jid; 053import org.jxmpp.jid.impl.JidCreate; 054 055/** 056 * The DataFormProvider parses DataForm packets. 057 * 058 * @author Gaston Dombiak 059 */ 060public class DataFormProvider extends ExtensionElementProvider<DataForm> { 061 062 private static final Logger LOGGER = Logger.getLogger(DataFormProvider.class.getName()); 063 064 public static final DataFormProvider INSTANCE = new DataFormProvider(); 065 066 @Override 067 public DataForm parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { 068 DataForm.Type dataFormType = DataForm.Type.fromString(parser.getAttributeValue("", "type")); 069 DataForm.Builder dataForm = DataForm.builder(dataFormType); 070 071 String formType = null; 072 DataForm.ReportedData reportedData = null; 073 074 outerloop: while (true) { 075 XmlPullParser.Event eventType = parser.next(); 076 switch (eventType) { 077 case START_ELEMENT: 078 String name = parser.getName(); 079 String namespace = parser.getNamespace(); 080 XmlEnvironment elementXmlEnvironment = XmlEnvironment.from(parser, xmlEnvironment); 081 switch (name) { 082 case "instructions": 083 dataForm.addInstruction(parser.nextText()); 084 break; 085 case "title": 086 dataForm.setTitle(parser.nextText()); 087 break; 088 case "field": 089 // Note that we parse this form field without any potential reportedData. We only use reportedData 090 // to lookup form field types of fields under <item/>. 091 FormField formField = parseField(parser, elementXmlEnvironment, formType); 092 093 TextSingleFormField hiddenFormTypeField = formField.asHiddenFormTypeFieldIfPossible(); 094 if (hiddenFormTypeField != null) { 095 if (formType != null) { 096 throw new SmackParsingException("Multiple hidden form type fields"); 097 } 098 formType = hiddenFormTypeField.getValue(); 099 } 100 101 dataForm.addField(formField); 102 break; 103 case "item": 104 DataForm.Item item = parseItem(parser, elementXmlEnvironment, formType, reportedData); 105 dataForm.addItem(item); 106 break; 107 case "reported": 108 if (reportedData != null) { 109 throw new SmackParsingException("Data form with multiple <reported/> elements"); 110 } 111 reportedData = parseReported(parser, elementXmlEnvironment, formType); 112 dataForm.setReportedData(reportedData); 113 break; 114 // See XEP-133 Example 32 for a corner case where the data form contains this extension. 115 case RosterPacket.ELEMENT: 116 if (namespace.equals(RosterPacket.NAMESPACE)) { 117 dataForm.addExtensionElement(RosterPacketProvider.INSTANCE.parse(parser)); 118 } 119 break; 120 // See XEP-141 Data Forms Layout 121 case DataLayout.ELEMENT: 122 if (namespace.equals(DataLayout.NAMESPACE)) { 123 dataForm.addExtensionElement(DataLayoutProvider.parse(parser)); 124 } 125 break; 126 } 127 break; 128 case END_ELEMENT: 129 if (parser.getDepth() == initialDepth) { 130 break outerloop; 131 } 132 break; 133 default: 134 // Catch all for incomplete switch (MissingCasesInEnumSwitch) statement. 135 break; 136 } 137 } 138 return dataForm.build(); 139 } 140 141 private static FormField parseField(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType) 142 throws XmlPullParserException, IOException, SmackParsingException { 143 return parseField(parser, xmlEnvironment, formType, null); 144 } 145 146 private static FormField parseField(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType, DataForm.ReportedData reportedData) 147 throws XmlPullParserException, IOException, SmackParsingException { 148 final int initialDepth = parser.getDepth(); 149 150 final String fieldName = parser.getAttributeValue("var"); 151 final String label = parser.getAttributeValue("", "label"); 152 153 FormField.Type type = null; 154 { 155 String fieldTypeString = parser.getAttributeValue("type"); 156 if (fieldTypeString != null) { 157 type = FormField.Type.fromString(fieldTypeString); 158 } 159 } 160 161 List<FormField.Value> values = new ArrayList<>(); 162 List<FormField.Option> options = new ArrayList<>(); 163 List<FormFieldChildElement> childElements = new ArrayList<>(); 164 boolean required = false; 165 166 outerloop: while (true) { 167 XmlPullParser.TagEvent eventType = parser.nextTag(); 168 switch (eventType) { 169 case START_ELEMENT: 170 QName qname = parser.getQName(); 171 if (qname.equals(FormField.Value.QNAME)) { 172 FormField.Value value = parseValue(parser); 173 values.add(value); 174 } else if (qname.equals(FormField.Option.QNAME)) { 175 FormField.Option option = parseOption(parser); 176 options.add(option); 177 } else if (qname.equals(FormField.Required.QNAME)) { 178 required = true; 179 } else { 180 FormFieldChildElementProvider<?> provider = FormFieldChildElementProviderManager.getFormFieldChildElementProvider( 181 qname); 182 if (provider == null) { 183 LOGGER.warning("Unknown form field child element " + qname + " ignored"); 184 continue; 185 } 186 FormFieldChildElement formFieldChildElement = provider.parse(parser, 187 XmlEnvironment.from(parser, xmlEnvironment)); 188 childElements.add(formFieldChildElement); 189 } 190 break; 191 case END_ELEMENT: 192 if (parser.getDepth() == initialDepth) { 193 break outerloop; 194 } 195 break; 196 } 197 } 198 199 // Data forms of type 'result' may contain <reported/> and <item/> elements. If this is the case, then the type 200 // of the <field/>s within the <item/> elements is determined by the information found in <reported/>. See 201 // XEP-0004 ยง 3.4 and SMACK-902 202 if (type == null && reportedData != null) { 203 FormField reportedFormField = reportedData.getField(fieldName); 204 if (reportedFormField != null) { 205 type = reportedFormField.getType(); 206 } 207 } 208 209 if (type == null) { 210 // The field name 'FORM_TYPE' is magic. 211 if (fieldName.equals(FormField.FORM_TYPE)) { 212 type = FormField.Type.hidden; 213 } else { 214 // If no field type was explicitly provided, then we need to lookup the 215 // field's type in the registry. 216 type = FormFieldRegistry.lookup(formType, fieldName); 217 if (type == null) { 218 LOGGER.warning("The Field '" + fieldName + "' from FORM_TYPE '" + formType 219 + "' is not registered. Field type is unknown, assuming text-single."); 220 // As per XEP-0004, text-single is the default form field type, which we use as emergency fallback here. 221 type = FormField.Type.text_single; 222 } 223 } 224 } 225 226 FormField.Builder<?, ?> builder; 227 switch (type) { 228 case bool: 229 builder = parseBooleanFormField(fieldName, values); 230 break; 231 case fixed: 232 builder = parseSingleKindFormField(FormField.fixedBuilder(fieldName), values); 233 break; 234 case hidden: 235 builder = parseSingleKindFormField(FormField.hiddenBuilder(fieldName), values); 236 break; 237 case jid_multi: 238 JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName); 239 for (FormField.Value value : values) { 240 Jid jid = JidCreate.from(value.getValue()); 241 jidMultiBuilder.addValue(jid); 242 } 243 builder = jidMultiBuilder; 244 break; 245 case jid_single: 246 ensureAtMostSingleValue(type, values); 247 JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName); 248 if (!values.isEmpty()) { 249 CharSequence jidCharSequence = values.get(0).getValue(); 250 Jid jid = JidCreate.from(jidCharSequence); 251 jidSingleBuilder.setValue(jid); 252 } 253 builder = jidSingleBuilder; 254 break; 255 case list_multi: 256 ListMultiFormField.Builder listMultiBuilder = FormField.listMultiBuilder(fieldName); 257 addOptionsToBuilder(options, listMultiBuilder); 258 builder = parseMultiKindFormField(listMultiBuilder, values); 259 break; 260 case list_single: 261 ListSingleFormField.Builder listSingleBuilder = FormField.listSingleBuilder(fieldName); 262 addOptionsToBuilder(options, listSingleBuilder); 263 builder = parseSingleKindFormField(listSingleBuilder, values); 264 break; 265 case text_multi: 266 builder = parseMultiKindFormField(FormField.textMultiBuilder(fieldName), values); 267 break; 268 case text_private: 269 builder = parseSingleKindFormField(FormField.textPrivateBuilder(fieldName), values); 270 break; 271 case text_single: 272 builder = parseSingleKindFormField(FormField.textSingleBuilder(fieldName), values); 273 break; 274 default: 275 // Should never happen, as we cover all types in the switch/case. 276 throw new AssertionError("Unknown type " + type); 277 } 278 279 280 switch (type) { 281 case list_multi: 282 case list_single: 283 break; 284 default: 285 if (!options.isEmpty()) { 286 throw new SmackParsingException("Form fields of type " + type + " must not have options. This one had " 287 + options.size()); 288 } 289 break; 290 } 291 292 if (label != null) { 293 builder.setLabel(label); 294 } 295 builder.setRequired(required); 296 builder.addFormFieldChildElements(childElements); 297 298 return builder.build(); 299 } 300 301 private static FormField.Builder<?, ?> parseBooleanFormField(String fieldName, List<FormField.Value> values) throws SmackParsingException { 302 BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName); 303 if (values.size() == 1) { 304 String value = values.get(0).getValue().toString(); 305 builder.setValue(value); 306 } 307 return builder; 308 } 309 310 private static AbstractSingleStringValueFormField.Builder<?, ?> parseSingleKindFormField( 311 AbstractSingleStringValueFormField.Builder<?, ?> builder, List<FormField.Value> values) 312 throws SmackParsingException { 313 ensureAtMostSingleValue(builder.getType(), values); 314 if (values.size() == 1) { 315 String value = values.get(0).getValue().toString(); 316 builder.setValue(value); 317 } 318 return builder; 319 } 320 321 private static AbstractMultiFormField.Builder<?, ?> parseMultiKindFormField(AbstractMultiFormField.Builder<?, ?> builder, 322 List<FormField.Value> values) { 323 for (FormField.Value value : values) { 324 builder.addValue(value.getValue()); 325 } 326 return builder; 327 } 328 329 private static DataForm.Item parseItem(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType, 330 DataForm.ReportedData reportedData) 331 throws XmlPullParserException, IOException, SmackParsingException { 332 final int initialDepth = parser.getDepth(); 333 List<FormField> fields = new ArrayList<>(); 334 outerloop: while (true) { 335 XmlPullParser.TagEvent eventType = parser.nextTag(); 336 switch (eventType) { 337 case START_ELEMENT: 338 String name = parser.getName(); 339 switch (name) { 340 case "field": 341 FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType, 342 reportedData); 343 fields.add(field); 344 break; 345 } 346 break; 347 case END_ELEMENT: 348 if (parser.getDepth() == initialDepth) { 349 break outerloop; 350 } 351 break; 352 } 353 } 354 return new DataForm.Item(fields); 355 } 356 357 private static DataForm.ReportedData parseReported(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType) 358 throws XmlPullParserException, IOException, SmackParsingException { 359 final int initialDepth = parser.getDepth(); 360 List<FormField> fields = new ArrayList<>(); 361 outerloop: while (true) { 362 XmlPullParser.TagEvent eventType = parser.nextTag(); 363 switch (eventType) { 364 case START_ELEMENT: 365 String name = parser.getName(); 366 switch (name) { 367 case "field": 368 FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType); 369 fields.add(field); 370 break; 371 } 372 break; 373 case END_ELEMENT: 374 if (parser.getDepth() == initialDepth) { 375 break outerloop; 376 } 377 break; 378 } 379 } 380 return new DataForm.ReportedData(fields); 381 } 382 383 public static FormField.Value parseValue(XmlPullParser parser) throws IOException, XmlPullParserException { 384 String value = parser.nextText(); 385 return new FormField.Value(value); 386 } 387 388 public static FormField.Option parseOption(XmlPullParser parser) throws IOException, XmlPullParserException { 389 int initialDepth = parser.getDepth(); 390 FormField.Option option = null; 391 String label = parser.getAttributeValue("", "label"); 392 outerloop: while (true) { 393 XmlPullParser.TagEvent eventType = parser.nextTag(); 394 switch (eventType) { 395 case START_ELEMENT: 396 String name = parser.getName(); 397 switch (name) { 398 case "value": 399 option = new FormField.Option(label, parser.nextText()); 400 break; 401 } 402 break; 403 case END_ELEMENT: 404 if (parser.getDepth() == initialDepth) { 405 break outerloop; 406 } 407 break; 408 } 409 } 410 return option; 411 } 412 413 private static void ensureAtMostSingleValue(FormField.Type type, List<FormField.Value> values) throws SmackParsingException { 414 if (values.size() > 1) { 415 throw new SmackParsingException(type + " fields can have at most one value, this one had " + values.size()); 416 } 417 } 418 419 private static void addOptionsToBuilder(Collection<FormField.Option> options, FormFieldWithOptions.Builder<?> builder) { 420 for (FormField.Option option : options) { 421 builder.addOption(option); 422 } 423 } 424}