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            String rawValue = value.getValue().toString();
321            builder.addValue(rawValue);
322        }
323        return builder;
324    }
325
326    private static DataForm.Item parseItem(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType,
327                    DataForm.ReportedData reportedData)
328                    throws XmlPullParserException, IOException, SmackParsingException {
329        final int initialDepth = parser.getDepth();
330        List<FormField> fields = new ArrayList<>();
331        outerloop: while (true) {
332            XmlPullParser.TagEvent eventType = parser.nextTag();
333            switch (eventType) {
334            case START_ELEMENT:
335                String name = parser.getName();
336                switch (name) {
337                case "field":
338                    FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType,
339                                    reportedData);
340                    fields.add(field);
341                    break;
342                }
343                break;
344            case END_ELEMENT:
345                if (parser.getDepth() == initialDepth) {
346                    break outerloop;
347                }
348                break;
349            }
350        }
351        return new DataForm.Item(fields);
352    }
353
354    private static DataForm.ReportedData parseReported(XmlPullParser parser, XmlEnvironment xmlEnvironment, String formType)
355                    throws XmlPullParserException, IOException, SmackParsingException {
356        final int initialDepth = parser.getDepth();
357        List<FormField> fields = new ArrayList<>();
358        outerloop: while (true) {
359            XmlPullParser.TagEvent eventType = parser.nextTag();
360            switch (eventType) {
361            case START_ELEMENT:
362                String name = parser.getName();
363                switch (name) {
364                case "field":
365                    FormField field = parseField(parser, XmlEnvironment.from(parser, xmlEnvironment), formType);
366                    fields.add(field);
367                    break;
368                }
369                break;
370            case END_ELEMENT:
371                if (parser.getDepth() == initialDepth) {
372                    break outerloop;
373                }
374                break;
375            }
376        }
377        return new DataForm.ReportedData(fields);
378    }
379
380    public static FormField.Value parseValue(XmlPullParser parser) throws IOException, XmlPullParserException {
381        String value = parser.nextText();
382        return new FormField.Value(value);
383    }
384
385    public static FormField.Option parseOption(XmlPullParser parser) throws IOException, XmlPullParserException {
386        int initialDepth = parser.getDepth();
387        FormField.Option option = null;
388        String label = parser.getAttributeValue("", "label");
389        outerloop: while (true) {
390            XmlPullParser.TagEvent eventType = parser.nextTag();
391            switch (eventType) {
392            case START_ELEMENT:
393                String name = parser.getName();
394                switch (name) {
395                case "value":
396                    option = new FormField.Option(label, parser.nextText());
397                    break;
398                }
399                break;
400            case END_ELEMENT:
401                if (parser.getDepth() == initialDepth) {
402                    break outerloop;
403                }
404                break;
405            }
406        }
407        return option;
408    }
409
410    private static void ensureAtMostSingleValue(FormField.Type type, List<FormField.Value> values) throws SmackParsingException {
411        if (values.size() > 1) {
412            throw new SmackParsingException(type + " fields can have at most one value, this one had " + values.size());
413        }
414    }
415
416    private static void addOptionsToBuilder(Collection<FormField.Option> options, FormFieldWithOptions.Builder<?> builder) {
417        for (FormField.Option option : options) {
418            builder.addOption(option);
419        }
420    }
421}