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