From 68edc8b9f58489bf338a2da174ce1d01db39e98d Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 1 Apr 2021 12:54:52 +0200 Subject: [PATCH 01/22] [xdata] Allow FillableForm.write() ot overwrite already filled fields --- .../java/org/jivesoftware/smackx/xdata/form/FillableForm.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java index 7e90c7711..22f257a4e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java @@ -224,9 +224,6 @@ public class FillableForm extends FilledForm { if (!getDataForm().hasField(fieldName)) { throw new IllegalArgumentException(); } - if (filledFields.containsKey(fieldName)) { - throw new IllegalArgumentException(); - } // Perform validation, e.g. using XEP-0122. // TODO: We could also perform list-* option validation, but this has to take xep122's into account. From 090858f467c8211b97dc487266a822f5731a252f Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 1 Apr 2021 12:55:28 +0200 Subject: [PATCH 02/22] [xdata] Automatically fill required fields with a default value --- .../org/jivesoftware/smackx/xdata/form/FillableForm.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java index 22f257a4e..b40cb92a8 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java @@ -59,7 +59,13 @@ public class FillableForm extends FilledForm { if (formField.isRequired()) { String fieldName = formField.getFieldName(); requiredFields.add(fieldName); - missingRequiredFields.add(fieldName); + + if (formField.hasValueSet()) { + // This is a form field with a default value. + write(formField); + } else { + missingRequiredFields.add(fieldName); + } } } this.requiredFields = Collections.unmodifiableSet(requiredFields); From 26e6efa767c87e64fdf815f51b755fe1f175f49a Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 3 Jun 2021 19:10:07 +0200 Subject: [PATCH 03/22] [address] Fix NPE in MultipleRecipientManager The arguments 'to', 'cc, and 'bcc' may be null, hence ensure that they are non-null when calling e.g. size() on them. Fixes SMACK-907 Fixes: df96c5709379 ("[address] Get rid of PacketCopy workaround") --- .../address/MultipleRecipientManager.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/address/MultipleRecipientManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/address/MultipleRecipientManager.java index a2c73552a..630d37c8b 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/address/MultipleRecipientManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/address/MultipleRecipientManager.java @@ -19,6 +19,7 @@ package org.jivesoftware.smackx.address; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.jivesoftware.smack.SmackException.FeatureNotSupportedException; @@ -228,18 +229,16 @@ public class MultipleRecipientManager { throw new AssertionError(); } + if (to == null) to = Collections.emptyList(); + if (cc == null) cc = Collections.emptyList(); + if (bcc == null) bcc = Collections.emptyList(); + final int numRecipients = to.size() + cc.size() + bcc.size(); final List recipients = new ArrayList<>(numRecipients); - if (to != null) { - recipients.addAll(to); - } - if (cc != null) { - recipients.addAll(cc); - } - if (bcc != null) { - recipients.addAll(bcc); - } + recipients.addAll(to); + recipients.addAll(cc); + recipients.addAll(bcc); final List stanzasToSend = new ArrayList<>(numRecipients); for (Jid recipient : recipients) { From cd6ff363c6babe08ab21340ee49544a482af93d8 Mon Sep 17 00:00:00 2001 From: Dan Caseley Date: Tue, 15 Jun 2021 13:03:03 +0100 Subject: [PATCH 04/22] [sinttest] Refactor MUC tests ready to add more --- .../AbstractMultiUserChatIntegrationTest.java | 107 +++ .../muc/MultiUserChatIntegrationTest.java | 655 +----------------- ...AffiliationsPrivilegesIntegrationTest.java | 622 +++++++++++++++++ 3 files changed, 740 insertions(+), 644 deletions(-) create mode 100644 smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java create mode 100644 smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java new file mode 100644 index 000000000..7b1142081 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java @@ -0,0 +1,107 @@ +/** + * + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import java.util.List; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.xdata.form.FillableForm; +import org.jivesoftware.smackx.xdata.form.Form; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.TestNotPossibleException; +import org.jxmpp.jid.DomainBareJid; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.jid.parts.Localpart; +import org.jxmpp.jid.parts.Resourcepart; +import org.jxmpp.stringprep.XmppStringprepException; + + +public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { + + final String randomString = StringUtils.insecureRandomString(6); + + final MultiUserChatManager mucManagerOne; + final MultiUserChatManager mucManagerTwo; + final MultiUserChatManager mucManagerThree; + final DomainBareJid mucService; + + public AbstractMultiUserChatIntegrationTest(SmackIntegrationTestEnvironment environment) + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, + InterruptedException, TestNotPossibleException { + super(environment); + mucManagerOne = MultiUserChatManager.getInstanceFor(conOne); + mucManagerTwo = MultiUserChatManager.getInstanceFor(conTwo); + mucManagerThree = MultiUserChatManager.getInstanceFor(conThree); + + List services = mucManagerOne.getMucServiceDomains(); + if (services.isEmpty()) { + throw new TestNotPossibleException("No MUC (XEP-45) service found"); + } + else { + mucService = services.get(0); + } + } + + /** + * Gets a random room name. + * + * @param prefix A prefix to add to the room name for descriptive purposes + * @return the bare JID of a random room + * @throws XmppStringprepException if the prefix isn't a valid XMPP Localpart + */ + public EntityBareJid getRandomRoom(String prefix) throws XmppStringprepException { + final String roomNameLocal = String.join("-", prefix, testRunId, StringUtils.insecureRandomString(6)); + return JidCreate.entityBareFrom(Localpart.from(roomNameLocal), mucService.getDomain()); + } + + /** + * Destroys a MUC room, ignoring any exceptions. + * + * @param muc The room to destroy (can be null). + * @throws InterruptedException if the calling thread was interrupted. + * @throws SmackException.NotConnectedException if the XMPP connection is not connected. + * @throws XMPPException.XMPPErrorException if there was an XMPP error returned. + * @throws SmackException.NoResponseException if there was no response from the remote entity. + */ + static void tryDestroy(final MultiUserChat muc) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException { + if (muc == null) { + return; + } + muc.destroy("test fixture teardown", null); + } + + static void createMUC(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException { + MultiUserChat.MucCreateConfigFormHandle handle = muc.create(Resourcepart.from(resourceName)); + if (handle != null) { + handle.makeInstant(); + } + } + + static void createModeratedMUC(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException { + muc.create(Resourcepart.from(resourceName)); + Form configForm = muc.getConfigurationForm(); + FillableForm answerForm = configForm.getFillableForm(); + answerForm.setAnswer("muc#roomconfig_moderatedroom", true); //TODO Add this to the MucConfigFormManager? + muc.sendConfigurationForm(answerForm); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java index 3c4a8f5d4..bcaedfb8c 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java @@ -16,66 +16,35 @@ */ package org.jivesoftware.smackx.muc; -import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.List; import java.util.concurrent.TimeoutException; import org.jivesoftware.smack.MessageListener; -import org.jivesoftware.smack.SmackException.NoResponseException; -import org.jivesoftware.smack.SmackException.NotConnectedException; -import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Presence; -import org.jivesoftware.smack.util.StringUtils; -import org.jivesoftware.smackx.muc.MultiUserChat.MucCreateConfigFormHandle; import org.jivesoftware.smackx.muc.packet.MUCUser; -import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; import org.igniterealtime.smack.inttest.TestNotPossibleException; import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; import org.igniterealtime.smack.inttest.util.ResultSyncPoint; import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; -import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityBareJid; -import org.jxmpp.jid.EntityFullJid; -import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; -import org.jxmpp.jid.parts.Localpart; import org.jxmpp.jid.parts.Resourcepart; -import org.jxmpp.stringprep.XmppStringprepException; -public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { - - private final String randomString = StringUtils.insecureRandomString(6); - - private final MultiUserChatManager mucManagerOne; - private final MultiUserChatManager mucManagerTwo; - private final MultiUserChatManager mucManagerThree; - private final DomainBareJid mucService; +public class MultiUserChatIntegrationTest extends AbstractMultiUserChatIntegrationTest { public MultiUserChatIntegrationTest(SmackIntegrationTestEnvironment environment) - throws NoResponseException, XMPPErrorException, NotConnectedException, - InterruptedException, TestNotPossibleException { + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, + InterruptedException, TestNotPossibleException { super(environment); - mucManagerOne = MultiUserChatManager.getInstanceFor(conOne); - mucManagerTwo = MultiUserChatManager.getInstanceFor(conTwo); - mucManagerThree = MultiUserChatManager.getInstanceFor(conThree); - - List services = mucManagerOne.getMucServiceDomains(); - if (services.isEmpty()) { - throw new TestNotPossibleException("No MUC (XEP-45) service found"); - } - else { - mucService = services.get(0); - } } /** @@ -91,7 +60,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { */ @SmackIntegrationTest public void mucJoinTest() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest-join-leave"); + EntityBareJid mucAddress = getRandomRoom("smack-inttest-join"); MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress); try { @@ -122,7 +91,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { */ @SmackIntegrationTest public void mucLeaveTest() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest-join-leave"); + EntityBareJid mucAddress = getRandomRoom("smack-inttest-leave"); MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress); try { @@ -137,13 +106,14 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { assertEquals(mucAddress + "/nick-one", reflectedLeavePresence.getFrom().toString()); assertEquals(conOne.getUser().asEntityFullJidIfPossible().toString(), reflectedLeavePresence.getTo().toString()); } finally { + muc.join(Resourcepart.from("nick-one")); // We need to be in the room to destroy the room tryDestroy(muc); } } @SmackIntegrationTest public void mucTest() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + EntityBareJid mucAddress = getRandomRoom("smack-inttest-message"); MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); @@ -173,581 +143,9 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { } } - /** - * Asserts that a user who undergoes a role change receives that change as a presence update - * - *

From XEP-0045 § 5.1.3:

- *
- * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change - * to all occupants... - *
- * - *

From XEP-0045 § 9.6:

- *
- * The service MUST then send updated presence from this individual to all occupants, indicating the addition of - * moderator status... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucRoleTestForReceivingModerator() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { - @Override - public void moderatorGranted() { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - - // This implicitly tests "The service MUST add the user to the moderator list and then inform the admin of - // success" in §9.6, since it'll throw on either an error IQ or on no response. - mucAsSeenByOne.grantModerator(nicknameTwo); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who is present when another user undergoes a role change receives that change as a presence update - * - *

From XEP-0045 § 5.1.3:

- *
- * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change - * to all occupants... - *
- * - *

From XEP-0045 § 9.6:

- *
- * The service MUST then send updated presence from this individual to all occupants, indicating the addition of - * moderator status... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucRoleTestForWitnessingModerator() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { - @Override - public void moderatorGranted(EntityFullJid participant) { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - mucAsSeenByThree.join(nicknameThree); - - mucAsSeenByOne.grantModerator(nicknameTwo); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - - } - - /** - * Asserts that a user who undergoes a role change receives that change as a presence update - * - *

From XEP-0045 § 5.1.3:

- *
- * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change - * to all occupants... - *
- * - *

From XEP-0045 § 9.7:

- *
- * The service MUST then send updated presence from this individual to all occupants, indicating the removal of - * moderator status... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucRoleTestForRemovingModerator() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { - @Override - public void moderatorRevoked() { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - - mucAsSeenByOne.grantModerator(nicknameTwo); - mucAsSeenByOne.revokeModerator(nicknameTwo); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who is present when another user undergoes a role change receives that change as a presence update - * - *

From XEP-0045 § 5.1.3:

- *
- * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change - * to all occupants... - *
- * - *

From XEP-0045 § 9.7:

- *
- * The service MUST then send updated presence from this individual to all occupants, indicating the removal of - * moderator status... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucRoleTestForWitnessingModeratorRemoval() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { - @Override - public void moderatorRevoked(EntityFullJid participant) { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - mucAsSeenByThree.join(nicknameThree); - - mucAsSeenByOne.grantModerator(nicknameTwo); - mucAsSeenByOne.revokeModerator(nicknameTwo); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user in an unmoderated room who undergoes an afilliation change receives that change as a presence update - * - *

From XEP-0045 § 5.1.3:

- *
- * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change - * to all occupants... - *
- * - *

From XEP-0045 § 8.4:

- *
- * The service MUST then send updated presence from this individual to all occupants, indicating the removal of - * voice privileges... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucRoleTestForRevokingVoice() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { - @Override - public void voiceRevoked() { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - mucAsSeenByOne.revokeVoice(nicknameTwo); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who is present when another user undergoes a role change receives that change as a presence update - * - *

From XEP-0045 § 5.1.3:

- *
- * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change - * to all occupants... - *
- * - *

From XEP-0045 § 8.4:

- *
- * The service MUST then send updated presence from this individual to all occupants, indicating the removal of - * voice privileges... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucRoleTestForWitnessingRevokingVoice() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { - @Override - public void voiceRevoked(EntityFullJid participant) { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - mucAsSeenByThree.join(nicknameThree); - - mucAsSeenByOne.revokeVoice(nicknameTwo); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who undergoes an affiliation change receives that change as a presence update - * - *

From XEP-0045 § 5.2.2:

- *
- * ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that - * to all occupants... - *
- * - *

From XEP-0045 § 10.6:

- *
- * If the user is in the room, the service MUST then send updated presence from this individual to all occupants, - * indicating the granting of admin status... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucAffiliationTestForReceivingAdmin() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - - mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { - @Override - public void adminGranted() { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - - // This implicitly tests "The service MUST add the user to the admin list and then inform the owner of success" in §10.6, since it'll throw on either an error IQ or on no response. - mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who is present when another user undergoes an affiliation change receives that change as a - * presence update - * - *

From XEP-0045 § 5.2.2:

- *
- * ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that - * to all occupants... - *
- * - *

From XEP-0045 § 10.6:

- *
- * If the user is in the room, the service MUST then send updated presence from this individual to all occupants, - * indicating the granting of admin status... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucAffiliationTestForWitnessingAdmin() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { - @Override - public void adminGranted(EntityFullJid participant) { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - mucAsSeenByThree.join(nicknameThree); - - mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who undergoes a role change receives that change as a presence update - * - *

From XEP-0045 § 5.2.2:

- *
- * ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to - * all occupants... - *
- * - *

From XEP-0045 § 10.7:

- *
- * If the user is in the room, the service MUST then send updated presence from this individual to all occupants, - * indicating the loss of admin status by sending a presence element... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucAffiliationTestForRemovingAdmin() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { - @Override - public void adminRevoked() { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - - mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); - mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid()); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who is present when another user undergoes a role change receives that change as a presence update - * - *

From XEP-0045 § 5.2.2:

- *
- * ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to - * all occupants... - *
- * - *

From XEP-0045 § 10.7:

- *
- * If the user is in the room, the service MUST then send updated presence from this individual to all occupants, - * indicating the loss of admin status by sending a presence element... - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucAffiliationTestForWitnessingAdminRemoval() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - - mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { - @Override - public void adminRevoked(EntityFullJid participant) { - resultSyncPoint.signal("done"); - } - }); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - mucAsSeenByThree.join(nicknameThree); - mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); - - mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid()); - try { - resultSyncPoint.waitForResult(timeout); - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who gets kicked receives that change as a presence update - * - *

From XEP-0045 § 8.2:

- *
- * The kick is performed based on the occupant's room nickname and is completed by setting the role of a - * participant or visitor to a value of "none". - * - * The service MUST remove the kicked occupant by sending a presence stanza of type "unavailable" to each kicked - * occupant, including status code 307 in the extended presence information, optionally along with the reason (if - * provided) and the roomnick or bare JID of the user who initiated the kick. - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucPresenceTestForGettingKicked() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - mucAsSeenByTwo.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence)); - - mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test."); - try { - Presence kickPresence = resultSyncPoint.waitForResult(timeout); - MUCUser mucUser = MUCUser.from(kickPresence); - assertNotNull(mucUser); - assertAll( - () -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Missing self-presence status code in kick presence"), - () -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.KICKED_307), "Missing kick status code in kick presence"), - () -> assertEquals(MUCRole.none, mucUser.getItem().getRole(), "Role other than 'none' in kick presence") - ); - Jid itemJid = mucUser.getItem().getJid(); - if (itemJid != null) { - assertEquals(conTwo.getUser().asEntityFullJidIfPossible(), itemJid, "Incorrect kicked user in kick presence"); - } - } finally { - tryDestroy(mucAsSeenByOne); - } - } - - /** - * Asserts that a user who is present when another user gets kicked receives that change as a presence update - * - *

From XEP-0045 § 8.2:

- *
- * ...the service MUST then inform all of the remaining occupants that the kicked occupant is no longer in the room - * by sending presence stanzas of type "unavailable" from the individual's roomnick (<room@service/nick>) to all - * the remaining occupants (just as it does when occupants exit the room of their own volition), including the - * status code and optionally the reason and actor. - *
- * - * @throws Exception when errors occur - */ - @SmackIntegrationTest - public void mucPresenceTestForWitnessingKick() throws Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest"); - - MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); - - createMUC(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - mucAsSeenByThree.join(nicknameThree); - - final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); - mucAsSeenByThree.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence)); - - mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test."); - try { - Presence kickPresence = resultSyncPoint.waitForResult(timeout); - MUCUser mucUser = MUCUser.from(kickPresence); - assertNotNull(mucUser); - assertAll( - () -> assertFalse(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Incorrect self-presence status code in kick presence"), - () -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.KICKED_307), "Missing kick status code in kick presence"), - () -> assertEquals(MUCRole.none, mucUser.getItem().getRole(), "Role other than 'none' in kick presence") - ); - Jid itemJid = mucUser.getItem().getJid(); - if (itemJid != null) { - assertEquals(conTwo.getUser().asEntityFullJidIfPossible(), itemJid, "Incorrect kicked user in kick presence"); - } - } finally { - tryDestroy(mucAsSeenByOne); - } - - } /** - * Asserts that a user who is present when another user undergoes a role change receives that change as a presence update + * Asserts that a user is notified when a room is destroyed * *

From XEP-0045 § 10.9:

*
@@ -760,7 +158,7 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { @SmackIntegrationTest public void mucDestroyTest() throws TimeoutException, Exception { - EntityBareJid mucAddress = getRandomRoom("smack-inttest-join-leave"); + EntityBareJid mucAddress = getRandomRoom("smack-inttest-destroy"); MultiUserChat muc = mucManagerOne.getMultiUserChat(mucAddress); muc.join(Resourcepart.from("nick-one")); @@ -793,37 +191,6 @@ public class MultiUserChatIntegrationTest extends AbstractSmackIntegrationTest { assertNull(muc.getNickname()); } - /** - * Gets a random room name - * - * @param prefix A prefix to add to the room name for descriptive purposes - */ - private EntityBareJid getRandomRoom(String prefix) throws XmppStringprepException { - final String roomNameLocal = String.join("-", prefix, testRunId, StringUtils.insecureRandomString(6)); - return JidCreate.entityBareFrom(Localpart.from(roomNameLocal), mucService.getDomain()); - } - /** - * Destroys a MUC room, ignoring any exceptions. - * - * @param muc The room to destroy (can be null). - * @throws InterruptedException if the calling thread was interrupted. - * @throws NotConnectedException if the XMPP connection is not connected. - * @throws XMPPErrorException if there was an XMPP error returned. - * @throws NoResponseException if there was no response from the remote entity. - */ - static void tryDestroy(final MultiUserChat muc) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - if (muc == null) { - return; - } - muc.destroy("test fixture teardown", null); - } - - private static void createMUC(MultiUserChat muc, String resourceName) throws NoResponseException, XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException { - MucCreateConfigFormHandle handle = muc.create(Resourcepart.from(resourceName)); - if (handle != null) { - handle.makeInstant(); - } - } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java new file mode 100644 index 000000000..1943d1a0b --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java @@ -0,0 +1,622 @@ +/** + * + * Copyright 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jivesoftware.smackx.muc; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smackx.muc.packet.MUCUser; + +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.TestNotPossibleException; +import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.util.ResultSyncPoint; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.Jid; +import org.jxmpp.jid.parts.Resourcepart; + + +public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends AbstractMultiUserChatIntegrationTest{ + + public MultiUserChatRolesAffiliationsPrivilegesIntegrationTest(SmackIntegrationTestEnvironment environment) + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, + InterruptedException, TestNotPossibleException { + super(environment); + } + + /** + * Asserts that a user who undergoes a role change receives that change as a presence update + * + *

From XEP-0045 § 5.1.3:

+ *
+ * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change + * to all occupants... + *
+ * + *

From XEP-0045 § 9.6:

+ *
+ * The service MUST then send updated presence from this individual to all occupants, indicating the addition of + * moderator status... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucRoleTestForReceivingModerator() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { + @Override + public void moderatorGranted() { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + + // This implicitly tests "The service MUST add the user to the moderator list and then inform the admin of + // success" in §9.6, since it'll throw on either an error IQ or on no response. + mucAsSeenByOne.grantModerator(nicknameTwo); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who is present when another user undergoes a role change receives that change as a presence update + * + *

From XEP-0045 § 5.1.3:

+ *
+ * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change + * to all occupants... + *
+ * + *

From XEP-0045 § 9.6:

+ *
+ * The service MUST then send updated presence from this individual to all occupants, indicating the addition of + * moderator status... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucRoleTestForWitnessingModerator() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { + @Override + public void moderatorGranted(EntityFullJid participant) { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + mucAsSeenByOne.grantModerator(nicknameTwo); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + + } + + /** + * Asserts that a user who undergoes a role change receives that change as a presence update + * + *

From XEP-0045 § 5.1.3:

+ *
+ * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change + * to all occupants... + *
+ * + *

From XEP-0045 § 9.7:

+ *
+ * The service MUST then send updated presence from this individual to all occupants, indicating the removal of + * moderator status... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucRoleTestForRemovingModerator() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { + @Override + public void moderatorRevoked() { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + + mucAsSeenByOne.grantModerator(nicknameTwo); + mucAsSeenByOne.revokeModerator(nicknameTwo); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who is present when another user undergoes a role change receives that change as a presence update + * + *

From XEP-0045 § 5.1.3:

+ *
+ * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change + * to all occupants... + *
+ * + *

From XEP-0045 § 9.7:

+ *
+ * The service MUST then send updated presence from this individual to all occupants, indicating the removal of + * moderator status... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucRoleTestForWitnessingModeratorRemoval() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { + @Override + public void moderatorRevoked(EntityFullJid participant) { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + mucAsSeenByOne.grantModerator(nicknameTwo); + mucAsSeenByOne.revokeModerator(nicknameTwo); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user in an unmoderated room who undergoes an afilliation change receives that change as a presence update + * + *

From XEP-0045 § 5.1.3:

+ *
+ * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change + * to all occupants... + *
+ * + *

From XEP-0045 § 8.4:

+ *
+ * The service MUST then send updated presence from this individual to all occupants, indicating the removal of + * voice privileges... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucRoleTestForRevokingVoice() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { + @Override + public void voiceRevoked() { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByOne.revokeVoice(nicknameTwo); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who is present when another user undergoes a role change receives that change as a presence update + * + *

From XEP-0045 § 5.1.3:

+ *
+ * ...a MUC service implementation MUST change the occupant's role to reflect the change and communicate the change + * to all occupants... + *
+ * + *

From XEP-0045 § 8.4:

+ *
+ * The service MUST then send updated presence from this individual to all occupants, indicating the removal of + * voice privileges... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucRoleTestForWitnessingRevokingVoice() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { + @Override + public void voiceRevoked(EntityFullJid participant) { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + mucAsSeenByOne.revokeVoice(nicknameTwo); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who undergoes an affiliation change receives that change as a presence update + * + *

From XEP-0045 § 5.2.2:

+ *
+ * ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that + * to all occupants... + *
+ * + *

From XEP-0045 § 10.6:

+ *
+ * If the user is in the room, the service MUST then send updated presence from this individual to all occupants, + * indicating the granting of admin status... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucAffiliationTestForReceivingAdmin() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + + mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { + @Override + public void adminGranted() { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + + // This implicitly tests "The service MUST add the user to the admin list and then inform the owner of success" in §10.6, since it'll throw on either an error IQ or on no response. + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who is present when another user undergoes an affiliation change receives that change as a + * presence update + * + *

From XEP-0045 § 5.2.2:

+ *
+ * ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that + * to all occupants... + *
+ * + *

From XEP-0045 § 10.6:

+ *
+ * If the user is in the room, the service MUST then send updated presence from this individual to all occupants, + * indicating the granting of admin status... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucAffiliationTestForWitnessingAdmin() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { + @Override + public void adminGranted(EntityFullJid participant) { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who undergoes an affiliation change receives that change as a presence update + * + *

From XEP-0045 § 5.2.2:

+ *
+ * ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to + * all occupants... + *
+ * + *

From XEP-0045 § 10.7:

+ *
+ * If the user is in the room, the service MUST then send updated presence from this individual to all occupants, + * indicating the loss of admin status by sending a presence element... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucAffiliationTestForRemovingAdmin() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByTwo.addUserStatusListener(new UserStatusListener() { + @Override + public void adminRevoked() { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid()); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who is present when another user undergoes an affiliation change receives that change as a presence update + * + *

From XEP-0045 § 5.2.2:

+ *
+ * ...a MUC service implementation MUST change the user's affiliation to reflect the change and communicate that to + * all occupants... + *
+ * + *

From XEP-0045 § 10.7:

+ *
+ * If the user is in the room, the service MUST then send updated presence from this individual to all occupants, + * indicating the loss of admin status by sending a presence element... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucAffiliationTestForWitnessingAdminRemoval() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + + mucAsSeenByThree.addParticipantStatusListener(new ParticipantStatusListener() { + @Override + public void adminRevoked(EntityFullJid participant) { + resultSyncPoint.signal("done"); + } + }); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + + mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid()); + try { + resultSyncPoint.waitForResult(timeout); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who gets kicked receives that change as a presence update + * + *

From XEP-0045 § 8.2:

+ *
+ * The kick is performed based on the occupant's room nickname and is completed by setting the role of a + * participant or visitor to a value of "none". + * + * The service MUST remove the kicked occupant by sending a presence stanza of type "unavailable" to each kicked + * occupant, including status code 307 in the extended presence information, optionally along with the reason (if + * provided) and the roomnick or bare JID of the user who initiated the kick. + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucPresenceTestForGettingKicked() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + mucAsSeenByTwo.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence)); + + mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test."); + try { + Presence kickPresence = resultSyncPoint.waitForResult(timeout); + MUCUser mucUser = MUCUser.from(kickPresence); + assertNotNull(mucUser); + assertAll( + () -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Missing self-presence status code in kick presence"), + () -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.KICKED_307), "Missing kick status code in kick presence"), + () -> assertEquals(MUCRole.none, mucUser.getItem().getRole(), "Role other than 'none' in kick presence") + ); + Jid itemJid = mucUser.getItem().getJid(); + if (itemJid != null) { + assertEquals(conTwo.getUser().asEntityFullJidIfPossible(), itemJid, "Incorrect kicked user in kick presence"); + } + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a user who is present when another user gets kicked receives that change as a presence update + * + *

From XEP-0045 § 8.2:

+ *
+ * ...the service MUST then inform all of the remaining occupants that the kicked occupant is no longer in the room + * by sending presence stanzas of type "unavailable" from the individual's roomnick (<room@service/nick>) to all + * the remaining occupants (just as it does when occupants exit the room of their own volition), including the + * status code and optionally the reason and actor. + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucPresenceTestForWitnessingKick() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + createMUC(mucAsSeenByOne, "one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + mucAsSeenByThree.addParticipantListener(kickPresence -> resultSyncPoint.signal(kickPresence)); + + mucAsSeenByOne.kickParticipant(nicknameTwo, "Nothing personal. Just a test."); + try { + Presence kickPresence = resultSyncPoint.waitForResult(timeout); + MUCUser mucUser = MUCUser.from(kickPresence); + assertNotNull(mucUser); + assertAll( + () -> assertFalse(mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110), "Incorrect self-presence status code in kick presence"), + () -> assertTrue(mucUser.getStatus().contains(MUCUser.Status.KICKED_307), "Missing kick status code in kick presence"), + () -> assertEquals(MUCRole.none, mucUser.getItem().getRole(), "Role other than 'none' in kick presence") + ); + Jid itemJid = mucUser.getItem().getJid(); + if (itemJid != null) { + assertEquals(conTwo.getUser().asEntityFullJidIfPossible(), itemJid, "Incorrect kicked user in kick presence"); + } + } finally { + tryDestroy(mucAsSeenByOne); + } + + } + +} From cedced5b1362a297df7f8e68a6371e941469a494 Mon Sep 17 00:00:00 2001 From: Dan Caseley Date: Thu, 15 Oct 2020 10:20:35 +0100 Subject: [PATCH 05/22] [doc] Fix some wording in integrationtest.md --- documentation/developer/integrationtest.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/documentation/developer/integrationtest.md b/documentation/developer/integrationtest.md index fe006b58c..30595353c 100644 --- a/documentation/developer/integrationtest.md +++ b/documentation/developer/integrationtest.md @@ -4,8 +4,8 @@ Smack's Integration Test Framework Introduction ------------ -Smack's Integration Test Framwork is used to run a set of tests against a real XMPP service. -The framework discovers on startup the available tests by reflection. +Smack's Integration Test Framework is used to run a set of tests against a real XMPP service. +The framework discovers on start-up the available tests by reflection. Quickstart ---------- @@ -120,7 +120,7 @@ The methods are supposed to throw an exception if their integration test fails. ### `TestNotPossibleException` -Can be thrown by test methods or constructors to signal that their test it no possible, e.g. because the service does not support the required feature. +Can be thrown by test methods or constructors to signal that their test is not possible, e.g. because the service does not support the required feature. Running the integration tests ----------------------------- @@ -130,6 +130,7 @@ Smack's Gradle build system is configured with a special task called `integratio ```bash $ gradle integrationTest -Dsinttest.service=my.xmppservice.org ``` + If one of `accountOneUsername`, `accountOnePassword`, `accountTwoUsername` or `accountTwoPassword` is not configured, then the framework will automatically create the accounts on the service. Of course this requires account registration (IBR) to be enabled. If the accounts got created automatically by the framework, then they will also be deleted at the end of the test. From a0b8ad98c9633db9b11f43929324771187775835 Mon Sep 17 00:00:00 2001 From: Dan Caseley Date: Wed, 16 Jun 2021 16:33:41 +0100 Subject: [PATCH 06/22] [doc] Add debugging section to sinttest doc --- documentation/developer/integrationtest.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/documentation/developer/integrationtest.md b/documentation/developer/integrationtest.md index 30595353c..60f3142b8 100644 --- a/documentation/developer/integrationtest.md +++ b/documentation/developer/integrationtest.md @@ -162,6 +162,19 @@ The test methods can declare as many parameters as they need to, but every param The framework will automatically create, register and login the connections. After the test is finished, the connections will be unregistered with the XMPP service and terminated. +Debugging Integration Tests +------------------------------ + +A test, like any other code, may not be perfect on the first attempt, and you may require more information in order to ascertain quite what's wrong. + +### Smack Debugger options + +As listed in the main Smack [Debugging](../debugging.md) doc, there are two built-in debuggers that could surface you more information. Using the 'enhanced' debugger config option listed above, you'll get the Smack Debug Window launching when your tests launch, and you'll get a stanza-by-stanza account of what happened on each connection, hopefully enough to diagnose what went wrong. + +### Debugging in the IDE + +If the output isn't enough, you may need to debug and inspect running code within the IDE. Depending on the IDE, in order to get execution to pause at your breakpoints, you may need to switch your configuration. Instead of running `gradle integrationTest`, instead run the `SmackIntegrationTestFramework` class directly with the same command-line options. + Running your own integration tests ---------------------------------- From 7b3bd6ff963d9a4d5f328baa2572625c0a34296e Mon Sep 17 00:00:00 2001 From: Dan Caseley Date: Wed, 16 Jun 2021 16:34:11 +0100 Subject: [PATCH 07/22] [doc] Fix config example in MUC doc --- documentation/extensions/muc.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/extensions/muc.md b/documentation/extensions/muc.md index a89f2b22f..40d7e2024 100644 --- a/documentation/extensions/muc.md +++ b/documentation/extensions/muc.md @@ -472,7 +472,7 @@ muc = manager.getMultiUserChat("myroom@conference.jabber.org"); muc.create("testbot"); // User1 (which is the room owner) configures the room as a moderated room Form form = muc.getConfigurationForm(); -Form answerForm = form.createAnswerForm(); +FillableForm answerForm = configForm.getFillableForm(); answerForm.setAnswer("muc#roomconfig_moderatedroom", "1"); muc.sendConfigurationForm(answerForm); @@ -612,7 +612,7 @@ muc = manager.getMultiUserChat("myroom@conference.jabber.org"); muc.create("testbot"); // User1 (which is the room owner) configures the room as a moderated room Form form = muc.getConfigurationForm(); -Form answerForm = form.createAnswerForm(); +FillableForm answerForm = configForm.getFillableForm(); answerForm.setAnswer("muc#roomconfig_moderatedroom", "1"); muc.sendConfigurationForm(answerForm); From e0754df0438c8dedc40ff58826dc2588c5f0897e Mon Sep 17 00:00:00 2001 From: Dan Caseley Date: Wed, 16 Jun 2021 17:48:59 +0100 Subject: [PATCH 08/22] [doc] Initial IDE config guide --- documentation/developer/building.md | 13 ++++++ resources/intellij/Smack Import Order.xml | 18 -------- resources/intellij/smack_formatter.xml | 51 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 18 deletions(-) delete mode 100644 resources/intellij/Smack Import Order.xml create mode 100644 resources/intellij/smack_formatter.xml diff --git a/documentation/developer/building.md b/documentation/developer/building.md index 84163fc7f..2e35e634b 100644 --- a/documentation/developer/building.md +++ b/documentation/developer/building.md @@ -48,3 +48,16 @@ git clone git@github.com:igniterealtime/Smack.git cd Smack gradle assemble ``` + +IDE Config +---------- + +### Eclipse + +Import IDE settings from `./resources/eclipse/` to configure proper ordering of imports and correct formatting that should pass the CheckStyle rules. + +### IntelliJ IDEA + +Import Java Code Style settings from `./resources/intellij/smack_formatter.xml` to configure import optimisation and code formatting to pass the CheckStyle rules when building or submitting PRs. + +_We've noticed, at time of writing, that IntelliJ often requires a restart when applying new rules - no amount of OK/Apply will do the trick._ diff --git a/resources/intellij/Smack Import Order.xml b/resources/intellij/Smack Import Order.xml deleted file mode 100644 index 3209e4f5f..000000000 --- a/resources/intellij/Smack Import Order.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - diff --git a/resources/intellij/smack_formatter.xml b/resources/intellij/smack_formatter.xml new file mode 100644 index 000000000..961dda2b6 --- /dev/null +++ b/resources/intellij/smack_formatter.xml @@ -0,0 +1,51 @@ + + \ No newline at end of file From 6434e773363f64dee7ef4b77ab9e1e49a9f93d46 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 21 Jun 2021 14:52:18 +0200 Subject: [PATCH 09/22] [sinttest] Use correct camel case in method name: s/MUC/muc/ See also https://google.github.io/styleguide/javaguide.html#s5.3-camel-case --- .../AbstractMultiUserChatIntegrationTest.java | 4 ++-- .../muc/MultiUserChatIntegrationTest.java | 2 +- ...AffiliationsPrivilegesIntegrationTest.java | 24 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java index 7b1142081..597b767e6 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/AbstractMultiUserChatIntegrationTest.java @@ -90,14 +90,14 @@ public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrati muc.destroy("test fixture teardown", null); } - static void createMUC(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException { + static void createMuc(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException { MultiUserChat.MucCreateConfigFormHandle handle = muc.create(Resourcepart.from(resourceName)); if (handle != null) { handle.makeInstant(); } } - static void createModeratedMUC(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException { + static void createModeratedMuc(MultiUserChat muc, String resourceName) throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException { muc.create(Resourcepart.from(resourceName)); Form configForm = muc.getConfigurationForm(); FillableForm answerForm = configForm.getFillableForm(); diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java index bcaedfb8c..36fa47815 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatIntegrationTest.java @@ -131,7 +131,7 @@ public class MultiUserChatIntegrationTest extends AbstractMultiUserChatIntegrati } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); mucAsSeenByTwo.join(Resourcepart.from("two-" + randomString)); mucAsSeenByOne.sendMessage(mucMessage); try { diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java index 1943d1a0b..966ec3e97 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatRolesAffiliationsPrivilegesIntegrationTest.java @@ -79,7 +79,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -127,7 +127,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -175,7 +175,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -222,7 +222,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -270,7 +270,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); mucAsSeenByTwo.join(nicknameTwo); mucAsSeenByOne.revokeVoice(nicknameTwo); @@ -315,7 +315,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -363,7 +363,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -411,7 +411,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -458,7 +458,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -506,7 +506,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } }); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -543,7 +543,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); mucAsSeenByTwo.join(nicknameTwo); @@ -590,7 +590,7 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); - createMUC(mucAsSeenByOne, "one-" + randomString); + createMuc(mucAsSeenByOne, "one-" + randomString); final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); mucAsSeenByTwo.join(nicknameTwo); From 2762325639d8226d1f1bfa51b495553a77bc0dbe Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 21 Jun 2021 15:04:26 +0200 Subject: [PATCH 10/22] [repl] Bump Scala to 2.13.6 and Ammonite to 2.4.0 --- smack-repl/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/smack-repl/build.gradle b/smack-repl/build.gradle index 739e6a1f3..70e055ab7 100644 --- a/smack-repl/build.gradle +++ b/smack-repl/build.gradle @@ -9,7 +9,7 @@ apply plugin: 'scala' apply plugin: 'com.github.alisiikh.scalastyle_2.12' ext { - scalaVersion = '2.12.1' + scalaVersion = '2.13.6' } dependencies { @@ -20,7 +20,7 @@ dependencies { api project(':smack-omemo-signal') implementation "org.scala-lang:scala-library:$scalaVersion" - implementation "com.lihaoyi:ammonite_$scalaVersion:1.3.2" + implementation "com.lihaoyi:ammonite_$scalaVersion:2.4.0" } scalaStyle { From 4120b427619b36befe1bce18b231f0541ef100ef Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 6 Jul 2021 12:31:47 +0200 Subject: [PATCH 11/22] [omemo] Add OmemoManager.purgeEveryting() This is basically the already existing method from OmemoManagerSetupHelper.cleanUpPubSub() moved over into OmemoManager as purgeEverything(). --- .../AbstractTwoUsersOmemoIntegrationTest.java | 3 +- .../smackx/omemo/OmemoManagerSetupHelper.java | 51 +++---------------- .../smackx/omemo/OmemoManager.java | 46 +++++++++++++++++ 3 files changed, 54 insertions(+), 46 deletions(-) diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java index 1057e695c..1720c0725 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import java.io.IOException; import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPException; import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; @@ -63,7 +64,7 @@ public abstract class AbstractTwoUsersOmemoIntegrationTest extends AbstractOmemo } @AfterClass - public void cleanUp() throws IOException { + public void cleanUp() throws IOException, NotConnectedException, InterruptedException { alice.stopStanzaAndPEPListeners(); bob.stopStanzaAndPEPListeners(); OmemoManagerSetupHelper.cleanUpPubSub(alice); diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoManagerSetupHelper.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoManagerSetupHelper.java index 467650409..1a8093d63 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoManagerSetupHelper.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoManagerSetupHelper.java @@ -22,8 +22,10 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.HashMap; +import java.util.List; import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.roster.Roster; @@ -31,12 +33,9 @@ import org.jivesoftware.smack.roster.RosterEntry; import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException; import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; -import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList; import org.jivesoftware.smackx.omemo.internal.OmemoDevice; import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint; -import org.jivesoftware.smackx.omemo.util.OmemoConstants; import org.jivesoftware.smackx.pubsub.PubSubException; -import org.jivesoftware.smackx.pubsub.PubSubManager; import com.google.common.collect.Maps; @@ -124,48 +123,10 @@ public class OmemoManagerSetupHelper { } } - public static void cleanUpPubSub(OmemoManager omemoManager) throws IOException { - PubSubManager pm = PubSubManager.getInstanceFor(omemoManager.getConnection(), omemoManager.getOwnJid()); - try { - omemoManager.requestDeviceListUpdateFor(omemoManager.getOwnJid()); - } catch (SmackException.NotConnectedException | InterruptedException | SmackException.NoResponseException | PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException e) { - // ignore - } - - OmemoCachedDeviceList deviceList = OmemoService.getInstance().getOmemoStoreBackend() - .loadCachedDeviceList(omemoManager.getOwnDevice(), omemoManager.getOwnJid()); - - for (int id : deviceList.getAllDevices()) { - try { - pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems(); - } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | - PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | - PubSubException.NotAPubSubNodeException e) { - // Silent - } - - try { - pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)); - } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException - | XMPPException.XMPPErrorException e) { - // Silent - } - } - - try { - pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems(); - } catch (InterruptedException | SmackException.NoResponseException | SmackException.NotConnectedException | - PubSubException.NotALeafNodeException | XMPPException.XMPPErrorException | - PubSubException.NotAPubSubNodeException e) { - // Silent - } - - try { - pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST); - } catch (SmackException.NoResponseException | InterruptedException | SmackException.NotConnectedException | - XMPPException.XMPPErrorException e) { - // Silent - } + public static void cleanUpPubSub(OmemoManager omemoManager) + throws IOException, NotConnectedException, InterruptedException { + List exceptions = omemoManager.purgeEverything(); + assertTrue(exceptions.isEmpty(), "There where exceptions while purging OMEMO: " + exceptions); } public static void cleanUpRoster(OmemoManager omemoManager) { diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java index 35f7f407e..92889f14e 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java @@ -36,6 +36,7 @@ import java.util.logging.Logger; import org.jivesoftware.smack.ConnectionListener; import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Message; @@ -73,6 +74,7 @@ import org.jivesoftware.smackx.omemo.util.OmemoConstants; import org.jivesoftware.smackx.pep.PepEventListener; import org.jivesoftware.smackx.pep.PepManager; import org.jivesoftware.smackx.pubsub.PubSubException; +import org.jivesoftware.smackx.pubsub.PubSubManager; import org.jivesoftware.smackx.pubsub.packet.PubSub; import org.jxmpp.jid.BareJid; @@ -727,6 +729,50 @@ public final class OmemoManager extends Manager { getOmemoService().purgeDeviceList(new LoggedInOmemoManager(this)); } + public List purgeEverything() throws NotConnectedException, InterruptedException, IOException { + List exceptions = new ArrayList<>(5); + PubSubManager pm = PubSubManager.getInstanceFor(getConnection(), getOwnJid()); + try { + requestDeviceListUpdateFor(getOwnJid()); + } catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException + | XMPPException.XMPPErrorException e) { + exceptions.add(e); + } + + OmemoCachedDeviceList deviceList = OmemoService.getInstance().getOmemoStoreBackend() + .loadCachedDeviceList(getOwnDevice(), getOwnJid()); + + for (int id : deviceList.getAllDevices()) { + try { + pm.getLeafNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)).deleteAllItems(); + } catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException + | XMPPException.XMPPErrorException | PubSubException.NotAPubSubNodeException e) { + exceptions.add(e); + } + + try { + pm.deleteNode(OmemoConstants.PEP_NODE_BUNDLE_FROM_DEVICE_ID(id)); + } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException e) { + exceptions.add(e); + } + } + + try { + pm.getLeafNode(OmemoConstants.PEP_NODE_DEVICE_LIST).deleteAllItems(); + } catch (SmackException.NoResponseException | PubSubException.NotALeafNodeException + | XMPPException.XMPPErrorException | PubSubException.NotAPubSubNodeException e) { + exceptions.add(e); + } + + try { + pm.deleteNode(OmemoConstants.PEP_NODE_DEVICE_LIST); + } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException e) { + exceptions.add(e); + } + + return exceptions; + } + /** * Rotate the signedPreKey published in our OmemoBundle and republish it. This should be done every now and * then (7-14 days). The old signedPreKey should be kept for some more time (a month or so) to enable decryption From 5eef31e49c21f0e99103c7dfccd9fded01395236 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 6 Jul 2021 12:33:11 +0200 Subject: [PATCH 12/22] Do not call XmlPullParser.getName() when the event is unknown XmlPullParser.getName() only returns a result if the current parser event is START_ELEMENT or END_ELEMENT. If this is not the case, then the method may throw (if StAX is used). --- .../control/provider/IoTSetRequestProvider.java | 4 ++-- .../provider/IoTFieldsExtensionProvider.java | 2 +- .../smackx/mam/provider/MamPrefsIQProvider.java | 4 ++-- .../smackx/mam/provider/MamQueryIQProvider.java | 2 +- .../smackx/mam/provider/MamResultProvider.java | 2 +- .../provider/MarkupElementProvider.java | 11 +++++++---- .../socks5/provider/BytestreamsProvider.java | 3 ++- .../provider/AdHocCommandDataProvider.java | 2 +- .../provider/JingleS5BTransportProvider.java | 6 ++---- .../smackx/mood/provider/MoodProvider.java | 6 +++--- .../si/provider/StreamInitiationProvider.java | 17 +++++++---------- .../provider/XHTMLExtensionProvider.java | 4 ++-- .../smackx/jingleold/nat/RTPBridge.java | 3 +-- .../JingleContentDescriptionProvider.java | 9 ++++----- .../provider/JingleDescriptionProvider.java | 10 ++++------ .../jingleold/provider/JingleProvider.java | 4 ++-- .../provider/JingleTransportProvider.java | 9 ++++----- .../workgroup/agent/OfferConfirmation.java | 7 +++---- .../smackx/workgroup/packet/QueueUpdate.java | 7 +++---- .../smackx/workgroup/packet/RoomInvitation.java | 9 ++++----- .../smackx/workgroup/packet/RoomTransfer.java | 9 ++++----- .../provider/OmemoBundleVAxolotlProvider.java | 10 ++++------ .../OmemoDeviceListVAxolotlProvider.java | 12 +++++------- .../omemo/provider/OmemoVAxolotlProvider.java | 10 ++++------ .../ox/provider/PubkeyElementProvider.java | 2 +- 25 files changed, 74 insertions(+), 90 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java index 029e1f15e..952ebcdd8 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2016-2019 Florian Schmaus + * Copyright © 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. @@ -39,9 +39,9 @@ public class IoTSetRequestProvider extends IQProvider { List data = new ArrayList<>(4); outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); switch (name) { case "bool": { String valueName = parser.getAttributeValue(null, "name"); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java index c6d28c370..82a0e866f 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java @@ -110,9 +110,9 @@ public class IoTFieldsExtensionProvider extends ExtensionElementProvider fields = new ArrayList<>(); outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); IoTDataField field = null; final String fieldName = parser.getAttributeValue(null, "name"); final String fieldValue = parser.getAttributeValue(null, "value"); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java index 632ae4946..2b4795036 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java @@ -59,9 +59,9 @@ public class MamPrefsIQProvider extends IQProvider { outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); switch (name) { case "always": alwaysJids = iterateJids(parser); @@ -92,9 +92,9 @@ public class MamPrefsIQProvider extends IQProvider { outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); switch (name) { case "jid": parser.next(); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java index 853f199f4..d0971c5de 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java @@ -47,10 +47,10 @@ public class MamQueryIQProvider extends IQProvider { outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); switch (name) { case DataForm.ELEMENT: dataForm = DataFormProvider.INSTANCE.parse(parser); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java index 430b628a1..21012f873 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java @@ -49,9 +49,9 @@ public class MamResultProvider extends ExtensionElementProvider { String elementName; while (!done) { eventType = parser.next(); - elementName = parser.getName(); if (eventType == XmlPullParser.Event.START_ELEMENT) { + elementName = parser.getName(); if (elementName.equals(Bytestream.StreamHost.ELEMENT)) { JID = ParserUtils.getJidAttribute(parser); host = parser.getAttributeValue("", "host"); @@ -70,6 +70,7 @@ public class BytestreamsProvider extends IQProvider { } } else if (eventType == XmlPullParser.Event.END_ELEMENT) { + elementName = parser.getName(); if (elementName.equals("streamhost")) { if (port == null) { toReturn.addStreamHost(JID, host); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/provider/AdHocCommandDataProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/provider/AdHocCommandDataProvider.java index 4aeb9a700..13cb0b3cb 100755 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/provider/AdHocCommandDataProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/provider/AdHocCommandDataProvider.java @@ -78,9 +78,9 @@ public class AdHocCommandDataProvider extends IQProvider { } while (!done) { eventType = parser.next(); - elementName = parser.getName(); namespace = parser.getNamespace(); if (eventType == XmlPullParser.Event.START_ELEMENT) { + elementName = parser.getName(); if (parser.getName().equals("actions")) { String execute = parser.getAttributeValue("", "execute"); if (execute != null) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/provider/JingleS5BTransportProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/provider/JingleS5BTransportProvider.java index 7f85961db..c88651661 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/provider/JingleS5BTransportProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/provider/JingleS5BTransportProvider.java @@ -31,7 +31,6 @@ import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; -import org.jivesoftware.smackx.jingle.element.JingleContentTransport; import org.jivesoftware.smackx.jingle.element.JingleContentTransportCandidate; import org.jivesoftware.smackx.jingle.provider.JingleContentTransportProvider; import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport; @@ -62,9 +61,9 @@ public class JingleS5BTransportProvider extends JingleContentTransportProvider { outerloop: while (true) { XmlPullParser.Event tag = parser.next(); - String name = parser.getName(); - String namespace = parser.getNamespace(); switch (tag) { case START_ELEMENT: + String name = parser.getName(); + String namespace = parser.getNamespace(); if (MoodElement.ELEM_TEXT.equals(name)) { text = parser.nextText(); continue outerloop; @@ -74,7 +74,7 @@ public class MoodProvider extends ExtensionElementProvider { } case END_ELEMENT: - if (MoodElement.ELEMENT.equals(name)) { + if (MoodElement.ELEMENT.equals(parser.getName())) { MoodElement.MoodSubjectElement subjectElement = (mood == null && concretisation == null) ? null : new MoodElement.MoodSubjectElement(mood, concretisation); return new MoodElement(subjectElement, text); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java index f1719682c..6da4cafc9 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java @@ -46,8 +46,6 @@ public class StreamInitiationProvider extends IQProvider { @Override public StreamInitiation parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { - boolean done = false; - // si String id = parser.getAttributeValue("", "id"); String mimeType = parser.getAttributeValue("", "mime-type"); @@ -66,13 +64,11 @@ public class StreamInitiationProvider extends IQProvider { DataForm form = null; DataFormProvider dataFormProvider = new DataFormProvider(); - String elementName; - String namespace; - while (!done) { + outerloop: while (true) { XmlPullParser.Event eventType = parser.next(); - elementName = parser.getName(); - namespace = parser.getNamespace(); if (eventType == XmlPullParser.Event.START_ELEMENT) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); if (elementName.equals("file")) { name = parser.getAttributeValue("", "name"); size = parser.getAttributeValue("", "size"); @@ -87,9 +83,10 @@ public class StreamInitiationProvider extends IQProvider { form = dataFormProvider.parse(parser); } } else if (eventType == XmlPullParser.Event.END_ELEMENT) { - if (elementName.equals("si")) { - done = true; - } else if (elementName.equals("file")) { + if (parser.getDepth() == initialDepth) { + break outerloop; + } + if (parser.getName().equals("file")) { long fileSize = 0; if (size != null && size.trim().length() != 0) { try { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java index 71f15fe53..a09741400 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2014-2019 Florian Schmaus + * Copyright 2003-2007 Jive Software, 2014-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. @@ -40,8 +40,8 @@ public class XHTMLExtensionProvider extends ExtensionElementProvider { // Start processing sub-elements while (!done) { eventType = parser.next(); - elementName = parser.getName(); - namespace = parser.getNamespace(); if (eventType == XmlPullParser.Event.START_ELEMENT) { + elementName = parser.getName(); + namespace = parser.getNamespace(); // Parse some well know subelements, depending on the namespaces // and element names... diff --git a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/provider/JingleTransportProvider.java b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/provider/JingleTransportProvider.java index 1fe825482..0edb7d887 100644 --- a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/provider/JingleTransportProvider.java +++ b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/provider/JingleTransportProvider.java @@ -54,14 +54,13 @@ public abstract class JingleTransportProvider extends ExtensionElementProvider { @Override public OmemoBundleElement_VAxolotl parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException { - boolean stop = false; boolean inPreKeys = false; int signedPreKeyId = -1; @@ -52,11 +50,11 @@ public class OmemoBundleVAxolotlProvider extends ExtensionElementProvider preKeys = new HashMap<>(); - while (!stop) { + outerloop: while (true) { XmlPullParser.Event tag = parser.next(); - String name = parser.getName(); switch (tag) { case START_ELEMENT: + String name = parser.getName(); final int attributeCount = parser.getAttributeCount(); // if (name.equals(SIGNED_PRE_KEY_PUB)) { @@ -91,8 +89,8 @@ public class OmemoBundleVAxolotlProvider extends ExtensionElementProvider deviceListIds = new HashSet<>(); - boolean stop = false; - while (!stop) { + outerloop: while (true) { XmlPullParser.Event tag = parser.next(); - String name = parser.getName(); switch (tag) { case START_ELEMENT: + String name = parser.getName(); if (name.equals(DEVICE)) { for (int i = 0; i < parser.getAttributeCount(); i++) { if (parser.getAttributeName(i).equals(ID)) { @@ -57,8 +55,8 @@ public class OmemoDeviceListVAxolotlProvider extends ExtensionElementProvider keys = new ArrayList<>(); byte[] iv = null; byte[] payload = null; - while (inEncrypted) { + outerloop: while (true) { XmlPullParser.Event tag = parser.next(); - String name = parser.getName(); switch (tag) { case START_ELEMENT: + String name = parser.getName(); switch (name) { case OmemoHeaderElement.ELEMENT: for (int i = 0; i < parser.getAttributeCount(); i++) { @@ -82,8 +80,8 @@ public class OmemoVAxolotlProvider extends ExtensionElementProvider Date: Tue, 6 Jul 2021 12:33:11 +0200 Subject: [PATCH 13/22] Do not call XmlPullParser.getName() when the event is unknown XmlPullParser.getName() only returns a result if the current parser event is START_ELEMENT or END_ELEMENT. If this is not the case, then the method may throw (if StAX is used). --- .../provider/IoTSetRequestProvider.java | 4 ++-- .../provider/IoTFieldsExtensionProvider.java | 2 +- .../mam/provider/MamPrefsIQProvider.java | 4 ++-- .../mam/provider/MamQueryIQProvider.java | 2 +- .../mam/provider/MamResultProvider.java | 2 +- .../provider/MarkupElementProvider.java | 11 +++++---- .../provider/AdHocCommandDataProvider.java | 2 +- .../provider/JingleS5BTransportProvider.java | 6 ++--- .../smackx/mood/provider/MoodProvider.java | 6 ++--- .../si/provider/StreamInitiationProvider.java | 17 ++++++-------- .../provider/XHTMLExtensionProvider.java | 4 ++-- .../smackx/jingleold/nat/RTPBridge.java | 3 +-- .../JingleContentDescriptionProvider.java | 9 ++++---- .../provider/JingleDescriptionProvider.java | 10 ++++---- .../jingleold/provider/JingleProvider.java | 4 ++-- .../provider/JingleTransportProvider.java | 9 ++++---- .../workgroup/agent/OfferConfirmation.java | 19 ++++----------- .../smackx/workgroup/packet/QueueUpdate.java | 23 ++++++++----------- .../workgroup/packet/RoomInvitation.java | 9 ++++---- .../smackx/workgroup/packet/RoomTransfer.java | 9 ++++---- .../provider/OmemoBundleVAxolotlProvider.java | 10 ++++---- .../OmemoDeviceListVAxolotlProvider.java | 12 ++++------ .../omemo/provider/OmemoVAxolotlProvider.java | 10 ++++---- .../ox/provider/PubkeyElementProvider.java | 2 +- 24 files changed, 80 insertions(+), 109 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java index 029e1f15e..952ebcdd8 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/control/provider/IoTSetRequestProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright © 2016-2019 Florian Schmaus + * Copyright © 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. @@ -39,9 +39,9 @@ public class IoTSetRequestProvider extends IQProvider { List data = new ArrayList<>(4); outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); switch (name) { case "bool": { String valueName = parser.getAttributeValue(null, "name"); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java index fbe9b2d7f..5dfeaa5ba 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/iot/data/provider/IoTFieldsExtensionProvider.java @@ -115,9 +115,9 @@ public class IoTFieldsExtensionProvider extends ExtensionElementProvider fields = new ArrayList<>(); outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); IoTDataField field = null; final String fieldName = parser.getAttributeValue(null, "name"); final String fieldValue = parser.getAttributeValue(null, "value"); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java index 632ae4946..2b4795036 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamPrefsIQProvider.java @@ -59,9 +59,9 @@ public class MamPrefsIQProvider extends IQProvider { outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); switch (name) { case "always": alwaysJids = iterateJids(parser); @@ -92,9 +92,9 @@ public class MamPrefsIQProvider extends IQProvider { outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); switch (name) { case "jid": parser.next(); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java index 853f199f4..d0971c5de 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamQueryIQProvider.java @@ -47,10 +47,10 @@ public class MamQueryIQProvider extends IQProvider { outerloop: while (true) { final XmlPullParser.Event eventType = parser.next(); - final String name = parser.getName(); switch (eventType) { case START_ELEMENT: + final String name = parser.getName(); switch (name) { case DataForm.ELEMENT: dataForm = DataFormProvider.INSTANCE.parse(parser); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java index 7999a0e13..f0d465ffb 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamResultProvider.java @@ -47,9 +47,9 @@ public class MamResultProvider extends ExtensionElementProvider { } while (!done) { eventType = parser.next(); - elementName = parser.getName(); namespace = parser.getNamespace(); if (eventType == XmlPullParser.Event.START_ELEMENT) { + elementName = parser.getName(); if (parser.getName().equals("actions")) { String execute = parser.getAttributeValue("", "execute"); if (execute != null) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/provider/JingleS5BTransportProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/provider/JingleS5BTransportProvider.java index 7f85961db..c88651661 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/provider/JingleS5BTransportProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/provider/JingleS5BTransportProvider.java @@ -31,7 +31,6 @@ import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; -import org.jivesoftware.smackx.jingle.element.JingleContentTransport; import org.jivesoftware.smackx.jingle.element.JingleContentTransportCandidate; import org.jivesoftware.smackx.jingle.provider.JingleContentTransportProvider; import org.jivesoftware.smackx.jingle.transports.jingle_s5b.elements.JingleS5BTransport; @@ -62,9 +61,9 @@ public class JingleS5BTransportProvider extends JingleContentTransportProvider { outerloop: while (true) { XmlPullParser.Event tag = parser.next(); - String name = parser.getName(); - String namespace = parser.getNamespace(); switch (tag) { case START_ELEMENT: + String name = parser.getName(); + String namespace = parser.getNamespace(); if (MoodElement.ELEM_TEXT.equals(name)) { text = parser.nextText(); continue outerloop; @@ -74,7 +74,7 @@ public class MoodProvider extends ExtensionElementProvider { } case END_ELEMENT: - if (MoodElement.ELEMENT.equals(name)) { + if (MoodElement.ELEMENT.equals(parser.getName())) { MoodElement.MoodSubjectElement subjectElement = (mood == null && concretisation == null) ? null : new MoodElement.MoodSubjectElement(mood, concretisation); return new MoodElement(subjectElement, text); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java index f1719682c..6da4cafc9 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/si/provider/StreamInitiationProvider.java @@ -46,8 +46,6 @@ public class StreamInitiationProvider extends IQProvider { @Override public StreamInitiation parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { - boolean done = false; - // si String id = parser.getAttributeValue("", "id"); String mimeType = parser.getAttributeValue("", "mime-type"); @@ -66,13 +64,11 @@ public class StreamInitiationProvider extends IQProvider { DataForm form = null; DataFormProvider dataFormProvider = new DataFormProvider(); - String elementName; - String namespace; - while (!done) { + outerloop: while (true) { XmlPullParser.Event eventType = parser.next(); - elementName = parser.getName(); - namespace = parser.getNamespace(); if (eventType == XmlPullParser.Event.START_ELEMENT) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); if (elementName.equals("file")) { name = parser.getAttributeValue("", "name"); size = parser.getAttributeValue("", "size"); @@ -87,9 +83,10 @@ public class StreamInitiationProvider extends IQProvider { form = dataFormProvider.parse(parser); } } else if (eventType == XmlPullParser.Event.END_ELEMENT) { - if (elementName.equals("si")) { - done = true; - } else if (elementName.equals("file")) { + if (parser.getDepth() == initialDepth) { + break outerloop; + } + if (parser.getName().equals("file")) { long fileSize = 0; if (size != null && size.trim().length() != 0) { try { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java index 71f15fe53..a09741400 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2014-2019 Florian Schmaus + * Copyright 2003-2007 Jive Software, 2014-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. @@ -40,8 +40,8 @@ public class XHTMLExtensionProvider extends ExtensionElementProvider { // Start processing sub-elements while (!done) { eventType = parser.next(); - elementName = parser.getName(); - namespace = parser.getNamespace(); if (eventType == XmlPullParser.Event.START_ELEMENT) { + elementName = parser.getName(); + namespace = parser.getNamespace(); // Parse some well know subelements, depending on the namespaces // and element names... diff --git a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/provider/JingleTransportProvider.java b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/provider/JingleTransportProvider.java index 12d98e8a1..7462de72e 100644 --- a/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/provider/JingleTransportProvider.java +++ b/smack-jingle-old/src/main/java/org/jivesoftware/smackx/jingleold/provider/JingleTransportProvider.java @@ -54,14 +54,13 @@ public abstract class JingleTransportProvider extends ExtensionElementProvider { @Override public OmemoBundleElement_VAxolotl parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException { - boolean stop = false; boolean inPreKeys = false; int signedPreKeyId = -1; @@ -52,11 +50,11 @@ public class OmemoBundleVAxolotlProvider extends ExtensionElementProvider preKeys = new HashMap<>(); - while (!stop) { + outerloop: while (true) { XmlPullParser.Event tag = parser.next(); - String name = parser.getName(); switch (tag) { case START_ELEMENT: + String name = parser.getName(); final int attributeCount = parser.getAttributeCount(); // if (name.equals(SIGNED_PRE_KEY_PUB)) { @@ -91,8 +89,8 @@ public class OmemoBundleVAxolotlProvider extends ExtensionElementProvider deviceListIds = new HashSet<>(); - boolean stop = false; - while (!stop) { + outerloop: while (true) { XmlPullParser.Event tag = parser.next(); - String name = parser.getName(); switch (tag) { case START_ELEMENT: + String name = parser.getName(); if (name.equals(DEVICE)) { for (int i = 0; i < parser.getAttributeCount(); i++) { if (parser.getAttributeName(i).equals(ID)) { @@ -57,8 +55,8 @@ public class OmemoDeviceListVAxolotlProvider extends ExtensionElementProvider keys = new ArrayList<>(); byte[] iv = null; byte[] payload = null; - while (inEncrypted) { + outerloop: while (true) { XmlPullParser.Event tag = parser.next(); - String name = parser.getName(); switch (tag) { case START_ELEMENT: + String name = parser.getName(); switch (name) { case OmemoHeaderElement.ELEMENT: for (int i = 0; i < parser.getAttributeCount(); i++) { @@ -82,8 +80,8 @@ public class OmemoVAxolotlProvider extends ExtensionElementProvider Date: Tue, 6 Jul 2021 14:06:39 +0200 Subject: [PATCH 14/22] [core] Introduce Builder.failOnUnknownStates() and unit tests The previous approach of emitting a severe log message when a state (descriptor) was unknown was misleading. There are valid cases where some states are not known, if, for example, a module was explicitly disabled. Using Builder.failOnUnknownStates() in unit tests is far cleaner, as the existence of unknown states is tested in a controlled environment: one where are states are supposed to be known. --- ...ClientToServerConnectionConfiguration.java | 15 +++++++++- .../smack/fsm/StateDescriptorGraph.java | 28 +++++++++---------- ...lientToServerConnectionStateGraphTest.java | 26 ++++++++++++++++- 3 files changed, 52 insertions(+), 17 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java index 03982d647..74e20ba50 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/c2s/ModularXmppClientToServerConnectionConfiguration.java @@ -54,7 +54,7 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn } try { - initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors); + initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors, builder.failOnUnknownStates); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { @@ -91,6 +91,8 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn private final Map, ModularXmppClientToServerConnectionModuleDescriptor> modulesDescriptors = new HashMap<>(); + private boolean failOnUnknownStates; + private Builder() { SmackConfiguration.addAllKnownModulesTo(this); } @@ -163,6 +165,17 @@ public final class ModularXmppClientToServerConnectionConfiguration extends Conn return getThis(); } + /** + * Fail if there are unknown states in Smack's state descriptor graph. This method is used mostly for testing + * the internals of Smack. Users can safely ignore it. + * + * @return a reference to this builder. + */ + public Builder failOnUnknownStates() { + failOnUnknownStates = true; + return getThis(); + } + @Override protected Builder getThis() { return this; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java index bbece8898..35144f73a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/fsm/StateDescriptorGraph.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018 Florian Schmaus + * Copyright 2018-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. @@ -28,7 +28,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.logging.Logger; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.DisconnectedStateDescriptor; import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal; @@ -47,8 +46,6 @@ import org.jivesoftware.smack.util.MultiMap; */ public class StateDescriptorGraph { - private static final Logger LOGGER = Logger.getLogger(StateDescriptorGraph.class.getName()); - private static GraphVertex addNewStateDescriptorGraphVertex( Class stateDescriptorClass, Map, GraphVertex> graphVertexes) @@ -102,7 +99,8 @@ public class StateDescriptorGraph { } private static void handleStateDescriptorGraphVertex(GraphVertex node, - HandleStateDescriptorGraphVertexContext context) + HandleStateDescriptorGraphVertexContext context, + boolean failOnUnknownStates) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Class stateDescriptorClass = node.element.getClass(); boolean alreadyHandled = context.recurseInto(stateDescriptorClass); @@ -126,7 +124,7 @@ public class StateDescriptorGraph { case 1: GraphVertex soleSuccessorNode = successorStateDescriptors.values().iterator().next(); node.addOutgoingEdge(soleSuccessorNode); - handleStateDescriptorGraphVertex(soleSuccessorNode, context); + handleStateDescriptorGraphVertex(soleSuccessorNode, context, failOnUnknownStates); return; } @@ -144,9 +142,8 @@ public class StateDescriptorGraph { StateDescriptor successorStateDescriptor = successorStateDescriptorGraphNode.element; Class successorStateDescriptorClass = successorStateDescriptor.getClass(); for (Class subordinateClass : successorStateDescriptor.getSubordinates()) { - if (!successorClasses.contains(subordinateClass)) { - LOGGER.severe(successorStateDescriptor + " points to a subordinate '" + subordinateClass + "' which is not part of the successor set"); - continue; + if (failOnUnknownStates && !successorClasses.contains(subordinateClass)) { + throw new IllegalStateException(successorStateDescriptor + " points to a subordinate '" + subordinateClass + "' which is not part of the successor set"); } GraphVertex> superiorClassNode = lookupAndCreateIfRequired( @@ -157,10 +154,9 @@ public class StateDescriptorGraph { superiorClassNode.addOutgoingEdge(subordinateClassNode); } for (Class superiorClass : successorStateDescriptor.getSuperiors()) { - if (!successorClasses.contains(superiorClass)) { - LOGGER.severe(successorStateDescriptor + " points to a superior '" + superiorClass + if (failOnUnknownStates && !successorClasses.contains(superiorClass)) { + throw new IllegalStateException(successorStateDescriptor + " points to a superior '" + superiorClass + "' which is not part of the successor set"); - continue; } GraphVertex> subordinateClassNode = lookupAndCreateIfRequired( @@ -193,11 +189,13 @@ public class StateDescriptorGraph { node.addOutgoingEdge(successorVertex); // Recurse further. - handleStateDescriptorGraphVertex(successorVertex, context); + handleStateDescriptorGraphVertex(successorVertex, context, failOnUnknownStates); } } - public static GraphVertex constructStateDescriptorGraph(Set> backwardEdgeStateDescriptors) + public static GraphVertex constructStateDescriptorGraph( + Set> backwardEdgeStateDescriptors, + boolean failOnUnknownStates) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { Map, GraphVertex> graphVertexes = new HashMap<>(); @@ -219,7 +217,7 @@ public class StateDescriptorGraph { } HandleStateDescriptorGraphVertexContext context = new HandleStateDescriptorGraphVertexContext(graphVertexes, inferredForwardEdges); - handleStateDescriptorGraphVertex(initialNode, context); + handleStateDescriptorGraphVertex(initialNode, context, failOnUnknownStates); return initialNode; } diff --git a/smack-java8-full/src/test/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionStateGraphTest.java b/smack-java8-full/src/test/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionStateGraphTest.java index 624679481..961e7527d 100644 --- a/smack-java8-full/src/test/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionStateGraphTest.java +++ b/smack-java8-full/src/test/java/org/jivesoftware/smack/full/ModularXmppClientToServerConnectionStateGraphTest.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,7 @@ package org.jivesoftware.smack.full; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import java.io.IOException; import java.io.PrintWriter; @@ -25,6 +26,8 @@ import java.io.StringWriter; import java.net.URL; import java.nio.charset.StandardCharsets; +import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnectionConfiguration; +import org.jivesoftware.smack.compression.CompressionModuleDescriptor; import org.jivesoftware.smack.util.EqualsUtil; import org.jivesoftware.smack.util.HashCode; @@ -34,6 +37,7 @@ import org.jgrapht.graph.DirectedPseudograph; import org.jgrapht.io.DOTImporter; import org.jgrapht.io.ImportException; import org.junit.jupiter.api.Test; +import org.jxmpp.stringprep.XmppStringprepException; public class ModularXmppClientToServerConnectionStateGraphTest { @@ -80,4 +84,24 @@ public class ModularXmppClientToServerConnectionStateGraphTest { assertEquals(expectedStateGraph, currentStateGraph); } + @Test + public void testNoUnknownStates() throws XmppStringprepException { + ModularXmppClientToServerConnectionConfiguration.builder() + .setUsernameAndPassword("user", "password") + .setXmppDomain("example.org") + .failOnUnknownStates() // This is the actual option that enableds this test. + .build(); + } + + @Test + public void throwsOnUnknownStates() throws XmppStringprepException { + assertThrows(IllegalStateException.class, () -> + ModularXmppClientToServerConnectionConfiguration.builder() + .setUsernameAndPassword("user", "password") + .setXmppDomain("example.org") + .removeModule(CompressionModuleDescriptor.class) + .failOnUnknownStates() // This is the actual option that enableds this test. + .build() + ); + } } From 524e4b1d9b38a48771a7c76ed75a8d34c2234cfb Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 6 Jul 2021 14:33:15 +0200 Subject: [PATCH 15/22] Smack 4.4.3 --- resources/releasedocs/changelog.html | 11 +++++++++++ version | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/resources/releasedocs/changelog.html b/resources/releasedocs/changelog.html index eda807c81..8d8e20366 100644 --- a/resources/releasedocs/changelog.html +++ b/resources/releasedocs/changelog.html @@ -141,6 +141,17 @@ hr {
+

4.4.3 -- 2021-07-06

+ +

Bug +

+
    +
  • [SMACK-905] - The class org.jivesoftware.smackx.offline.packet.OfflineMessageInfo has no ELEMENT, NAMESPACE or QNAME member +
  • +
  • [SMACK-907] - Possible NPE in MultipleRecipientManager +
  • +
+

4.4.2 -- 2021-03-25

Bug diff --git a/version b/version index 40d873fca..9e3a93350 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.4.3-SNAPSHOT +4.4.3 From 0a8da1a07f4989273e78b53a2f2d252298d2d8e2 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Thu, 8 Jul 2021 10:32:19 +0200 Subject: [PATCH 16/22] SMACK-908: Don't use components to count tabs in Debugger Although the amount of components in a JTabbedPane apparently is often equal to the amount of tabs in it, that need not be the case. --- .../smackx/debugger/EnhancedDebuggerWindow.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java index 9822b59f9..5755207d8 100644 --- a/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java +++ b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebuggerWindow.java @@ -140,12 +140,12 @@ public final class EnhancedDebuggerWindow { if (frame == null) { createDebug(); } - debugger.tabbedPane.setName("XMPPConnection_" + tabbedPane.getComponentCount()); - tabbedPane.add(debugger.tabbedPane, tabbedPane.getComponentCount() - 1); + debugger.tabbedPane.setName("XMPPConnection_" + tabbedPane.getTabCount()); + tabbedPane.add(debugger.tabbedPane, -1); tabbedPane.setIconAt(tabbedPane.indexOfComponent(debugger.tabbedPane), connectionCreatedIcon); tabbedPane.setSelectedIndex(tabbedPane.indexOfComponent(debugger.tabbedPane)); frame.setTitle( - "Smack Debug Window -- Total connections: " + (tabbedPane.getComponentCount() - 1)); + "Smack Debug Window -- Total connections: " + (tabbedPane.getTabCount() - 1)); // Keep the added debugger for later access debuggers.add(debugger); } @@ -286,7 +286,7 @@ public final class EnhancedDebuggerWindow { @Override public void actionPerformed(ActionEvent e) { // Remove the selected tab pane if it's not the Smack info pane - if (tabbedPane.getSelectedIndex() < tabbedPane.getComponentCount() - 1) { + if (tabbedPane.getSelectedIndex() < tabbedPane.getTabCount() - 1) { int index = tabbedPane.getSelectedIndex(); // Notify to the debugger to stop debugging EnhancedDebugger debugger = debuggers.get(index); @@ -297,7 +297,7 @@ public final class EnhancedDebuggerWindow { // Update the root window title frame.setTitle( "Smack Debug Window -- Total connections: " - + (tabbedPane.getComponentCount() - 1)); + + (tabbedPane.getTabCount() - 1)); } } }); @@ -309,7 +309,7 @@ public final class EnhancedDebuggerWindow { public void actionPerformed(ActionEvent e) { ArrayList debuggersToRemove = new ArrayList<>(); // Remove all the debuggers of which their connections are no longer valid - for (int index = 0; index < tabbedPane.getComponentCount() - 1; index++) { + for (int index = 0; index < tabbedPane.getTabCount() - 1; index++) { EnhancedDebugger debugger = debuggers.get(index); if (!debugger.isConnectionActive()) { // Notify to the debugger to stop debugging @@ -325,7 +325,7 @@ public final class EnhancedDebuggerWindow { // Update the root window title frame.setTitle( "Smack Debug Window -- Total connections: " - + (tabbedPane.getComponentCount() - 1)); + + (tabbedPane.getTabCount() - 1)); } }); menu.add(menuItem); From f64288ba6d3b6b16c8f0eeb2b7b933d782ee6106 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 16 Jul 2021 08:55:40 +0200 Subject: [PATCH 17/22] [sinttest] Treat NPE as test failure not as abort condition --- .../smack/inttest/SmackIntegrationTestFramework.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java index 5eff3eeba..769146c6f 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java @@ -590,6 +590,14 @@ public class SmackIntegrationTestFramework { if (e instanceof InterruptedException) { throw (InterruptedException) e; } + + // We handle NullPointerException as a non fatal exception, as they are mostly caused by an invalid reply where + // a extension element is missing. Consider for example + // assertEquals(StanzaError.Condition.foo, response.getError().getCondition()) + // Otherwise NPEs could be caused by an internal bug in Smack, e.g. missing null handling. + if (e instanceof NullPointerException) { + return (NullPointerException) e; + } if (e instanceof RuntimeException) { throw (RuntimeException) e; } From a057db107e8fc9eefabf219eb7e2d7d0e6713650 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Fri, 16 Jul 2021 08:56:14 +0200 Subject: [PATCH 18/22] [ox-im] Annotate sporadically failing assertTrue() --- .../smackx/ox_im/OXInstantMessagingManagerTest.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java index 8d9d74ba0..13363cd58 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox_im/OXInstantMessagingManagerTest.java @@ -50,6 +50,7 @@ import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException; import org.jivesoftware.smackx.ox.store.filebased.FileBasedOpenPgpStore; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.jupiter.api.Test; @@ -156,9 +157,13 @@ public class OXInstantMessagingManagerTest extends SmackTestSuite { // Check, if one of Bobs keys was used for decryption assertNotNull(bobSelf.getSigningKeyRing().getPublicKey(metadata.getDecryptionFingerprint().getKeyId())); + // TODO: I observed this assertTrue() to fail sporadically. As a first attempt to diagnose this, a message was + // added to the assertion. However since most (all?) objects used in the message do not implement a proper + // toString() this is probably not really helpful as it is. + PGPPublicKeyRingCollection pubKeys = aliceForBob.getTrustedAnnouncedKeys(); // Check if one of Alice' keys was used for signing assertTrue(metadata.containsVerifiedSignatureFrom( - aliceForBob.getTrustedAnnouncedKeys().iterator().next())); + pubKeys.iterator().next()), metadata + " did not contain one of alice' keys " + pubKeys); } @AfterClass From 4643d07ef410c91f0fa31333541d7419876b6b0c Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 18 Jul 2021 17:20:50 +0200 Subject: [PATCH 19/22] [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 097d2453582f32729be1f00dd421e399c52dbc4d Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 18 Jul 2021 17:21:50 +0200 Subject: [PATCH 20/22] [xdata] Safe the raw character data of form field values --- .../smack/util/CollectionUtil.java | 9 +++- .../smackx/xdata/AbstractMultiFormField.java | 19 +++++++-- .../AbstractSingleStringValueFormField.java | 13 +++--- .../smackx/xdata/BooleanFormField.java | 9 ++-- .../jivesoftware/smackx/xdata/FormField.java | 4 +- .../smackx/xdata/JidMultiFormField.java | 28 ++++++++++++- .../smackx/xdata/JidSingleFormField.java | 14 ++++++- .../smackx/xdata/SingleValueFormField.java | 42 ++++++++++++++++--- .../xdata/provider/DataFormProvider.java | 14 ++++--- 9 files changed, 121 insertions(+), 31 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-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java index 31c778a87..4b1c092b3 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. @@ -29,9 +29,12 @@ public class AbstractMultiFormField extends FormField { private final List values; + private final List rawValues; + protected AbstractMultiFormField(Builder builder) { super(builder); values = CollectionUtil.cloneAndSeal(builder.values); + rawValues = CollectionUtil.cloneAndSeal(builder.rawValues); } @Override @@ -39,11 +42,16 @@ public class AbstractMultiFormField extends FormField { return values; } + @Override + public final List getRawValues() { + return rawValues; + } - public abstract static class Builder> + public abstract static class Builder> extends FormField.Builder { private List values; + private List rawValues; protected Builder(AbstractMultiFormField formField) { super(formField); @@ -57,6 +65,7 @@ public class AbstractMultiFormField extends FormField { private void ensureValuesAreInitialized() { if (values == null) { values = new ArrayList<>(); + rawValues = new ArrayList<>(); } } @@ -70,7 +79,9 @@ public class AbstractMultiFormField extends FormField { public B addValueVerbatim(CharSequence value) { ensureValuesAreInitialized(); - values.add(value.toString()); + String valueString = value.toString(); + values.add(valueString); + rawValues.add(valueString); return getThis(); } @@ -83,7 +94,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..4530d3968 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; @@ -74,18 +75,16 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField { } public B setValue(CharSequence value) { - this.value = value.toString(); + this.rawValue = this.value = value.toString(); 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..e87278ae9 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; } @@ -74,11 +75,13 @@ public class BooleanFormField extends SingleValueFormField { } public Builder setValue(CharSequence value) { - boolean valueBoolean = ParserUtils.parseXmlBoolean(value.toString()); + rawValue = value.toString(); + boolean valueBoolean = ParserUtils.parseXmlBoolean(rawValue); return setValue(valueBoolean); } public Builder setValue(boolean value) { + rawValue = 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..545083eee 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. @@ -271,6 +271,8 @@ public abstract class FormField implements FullyQualifiedElement { */ public abstract List getValues(); + public abstract List getRawValues(); + public boolean hasValueSet() { List values = getValues(); return !values.isEmpty(); 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..bfb405b97 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,18 @@ import java.util.List; import org.jivesoftware.smack.util.CollectionUtil; import org.jxmpp.jid.Jid; +import org.jxmpp.jid.util.JidUtil; -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 +42,11 @@ public class JidMultiFormField extends FormField { return values; } + @Override + public List getRawValues() { + return rawValues; + } + public Builder asBuilder() { return new Builder(this); } @@ -45,6 +54,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,18 +68,30 @@ 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) { + return addValue(jid, null); + } + + public Builder addValue(Jid jid, String rawValue) { + if (rawValue == null) { + rawValue = jid.toString(); + } + ensureValuesAreInitialized(); values.add(jid); + rawValues.add(rawValue); + return this; } @@ -76,6 +99,7 @@ public class JidMultiFormField extends FormField { ensureValuesAreInitialized(); values.addAll(jids); + rawValues.addAll(JidUtil.toStringList(jids)); 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..7948971a3 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. @@ -36,7 +36,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 +50,21 @@ public class JidSingleFormField extends SingleValueFormField { @Override protected void resetInternal() { + super.resetInternal(); value = null; } public Builder setValue(Jid value) { + return setValue(value, null); + } + + public Builder setValue(Jid value, String rawValue) { this.value = value; + if (rawValue != null) { + this.rawValue = rawValue; + } else { + this.rawValue = value.toString(); + } 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..796abd654 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,23 +19,35 @@ 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 String 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 String getRawValue() { + return rawValue; + } + + @Override + public final List getRawValues() { + String rawValue = getRawValue(); + return CollectionUtil.emptyOrSingletonListFrom(rawValue); + } + @Override protected void populateExtraXmlChildElements() { CharSequence value = getValue(); @@ -45,4 +57,24 @@ public abstract class SingleValueFormField extends FormField { extraXmlChildElements = Collections.singletonList(new Value(value)); } + + public abstract static class Builder> + extends FormField.Builder { + + protected Builder(String fieldName, Type type) { + super(fieldName, type); + } + + protected Builder(FormField formField) { + super(formField); + } + + protected String rawValue; + + @Override + protected void resetInternal() { + rawValue = null; + }; + + } } 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..f6362660c 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 @@ -237,8 +237,9 @@ 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); + String rawValue = value.getValue().toString(); + Jid jid = JidCreate.from(rawValue); + jidMultiBuilder.addValue(jid, rawValue); } builder = jidMultiBuilder; break; @@ -246,9 +247,9 @@ 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); + String rawValue = values.get(0).getValue().toString(); + Jid jid = JidCreate.from(rawValue); + jidSingleBuilder.setValue(jid, rawValue); } builder = jidSingleBuilder; break; @@ -322,7 +323,8 @@ public class DataFormProvider extends ExtensionElementProvider { private static AbstractMultiFormField.Builder parseMultiKindFormField(AbstractMultiFormField.Builder builder, List values) { for (FormField.Value value : values) { - builder.addValue(value.getValue()); + String rawValue = value.getValue().toString(); + builder.addValue(rawValue); } return builder; } From b47225c2c1048290d0d5712c357e7b46732eb025 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 18 Jul 2021 17:22:06 +0200 Subject: [PATCH 21/22] [caps] Use the raw character data of form fields when caclulating the hash --- .../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..36429ca5a 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.getRawValues(), sb); } } From b9be45ae2ccf860c62642092c61f5847c312f1f0 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 3 Aug 2021 22:07:04 +0200 Subject: [PATCH 22/22] [xdata] Fix ProtectedMembersInFinalClass Error Prone warning --- .../java/org/jivesoftware/smackx/xdata/JidMultiFormField.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bfb405b97..a5c869c18 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 @@ -31,7 +31,7 @@ public final class JidMultiFormField extends FormField { private final List rawValues; - protected JidMultiFormField(Builder builder) { + JidMultiFormField(Builder builder) { super(builder); values = CollectionUtil.cloneAndSeal(builder.values); rawValues = CollectionUtil.cloneAndSeal(builder.rawValues);