From 95adfb3cdf8d2d85f10d91dd0399e7f13072130e Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 25 Jun 2024 17:14:24 +0200 Subject: [PATCH 1/3] [caps] Ensure dataforms are ordered prior to ver calculation Fixes SMACK-944. --- .../smackx/caps/EntityCapsManager.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 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 9b736f6d4..15c941ba7 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 @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -69,6 +70,7 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity; import org.jivesoftware.smackx.disco.packet.DiscoverInfoBuilder; import org.jivesoftware.smackx.disco.packet.DiscoverInfoView; import org.jivesoftware.smackx.xdata.FormField; +import org.jivesoftware.smackx.xdata.TextSingleFormField; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.DomainBareJid; @@ -667,16 +669,30 @@ public final class EntityCapsManager extends Manager { } List extendedInfos = discoverInfo.getExtensions(DataForm.class); - for (DataForm extendedInfo : extendedInfos) { - if (!extendedInfo.hasHiddenFormTypeField()) { + final Iterator iter = extendedInfos.iterator(); + while (iter.hasNext()) { + if (!iter.next().hasHiddenFormTypeField()) { // Only use the data form for calculation is it has a hidden FORM_TYPE field. // See XEP-0115 5.4 step 3.f - continue; + iter.remove(); } + } - // 6. If the service discovery information response includes - // XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e., - // by the XML character data of the element). + // 6. If the service discovery information response includes + // XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e., + // by the XML character data of the element). + Collections.sort(extendedInfos, new Comparator() { + @Override + public int compare(DataForm d1, DataForm d2) { + final TextSingleFormField hft1 = d1.getHiddenFormTypeField(); + assert hft1 != null; // ensured by the previous step. + final TextSingleFormField hft2 = d2.getHiddenFormTypeField(); + assert hft2 != null; // ensured by the previous step. + return hft1.getFirstValue().compareTo(hft2.getFirstValue()); + } + }); + + for (DataForm extendedInfo : extendedInfos) { SortedSet fs = new TreeSet<>(new Comparator() { @Override public int compare(FormField f1, FormField f2) { From a1e85d644f188325251dc42d6b40247b64815d53 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 25 Jun 2024 16:43:48 +0200 Subject: [PATCH 2/3] [caps] Additional test that asserts CAPS dataform ordering XEP-0115 defines that any dataforms in the disco#info stanza is ordered prior to the computation of the verification string. This commit adds a test that verifies that this is done by Smack. See SMACK-944. --- .../smackx/caps/EntityCapsManagerTest.java | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/caps/EntityCapsManagerTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/caps/EntityCapsManagerTest.java index 87700708e..94f0fdaaa 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/caps/EntityCapsManagerTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/caps/EntityCapsManagerTest.java @@ -47,6 +47,41 @@ import org.jxmpp.stringprep.XmppStringprepException; public class EntityCapsManagerTest extends SmackTestSuite { + /** + * XEP- + * 0115 Simple Generation Example. + * @throws XmppStringprepException if the provided string is invalid. + */ + @Test + public void testSimpleGenerationExample() throws XmppStringprepException { + DiscoverInfo di = createSimpleSamplePacket(); + + CapsVersionAndHash versionAndHash = EntityCapsManager.generateVerificationString(di, StringUtils.SHA1); + assertEquals("QgayPKawpkPSDYmwT/WM94uAlu0=", versionAndHash.version); + } + + /** + * Asserts that the order in which data forms are present in the disco/info does not affect the calculated + * verification string, as the XEP mandates that these are ordered by FORM_TYPE (i.e., by the XML character data of + * the element). + * @throws XmppStringprepException if the provided string is invalid. + */ + @Test + public void testReversedDataFormOrder() throws XmppStringprepException { + final DiscoverInfoBuilder builderA = createSimpleSampleBuilder(); + builderA.addExtension(createSampleServerInfoDataForm()); // This works, as the underlying MultiMap maintains insertion-order. + builderA.addExtension(createSampleSoftwareInfoDataForm()); + + final DiscoverInfoBuilder builderB = createSimpleSampleBuilder(); + builderB.addExtension(createSampleSoftwareInfoDataForm()); + builderB.addExtension(createSampleServerInfoDataForm()); + + CapsVersionAndHash versionAndHashA = EntityCapsManager.generateVerificationString(builderA.build(), StringUtils.SHA1); + CapsVersionAndHash versionAndHashB = EntityCapsManager.generateVerificationString(builderB.build(), StringUtils.SHA1); + + assertEquals(versionAndHashA.version, versionAndHashB.version); + } + /** * XEP- * 0115 Complex Generation Example. @@ -142,6 +177,41 @@ public class EntityCapsManagerTest extends SmackTestSuite { return df.build(); } + private static DataForm createSampleServerInfoDataForm() { + DataForm.Builder df = DataForm.builder(DataForm.Type.result); + + { + TextMultiFormField.Builder ff = FormField.textMultiBuilder("admin-addresses"); + ff.addValue("xmpp:admin@example.org"); + ff.addValue("mailto:admin@example.com"); + df.addField(ff.build()); + } + + { + TextSingleFormField.Builder ff = FormField.hiddenBuilder("FORM_TYPE"); + ff.setValue("http://jabber.org/network/serverinfo"); + df.addField(ff.build()); + } + + return df.build(); + } + + private static DiscoverInfoBuilder createSimpleSampleBuilder() throws XmppStringprepException { + DiscoverInfoBuilder di = DiscoverInfo.builder("disco1"); + di.ofType(IQ.Type.result); + + di.addIdentity(new DiscoverInfo.Identity("client", "Exodus 0.9.1", "pc")); + di.addFeature("http://jabber.org/protocol/disco#info"); + di.addFeature("http://jabber.org/protocol/disco#items"); + di.addFeature("http://jabber.org/protocol/muc"); + di.addFeature("http://jabber.org/protocol/caps"); + + return di; + } + private static DiscoverInfo createSimpleSamplePacket() throws XmppStringprepException { + return createSimpleSampleBuilder().build(); + } + private static DiscoverInfo createComplexSamplePacket() throws XmppStringprepException { DiscoverInfoBuilder di = DiscoverInfo.builder("disco1"); di.from(JidCreate.from("benvolio@capulet.lit/230193")); From ba02a868f60d7b58224e190baf7654ef87251df3 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 27 Jun 2024 17:04:05 +0200 Subject: [PATCH 3/3] [caps] Use DataForm.getFormType() when sorting --- .../jivesoftware/smackx/caps/EntityCapsManager.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 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 15c941ba7..32c1af456 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 @@ -70,7 +70,6 @@ import org.jivesoftware.smackx.disco.packet.DiscoverInfo.Identity; import org.jivesoftware.smackx.disco.packet.DiscoverInfoBuilder; import org.jivesoftware.smackx.disco.packet.DiscoverInfoView; import org.jivesoftware.smackx.xdata.FormField; -import org.jivesoftware.smackx.xdata.TextSingleFormField; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.jid.DomainBareJid; @@ -683,12 +682,12 @@ public final class EntityCapsManager extends Manager { // by the XML character data of the element). Collections.sort(extendedInfos, new Comparator() { @Override - public int compare(DataForm d1, DataForm d2) { - final TextSingleFormField hft1 = d1.getHiddenFormTypeField(); - assert hft1 != null; // ensured by the previous step. - final TextSingleFormField hft2 = d2.getHiddenFormTypeField(); - assert hft2 != null; // ensured by the previous step. - return hft1.getFirstValue().compareTo(hft2.getFirstValue()); + public int compare(DataForm dataFormLeft, DataForm dataFormRight) { + final String formTypeLeft = dataFormLeft.getFormType(); + assert formTypeLeft != null; // ensured by the previous step. + final String formTypeRight = dataFormRight.getFormType(); + assert formTypeRight != null; // ensured by the previous step. + return formTypeLeft.compareTo(formTypeRight); } });