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}