From 4643d07ef410c91f0fa31333541d7419876b6b0c Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 18 Jul 2021 17:20:50 +0200 Subject: [PATCH 1/3] [xdata] Add missing ensureAtMostSingleValue() to parseBooleanFormField --- .../org/jivesoftware/smackx/xdata/provider/DataFormProvider.java | 1 + 1 file changed, 1 insertion(+) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java index 6465efacb..d858ee666 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java @@ -300,6 +300,7 @@ public class DataFormProvider extends ExtensionElementProvider { private static FormField.Builder parseBooleanFormField(String fieldName, List values) throws SmackParsingException { BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName); + ensureAtMostSingleValue(builder.getType(), values); if (values.size() == 1) { String value = values.get(0).getValue().toString(); builder.setValue(value); From e626580f682bc1b924014fbb09adefffc89f33c6 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 18 Jul 2021 17:21:50 +0200 Subject: [PATCH 2/3] [xdata] Safe the raw character data of form field values Related to SMACK-909. --- .../smack/util/CollectionUtil.java | 9 +++- .../jivesoftware/smack/util/StringUtils.java | 12 ++++- .../smackx/xdata/AbstractMultiFormField.java | 21 ++++---- .../AbstractSingleStringValueFormField.java | 18 ++++--- .../smackx/xdata/BooleanFormField.java | 17 +++++-- .../jivesoftware/smackx/xdata/FormField.java | 36 +++++++++++--- .../smackx/xdata/JidMultiFormField.java | 39 ++++++++++++--- .../smackx/xdata/JidSingleFormField.java | 14 +++++- .../smackx/xdata/SingleValueFormField.java | 48 +++++++++++++++---- .../smackx/xdata/form/FormReader.java | 7 ++- .../xdata/provider/DataFormProvider.java | 13 ++--- 11 files changed, 178 insertions(+), 56 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java index b35dfb4a0..24b73b8b0 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015-2020 Florian Schmaus + * Copyright 2015-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,4 +82,11 @@ public class CollectionUtil { } return new HashSet<>(collection); } + + public static List emptyOrSingletonListFrom(T element) { + if (element == null) { + return Collections.emptyList(); + } + return Collections.singletonList(element); + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index 77bc402c3..5ebe4c116 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2016-2020 Florian Schmaus. + * Copyright 2003-2007 Jive Software, 2016-2021 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ package org.jivesoftware.smack.util; import java.io.IOException; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; @@ -605,4 +606,13 @@ public class StringUtils { String[] lines = input.split(PORTABLE_NEWLINE_REGEX); return Arrays.asList(lines); } + + public static List toStrings(Collection charSequences) { + List res = new ArrayList<>(charSequences.size()); + for (CharSequence cs : charSequences) { + String string = cs.toString(); + res.add(string); + } + return res; + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java index 31c778a87..16ccc3c7f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus + * Copyright 2020-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import org.jxmpp.util.XmppDateTime; public class AbstractMultiFormField extends FormField { - private final List values; + private final List values; protected AbstractMultiFormField(Builder builder) { super(builder); @@ -35,19 +35,18 @@ public class AbstractMultiFormField extends FormField { } @Override - public final List getValues() { + public final List getRawValues() { return values; } - - public abstract static class Builder> + public abstract static class Builder> extends FormField.Builder { - private List values; + private List values; protected Builder(AbstractMultiFormField formField) { super(formField); - values = CollectionUtil.newListWith(formField.getValues()); + values = CollectionUtil.newListWith(formField.getRawValues()); } protected Builder(String fieldName, FormField.Type type) { @@ -68,9 +67,13 @@ public class AbstractMultiFormField extends FormField { public abstract B addValue(CharSequence value); public B addValueVerbatim(CharSequence value) { + return addValueVerbatim(new Value(value)); + } + + public B addValueVerbatim(Value value) { ensureValuesAreInitialized(); - values.add(value.toString()); + values.add(value); return getThis(); } @@ -83,7 +86,7 @@ public class AbstractMultiFormField extends FormField { ensureValuesAreInitialized(); for (CharSequence value : values) { - this.values.add(value.toString()); + addValueVerbatim(value); } return getThis(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java index 27fca4cde..9e57a294e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus. + * Copyright 2020-2021 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,8 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField { return Integer.valueOf(value); } - public abstract static class Builder> extends FormField.Builder { + public abstract static class Builder> + extends SingleValueFormField.Builder { private String value; @@ -73,19 +74,24 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField { return setValue(value); } + public B setValue(Value value) { + this.value = value.getValue().toString(); + this.rawValue = value; + return getThis(); + } + public B setValue(CharSequence value) { this.value = value.toString(); + rawValue = new Value(this.value); return getThis(); } public B setValue(Enum value) { - this.value = value.toString(); - return getThis(); + return setValue(value.toString()); } public B setValue(int value) { - this.value = Integer.toString(value); - return getThis(); + return setValue(Integer.toString(value)); } public B setValue(URL value) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java index 2b036b162..85e351649 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/BooleanFormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus. + * Copyright 2020-2021 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,7 @@ public class BooleanFormField extends SingleValueFormField { return new Builder(this); } - public static final class Builder extends FormField.Builder { + public static final class Builder extends SingleValueFormField.Builder { private Boolean value; private Builder(BooleanFormField booleanFormField) { @@ -57,6 +57,7 @@ public class BooleanFormField extends SingleValueFormField { @Override protected void resetInternal() { + super.resetInternal(); value = null; } @@ -70,15 +71,21 @@ public class BooleanFormField extends SingleValueFormField { @Deprecated // TODO: Remove in Smack 4.6. public Builder addValue(CharSequence value) { - return setValue(value); + return setValue(new Value(value)); } public Builder setValue(CharSequence value) { - boolean valueBoolean = ParserUtils.parseXmlBoolean(value.toString()); - return setValue(valueBoolean); + return setValue(new Value(value)); + } + + public Builder setValue(Value value) { + this.value = ParserUtils.parseXmlBoolean(value.getValue().toString()); + rawValue = value; + return getThis(); } public Builder setValue(boolean value) { + rawValue = new Value(Boolean.toString(value)); this.value = value; return this; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java index 4bc28a7ef..e87d2e5c6 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2019-2020 Florian Schmaus. + * Copyright 2003-2007 Jive Software, 2019-2021 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -269,7 +269,25 @@ public abstract class FormField implements FullyQualifiedElement { * * @return a List of the default values or answered values of the question. */ - public abstract List getValues(); + public List getValues() { + return getRawValueCharSequences(); + } + + public abstract List getRawValues(); + + private transient List rawValueCharSequences; + + public final List getRawValueCharSequences() { + if (rawValueCharSequences == null) { + List rawValues = getRawValues(); + rawValueCharSequences = new ArrayList<>(rawValues.size()); + for (Value value : rawValues) { + rawValueCharSequences.add(value.value); + } + } + + return rawValueCharSequences; + } public boolean hasValueSet() { List values = getValues(); @@ -383,12 +401,15 @@ public abstract class FormField implements FullyQualifiedElement { protected transient List extraXmlChildElements; + /** + * Populate @{link {@link #extraXmlChildElements}}. Note that this method may be overridden by subclasses. + */ protected void populateExtraXmlChildElements() { - List values = getValues(); + List values = getRawValues(); + // Note that we need to create a new ArrayList here, since subclasses may add to it by overriding + // populateExtraXmlChildElements. extraXmlChildElements = new ArrayList<>(values.size()); - for (CharSequence value : values) { - extraXmlChildElements.add(new Value(value)); - } + extraXmlChildElements.addAll(values); } @Override @@ -412,7 +433,8 @@ public abstract class FormField implements FullyQualifiedElement { populateExtraXmlChildElements(); } - if (formFieldChildElements.isEmpty() && extraXmlChildElements == null) { + if (formFieldChildElements.isEmpty() + && (extraXmlChildElements == null || extraXmlChildElements.isEmpty())) { buf.closeEmptyElement(); } else { buf.rightAngleBracket(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java index e1e93c0d3..e720e816d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidMultiFormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus. + * Copyright 2020-2021 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,14 +23,19 @@ import java.util.List; import org.jivesoftware.smack.util.CollectionUtil; import org.jxmpp.jid.Jid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; -public class JidMultiFormField extends FormField { +public final class JidMultiFormField extends FormField { private final List values; + private final List rawValues; + protected JidMultiFormField(Builder builder) { super(builder); values = CollectionUtil.cloneAndSeal(builder.values); + rawValues = CollectionUtil.cloneAndSeal(builder.rawValues); } @Override @@ -38,6 +43,11 @@ public class JidMultiFormField extends FormField { return values; } + @Override + public List getRawValues() { + return rawValues; + } + public Builder asBuilder() { return new Builder(this); } @@ -45,6 +55,8 @@ public class JidMultiFormField extends FormField { public static final class Builder extends FormField.Builder { private List values; + private List rawValues; + private Builder(JidMultiFormField jidMultiFormField) { super(jidMultiFormField); values = CollectionUtil.newListWith(jidMultiFormField.getValues()); @@ -57,25 +69,40 @@ public class JidMultiFormField extends FormField { private void ensureValuesAreInitialized() { if (values == null) { values = new ArrayList<>(); + rawValues = new ArrayList<>(); } } @Override protected void resetInternal() { values = null; + rawValues = null; } public Builder addValue(Jid jid) { - ensureValuesAreInitialized(); + Value value = new Value(jid); + ensureValuesAreInitialized(); values.add(jid); + rawValues.add(value); + + return getThis(); + } + + public Builder addValue(Value value) throws XmppStringprepException { + Jid jid = JidCreate.from(value.getValue()); + + ensureValuesAreInitialized(); + values.add(jid); + rawValues.add(value); + return this; } public Builder addValues(Collection jids) { - ensureValuesAreInitialized(); - - values.addAll(jids); + for (Jid jid : jids) { + addValue(jid); + } return this; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java index d1b0237de..07e2e158b 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus. + * Copyright 2020-2021 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ package org.jivesoftware.smackx.xdata; import org.jxmpp.jid.Jid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; public class JidSingleFormField extends SingleValueFormField { @@ -36,7 +38,7 @@ public class JidSingleFormField extends SingleValueFormField { return new Builder(this); } - public static final class Builder extends FormField.Builder { + public static final class Builder extends SingleValueFormField.Builder { private Jid value; private Builder(JidSingleFormField jidSingleFormField) { @@ -50,11 +52,19 @@ public class JidSingleFormField extends SingleValueFormField { @Override protected void resetInternal() { + super.resetInternal(); value = null; } public Builder setValue(Jid value) { this.value = value; + this.rawValue = new Value(value); + return getThis(); + } + + public Builder setValue(Value value) throws XmppStringprepException { + this.value = JidCreate.from(value.getValue()); + this.rawValue = value; return this; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java index 2c039f1ab..d4b389a96 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus. + * Copyright 2020-2021 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,30 +19,62 @@ package org.jivesoftware.smackx.xdata; import java.util.Collections; import java.util.List; +import org.jivesoftware.smack.util.CollectionUtil; + public abstract class SingleValueFormField extends FormField { + private final Value rawValue; + protected SingleValueFormField(Builder builder) { super(builder); + rawValue = builder.rawValue; } @Override public final List getValues() { CharSequence value = getValue(); - if (value == null) { - return Collections.emptyList(); - } - return Collections.singletonList(value); + return CollectionUtil.emptyOrSingletonListFrom(value); } public abstract CharSequence getValue(); + public final Value getRawValue() { + return rawValue; + } + + @Override + public final List getRawValues() { + Value rawValue = getRawValue(); + return CollectionUtil.emptyOrSingletonListFrom(rawValue); + } + @Override protected void populateExtraXmlChildElements() { - CharSequence value = getValue(); - if (value == null) { + if (rawValue == null) { return; } - extraXmlChildElements = Collections.singletonList(new Value(value)); + extraXmlChildElements = Collections.singletonList(rawValue); + } + + public abstract static class Builder> + extends FormField.Builder { + + protected Builder(String fieldName, Type type) { + super(fieldName, type); + } + + protected Builder(SingleValueFormField formField) { + super(formField); + rawValue = formField.getRawValue(); + } + + protected Value rawValue; + + @Override + protected void resetInternal() { + rawValue = null; + }; + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java index 493abd25a..043e9a1fe 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus + * Copyright 2020-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import org.jivesoftware.smack.util.StringUtils; + import org.jivesoftware.smackx.xdata.AbstractMultiFormField; import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField; import org.jivesoftware.smackx.xdata.BooleanFormField; @@ -54,7 +56,8 @@ public interface FormReader { return Collections.emptyList(); } AbstractMultiFormField multiFormField = formField.ifPossibleAs(AbstractMultiFormField.class); - return multiFormField.getValues(); + List charSequences = multiFormField.getValues(); + return StringUtils.toStrings(charSequences); } default Boolean readBoolean(String fieldName) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java index d858ee666..9fc8db616 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java @@ -49,9 +49,6 @@ import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdatalayout.packet.DataLayout; import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider; -import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; - /** * The DataFormProvider parses DataForm packets. * @@ -237,8 +234,7 @@ public class DataFormProvider extends ExtensionElementProvider { case jid_multi: JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName); for (FormField.Value value : values) { - Jid jid = JidCreate.from(value.getValue()); - jidMultiBuilder.addValue(jid); + jidMultiBuilder.addValue(value); } builder = jidMultiBuilder; break; @@ -246,9 +242,8 @@ public class DataFormProvider extends ExtensionElementProvider { ensureAtMostSingleValue(type, values); JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName); if (!values.isEmpty()) { - CharSequence jidCharSequence = values.get(0).getValue(); - Jid jid = JidCreate.from(jidCharSequence); - jidSingleBuilder.setValue(jid); + FormField.Value value = values.get(0); + jidSingleBuilder.setValue(value); } builder = jidSingleBuilder; break; @@ -302,7 +297,7 @@ public class DataFormProvider extends ExtensionElementProvider { BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName); ensureAtMostSingleValue(builder.getType(), values); if (values.size() == 1) { - String value = values.get(0).getValue().toString(); + FormField.Value value = values.get(0); builder.setValue(value); } return builder; From 8f760eaeb3190ee88d66e153592f91e46b96b0a8 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 18 Jul 2021 17:22:06 +0200 Subject: [PATCH 3/3] [caps] Use the raw character data of form fields when caclulating the hash Fixes SMACK-909. --- .../java/org/jivesoftware/smackx/caps/EntityCapsManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java index 523213513..ff3eb0c47 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/caps/EntityCapsManager.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2009 Jonas Ådahl, 2011-2020 Florian Schmaus + * Copyright © 2009 Jonas Ådahl, 2011-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -700,7 +700,7 @@ public final class EntityCapsManager extends Manager { for (FormField f : fs) { sb.append(f.getFieldName()); sb.append('<'); - formFieldValuesToCaps(f.getValues(), sb); + formFieldValuesToCaps(f.getRawValueCharSequences(), sb); } }