From c1b412c4579f4c9b1a12a49bc0d07d1d8ad7adea Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 5 Oct 2020 08:52:51 +0200 Subject: [PATCH 01/15] [xmlparser-stax] Disable external entities and DTD Before that, the StAX parser used by Smack for XML parsing had only external entity replacement disabled. We further harden the parser by disabling DTDs. See also: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#xmlinputfactory-a-stax-parser --- .../smack/xml/stax/StaxXmlPullParserFactory.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/smack-xmlparser-stax/src/main/java/org/jivesoftware/smack/xml/stax/StaxXmlPullParserFactory.java b/smack-xmlparser-stax/src/main/java/org/jivesoftware/smack/xml/stax/StaxXmlPullParserFactory.java index 6b442c5bb..7055d20de 100644 --- a/smack-xmlparser-stax/src/main/java/org/jivesoftware/smack/xml/stax/StaxXmlPullParserFactory.java +++ b/smack-xmlparser-stax/src/main/java/org/jivesoftware/smack/xml/stax/StaxXmlPullParserFactory.java @@ -1,6 +1,6 @@ /** * - * Copyright 2019 Florian Schmaus + * Copyright 2020-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,10 @@ public class StaxXmlPullParserFactory implements XmlPullParserFactory { // getText(). xmlInputFactory.setProperty(XMLInputFactory.IS_COALESCING, true); // Internal and external entity references are prohibited in XMPP (RFC 6120 § 11.1). + xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); xmlInputFactory.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, false); + // We don't need to support DTDs in XMPP. + xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); } @Override From 1a9ac238e85f4a3a028b4a23dde671bcc2170272 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 24 Oct 2020 19:15:11 +0200 Subject: [PATCH 02/15] OpenPgpManager: Expose methods to generate and import keys --- .../smackx/ox/OpenPgpManager.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java index 61c86b61f..ddef3aee9 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java @@ -295,14 +295,9 @@ public final class OpenPgpManager extends Manager { throwIfNoProviderSet(); OpenPgpStore store = provider.getStore(); - PGPKeyRing keys = store.generateKeyRing(ourJid); - try { - store.importSecretKey(ourJid, keys.getSecretKeys()); - store.importPublicKey(ourJid, keys.getPublicKeys()); - } catch (MissingUserIdOnKeyException e) { - // This should never throw, since we set our jid literally one line above this comment. - throw new AssertionError(e); - } + + PGPKeyRing keys = generateKeyRing(ourJid); + importKeyRing(ourJid, keys); OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(keys.getSecretKeys()); @@ -311,6 +306,23 @@ public final class OpenPgpManager extends Manager { return fingerprint; } + public PGPKeyRing generateKeyRing(BareJid ourJid) + throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { + throwIfNoProviderSet(); + PGPKeyRing keys = provider.getStore().generateKeyRing(ourJid); + return keys; + } + + private void importKeyRing(BareJid ourJid, PGPKeyRing keyRing) throws IOException, PGPException { + try { + provider.getStore().importSecretKey(ourJid, keyRing.getSecretKeys()); + provider.getStore().importPublicKey(ourJid, keyRing.getPublicKeys()); + } catch (MissingUserIdOnKeyException e) { + // This should never throw, since we set our jid literally one line above this comment. + throw new AssertionError(e); + } + } + /** * Return the upper-case hex encoded OpenPGP v4 fingerprint of our key pair. * From 55d7b9d4ebe3832e4f69ec5b44bffead5ad57346 Mon Sep 17 00:00:00 2001 From: Guus der Kinderen Date: Tue, 6 Oct 2020 14:57:25 +0200 Subject: [PATCH 03/15] Fix pubsub options rendering The exiting code generates an unintentional nested 'options' child element: ``` ``` This commit removes the undesired nesting, resulting in: ``` ``` --- .../org/jivesoftware/smackx/pubsub/OptionsExtension.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/OptionsExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/OptionsExtension.java index 7249f9eea..df39053b5 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/OptionsExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/OptionsExtension.java @@ -51,14 +51,9 @@ public class OptionsExtension extends NodeExtension { @Override protected void addXml(XmlStringBuilder xml) { - xml.rightAngleBracket(); - - xml.halfOpenElement(getElementName()); xml.attribute("jid", jid); - xml.optAttribute("node", getNode()); xml.optAttribute("subid", id); xml.closeEmptyElement(); - xml.closeElement(this); } } From cfccc78ba072e6a86b13c3242b607a4782257aef Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Tue, 3 Nov 2020 22:31:22 +0100 Subject: [PATCH 04/15] [muc] Rename local variable 'presence' to 'reflectedSelfPresence' To increase readability, and with that maintainability, we rename 'presence' to 'reflectedSelfPresence' in MultiUserChat.enter(), to make it clear what kind of presence this variable holds. Also mark the variable as final. --- .../java/org/jivesoftware/smackx/muc/MultiUserChat.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index f5ee3ec53..054d0ec22 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -378,7 +378,7 @@ public class MultiUserChat { ); // @formatter:on StanzaCollector presenceStanzaCollector = null; - Presence presence; + final Presence reflectedSelfPresence; try { // This stanza collector will collect the final self presence from the MUC, which also signals that we have successful entered the MUC. StanzaCollector selfPresenceCollector = connection.createStanzaCollectorAndSend(responseFilter, joinPresence); @@ -386,7 +386,7 @@ public class MultiUserChat { selfPresenceCollector).setStanzaFilter(presenceFromRoomFilter); // This stanza collector is used to reset the timeout of the selfPresenceCollector. presenceStanzaCollector = connection.createStanzaCollector(presenceStanzaCollectorConfguration); - presence = selfPresenceCollector.nextResultOrThrow(conf.getTimeout()); + reflectedSelfPresence = selfPresenceCollector.nextResultOrThrow(conf.getTimeout()); } catch (NotConnectedException | InterruptedException | NoResponseException | XMPPErrorException e) { // Ensure that all callbacks are removed if there is an exception @@ -401,12 +401,12 @@ public class MultiUserChat { // This presence must be send from a full JID. We use the resourcepart of this JID as nick, since the room may // performed roomnick rewriting - Resourcepart receivedNickname = presence.getFrom().getResourceOrThrow(); + Resourcepart receivedNickname = reflectedSelfPresence.getFrom().getResourceOrThrow(); setNickname(receivedNickname); // Update the list of joined rooms multiUserChatManager.addJoinedRoom(room); - return presence; + return reflectedSelfPresence; } private void setNickname(Resourcepart nickname) { From 74adcda23d5b459c06f238076f5d44a6fb807c45 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 5 Nov 2020 12:51:52 +0100 Subject: [PATCH 05/15] Add .mailmap --- .mailmap | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..4045d19c0 --- /dev/null +++ b/.mailmap @@ -0,0 +1,27 @@ +Paul Schaub +Aditya Borikar +Anno van Vliet +Daryl E. Herzmann +Damian Minkov +Craig Hesling +Dave Stanley +Marcel Heckel +Candy Lohse +Luca Stucchi +Luke Granger-Brown +Florian Kimmann +Adam Stawicki +Andrey Sokolov +Andri Khrisharyadi +Andriy Tsykholyas +Fernando Ramirez +Marcel Heckel +Robin Collier +Thibaut Le Guilly +Thomas Pocreau +Vadim Fite +Vaibhav Ranglani +Xiaowei YAN + +Guus der Kinderen guus +Jesus Fuentes Jesus From e1624e1ab96a0547ceab7fb57e42f4070f552bf8 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 5 Nov 2020 12:52:22 +0100 Subject: [PATCH 06/15] Add resources/get-contributors.sh --- resources/get-contributors.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100755 resources/get-contributors.sh diff --git a/resources/get-contributors.sh b/resources/get-contributors.sh new file mode 100755 index 000000000..c31236464 --- /dev/null +++ b/resources/get-contributors.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +git shortlog -s |\ + cut -f2- |\ + grep -v '(no author)' |\ + grep '\w \w.*' |\ + sort From 5dfed2935f036beb349d604e8ce16f823f749b8a Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 5 Nov 2020 12:54:54 +0100 Subject: [PATCH 07/15] Add NOTICE file --- NOTICE | 101 ++++++++++++++++++ .../resources/org.jivesoftware.smack/NOTICE | 1 + 2 files changed, 102 insertions(+) create mode 100644 NOTICE create mode 120000 smack-core/src/main/resources/org.jivesoftware.smack/NOTICE diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000..2c4a2fd15 --- /dev/null +++ b/NOTICE @@ -0,0 +1,101 @@ + Smack + + An open-source XMPP library + maintained by Florian Schmaus + + https://igniterealtime.org/projects/smack + + +Authors: + +Abmar Barros +Aditya Borikar +Alexander Tovstonozhenko +Alex Wenckus +Andrew Wright +Andrey Prokopenko +Andrey Sokolov +Andrey Starodubtsev +Andri Khrisharyadi +Andriy Tsykholyas +Anno van Vliet +Bastien Rouiller +Benjamin JALON +Bill Lynch +Boris Grozev +Candy Lohse +Cem Yabansu +Chris Deering +Christoph Fiehe +Craig Hesling +Damian Minkov +Daniele Ricci +Daniel Henninger +Daniel Hintze +Daryl E. Herzmann +Dave Cridland +Dave Stanley +David Black +Derek DeMoro +Dmitry Deshevoy +Eng ChongMeng +Fernando Martinez Herrera +Fernando Ramirez +Florian Kimmann +Florian Schmaus +Francisco Vives +Gaston Dombiak +Georg Lukas +Gilles Cornu +Gligor Selimovic +Greg Thomas +Grigory Fedorov +Günther Niess +Guus der Kinderen +Henning Staib +Holger Bergunde +Hugues Bruant +Ingo Bauersachs +Ishan Khanna +Jae Jang +Jared DiCioccio +Jason Sipula +Jay Kline +Jeff Williams +Jesus Fuentes +John Haubrich +Júlio Cesar Bueno Cotta +Lars Noschinski +Luca Stucchi +Luke Granger-Brown +Marcel Heckel +Marilyn Daum +Matteo Campana +Matthew Wild +Matt Tucker +Michael Will +Miguel Hincapie +Mohsen Hariri +Oliver Mihatsch +Paul Schaub +Pete Matern +Piotr Nosek +Rajat Kumar Gupta +Robin Collier +Simon Schuster +Son Goku +Tairs Rzajevs +Thiago Camargo +Thibaut Le Guilly +Thomas Pocreau +Tim Jentz +Timothy Pitt +Tomáš Havlas +Tomas Nosek +Vadim Fite +Vaibhav Ranglani +V Lau +Vyacheslav Blinov +Wolf Posdorfer +Xiaowei YAN +Yash Thakkar \ No newline at end of file diff --git a/smack-core/src/main/resources/org.jivesoftware.smack/NOTICE b/smack-core/src/main/resources/org.jivesoftware.smack/NOTICE new file mode 120000 index 000000000..1fb52812b --- /dev/null +++ b/smack-core/src/main/resources/org.jivesoftware.smack/NOTICE @@ -0,0 +1 @@ +../../../../../NOTICE \ No newline at end of file From f12fe2264a34418967be74bf0d0b58cad31c3efc Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 8 Nov 2020 19:37:41 +0100 Subject: [PATCH 08/15] [muc] Only retrieve MUCUser once in Presence listener --- .../java/org/jivesoftware/smackx/muc/MultiUserChat.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index 054d0ec22..b75f44d5e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -195,6 +195,7 @@ public class MultiUserChat { } final EntityFullJid myRoomJID = myRoomJid; final boolean isUserStatusModification = presence.getFrom().equals(myRoomJID); + final MUCUser mucUser = MUCUser.from(packet); switch (presence.getType()) { case available: @@ -205,9 +206,8 @@ public class MultiUserChat { MUCAffiliation oldAffiliation = mucExtension.getItem().getAffiliation(); MUCRole oldRole = mucExtension.getItem().getRole(); // Get the new occupant's affiliation & role - mucExtension = MUCUser.from(packet); - MUCAffiliation newAffiliation = mucExtension.getItem().getAffiliation(); - MUCRole newRole = mucExtension.getItem().getRole(); + MUCAffiliation newAffiliation = mucUser.getItem().getAffiliation(); + MUCRole newRole = mucUser.getItem().getRole(); // Fire role modification events checkRoleModifications(oldRole, newRole, isUserStatusModification, from); // Fire affiliation modification events @@ -228,7 +228,6 @@ public class MultiUserChat { break; case unavailable: occupantsMap.remove(from); - MUCUser mucUser = MUCUser.from(packet); if (mucUser != null && mucUser.hasStatus()) { if (isUserStatusModification) { userHasLeft(); From 7e311ab9df129b843e4fbcf0905834673624f220 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 8 Nov 2020 21:46:06 +0100 Subject: [PATCH 09/15] [muc] Prevent race condition on enter() by waiting This prevents a race condition of enter() with the presence listern by waiting until all presences have been processed. Reported-by: Guus der Kinderen --- .../smackx/muc/MultiUserChat.java | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index b75f44d5e..51f267694 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -149,6 +149,11 @@ public class MultiUserChat { private EntityFullJid myRoomJid; private StanzaCollector messageCollector; + /** + * Used to signal that the reflected self-presence was received and processed by us. + */ + private volatile boolean processedReflectedSelfPresence; + MultiUserChat(XMPPConnection connection, EntityBareJid room, MultiUserChatManager multiUserChatManager) { this.connection = connection; this.room = room; @@ -216,13 +221,15 @@ public class MultiUserChat { newAffiliation, isUserStatusModification, from); - } - else { + } else if (mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110)) { + processedReflectedSelfPresence = true; + synchronized (this) { + notify(); + } + } else { // A new occupant has joined the room - if (!isUserStatusModification) { - for (ParticipantStatusListener listener : participantStatusListeners) { - listener.joined(from); - } + for (ParticipantStatusListener listener : participantStatusListeners) { + listener.joined(from); } } break; @@ -376,6 +383,7 @@ public class MultiUserChat { ) ); // @formatter:on + processedReflectedSelfPresence = false; StanzaCollector presenceStanzaCollector = null; final Presence reflectedSelfPresence; try { @@ -398,6 +406,16 @@ public class MultiUserChat { } } + synchronized (presenceListener) { + // Only continue after we have received *and* processed the reflected self-presence. Since presences are + // handled in an extra listener, we may return from enter() without having processed all presences of the + // participants, resulting in a e.g. to low participant counter after enter(). Hence we wait here until the + // processing is done. + while (!processedReflectedSelfPresence) { + presenceListener.wait(); + } + } + // This presence must be send from a full JID. We use the resourcepart of this JID as nick, since the room may // performed roomnick rewriting Resourcepart receivedNickname = reflectedSelfPresence.getFrom().getResourceOrThrow(); From 72acd8e09509162b5fa748ebb65ce65a22d3882e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 8 Nov 2020 22:13:49 +0100 Subject: [PATCH 10/15] [core] Add StanzaBuilder.build() --- .../main/java/org/jivesoftware/smack/packet/IqBuilder.java | 1 + .../main/java/org/jivesoftware/smack/packet/IqData.java | 7 ++++++- .../smack/packet/MessageOrPresenceBuilder.java | 1 + .../java/org/jivesoftware/smack/packet/StanzaBuilder.java | 4 +++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/IqBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/IqBuilder.java index 644f077ea..03d15f973 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/IqBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/IqBuilder.java @@ -43,6 +43,7 @@ public abstract class IqBuilder, I extends IQ> return getThis(); } + @Override public abstract I build(); } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/IqData.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/IqData.java index deba5486e..18173ffeb 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/IqData.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/IqData.java @@ -1,6 +1,6 @@ /** * - * Copyright 2019 Florian Schmaus + * Copyright 2019-2020 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,4 +41,9 @@ public final class IqData extends AbstractIqBuilder { public IqData getThis() { return this; } + + @Override + public Stanza build() { + throw new UnsupportedOperationException(); + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageOrPresenceBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageOrPresenceBuilder.java index 9a3ff66a0..1caab5c04 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageOrPresenceBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/MessageOrPresenceBuilder.java @@ -37,6 +37,7 @@ public abstract class MessageOrPresenceBuilder> implements Stanz return getThis(); } + public abstract Stanza build(); + public abstract B getThis(); @Override From df96c5709379c5136d69a9a930b3e64f3c7201c0 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 8 Nov 2020 22:14:15 +0100 Subject: [PATCH 11/15] [address] Get rid of PacketCopy workaround PacketCopy subclassing Stanza was always a peculiarity. The only subclasses of Stanza should be Message, Presence, and IQ. --- .../address/MultipleRecipientManager.java | 89 ++++++++----------- 1 file changed, 37 insertions(+), 52 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 eb1d611de..a2c73552a 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 @@ -26,8 +26,13 @@ import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.packet.StanzaBuilder; +import org.jivesoftware.smack.packet.StanzaFactory; +import org.jivesoftware.smack.packet.StanzaView; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.address.packet.MultipleAddresses; @@ -207,25 +212,44 @@ public class MultipleRecipientManager { return extension == null ? null : new MultipleRecipientInfo(extension); } - private static void sendToIndividualRecipients(XMPPConnection connection, Stanza packet, + private static void sendToIndividualRecipients(XMPPConnection connection, StanzaView stanza, Collection to, Collection cc, Collection bcc) throws NotConnectedException, InterruptedException { + final StanzaFactory stanzaFactory = connection.getStanzaFactory(); + final StanzaBuilder stanzaBuilder; + if (stanza instanceof Message) { + Message message = (Message) stanza; + stanzaBuilder = stanzaFactory.buildMessageStanzaFrom(message); + } else if (stanza instanceof Presence) { + Presence presence = (Presence) stanza; + stanzaBuilder = stanzaFactory.buildPresenceStanzaFrom(presence); + } else if (stanza instanceof IQ) { + throw new IllegalArgumentException("IQ stanzas have no supported fallback in case no XEP-0033 service is available"); + } else { + throw new AssertionError(); + } + + final int numRecipients = to.size() + cc.size() + bcc.size(); + final List recipients = new ArrayList<>(numRecipients); + if (to != null) { - for (Jid jid : to) { - packet.setTo(jid); - connection.sendStanza(new PacketCopy(packet)); - } + recipients.addAll(to); } if (cc != null) { - for (Jid jid : cc) { - packet.setTo(jid); - connection.sendStanza(new PacketCopy(packet)); - } + recipients.addAll(cc); } if (bcc != null) { - for (Jid jid : bcc) { - packet.setTo(jid); - connection.sendStanza(new PacketCopy(packet)); - } + recipients.addAll(bcc); + } + + final List stanzasToSend = new ArrayList<>(numRecipients); + for (Jid recipient : recipients) { + Stanza stanzaToSend = stanzaBuilder.to(recipient).build(); + stanzasToSend.add(stanzaToSend); + } + + // TODO: Use XMPPConnection.sendStanzas(Collection) once this method exists. + for (Stanza stanzaToSend : stanzasToSend) { + connection.sendStanza(stanzaToSend); } } @@ -289,43 +313,4 @@ public class MultipleRecipientManager { return sdm.findService(MultipleAddresses.NAMESPACE, true); } - /** - * Stanza that holds the XML stanza to send. This class is useful when the same packet - * is needed to be sent to different recipients. Since using the same stanza is not possible - * (i.e. cannot change the TO address of a queues stanza to be sent) then this class was - * created to keep the XML stanza to send. - */ - private static final class PacketCopy extends Stanza { - - private final String elementName; - private final CharSequence text; - - /** - * Create a copy of a stanza with the text to send. The passed text must be a valid text to - * send to the server, no validation will be done on the passed text. - * - * @param text the whole text of the stanza to send - */ - private PacketCopy(Stanza stanza) { - this.elementName = stanza.getElementName(); - this.text = stanza.toXML(); - } - - @Override - public CharSequence toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - return text; - } - - @Override - public String toString() { - return toXML().toString(); - } - - @Override - public String getElementName() { - return elementName; - } - - } - } From 1f5ada482232a630d0eba3d2dfca6f4e89b09a9e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 8 Nov 2020 22:27:57 +0100 Subject: [PATCH 12/15] Add org.jivesoftware.smack.Smack with getVersion() and ensureInitialized() --- .../smack/AbstractXMPPConnection.java | 3 +- .../smack/ConnectionConfiguration.java | 4 +- .../java/org/jivesoftware/smack/Smack.java | 42 +++++++++++++++++++ .../smack/SmackConfiguration.java | 3 ++ .../smack/provider/ProviderManager.java | 4 +- .../smack/SmackConfigurationTest.java | 4 +- .../smack/test/util/SmackTestSuite.java | 4 +- .../debugger/EnhancedDebuggerWindow.java | 4 +- .../smackx/iqversion/VersionManager.java | 4 +- .../SmackIntegrationTestFramework.java | 3 +- .../smack/smackrepl/SmackRepl.java | 6 +-- 11 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 smack-core/src/main/java/org/jivesoftware/smack/Smack.java diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java index 530bf7b11..92a25b64e 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -171,8 +171,7 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { private static final AtomicInteger connectionCounter = new AtomicInteger(0); static { - // Ensure the SmackConfiguration class is loaded by calling a method in it. - SmackConfiguration.getVersion(); + Smack.ensureInitialized(); } protected enum SyncPointState { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java index e6009754b..23e664e94 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java @@ -112,9 +112,7 @@ import org.minidns.util.InetAddressUtil; public abstract class ConnectionConfiguration { static { - // Ensure that Smack is initialized when ConnectionConfiguration is used, or otherwise e.g. - // SmackConfiguration.DEBUG may not be initialized yet. - SmackConfiguration.getVersion(); + Smack.ensureInitialized(); } private static final Logger LOGGER = Logger.getLogger(ConnectionConfiguration.class.getName()); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/Smack.java b/smack-core/src/main/java/org/jivesoftware/smack/Smack.java new file mode 100644 index 000000000..f59ea632f --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/Smack.java @@ -0,0 +1,42 @@ +/** + * + * Copyright 2020 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.smack; + +import java.util.logging.Logger; + +public class Smack { + + private static final Logger LOGGER = Logger.getLogger(Smack.class.getName()); + + /** + * Returns the Smack version information, eg "1.3.0". + * + * @return the Smack version information. + */ + public static String getVersion() { + return SmackInitialization.SMACK_VERSION; + } + + public static void ensureInitialized() { + if (SmackConfiguration.isSmackInitialized()) { + return; + } + + String version = getVersion(); + LOGGER.finest("Smack " + version + " has been initialized"); + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java index 75f23a97f..746c611a0 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackConfiguration.java @@ -105,7 +105,10 @@ public final class SmackConfiguration { * Returns the Smack version information, eg "1.3.0". * * @return the Smack version information. + * @deprecated use {@link Smack#getVersion()} instead. */ + @Deprecated + // TODO: Remove in Smack 4.6 public static String getVersion() { return SmackInitialization.SMACK_VERSION; } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java b/smack-core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java index f9e2bb560..45260407e 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/provider/ProviderManager.java @@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import javax.xml.namespace.QName; -import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Smack; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Nonza; @@ -122,7 +122,7 @@ public final class ProviderManager { // registered providers do not get overwritten by a following Smack // initialization. This guarantees that Smack is initialized before a // new provider is registered - SmackConfiguration.getVersion(); + Smack.ensureInitialized(); } @SuppressWarnings("unchecked") diff --git a/smack-core/src/test/java/org/jivesoftware/smack/SmackConfigurationTest.java b/smack-core/src/test/java/org/jivesoftware/smack/SmackConfigurationTest.java index 2d9de46c1..364c98fd1 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/SmackConfigurationTest.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/SmackConfigurationTest.java @@ -49,8 +49,8 @@ public class SmackConfigurationTest { // *every* test, those tests are currently disabled. Hopefully this will change in the future. @Ignore @Test - public void smackconfigurationVersionShouldInitialzieSmacktTest() { - SmackConfiguration.getVersion(); + public void smackEnsureInitializedShouldInitialzieSmacktTest() { + Smack.ensureInitialized(); // Only a call to SmackConfiguration.getVersion() should cause Smack to become initialized. assertTrue(SmackConfiguration.isSmackInitialized()); diff --git a/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestSuite.java b/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestSuite.java index 8a34b991b..f88346694 100644 --- a/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestSuite.java +++ b/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestSuite.java @@ -19,7 +19,7 @@ package org.jivesoftware.smack.test.util; import java.security.Security; import java.util.Base64; -import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Smack; import org.jivesoftware.smack.util.stringencoder.Base64.Encoder; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -31,7 +31,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider; public class SmackTestSuite { static { - SmackConfiguration.getVersion(); + Smack.ensureInitialized(); org.jivesoftware.smack.util.stringencoder.Base64.setEncoder(new Encoder() { @Override 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 a41c9ab78..9822b59f9 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 @@ -43,7 +43,7 @@ import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; -import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Smack; import org.jivesoftware.smack.provider.ProviderManager; /** @@ -232,7 +232,7 @@ public final class EnhancedDebuggerWindow { versionPanel.setLayout(new BoxLayout(versionPanel, BoxLayout.X_AXIS)); versionPanel.setMaximumSize(new Dimension(2000, 31)); versionPanel.add(new JLabel(" Smack version: ")); - JFormattedTextField field = new JFormattedTextField(SmackConfiguration.getVersion()); + JFormattedTextField field = new JFormattedTextField(Smack.getVersion()); field.setEditable(false); field.setBorder(null); versionPanel.add(field); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/iqversion/VersionManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/iqversion/VersionManager.java index b111838d2..ec2a4f7d9 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/iqversion/VersionManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/iqversion/VersionManager.java @@ -22,7 +22,7 @@ import java.util.WeakHashMap; import org.jivesoftware.smack.ConnectionCreationListener; import org.jivesoftware.smack.Manager; -import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Smack; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPConnection; @@ -152,7 +152,7 @@ public final class VersionManager extends Manager { private static Version generateVersionFrom(String name, String version, String os) { if (autoAppendSmackVersion) { - name += " (Smack " + SmackConfiguration.getVersion() + ')'; + name += " (Smack " + Smack.getVersion() + ')'; } return new Version(name, version, os); } 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 e8d7758f8..9beacfca3 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 @@ -50,6 +50,7 @@ import java.util.logging.Logger; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; +import org.jivesoftware.smack.Smack; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NoResponseException; @@ -171,7 +172,7 @@ public class SmackIntegrationTestFramework { // Create a connection manager *after* we created the testRunId (in testRunResult). this.connectionManager = new XmppConnectionManager(this); - LOGGER.info("SmackIntegrationTestFramework [" + testRunResult.testRunId + ']' + ": Starting\nSmack version: " + SmackConfiguration.getVersion()); + LOGGER.info("SmackIntegrationTestFramework [" + testRunResult.testRunId + ']' + ": Starting\nSmack version: " + Smack.getVersion()); if (config.debugger != Configuration.Debugger.none) { // JUL Debugger will not print any information until configured to print log messages of // level FINE diff --git a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/SmackRepl.java b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/SmackRepl.java index 8573ae65a..6181d03a0 100644 --- a/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/SmackRepl.java +++ b/smack-repl/src/main/java/org/igniterealtime/smack/smackrepl/SmackRepl.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Florian Schmaus + * Copyright 2016-2020 Florian Schmaus * * This file is part of smack-repl. * @@ -20,13 +20,13 @@ */ package org.igniterealtime.smack.smackrepl; -import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Smack; import org.jivesoftware.smack.util.dns.javax.JavaxResolver; public class SmackRepl { public static void init() { - SmackConfiguration.getVersion(); + Smack.ensureInitialized(); // smack-repl also pulls in smack-resolver-minidns which has higher precedence the smack-resolver-javax but // won't work on Java SE platforms. Therefore explicitly setup JavaxResolver. JavaxResolver.setup(); From afd18f95c94842c28571d0ee4d18a4ea2a7feea9 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Sun, 8 Nov 2020 23:01:39 +0100 Subject: [PATCH 13/15] Add Smack.getNoticeStream() and 'License' section to README --- README.md | 9 +++ smack-core/build.gradle | 1 + .../java/org/jivesoftware/smack/Smack.java | 11 ++++ .../org/jivesoftware/smack/SmackTest.java | 57 +++++++++++++++++++ smack-java8-full/build.gradle | 2 +- 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 smack-core/src/test/java/org/jivesoftware/smack/SmackTest.java diff --git a/README.md b/README.md index 1e22423ce..4ffef04bd 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,15 @@ Start with having a look at the **[Documentation]** and the **[Javadoc]**. Instructions on how to use Smack in your Java or Android project are provided in the [Smack Readme and Upgrade Guide](https://igniterealtime.org/projects/smack/readme). +License +------- + +Most of Smack is governed by the Apache License 2.0 (SPDX License Identifier: Apache 2.0). This license requires that the contents of a NOICE text file are shown "…within a display generated by the Derivative Works, if and wherever such third-party notices normally appear.". + +Smack comes which such a NOTICE file. Moreover, since `smack-core` is licensed under the Apache License 2.0, the conditions apply to every project using Smack. The content of Smack's NOTICE file can conveniently be retrieved using `Smack.getNoticeStream()`. + +Some subprojects of Smack are governed by other licenses. Please refer to the individual subprojects. + Professional Services --------------------- diff --git a/smack-core/build.gradle b/smack-core/build.gradle index 9a8bd757d..b0cd79641 100644 --- a/smack-core/build.gradle +++ b/smack-core/build.gradle @@ -33,6 +33,7 @@ dependencies { testFixturesApi "org.assertj:assertj-core:3.11.1" testFixturesApi "org.xmlunit:xmlunit-assertj:$xmlUnitVersion" testFixturesApi 'org.hamcrest:hamcrest-library:2.2' + testFixturesApi 'com.google.guava:guava:28.2-jre' } class CreateFileTask extends DefaultTask { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/Smack.java b/smack-core/src/main/java/org/jivesoftware/smack/Smack.java index f59ea632f..690b9bb2d 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/Smack.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/Smack.java @@ -16,12 +16,17 @@ */ package org.jivesoftware.smack; +import java.io.InputStream; import java.util.logging.Logger; public class Smack { private static final Logger LOGGER = Logger.getLogger(Smack.class.getName()); + private static final String SMACK_ORG = "org.jivesoftware"; + + public static final String SMACK_PACKAGE = SMACK_ORG + ".smack"; + /** * Returns the Smack version information, eg "1.3.0". * @@ -31,6 +36,12 @@ public class Smack { return SmackInitialization.SMACK_VERSION; } + private static final String NOTICE_RESOURCE = SMACK_PACKAGE + "/NOTICE"; + + public static InputStream getNoticeStream() { + return ClassLoader.getSystemResourceAsStream(NOTICE_RESOURCE); + } + public static void ensureInitialized() { if (SmackConfiguration.isSmackInitialized()) { return; diff --git a/smack-core/src/test/java/org/jivesoftware/smack/SmackTest.java b/smack-core/src/test/java/org/jivesoftware/smack/SmackTest.java new file mode 100644 index 000000000..7dd49cc39 --- /dev/null +++ b/smack-core/src/test/java/org/jivesoftware/smack/SmackTest.java @@ -0,0 +1,57 @@ +/** + * + * Copyright 2020 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.smack; + +import static org.junit.Assert.assertTrue; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Set; + +import com.google.common.collect.Sets; +import org.junit.jupiter.api.Test; + +public class SmackTest { + + @Test + public void getNoticeStreamTest() throws IOException { + Set expectedStrings = Sets.newHashSet( + "Florian Schmaus" + , "Paul Schaub" + ); + int maxLineLength = 0; + + try (InputStream inputStream = Smack.getNoticeStream()) { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + + while (reader.ready()) { + String line = reader.readLine(); + + int lineLength = line.length(); + maxLineLength = Math.max(maxLineLength, lineLength); + + expectedStrings.removeIf(s -> s.equals(line)); + } + } + + assertTrue(expectedStrings.isEmpty()); + assertTrue(maxLineLength < 60); + } +} diff --git a/smack-java8-full/build.gradle b/smack-java8-full/build.gradle index 5292c5cff..47253d4e6 100644 --- a/smack-java8-full/build.gradle +++ b/smack-java8-full/build.gradle @@ -14,7 +14,7 @@ dependencies { api project(':smack-resolver-minidns-dox') api project(':smack-tcp') - testImplementation 'com.google.guava:guava:28.2-jre' + testImplementation(testFixtures(project(":smack-core"))) testImplementation 'org.jgrapht:jgrapht-io:1.3.1' } From e6236b0c21c459cf0cce2141149f8ee902e3bd82 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 9 Nov 2020 08:54:18 +0100 Subject: [PATCH 14/15] Smack 4.4.0-rc1 --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 2bc346d33..6e767c145 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.4.0-beta3-SNAPSHOT +4.4.0-rc1 From e117f431bc1571c9cc5dd3db90a9d1ec1db5ce9d Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Mon, 9 Nov 2020 09:32:29 +0100 Subject: [PATCH 15/15] Smack 4.4.0-rc2-SNAPSHOT --- version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version b/version index 6e767c145..0bc786cbf 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.4.0-rc1 +4.4.0-rc2-SNAPSHOT