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