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