[xdata] Safe the raw character data of form field values

Related to SMACK-909.
This commit is contained in:
Florian Schmaus 2021-07-18 17:21:50 +02:00
parent 4643d07ef4
commit e626580f68
11 changed files with 178 additions and 56 deletions

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2020 Florian Schmaus * Copyright 2015-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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); return new HashSet<>(collection);
} }
public static <T> List<T> emptyOrSingletonListFrom(T element) {
if (element == null) {
return Collections.emptyList();
}
return Collections.singletonList(element);
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.io.IOException;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
@ -605,4 +606,13 @@ public class StringUtils {
String[] lines = input.split(PORTABLE_NEWLINE_REGEX); String[] lines = input.split(PORTABLE_NEWLINE_REGEX);
return Arrays.asList(lines); return Arrays.asList(lines);
} }
public static List<String> toStrings(Collection<? extends CharSequence> charSequences) {
List<String> res = new ArrayList<>(charSequences.size());
for (CharSequence cs : charSequences) {
String string = cs.toString();
res.add(string);
}
return res;
}
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus * Copyright 2020-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 { public class AbstractMultiFormField extends FormField {
private final List<String> values; private final List<Value> values;
protected AbstractMultiFormField(Builder<?, ?> builder) { protected AbstractMultiFormField(Builder<?, ?> builder) {
super(builder); super(builder);
@ -35,19 +35,18 @@ public class AbstractMultiFormField extends FormField {
} }
@Override @Override
public final List<String> getValues() { public final List<Value> getRawValues() {
return values; return values;
} }
public abstract static class Builder<F extends AbstractMultiFormField, B extends FormField.Builder<F, B>>
public abstract static class Builder<F extends FormField, B extends FormField.Builder<F, B>>
extends FormField.Builder<F, B> { extends FormField.Builder<F, B> {
private List<String> values; private List<Value> values;
protected Builder(AbstractMultiFormField formField) { protected Builder(AbstractMultiFormField formField) {
super(formField); super(formField);
values = CollectionUtil.newListWith(formField.getValues()); values = CollectionUtil.newListWith(formField.getRawValues());
} }
protected Builder(String fieldName, FormField.Type type) { protected Builder(String fieldName, FormField.Type type) {
@ -68,9 +67,13 @@ public class AbstractMultiFormField extends FormField {
public abstract B addValue(CharSequence value); public abstract B addValue(CharSequence value);
public B addValueVerbatim(CharSequence value) { public B addValueVerbatim(CharSequence value) {
return addValueVerbatim(new Value(value));
}
public B addValueVerbatim(Value value) {
ensureValuesAreInitialized(); ensureValuesAreInitialized();
values.add(value.toString()); values.add(value);
return getThis(); return getThis();
} }
@ -83,7 +86,7 @@ public class AbstractMultiFormField extends FormField {
ensureValuesAreInitialized(); ensureValuesAreInitialized();
for (CharSequence value : values) { for (CharSequence value : values) {
this.values.add(value.toString()); addValueVerbatim(value);
} }
return getThis(); return getThis();

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus. * Copyright 2020-2021 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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); return Integer.valueOf(value);
} }
public abstract static class Builder<F extends FormField, B extends FormField.Builder<F, B>> extends FormField.Builder<F, B> { public abstract static class Builder<F extends SingleValueFormField, B extends SingleValueFormField.Builder<F, B>>
extends SingleValueFormField.Builder<F, B> {
private String value; private String value;
@ -73,19 +74,24 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField {
return setValue(value); return setValue(value);
} }
public B setValue(Value value) {
this.value = value.getValue().toString();
this.rawValue = value;
return getThis();
}
public B setValue(CharSequence value) { public B setValue(CharSequence value) {
this.value = value.toString(); this.value = value.toString();
rawValue = new Value(this.value);
return getThis(); return getThis();
} }
public B setValue(Enum<?> value) { public B setValue(Enum<?> value) {
this.value = value.toString(); return setValue(value.toString());
return getThis();
} }
public B setValue(int value) { public B setValue(int value) {
this.value = Integer.toString(value); return setValue(Integer.toString(value));
return getThis();
} }
public B setValue(URL value) { public B setValue(URL value) {

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus. * Copyright 2020-2021 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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); return new Builder(this);
} }
public static final class Builder extends FormField.Builder<BooleanFormField, BooleanFormField.Builder> { public static final class Builder extends SingleValueFormField.Builder<BooleanFormField, BooleanFormField.Builder> {
private Boolean value; private Boolean value;
private Builder(BooleanFormField booleanFormField) { private Builder(BooleanFormField booleanFormField) {
@ -57,6 +57,7 @@ public class BooleanFormField extends SingleValueFormField {
@Override @Override
protected void resetInternal() { protected void resetInternal() {
super.resetInternal();
value = null; value = null;
} }
@ -70,15 +71,21 @@ public class BooleanFormField extends SingleValueFormField {
@Deprecated @Deprecated
// TODO: Remove in Smack 4.6. // TODO: Remove in Smack 4.6.
public Builder addValue(CharSequence value) { public Builder addValue(CharSequence value) {
return setValue(value); return setValue(new Value(value));
} }
public Builder setValue(CharSequence value) { public Builder setValue(CharSequence value) {
boolean valueBoolean = ParserUtils.parseXmlBoolean(value.toString()); return setValue(new Value(value));
return setValue(valueBoolean); }
public Builder setValue(Value value) {
this.value = ParserUtils.parseXmlBoolean(value.getValue().toString());
rawValue = value;
return getThis();
} }
public Builder setValue(boolean value) { public Builder setValue(boolean value) {
rawValue = new Value(Boolean.toString(value));
this.value = value; this.value = value;
return this; return this;
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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. * @return a List of the default values or answered values of the question.
*/ */
public abstract List<? extends CharSequence> getValues(); public List<? extends CharSequence> getValues() {
return getRawValueCharSequences();
}
public abstract List<Value> getRawValues();
private transient List<CharSequence> rawValueCharSequences;
public final List<CharSequence> getRawValueCharSequences() {
if (rawValueCharSequences == null) {
List<Value> rawValues = getRawValues();
rawValueCharSequences = new ArrayList<>(rawValues.size());
for (Value value : rawValues) {
rawValueCharSequences.add(value.value);
}
}
return rawValueCharSequences;
}
public boolean hasValueSet() { public boolean hasValueSet() {
List<?> values = getValues(); List<?> values = getValues();
@ -383,12 +401,15 @@ public abstract class FormField implements FullyQualifiedElement {
protected transient List<FullyQualifiedElement> extraXmlChildElements; protected transient List<FullyQualifiedElement> extraXmlChildElements;
/**
* Populate @{link {@link #extraXmlChildElements}}. Note that this method may be overridden by subclasses.
*/
protected void populateExtraXmlChildElements() { protected void populateExtraXmlChildElements() {
List<? extends CharSequence> values = getValues(); List<Value> 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()); extraXmlChildElements = new ArrayList<>(values.size());
for (CharSequence value : values) { extraXmlChildElements.addAll(values);
extraXmlChildElements.add(new Value(value));
}
} }
@Override @Override
@ -412,7 +433,8 @@ public abstract class FormField implements FullyQualifiedElement {
populateExtraXmlChildElements(); populateExtraXmlChildElements();
} }
if (formFieldChildElements.isEmpty() && extraXmlChildElements == null) { if (formFieldChildElements.isEmpty()
&& (extraXmlChildElements == null || extraXmlChildElements.isEmpty())) {
buf.closeEmptyElement(); buf.closeEmptyElement();
} else { } else {
buf.rightAngleBracket(); buf.rightAngleBracket();

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus. * Copyright 2020-2021 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.jivesoftware.smack.util.CollectionUtil;
import org.jxmpp.jid.Jid; 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<Jid> values; private final List<Jid> values;
private final List<Value> rawValues;
protected JidMultiFormField(Builder builder) { protected JidMultiFormField(Builder builder) {
super(builder); super(builder);
values = CollectionUtil.cloneAndSeal(builder.values); values = CollectionUtil.cloneAndSeal(builder.values);
rawValues = CollectionUtil.cloneAndSeal(builder.rawValues);
} }
@Override @Override
@ -38,6 +43,11 @@ public class JidMultiFormField extends FormField {
return values; return values;
} }
@Override
public List<Value> getRawValues() {
return rawValues;
}
public Builder asBuilder() { public Builder asBuilder() {
return new Builder(this); return new Builder(this);
} }
@ -45,6 +55,8 @@ public class JidMultiFormField extends FormField {
public static final class Builder extends FormField.Builder<JidMultiFormField, JidMultiFormField.Builder> { public static final class Builder extends FormField.Builder<JidMultiFormField, JidMultiFormField.Builder> {
private List<Jid> values; private List<Jid> values;
private List<Value> rawValues;
private Builder(JidMultiFormField jidMultiFormField) { private Builder(JidMultiFormField jidMultiFormField) {
super(jidMultiFormField); super(jidMultiFormField);
values = CollectionUtil.newListWith(jidMultiFormField.getValues()); values = CollectionUtil.newListWith(jidMultiFormField.getValues());
@ -57,25 +69,40 @@ public class JidMultiFormField extends FormField {
private void ensureValuesAreInitialized() { private void ensureValuesAreInitialized() {
if (values == null) { if (values == null) {
values = new ArrayList<>(); values = new ArrayList<>();
rawValues = new ArrayList<>();
} }
} }
@Override @Override
protected void resetInternal() { protected void resetInternal() {
values = null; values = null;
rawValues = null;
} }
public Builder addValue(Jid jid) { public Builder addValue(Jid jid) {
ensureValuesAreInitialized(); Value value = new Value(jid);
ensureValuesAreInitialized();
values.add(jid); 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; return this;
} }
public Builder addValues(Collection<? extends Jid> jids) { public Builder addValues(Collection<? extends Jid> jids) {
ensureValuesAreInitialized(); for (Jid jid : jids) {
addValue(jid);
values.addAll(jids); }
return this; return this;
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus. * Copyright 2020-2021 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,8 @@
package org.jivesoftware.smackx.xdata; package org.jivesoftware.smackx.xdata;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public class JidSingleFormField extends SingleValueFormField { public class JidSingleFormField extends SingleValueFormField {
@ -36,7 +38,7 @@ public class JidSingleFormField extends SingleValueFormField {
return new Builder(this); return new Builder(this);
} }
public static final class Builder extends FormField.Builder<JidSingleFormField, JidSingleFormField.Builder> { public static final class Builder extends SingleValueFormField.Builder<JidSingleFormField, JidSingleFormField.Builder> {
private Jid value; private Jid value;
private Builder(JidSingleFormField jidSingleFormField) { private Builder(JidSingleFormField jidSingleFormField) {
@ -50,11 +52,19 @@ public class JidSingleFormField extends SingleValueFormField {
@Override @Override
protected void resetInternal() { protected void resetInternal() {
super.resetInternal();
value = null; value = null;
} }
public Builder setValue(Jid value) { public Builder setValue(Jid value) {
this.value = 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; return this;
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus. * Copyright 2020-2021 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Collections;
import java.util.List; import java.util.List;
import org.jivesoftware.smack.util.CollectionUtil;
public abstract class SingleValueFormField extends FormField { public abstract class SingleValueFormField extends FormField {
private final Value rawValue;
protected SingleValueFormField(Builder<?, ?> builder) { protected SingleValueFormField(Builder<?, ?> builder) {
super(builder); super(builder);
rawValue = builder.rawValue;
} }
@Override @Override
public final List<CharSequence> getValues() { public final List<CharSequence> getValues() {
CharSequence value = getValue(); CharSequence value = getValue();
if (value == null) { return CollectionUtil.emptyOrSingletonListFrom(value);
return Collections.emptyList();
}
return Collections.singletonList(value);
} }
public abstract CharSequence getValue(); public abstract CharSequence getValue();
public final Value getRawValue() {
return rawValue;
}
@Override
public final List<Value> getRawValues() {
Value rawValue = getRawValue();
return CollectionUtil.emptyOrSingletonListFrom(rawValue);
}
@Override @Override
protected void populateExtraXmlChildElements() { protected void populateExtraXmlChildElements() {
CharSequence value = getValue(); if (rawValue == null) {
if (value == null) {
return; return;
} }
extraXmlChildElements = Collections.singletonList(new Value(value)); extraXmlChildElements = Collections.singletonList(rawValue);
}
public abstract static class Builder<F extends SingleValueFormField, B extends Builder<F, B>>
extends FormField.Builder<F, B> {
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;
};
} }
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2020 Florian Schmaus * Copyright 2020-2021 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.Date;
import java.util.List; import java.util.List;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.xdata.AbstractMultiFormField; import org.jivesoftware.smackx.xdata.AbstractMultiFormField;
import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField; import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField;
import org.jivesoftware.smackx.xdata.BooleanFormField; import org.jivesoftware.smackx.xdata.BooleanFormField;
@ -54,7 +56,8 @@ public interface FormReader {
return Collections.emptyList(); return Collections.emptyList();
} }
AbstractMultiFormField multiFormField = formField.ifPossibleAs(AbstractMultiFormField.class); AbstractMultiFormField multiFormField = formField.ifPossibleAs(AbstractMultiFormField.class);
return multiFormField.getValues(); List<? extends CharSequence> charSequences = multiFormField.getValues();
return StringUtils.toStrings(charSequences);
} }
default Boolean readBoolean(String fieldName) { default Boolean readBoolean(String fieldName) {

View File

@ -49,9 +49,6 @@ import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jivesoftware.smackx.xdatalayout.packet.DataLayout; import org.jivesoftware.smackx.xdatalayout.packet.DataLayout;
import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider; import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider;
import org.jxmpp.jid.Jid;
import org.jxmpp.jid.impl.JidCreate;
/** /**
* The DataFormProvider parses DataForm packets. * The DataFormProvider parses DataForm packets.
* *
@ -237,8 +234,7 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
case jid_multi: case jid_multi:
JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName); JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName);
for (FormField.Value value : values) { for (FormField.Value value : values) {
Jid jid = JidCreate.from(value.getValue()); jidMultiBuilder.addValue(value);
jidMultiBuilder.addValue(jid);
} }
builder = jidMultiBuilder; builder = jidMultiBuilder;
break; break;
@ -246,9 +242,8 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
ensureAtMostSingleValue(type, values); ensureAtMostSingleValue(type, values);
JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName); JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName);
if (!values.isEmpty()) { if (!values.isEmpty()) {
CharSequence jidCharSequence = values.get(0).getValue(); FormField.Value value = values.get(0);
Jid jid = JidCreate.from(jidCharSequence); jidSingleBuilder.setValue(value);
jidSingleBuilder.setValue(jid);
} }
builder = jidSingleBuilder; builder = jidSingleBuilder;
break; break;
@ -302,7 +297,7 @@ public class DataFormProvider extends ExtensionElementProvider<DataForm> {
BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName); BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName);
ensureAtMostSingleValue(builder.getType(), values); ensureAtMostSingleValue(builder.getType(), values);
if (values.size() == 1) { if (values.size() == 1) {
String value = values.get(0).getValue().toString(); FormField.Value value = values.get(0);
builder.setValue(value); builder.setValue(value);
} }
return builder; return builder;