diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..cf36d4182 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1855 @@ +# Smack Changelog + +# 4.4.5 -- 2022-03-02 + +### Bug + +- [SMACK-923](https://igniterealtime.atlassian.net/browse/SMACK-923) Smack reactor should immediately handle scheduled actions that are due in zero milliseconds +- [SMACK-921](https://igniterealtime.atlassian.net/browse/SMACK-921) XmlStringBuilder.attribute\(String name, Enum value\) should use value.toString\(\) \(and not value.name\(\)\) +- [SMACK-920](https://igniterealtime.atlassian.net/browse/SMACK-920) SASL GSSAPI mechanism should be marked to not require a password +- [SMACK-918](https://igniterealtime.atlassian.net/browse/SMACK-918) Self presences in MUC are no longer handled correctly + +### Improvement + +- [SMACK-922](https://igniterealtime.atlassian.net/browse/SMACK-922) Support 'optional text' and arbitrary element in Jingle 'reason' element +- [SMACK-919](https://igniterealtime.atlassian.net/browse/SMACK-919) PubSub ItemProvider should ignore character data in s + +## 4.4.4 -- 2021-11-01 + +### Bug + +- [SMACK-916](https://igniterealtime.atlassian.net/browse/SMACK-916) - + XMPPErrorException.stanza is missing a getter method +- [SMACK-915](https://igniterealtgime.atlassian.net/browse/SMACK-915) - + Smack does not process MUC destroy message if they contain + \'status\' +- [SMACK-914](https://igniterealtime.atlassian.net/browse/SMACK-914) - + MultiUserChat may be become unjoinable due to a race condition +- [SMACK-913](https://igniterealtime.atlassian.net/browse/SMACK-913) - + MultiUserChat.serviceSupportsStableIds\\(\\) may throws a + NullPointerException +- [SMACK-912](https://igniterealtime.atlassian.net/browse/SMACK-912) - + Smack does not start the local SOCKS5 proxy automatically +- [SMACK-910](https://igniterealtime.atlassian.net/browse/SMACK-910) - + FormNode and FormNodeProvide should handle non-existent DataForm +- [SMACK-909](https://igniterealtime.atlassian.net/browse/SMACK-909) - + Must use the raw character data of a form field in entity caps hash + calculation + +## 4.4.3 -- 2021-07-06 + +### Bug + +- [SMACK-905](https://igniterealtime.atlassian.net/browse/SMACK-905) - + The class org.jivesoftware.smackx.offline.packet.OfflineMessageInfo + has no ELEMENT, NAMESPACE or QNAME member +- [SMACK-907](https://igniterealtime.atlassian.net/browse/SMACK-907) - + Possible NPE in MultipleRecipientManager + +## 4.4.2 -- 2021-03-25 + +### Bug + +- [SMACK-903](https://igniterealtime.atlassian.net/browse/SMACK-903) - + StaxXmlPullParser.getNamespace() may throws IllegalArgumentException +- [SMACK-904](https://igniterealtime.atlassian.net/browse/SMACK-904) - + XEP-0096 file transfer fails because of a hidden ClastCastException + +## 4.4.1 -- 2021-03-03 + +### Bug + +- [SMACK-895](https://igniterealtime.atlassian.net/browse/SMACK-895) - + BoBIQ#getIQChildElementBuilder throws NPE when the BoB data does not + contain 'max-age'. +- [SMACK-896](https://igniterealtime.atlassian.net/browse/SMACK-896) - + BoBDataExtension is missing getter for BoBData and ContentId +- [SMACK-897](https://igniterealtime.atlassian.net/browse/SMACK-897) - + DirectoryRosterStore.readEntry() should also catch + IllegalArgumentException +- [SMACK-898](https://igniterealtime.atlassian.net/browse/SMACK-898) - + AbstractProvider should also consider TypeVariable +- [SMACK-899](https://igniterealtime.atlassian.net/browse/SMACK-899) - + NullPointerException in EntityCapsManager.addCapsExtension +- [SMACK-900](https://igniterealtime.atlassian.net/browse/SMACK-900) - + NPE in DataForm.Builder.addItem() +- [SMACK-902](https://igniterealtime.atlassian.net/browse/SMACK-902) - + DataFormProvider should retrieve the type of fields from + \ elements if possible + +### Improvement + +- [SMACK-901](https://igniterealtime.atlassian.net/browse/SMACK-901) - + BoBDataExtension.from() should also allow IQs + +## 4.4.0 -- 2020-12-06 + +### Bug + +- [SMACK-561](https://igniterealtime.atlassian.net/browse/SMACK-561) - + Smack should not reply with multiple stream types after stream + initiation is offered +- [SMACK-624](https://igniterealtime.atlassian.net/browse/SMACK-624) - + AdHocCommandManager\'s session sweeping thread does never stop +- [SMACK-729](https://igniterealtime.atlassian.net/browse/SMACK-729) - + Not all providers from smack-legacy.jar are loaded +- [SMACK-770](https://igniterealtime.atlassian.net/browse/SMACK-770) - + There is no Bits of Binary Extension Element provider registered +- [SMACK-848](https://igniterealtime.atlassian.net/browse/SMACK-848) - + Make MultiUserChat.leave() wait for response +- [SMACK-874](https://igniterealtime.atlassian.net/browse/SMACK-874) - + PacketParserUtilsTest#invalidXMLInMessageBody() fails on non-english + machines +- [SMACK-881](https://igniterealtime.atlassian.net/browse/SMACK-881) - + Deadlock between reader and writer if Stream Mangement unacked + stanza queue is full +- [SMACK-888](https://igniterealtime.atlassian.net/browse/SMACK-888) - + MUC roomDestroyed() callback is not invoked + +### New Feature + +- [SMACK-257](https://igniterealtime.atlassian.net/browse/SMACK-257) - + Add support for XEP-0118: User Tune +- [SMACK-636](https://igniterealtime.atlassian.net/browse/SMACK-636) - + Add support for XEP-0319: Last User Interaction in Presence +- [SMACK-743](https://igniterealtime.atlassian.net/browse/SMACK-743) - + Add support for XEP-0384: OMEMO Encryption +- [SMACK-801](https://igniterealtime.atlassian.net/browse/SMACK-801) - + Update Smack to Java 8 +- [SMACK-824](https://igniterealtime.atlassian.net/browse/SMACK-824) - + Add support for XEP-0221: Data Forms Media Element +- [SMACK-862](https://igniterealtime.atlassian.net/browse/SMACK-862) - + Add support for XEP-0418: DNS Queries over XMPP (DoX) +- [SMACK-871](https://igniterealtime.atlassian.net/browse/SMACK-871) - + Add support for XEP-0350: Data Forms Geolocation Element +- [SMACK-872](https://igniterealtime.atlassian.net/browse/SMACK-872) - + Add support for XEP-0315: Data Forms XML Element +- [SMACK-878](https://igniterealtime.atlassian.net/browse/SMACK-878) - + Add support for XEP-0328: JID Prep +- [SMACK-884](https://igniterealtime.atlassian.net/browse/SMACK-884) - + Add support for XEP-0422: Message Fastening +- [SMACK-885](https://igniterealtime.atlassian.net/browse/SMACK-885) - + Add support for XEP-0420 Stanza Content Encryption +- [SMACK-889](https://igniterealtime.atlassian.net/browse/SMACK-889) - + Add support for XEP-0428: Fallback Indication + +### Improvement + +- [SMACK-591](https://igniterealtime.atlassian.net/browse/SMACK-591) - + Replace XPP3 by SmackXmlPullParser (wrapping Stax\'s XmlStreamReader + and XPP3 on Android) +- [SMACK-650](https://igniterealtime.atlassian.net/browse/SMACK-650) - + Enable Java8\'s javadoc doclint +- [SMACK-651](https://igniterealtime.atlassian.net/browse/SMACK-651) - + Perform sound cross-compilation: Use newer javac\'s \--release + feature +- [SMACK-718](https://igniterealtime.atlassian.net/browse/SMACK-718) - + Prevent extremely long reply timeouts from being set +- [SMACK-821](https://igniterealtime.atlassian.net/browse/SMACK-821) - + Make Forwarded a generic type +- [SMACK-822](https://igniterealtime.atlassian.net/browse/SMACK-822) - + Add API for XEP-0313 § 6.2 Advanced configuration via Ad-Hoc + commands +- [SMACK-825](https://igniterealtime.atlassian.net/browse/SMACK-825) - + Discourage Stanza.getExtension(String, String) in favor of + Stanza.getExtension(Class\) +- [SMACK-826](https://igniterealtime.atlassian.net/browse/SMACK-826) - + Add support for XEP-0373:\" OpenPGP for XMPP\" and XEP-0374: + \"OpenPGP for XMPP Instant Messaging\" +- [SMACK-828](https://igniterealtime.atlassian.net/browse/SMACK-828) - + Add support for XEP-0107: User Mood +- [SMACK-836](https://igniterealtime.atlassian.net/browse/SMACK-836) - + Save a ServiceDiscoveryManager instance in a private field of + MultiUserChatManger +- [SMACK-839](https://igniterealtime.atlassian.net/browse/SMACK-839) - + Provider.parse() should not throw a generic Exception, but instead + IOException and XmlPullParserException +- [SMACK-852](https://igniterealtime.atlassian.net/browse/SMACK-852) - + Message thread and subject should be designed and implemented as + ExtensionElements +- [SMACK-854](https://igniterealtime.atlassian.net/browse/SMACK-854) - + Rename smack-java7 to smack-java8 +- [SMACK-866](https://igniterealtime.atlassian.net/browse/SMACK-866) - + Remove all tabs from the source code and add checkstyle rule that + enforces no-tabs +- [SMACK-867](https://igniterealtime.atlassian.net/browse/SMACK-867) - + Extend HttpFileUploadManager by methods with InputStream parameter +- [SMACK-882](https://igniterealtime.atlassian.net/browse/SMACK-882) - + Add support for MUC status code 333 +- [SMACK-883](https://igniterealtime.atlassian.net/browse/SMACK-883) - + Add generic MUC callback for \"participant left\" caused by + unavailable presences +- [SMACK-890](https://igniterealtime.atlassian.net/browse/SMACK-890) - + Update Message Archive Management (XEP-0313) support to + urn:xmpp:mam:2 +- [SMACK-892](https://igniterealtime.atlassian.net/browse/SMACK-892) - + Smack performs unnecessary escaping in XML text + +### Task + +- [SMACK-750](https://igniterealtime.atlassian.net/browse/SMACK-750) - + Raise Smack\'s minimum required Android SDK level to 19 (Android + 4.4, Kit Kat, 2013-10) +- [SMACK-840](https://igniterealtime.atlassian.net/browse/SMACK-840) - + Remove smack-compression-jzlib, as it is obsolete (Smack uses Java 7 + de- and inflate API now) + +## 4.3.4 -- 2019-05-27 + +### Bug + +- [SMACK-861](https://igniterealtime.atlassian.net/browse/SMACK-861) - + Potential NPE in Roster.getPresencesInternal(BareJid) +- [SMACK-863](https://igniterealtime.atlassian.net/browse/SMACK-863) - + ServiceDiscoveryManger does not use the main identity, causing + setIdentity() to have no effect +- [SMACK-864](https://igniterealtime.atlassian.net/browse/SMACK-864) - + Potential Denial of Service (DOS) by remote entities caused by + unlimited threads for asynchronous operations +- [SMACK-865](https://igniterealtime.atlassian.net/browse/SMACK-865) - + Some Manager.getInsanceFor() methods are missing the + \'synchronized\' keyword +- [SMACK-868](https://igniterealtime.atlassian.net/browse/SMACK-868) - + XHTMLText.appendOpenBodyTag() produces invalid XML +- [SMACK-870](https://igniterealtime.atlassian.net/browse/SMACK-870) - + TLS X.509 certificate verification should be performed with the ACE + representation of the XMPP service domain when possible + +### Improvement + +- [SMACK-869](https://igniterealtime.atlassian.net/browse/SMACK-869) - + Exceptions in async tasks should not go uncaught to the call stack + to avoid program termination + +## 4.3.3 -- 2019-03-14 + +### Bug + +- [SMACK-856](https://igniterealtime.atlassian.net/browse/SMACK-856) - + Smack fails under JDK 11 because com.sun.jndi.dns.DnsContextFactory + is not inaccessible + +### Improvement + +- [SMACK-858](https://igniterealtime.atlassian.net/browse/SMACK-858) - + Dependency version specifier of jxmpp and MiniDNS include + alpha/beta/\... versions of the follow up version when Maven is used +- [SMACK-859](https://igniterealtime.atlassian.net/browse/SMACK-859) - + MultiUserChat enter() should reset the timeout of the collector + waiting for the final self presence to prevent timeouts for large + MUCs + +## 4.3.2 -- 2019-02-22 + +### Bug + +- [SMACK-842](https://igniterealtime.atlassian.net/browse/SMACK-842) - + The RFC 3920 xml-not-well-formed error condition should be handled + in stream error not a stanza error +- [SMACK-843](https://igniterealtime.atlassian.net/browse/SMACK-843) - + ManManager.pagePrevious() pages into the wrong direction +- [SMACK-844](https://igniterealtime.atlassian.net/browse/SMACK-844) - + Check if bounded unacknowledged stanzas queue is full before adding + to it to avoid IllegalStateException +- [SMACK-845](https://igniterealtime.atlassian.net/browse/SMACK-845) - + Ensure that IQ response \'to\' address and ID are set correctly +- [SMACK-846](https://igniterealtime.atlassian.net/browse/SMACK-846) - + XMPPTCPConnection does not wait for stream features after + authentication if compression is disabled +- [SMACK-848](https://igniterealtime.atlassian.net/browse/SMACK-848) - + Make MultiUserChat.leave() wait for response +- [SMACK-850](https://igniterealtime.atlassian.net/browse/SMACK-850) - + DeliveryReceiptManager should not send receipts with messages of + type \'groupchat\' +- [SMACK-855](https://igniterealtime.atlassian.net/browse/SMACK-855) - + XMPPTCPConnection sometimes has two writer threads running + +### Improvement + +- [SMACK-847](https://igniterealtime.atlassian.net/browse/SMACK-847) - + Make TCP socket connection attempt interruptable +- [SMACK-849](https://igniterealtime.atlassian.net/browse/SMACK-849) - + Smack Local SOCKS5 Proxy thread should be marked as daemon thread + +## 4.3.1 -- 2018-10-14 + +### Bug + +- [SMACK-833](https://igniterealtime.atlassian.net/browse/SMACK-833) - + XMLUtil.prettyFormatXml() throws on some Android devices + +### Improvement + +- [SMACK-829](https://igniterealtime.atlassian.net/browse/SMACK-829) - + Disconnect BOSH client on shutdown +- [SMACK-838](https://igniterealtime.atlassian.net/browse/SMACK-838) - + FormField.getFirstValue() throws IndexOutOfBoundsException if there + are no values + +## 4.3.0 -- 2018-08-02 + +### Bug + +- [SMACK-759](https://igniterealtime.atlassian.net/browse/SMACK-759) - + PubSubManager.getLeafNode() throws + PubSubAssertionError.DiscoInfoNodeAssertionError if node exists but + its not a PubSub Node +- [SMACK-814](https://igniterealtime.atlassian.net/browse/SMACK-814) - + NPE when using Node.getAffiliationsAsOwner() +- [SMACK-815](https://igniterealtime.atlassian.net/browse/SMACK-815) - + XEP-0184: DeliveryReceipt requires ID, although the XEP defines it + as optional attribute +- [SMACK-818](https://igniterealtime.atlassian.net/browse/SMACK-818) - + EntityCapsManager sends presences with multiple CapsExtension + causing disco#info lookup to fail +- [SMACK-819](https://igniterealtime.atlassian.net/browse/SMACK-819) - + ConcurrentModification Exception in MultiUserChatManager.java +- [SMACK-820](https://igniterealtime.atlassian.net/browse/SMACK-820) - + DNSUtil.setDaneProvider() does not set the DANE provider + +### Task + +- [SMACK-769](https://igniterealtime.atlassian.net/browse/SMACK-769) - + Rename XMPPError to StanzaError +- [SMACK-776](https://igniterealtime.atlassian.net/browse/SMACK-776) - + Remove deprecated reconnection callbacks in ConnectionListener + +### Improvement + +- [SMACK-761](https://igniterealtime.atlassian.net/browse/SMACK-761) - + Adopt ChatStateManager to new Chat API (chat2) +- [SMACK-812](https://igniterealtime.atlassian.net/browse/SMACK-812) - + Enable ModifierOrder checkstyle check +- [SMACK-816](https://igniterealtime.atlassian.net/browse/SMACK-816) - + SimplePayload should infer the XML element name and namespace + +## 4.2.4 -- 2018-04-15 + +### Bug + +- [SMACK-804](https://igniterealtime.atlassian.net/browse/SMACK-804) - + ServiceAdministrationManager does not use correct form actions +- [SMACK-805](https://igniterealtime.atlassian.net/browse/SMACK-805) - + ServiceDiscoveryManager.findService() only considers the first + service by feature +- [SMACK-813](https://igniterealtime.atlassian.net/browse/SMACK-813) - + Smack uses hostname instead of XMPP service name for SNI + +### New Feature + +- [SMACK-794](https://igniterealtime.atlassian.net/browse/SMACK-794) - + Add support for XEP-0394: Message Markup +- [SMACK-795](https://igniterealtime.atlassian.net/browse/SMACK-795) - + Add support for XEP-0382: Spoiler messages +- [SMACK-799](https://igniterealtime.atlassian.net/browse/SMACK-799) - + Add support for XEP-0372: References +- [SMACK-800](https://igniterealtime.atlassian.net/browse/SMACK-800) - + Add support for XEP-0392: Consistent Color Generation + +### Improvement + +- [SMACK-802](https://igniterealtime.atlassian.net/browse/SMACK-802) - + Rename and deprecate: addPacketSendingListener(), + removePacketSendingListener(), addPacketInterceptor() and + removePacketInterceptor() +- [SMACK-809](https://igniterealtime.atlassian.net/browse/SMACK-809) - + Make Roster\'s non-roster presence map second-level map bounded + +## 4.2.3 -- 2018-02-07 + +### Bug + +- [SMACK-788](https://igniterealtime.atlassian.net/browse/SMACK-788) - + NullPointerException if hostAddresses is null +- [SMACK-789](https://igniterealtime.atlassian.net/browse/SMACK-789) - + AffiliationsExtension toXml() produces invalid XML +- [SMACK-790](https://igniterealtime.atlassian.net/browse/SMACK-790) - + Some HTTP File Upload elements are not correctly parsed and + serialized +- [SMACK-791](https://igniterealtime.atlassian.net/browse/SMACK-791) - + NumberFormatException in IpAddressUtil.isIPv4LiteralAddress +- [SMACK-796](https://igniterealtime.atlassian.net/browse/SMACK-796) - + SOCKS5 authentication erroneously uses \'user\' when it should use + \'passwd\', causes authentication to fail + +## 4.2.2 -- 2017-11-25 + +### Bug + +- [SMACK-775](https://igniterealtime.atlassian.net/browse/SMACK-775) - + Create callback interface for ReconnectionManager +- [SMACK-778](https://igniterealtime.atlassian.net/browse/SMACK-778) - + ReconnectionManager.reconnect() can throw NotConnectedException +- [SMACK-779](https://igniterealtime.atlassian.net/browse/SMACK-779) - + smack-android erroneously depends on smack-omemo and + smack-omemo-signal +- [SMACK-780](https://igniterealtime.atlassian.net/browse/SMACK-780) - + PushNotificationManager\'s isSupported logic does query the server, + whereas it should query the bare JID +- [SMACK-781](https://igniterealtime.atlassian.net/browse/SMACK-781) - + MiniDnsResolver does not correctly handle the case when NOERROR is + returned together with an empty answer section. +- [SMACK-782](https://igniterealtime.atlassian.net/browse/SMACK-782) - + MultiUserChat does not remove the subject listener causing a memory + leak +- [SMACK-783](https://igniterealtime.atlassian.net/browse/SMACK-783) - + InvitationRejectionListener fires multiple times +- [SMACK-784](https://igniterealtime.atlassian.net/browse/SMACK-784) - + StringUtils.numbersAndLetters has the numbers twice, resulting in a + lower entropy +- [SMACK-785](https://igniterealtime.atlassian.net/browse/SMACK-785) - + OfflineMessageManager.getMessages() does count the pending messages + incorrectly, causing an unnecessary delay +- [SMACK-786](https://igniterealtime.atlassian.net/browse/SMACK-786) - + Race condition when resuming a stream +- [SMACK-787](https://igniterealtime.atlassian.net/browse/SMACK-787) - + Presence.getPriority() may return Integer.MIN_VALUE. + +## 4.2.1 -- 2017-08-14 + +### Bug + +- [SMACK-749](https://igniterealtime.atlassian.net/browse/SMACK-749) - + SCRAM-SHA-1 and SCRAM-SHA-1-PLUS SASL mechanisms have the same + priority, causing SASL authentication failures +- [SMACK-755](https://igniterealtime.atlassian.net/browse/SMACK-755) - + DIGEST-MD5 sometimes causes malformed request server response +- [SMACK-756](https://igniterealtime.atlassian.net/browse/SMACK-756) - + IoTIsFriendResponse has invalid name and produces invalid XML +- [SMACK-759](https://igniterealtime.atlassian.net/browse/SMACK-759) - + PubSubManager.getLeafNode() throws + PubSubAssertionError.DiscoInfoNodeAssertionError if node exists but + its not a PubSub Node +- [SMACK-764](https://igniterealtime.atlassian.net/browse/SMACK-764) - + NPE in hashCode() in Occupant when jid is null +- [SMACK-766](https://igniterealtime.atlassian.net/browse/SMACK-766) - + Smack possibly includes \'ask\' attribute in roster items when + sending requests +- [SMACK-768](https://igniterealtime.atlassian.net/browse/SMACK-768) - + Smack throws NoResponse timeout when waiting for IQ although there + was a response +- [SMACK-771](https://igniterealtime.atlassian.net/browse/SMACK-771) - + XMPPTCPConnection should use KeyManagerFactory.getDefaultAlgorithm() + instead of KeyManagerFactory.getInstance(\"sunX509\"); +- [SMACK-772](https://igniterealtime.atlassian.net/browse/SMACK-772) - + HostAddress must deal with \'fqdn\' being null. +- [SMACK-773](https://igniterealtime.atlassian.net/browse/SMACK-773) - + Allow roster pushes from our full JID for backwards compatibility +- [SMACK-774](https://igniterealtime.atlassian.net/browse/SMACK-774) - + HTTP File Upload\'s SlotRequest metadata should be attributes not + child elements + +### New Feature + +- [SMACK-746](https://igniterealtime.atlassian.net/browse/SMACK-746) - + Add support for XEP-0380: Explicit Message Encryption +- [SMACK-758](https://igniterealtime.atlassian.net/browse/SMACK-758) - + Add support for XEP-0334: Message Processing Hints +- [SMACK-760](https://igniterealtime.atlassian.net/browse/SMACK-760) - + Smack does not allow custom extension elements in SM\'s \ + +### Improvement + +- [SMACK-752](https://igniterealtime.atlassian.net/browse/SMACK-752) - + XEP-0357 Push Notification enable IQ uses wrong form type: Should be + \'submit\' instead of \'form\' +- [SMACK-754](https://igniterealtime.atlassian.net/browse/SMACK-754) - + Allow MUC room subject changes from the MUCs bare JID +- [SMACK-777](https://igniterealtime.atlassian.net/browse/SMACK-777) - + MamManager should use the user\'s bare JID to check if MAM is + supported + +## 4.2.0 -- 2017-03-10 + +## Sub-task + +- [SMACK-639](https://igniterealtime.atlassian.net/browse/SMACK-639) - + Add support for pre-approved subscription requests (RFC 6121 § 3.4) + +### Bug + +- [SMACK-306](https://igniterealtime.atlassian.net/browse/SMACK-306) - + loadRosterOnLogin has non-trivial side effect on getRoster +- [SMACK-416](https://igniterealtime.atlassian.net/browse/SMACK-416) - + Refactor PEP to make it use the existing pubsub API. +- [SMACK-674](https://igniterealtime.atlassian.net/browse/SMACK-674) - + PubSub Affiliation extension element is missing \'jid\' attribute, + and is using wrong element name \'subscription\' +- [SMACK-682](https://igniterealtime.atlassian.net/browse/SMACK-682) - + Add support for \"XEP-0360: Nonzas (are not Stanzas)\" +- [SMACK-683](https://igniterealtime.atlassian.net/browse/SMACK-683) - + Using a Proxy with XMPPTCPConnection failes with \"SocketException: + Unconnected sockets not implemented\" +- [SMACK-691](https://igniterealtime.atlassian.net/browse/SMACK-691) - + Add support for MUCItem\'s Actor \'nick\' +- [SMACK-705](https://igniterealtime.atlassian.net/browse/SMACK-705) - + PubSub\'s Affiliation.getElementName() returns wrong name +- [SMACK-722](https://igniterealtime.atlassian.net/browse/SMACK-722) - + SASL X-OAUTH2 implementation incorrectly performs Base64 encoding + twice +- [SMACK-723](https://igniterealtime.atlassian.net/browse/SMACK-723) - + Support \"Caps Optimizations\" (XEP-0115 § 8.4) +- [SMACK-724](https://igniterealtime.atlassian.net/browse/SMACK-724) - + Do not re-use the Socket after connect() failed. +- [SMACK-725](https://igniterealtime.atlassian.net/browse/SMACK-725) - + ReconnectionManager should handle AlreadyConnectedException and + AlreadyLoggedInException not as failure +- [SMACK-741](https://igniterealtime.atlassian.net/browse/SMACK-741) - + Ad-hoc command \'note\' element \'type\' attribute should be treated + as optional +- [SMACK-745](https://igniterealtime.atlassian.net/browse/SMACK-745) - + Memory leak in MultiUserChat + +### New Feature + +- [SMACK-366](https://igniterealtime.atlassian.net/browse/SMACK-366) - + Add support for DNSSEC. +- [SMACK-610](https://igniterealtime.atlassian.net/browse/SMACK-610) - + Add support for XEP-0080: User Location +- [SMACK-619](https://igniterealtime.atlassian.net/browse/SMACK-619) - + Add roomDestroyed to MUC UserStatusListener +- [SMACK-625](https://igniterealtime.atlassian.net/browse/SMACK-625) - + Add support for XEP-313: Message Archive Management +- [SMACK-675](https://igniterealtime.atlassian.net/browse/SMACK-675) - + Add support for PubSub affiliation actions as owner +- [SMACK-677](https://igniterealtime.atlassian.net/browse/SMACK-677) - + Add support for SASL \'authzid\' (Authorization Identity) +- [SMACK-690](https://igniterealtime.atlassian.net/browse/SMACK-690) - + Add support for DNS-Based Authentication of Named Entities (DANE, + RFC 6698) +- [SMACK-731](https://igniterealtime.atlassian.net/browse/SMACK-731) - + Add support for XEP-0191: Blocking Command +- [SMACK-732](https://igniterealtime.atlassian.net/browse/SMACK-732) - + Smack should be able to handle \"single equals sign\" SASL responses +- [SMACK-740](https://igniterealtime.atlassian.net/browse/SMACK-740) - + Add support for Multi-User Chat Light +- [SMACK-742](https://igniterealtime.atlassian.net/browse/SMACK-742) - + Add support for XEP-0133: Service Administration +- [SMACK-747](https://igniterealtime.atlassian.net/browse/SMACK-747) - + Add support for XEP-0363: HTTP File Upload + +### Task + +- [SMACK-638](https://igniterealtime.atlassian.net/browse/SMACK-638) - + Call connection creation listeners from within + AbstractXMPPConnection\'s constructor +- [SMACK-644](https://igniterealtime.atlassian.net/browse/SMACK-644) - + Throw exception if account creation or password change is performed + over insecure connections +- [SMACK-655](https://igniterealtime.atlassian.net/browse/SMACK-655) - + Enable StreamManagement by default + +### Improvement + +- [SMACK-372](https://igniterealtime.atlassian.net/browse/SMACK-372) - + Make package protected methods in PEPItem public +- [SMACK-572](https://igniterealtime.atlassian.net/browse/SMACK-572) - + Rejoin MUC rooms after reconnect +- [SMACK-628](https://igniterealtime.atlassian.net/browse/SMACK-628) - + Rework Roster handling with anonymous connections +- [SMACK-629](https://igniterealtime.atlassian.net/browse/SMACK-629) - + Rework how Smack handles anonymous connections +- [SMACK-631](https://igniterealtime.atlassian.net/browse/SMACK-631) - + Improve ParsingExceptionCallback, allow it to be a functional + interface +- [SMACK-632](https://igniterealtime.atlassian.net/browse/SMACK-632) - + Make Smack interruptible +- [SMACK-633](https://igniterealtime.atlassian.net/browse/SMACK-633) - + Allow clean and graceful disconnects (stream closing) +- [SMACK-634](https://igniterealtime.atlassian.net/browse/SMACK-634) - + Use jxmpp-jid, add Jid class to replace String\'s being used as JIDs +- [SMACK-646](https://igniterealtime.atlassian.net/browse/SMACK-646) - + Add support for MUC roomnick rewrite +- [SMACK-647](https://igniterealtime.atlassian.net/browse/SMACK-647) - + Don\'t automatically call login() on connect() if the connection was + authenticated before +- [SMACK-648](https://igniterealtime.atlassian.net/browse/SMACK-648) - + Improve MultiUserChat API +- [SMACK-657](https://igniterealtime.atlassian.net/browse/SMACK-657) - + Rename RosterEntry.getStatus and RosterPacket.ItemStatus to + ItemAskStatus +- [SMACK-663](https://igniterealtime.atlassian.net/browse/SMACK-663) - + Roster should be fully loaded when + Roster.getInstanceFor(XMPPConnection) is called with a authenticated + connection +- [SMACK-665](https://igniterealtime.atlassian.net/browse/SMACK-665) - + Rename \'serviceName\' to \'xmppServiceDomain\' +- [SMACK-666](https://igniterealtime.atlassian.net/browse/SMACK-666) - + Typo in \'RosterEntries.rosterEntires()\', change to + \'RosterEntries.rosterEntries()\' +- [SMACK-703](https://igniterealtime.atlassian.net/browse/SMACK-703) - + Limit the stored presences of entities not in Roster +- [SMACK-704](https://igniterealtime.atlassian.net/browse/SMACK-704) - + Pass down Message stanza in ChatStateListener +- [SMACK-711](https://igniterealtime.atlassian.net/browse/SMACK-711) - + Improve the logging of TCP connection attempts. +- [SMACK-720](https://igniterealtime.atlassian.net/browse/SMACK-720) - + Improve support for Tor and Hidden Services. +- [SMACK-721](https://igniterealtime.atlassian.net/browse/SMACK-721) - + Report illegal Stream Management states to avoid OOM Exception +- [SMACK-727](https://igniterealtime.atlassian.net/browse/SMACK-727) - + Add partial support for the IoT XEPs (XEP-0323, -0324, -0325, -0347) +- [SMACK-733](https://igniterealtime.atlassian.net/browse/SMACK-733) - + Handle outgoing \'unavailable\' Presences in Roster +- [SMACK-736](https://igniterealtime.atlassian.net/browse/SMACK-736) - + Add support for Chat Markers (XEP-0333) +- [SMACK-737](https://igniterealtime.atlassian.net/browse/SMACK-737) - + Add support for Bits of Binary (XEP-0231) +- [SMACK-738](https://igniterealtime.atlassian.net/browse/SMACK-738) - + Add support for Push Notifications (XEP-0357) + +## 4.1.9 -- 2016-11-19 + +### Bug + +- [SMACK-739](https://igniterealtime.atlassian.net/browse/SMACK-739) - + Smack starts SASL step without TLS in case STARTTLS is stripped even + if SecurityMode.Required is used +- [SMACK-735](https://igniterealtime.atlassian.net/browse/SMACK-735) - + Smack sometimes sends invalid SCRAM-SHA1 nonce + +## 4.1.8 -- 2016-07-30 + +### Bug + +- [SMACK-722](https://igniterealtime.atlassian.net/browse/SMACK-722) - + SASL X-OAUTH2 implementation incorrectly performs Base64 encoding + twice +- [SMACK-724](https://igniterealtime.atlassian.net/browse/SMACK-724) - + Do not re-use the Socket after connect() failed. +- [SMACK-725](https://igniterealtime.atlassian.net/browse/SMACK-725) - + ReconnectionManager should handle AlreadyConnectedException and + AlreadyLoggedInException not as failure +- [SMACK-726](https://igniterealtime.atlassian.net/browse/SMACK-726) - + \'purge\' and \'remove\' IQ of XEP-0013 must be of type \'set\' + +## 4.1.7 -- 2016-04-14 + +### Bug + +- [SMACK-712](https://igniterealtime.atlassian.net/browse/SMACK-712) - + XMPPTCPConnection\'s setEnabledSSL(Protocols\|Ciphers) has no effect +- [SMACK-716](https://igniterealtime.atlassian.net/browse/SMACK-716) - + EntityTimeManager.getTime() does not set the recipients JID +- [SMACK-719](https://igniterealtime.atlassian.net/browse/SMACK-719) - + XMPPError should use Locale.US in toUpperCase() + +### Improvement + +- [SMACK-715](https://igniterealtime.atlassian.net/browse/SMACK-715) - + Add Roster.setRosterLoadedAtLoginDefault(boolean) + +## 4.1.6 -- 2016-01-23 + +### Bug + +- [SMACK-705](https://igniterealtime.atlassian.net/browse/SMACK-705) - + PubSub\'s Affiliation.getElementName() returns wrong name +- [SMACK-706](https://igniterealtime.atlassian.net/browse/SMACK-706) - + Smack may sends \ and \ twice if Stream Management + is used and a previous SM state exists +- [SMACK-707](https://igniterealtime.atlassian.net/browse/SMACK-707) - + Infinite loop of NullPointerExceptions in Socks5Proxy +- [SMACK-708](https://igniterealtime.atlassian.net/browse/SMACK-708) - + DeliveryReceipt(Manager) should ensure that receipts (and requests) + have an ID set +- [SMACK-709](https://igniterealtime.atlassian.net/browse/SMACK-709) - + Don\'t request delivery receipts for messages without a body +- [SMACK-710](https://igniterealtime.atlassian.net/browse/SMACK-710) - + SASL DIGEST-MD5 backslash must be quoted + +## 4.1.5 -- 2015-11-22 + +### Bug + +- [SMACK-698](https://igniterealtime.atlassian.net/browse/SMACK-698) - + Time creates invalid XML +- [SMACK-700](https://igniterealtime.atlassian.net/browse/SMACK-700) - + Duplicate stanzas in unacknowledgedStanzas queue when stream is + resumed +- [SMACK-702](https://igniterealtime.atlassian.net/browse/SMACK-702) - + RejectedExecutionException in AbstractXMPPConnection.processPacket() + causes connection Termination + +## 4.1.4 -- 2015-09-14 + +### Bug + +- [SMACK-688](https://igniterealtime.atlassian.net/browse/SMACK-688) - + Reset carbons state if session got not resumed or cleanly + disconnected +- [SMACK-689](https://igniterealtime.atlassian.net/browse/SMACK-689) - + PEPPubSub creates malformed XML +- [SMACK-693](https://igniterealtime.atlassian.net/browse/SMACK-693) - + MultiUserChat\'s UserStatusListener is not getting triggered +- [SMACK-695](https://igniterealtime.atlassian.net/browse/SMACK-695) - + JSON and GCM parser does an erroneous extra next() +- [SMACK-697](https://igniterealtime.atlassian.net/browse/SMACK-697) - + PrivacyListManager should handle the case where not default and + active list are currently set + +### Improvement + +- [SMACK-686](https://igniterealtime.atlassian.net/browse/SMACK-686) - + Provide a hint that connect() needs to be called prior login() in + NotConnectedException +- [SMACK-687](https://igniterealtime.atlassian.net/browse/SMACK-687) - + Update to jxmpp 0.4.2 +- [SMACK-696](https://igniterealtime.atlassian.net/browse/SMACK-696) - + Drop stream state after stream error + +## 4.1.3 -- 2015-07-15 + +### Bug + +- [SMACK-679](https://igniterealtime.atlassian.net/browse/SMACK-679) - + Memory leak in Socks5BytestreamManager. Should use weak map for + \'managers\' +- [SMACK-680](https://igniterealtime.atlassian.net/browse/SMACK-680) - + XHTML bodies are un-escaped after parsing +- [SMACK-681](https://igniterealtime.atlassian.net/browse/SMACK-681) - + Roster presence callbacks may not be invoked right after login + +## 4.1.2 -- 2015-06-27 + +### Bug + +- [SMACK-664](https://igniterealtime.atlassian.net/browse/SMACK-664) - + Invalid IQ error response to OfferRequestPacket and + OfferRevokePacket +- [SMACK-668](https://igniterealtime.atlassian.net/browse/SMACK-668) - + ReconnectionManager\'s value of \'attempts\' is not reset after + successful reconnection +- [SMACK-669](https://igniterealtime.atlassian.net/browse/SMACK-669) - + Only add Entity Capabilities extension to available presences +- [SMACK-670](https://igniterealtime.atlassian.net/browse/SMACK-670) - + SASLMechanism.authenticate should treat an empty byte array like + \'null\' byte array +- [SMACK-672](https://igniterealtime.atlassian.net/browse/SMACK-672) - + Memory leak caused by RosterGroup declaring a strong reference to + XMPPConnection +- [SMACK-673](https://igniterealtime.atlassian.net/browse/SMACK-673) - + VCard API does not support all elements +- [SMACK-676](https://igniterealtime.atlassian.net/browse/SMACK-676) - + ConcurrentModificationException in ServerPingWithAlarmManager +- [SMACK-678](https://igniterealtime.atlassian.net/browse/SMACK-678) - + Login hangs if starttls advertised, but security is set to + \'disabled\' and compression is also advertised + +### Improvement + +- [SMACK-667](https://igniterealtime.atlassian.net/browse/SMACK-667) - + Request Stream Mangement Acknowledgement after re-sending unack\'ed + stanzas after stream resumption +- [SMACK-671](https://igniterealtime.atlassian.net/browse/SMACK-671) - + Don\'t disable Scoks5BytestreamManager on connection termination + +## 4.1.1 -- 2015-05-09 + +### Bug + +- [SMACK-649](https://igniterealtime.atlassian.net/browse/SMACK-649) - + DIGEST-MD5 challenge/response parsing must handle linear white + spaces after the comma +- [SMACK-652](https://igniterealtime.atlassian.net/browse/SMACK-652) - + SynchronizationPoint should use signalAll +- [SMACK-653](https://igniterealtime.atlassian.net/browse/SMACK-653) - + Integer overflow if both client and server don\'t specify a max + resumption time +- [SMACK-654](https://igniterealtime.atlassian.net/browse/SMACK-654) - + isSmResumptionPossible() returns wrong values +- [SMACK-656](https://igniterealtime.atlassian.net/browse/SMACK-656) - + DeliveryReceipts auto add should use packet interceptors and should + not be requested for messages with ACKs. +- [SMACK-659](https://igniterealtime.atlassian.net/browse/SMACK-659) - + Memory leak caused by RosterEntry declaring a strong reference to + XMPPConnection +- [SMACK-660](https://igniterealtime.atlassian.net/browse/SMACK-660) - + ReconnectionManager\'s RANDOM_INCREASING_DELAY is erroneously using + a fixed value. +- [SMACK-661](https://igniterealtime.atlassian.net/browse/SMACK-661) - + Add method to set ProxyInfo in ConnectionConfiguration.Builder +- [SMACK-662](https://igniterealtime.atlassian.net/browse/SMACK-662) - + RosterEntry.setName() does not change the name + +## 4.1.0 -- 2015-03-29 + +## Sub-task + +- [SMACK-398](https://igniterealtime.atlassian.net/browse/SMACK-398) - + Implement SCRAM support + +### Bug + +- [SMACK-65](https://igniterealtime.atlassian.net/browse/SMACK-65) - + Packet parsing should look for depth +- [SMACK-237](https://igniterealtime.atlassian.net/browse/SMACK-237) - + Handle more vCard values (XEP-0054) +- [SMACK-383](https://igniterealtime.atlassian.net/browse/SMACK-383) - + Allow the garbage collection of all object instances of a closed and + unreferenced connection +- [SMACK-424](https://igniterealtime.atlassian.net/browse/SMACK-424) - + Add a MultiUserChat.presenceChanged callback method to be informed + if a presence within a MUC has changed (joined, leaved, status + change) +- [SMACK-542](https://igniterealtime.atlassian.net/browse/SMACK-542) - + MUC: RoomInfo should hold more data if the result contains a + FORM_TYPE field +- [SMACK-549](https://igniterealtime.atlassian.net/browse/SMACK-549) - + MUCUser#getStatus should be a List +- [SMACK-564](https://igniterealtime.atlassian.net/browse/SMACK-564) - + Some tests fail with Java 8 +- [SMACK-570](https://igniterealtime.atlassian.net/browse/SMACK-570) - + Smack does not support resourceparts which contain the \'@\' + character. +- [SMACK-571](https://igniterealtime.atlassian.net/browse/SMACK-571) - + Don\'t remove the MUC listeners after a disconnect() , keep state of + Connection between disconnect() and connect()/login() +- [SMACK-573](https://igniterealtime.atlassian.net/browse/SMACK-573) - + MessageEventManager treats error replies as message events +- [SMACK-583](https://igniterealtime.atlassian.net/browse/SMACK-583) - + PacketListeners may not be invoked in delivery order +- [SMACK-585](https://igniterealtime.atlassian.net/browse/SMACK-585) - + XMPPTCPConnection does not set \'host\' and \'port\' +- [SMACK-590](https://igniterealtime.atlassian.net/browse/SMACK-590) - + Don\'t use IQReplyFilter for the bind set/result exchange +- [SMACK-597](https://igniterealtime.atlassian.net/browse/SMACK-597) - + PingManager.getLastReceivedPong() always returns -1 +- [SMACK-604](https://igniterealtime.atlassian.net/browse/SMACK-604) - + MUCUser must support multiple status codes +- [SMACK-620](https://igniterealtime.atlassian.net/browse/SMACK-620) - + Smack should use a safe SAX parser, e.g. with entity reference + expansion disabled +- [SMACK-635](https://igniterealtime.atlassian.net/browse/SMACK-635) - + Typo DNSUtil.init() prevents DNS SRV lookups to fail in some cases +- [SMACK-643](https://igniterealtime.atlassian.net/browse/SMACK-643) - + Smack should not set the service name to the vale of the \'from\' + attribute of the opening stream element received from the service + +### Improvement + +- [SMACK-340](https://igniterealtime.atlassian.net/browse/SMACK-340) - + Should make the wait/timeouts on SASL authentication configurable. +- [SMACK-402](https://igniterealtime.atlassian.net/browse/SMACK-402) - + Update obsolete \"Message Delivery Receipts\" support from + (JEP\|XEP)-0022 to XEP-0184 +- [SMACK-453](https://igniterealtime.atlassian.net/browse/SMACK-453) - + Add support for all primitive types in + IntrospectionProvider.decode() +- [SMACK-521](https://igniterealtime.atlassian.net/browse/SMACK-521) - + Clear PacketWriters queue when the connection is shut down +- [SMACK-532](https://igniterealtime.atlassian.net/browse/SMACK-532) - + Evaluate if its possible to guarantee the order of listeners by + using a LinkedHashMap +- [SMACK-566](https://igniterealtime.atlassian.net/browse/SMACK-566) - + Create public method that parses Strings/CharSequences to messages, + IQs and presence instances +- [SMACK-587](https://igniterealtime.atlassian.net/browse/SMACK-587) - + Subprojects should uses versions when importing the OSGi smack-core + components +- [SMACK-595](https://igniterealtime.atlassian.net/browse/SMACK-595) - + Add an API to send a stanza and wait asynchronously for a response +- [SMACK-599](https://igniterealtime.atlassian.net/browse/SMACK-599) - + Provide string messages to all exceptions thrown by Smack +- [SMACK-600](https://igniterealtime.atlassian.net/browse/SMACK-600) - + RoomInfo Class should add the information from the Identity element. +- [SMACK-608](https://igniterealtime.atlassian.net/browse/SMACK-608) - + Add support for XMPP error conditions text +- [SMACK-622](https://igniterealtime.atlassian.net/browse/SMACK-622) - + Add support for \'optional\' in session stream features +- [SMACK-626](https://igniterealtime.atlassian.net/browse/SMACK-626) - + Add support for \'ofrom\' Extended Stanza Addressing type +- [SMACK-627](https://igniterealtime.atlassian.net/browse/SMACK-627) - + Smack should allow null usernames under certain circumstances +- [SMACK-645](https://igniterealtime.atlassian.net/browse/SMACK-645) - + Roster should not leak internal state e.g. presences + +### New Feature + +- [SMACK-234](https://igniterealtime.atlassian.net/browse/SMACK-234) - + Add support for SASL EXTERNAL: PKI (Client SSL Cert) Support +- [SMACK-333](https://igniterealtime.atlassian.net/browse/SMACK-333) - + Implement XEP-0198: Stream Management +- [SMACK-378](https://igniterealtime.atlassian.net/browse/SMACK-378) - + Give access to the socket outside the XMPPconnection +- [SMACK-581](https://igniterealtime.atlassian.net/browse/SMACK-581) - + Add support for \"Result Set Management\" (XEP-59) +- [SMACK-607](https://igniterealtime.atlassian.net/browse/SMACK-607) - + Add support for XEP-0352: Client State Indication +- [SMACK-612](https://igniterealtime.atlassian.net/browse/SMACK-612) - + Add support for XEP-0141: Data Forms Layout +- [SMACK-621](https://igniterealtime.atlassian.net/browse/SMACK-621) - + Add support for XEP-0122: Data Forms Validation +- [SMACK-623](https://igniterealtime.atlassian.net/browse/SMACK-623) - + Add API to retrieve the subscriptions of a PubSub node as owner + +### Task + +- [SMACK-365](https://igniterealtime.atlassian.net/browse/SMACK-365) - + SmackConfiguration should only report errors if the file fails to + load, not when it fails to load for a specific classloader. +- [SMACK-371](https://igniterealtime.atlassian.net/browse/SMACK-371) - + Some MUC tasks are using stanza\'s as defined in an older version of + the spec. Fails to work on some servers. +- [SMACK-569](https://igniterealtime.atlassian.net/browse/SMACK-569) - + Move Message Event code to legacy subproject +- [SMACK-578](https://igniterealtime.atlassian.net/browse/SMACK-578) - + Remove decorators for \"Legacy Delayed Delivery\" (XEP-91) in favor + of Delayed Delivery (XEP-203) +- [SMACK-579](https://igniterealtime.atlassian.net/browse/SMACK-579) - + FileTransferManager and FileTransferNegoiator should use + WeakHashMaps and extend Manager +- [SMACK-582](https://igniterealtime.atlassian.net/browse/SMACK-582) - + Change ReceiptReceivedListener.onReceiptReceived parameters +- [SMACK-637](https://igniterealtime.atlassian.net/browse/SMACK-637) - + Move Roster and Chat code to new smack-im subproject + +## 4.0.7 -- 2015-02-20 + +### Bug + +- [SMACK-635](https://igniterealtime.atlassian.net/browse/SMACK-635) - + Typo DNSUtil.init() prevents DNS SRV lookups to fail in some cases +- [SMACK-643](https://igniterealtime.atlassian.net/browse/SMACK-643) - + Smack should not set the service name to the vale of the \'from\' + attribute of the opening stream element received from the service + +## 4.0.6 -- 2014-11-23 + +### Bug + +- [SMACK-616](https://igniterealtime.atlassian.net/browse/SMACK-616) - + Smack should fallback to using host with default port if DNS SRV + lookup fails +- [SMACK-617](https://igniterealtime.atlassian.net/browse/SMACK-617) - + Message Digest in EntityCapsManager should be synchronized + +## 4.0.5 -- 2014-10-22 + +### Bug + +- [SMACK-609](https://igniterealtime.atlassian.net/browse/SMACK-609) - + PingManager.ping(String, long) does not respect timeout +- [SMACK-613](https://igniterealtime.atlassian.net/browse/SMACK-613) - + Parsing exception causes infinite loop if the exception is not + thrown + +## 4.0.4 -- 2014-09-05 + +### Bug + +- [SMACK-596](https://igniterealtime.atlassian.net/browse/SMACK-596) - + Smack should load roster before sending the initial presence +- [SMACK-598](https://igniterealtime.atlassian.net/browse/SMACK-598) - + Smack should allow the empty string as content of message body + element +- [SMACK-601](https://igniterealtime.atlassian.net/browse/SMACK-601) - + PubSub ItemProvider does only process the outermost namespace + definition when creating PayloadItems +- [SMACK-602](https://igniterealtime.atlassian.net/browse/SMACK-602) - + PacketCollector must handle InterruptException +- [SMACK-603](https://igniterealtime.atlassian.net/browse/SMACK-603) - + XMPPError.Condition.equals() should be null-safe + +## 4.0.3 -- 2014-08-16 + +### Bug + +- [SMACK-589](https://igniterealtime.atlassian.net/browse/SMACK-589) - + FormField.Option toXML() produces malformed XML +- [SMACK-592](https://igniterealtime.atlassian.net/browse/SMACK-592) - + OfflineMessagesManager.getMessages() does send request before + collector is set up and could leak collector +- [SMACK-594](https://igniterealtime.atlassian.net/browse/SMACK-594) - + PrivateData Bookmarks.toXML() returns invalid XML + +### Improvement + +- [SMACK-539](https://igniterealtime.atlassian.net/browse/SMACK-539) - + Verify ConnectionConfiguration parameters +- [SMACK-588](https://igniterealtime.atlassian.net/browse/SMACK-588) - + Typo in org.jivesoftware.smackx.pubsub.ConfigureForm: + s/isSubscibe/isSubscribe/ +- [SMACK-593](https://igniterealtime.atlassian.net/browse/SMACK-593) - + Smack should prefer full flush over sync flush when using + compression + +## 4.0.2 -- 2014-07-27 + +### Improvement + +- [SMACK-576](https://igniterealtime.atlassian.net/browse/SMACK-576) - + smack-resolver-javax should become a OSGi ServiceComponent +- [SMACK-586](https://igniterealtime.atlassian.net/browse/SMACK-586) - + Extend API to configure a HostnameVerifier + +## 4.0.1 -- 2014-07-20 + +## Sub-task + +- [SMACK-346](https://igniterealtime.atlassian.net/browse/SMACK-346) - + Bug in return code for rejection handling in FileTransferManager + +### Bug + +- [SMACK-574](https://igniterealtime.atlassian.net/browse/SMACK-574) - + Documentation still refers at some places to Connection +- [SMACK-575](https://igniterealtime.atlassian.net/browse/SMACK-575) - + PingManager schedules pings after pingInterval when it should be use + nextPingIn instead +- [SMACK-577](https://igniterealtime.atlassian.net/browse/SMACK-577) - + Bookmarks and FormField toXml() methods do not properly escape XML +- [SMACK-583](https://igniterealtime.atlassian.net/browse/SMACK-583) - + PacketListeners may not be invoked in delivery order + +### Improvement + +- [SMACK-576](https://igniterealtime.atlassian.net/browse/SMACK-576) - + smack-resolver-javax should become a OSGi ServiceComponent + +### New Feature + +- [SMACK-580](https://igniterealtime.atlassian.net/browse/SMACK-580) - + Add support for retrieving a PubSub node\'s affiliations + +## 4.0.0 -- 2014-06-08 + +## Sub-task + +- [SMACK-399](https://igniterealtime.atlassian.net/browse/SMACK-399) - + Add support for Roster Versioning (was XEP-0237, now in RFC 6121) +- [SMACK-400](https://igniterealtime.atlassian.net/browse/SMACK-400) - + Change xml-not-well-formed to not-well-formed +- [SMACK-401](https://igniterealtime.atlassian.net/browse/SMACK-401) - + Remove \ +- [SMACK-445](https://igniterealtime.atlassian.net/browse/SMACK-445) - + XMPPError class is based on deprecated XEP-0086 + +### Bug + +- [SMACK-357](https://igniterealtime.atlassian.net/browse/SMACK-357) - + Error in SASL authentication when SASL authzid parameter is null +- [SMACK-410](https://igniterealtime.atlassian.net/browse/SMACK-410) - + Any valid SSL server certificate can be used to perform a + man-in-the-middle attack +- [SMACK-411](https://igniterealtime.atlassian.net/browse/SMACK-411) - + ServiceDiscoveryManager identities should be non-static and kept in + a Set to allow multiple identities as per XEP-30 +- [SMACK-414](https://igniterealtime.atlassian.net/browse/SMACK-414) - + Smack does not announce the support for XEP-54 aka vcard-temp +- [SMACK-427](https://igniterealtime.atlassian.net/browse/SMACK-427) - + Typo in code - StreamInitiation.setSesssionID() +- [SMACK-467](https://igniterealtime.atlassian.net/browse/SMACK-467) - + Don\'t use the default locale for machine-readable output, use + Locale.US instead +- [SMACK-531](https://igniterealtime.atlassian.net/browse/SMACK-531) - + Add missing namespace attribute to XHTML-IM body tags +- [SMACK-533](https://igniterealtime.atlassian.net/browse/SMACK-533) - + Smack should prevent IQ response spoofing +- [SMACK-535](https://igniterealtime.atlassian.net/browse/SMACK-535) - + jul.properties should only configure the \'org.igniterealtime\' + namespace +- [SMACK-538](https://igniterealtime.atlassian.net/browse/SMACK-538) - + ParseRoster does not check the sender of the roster and for pending + roster queries +- [SMACK-541](https://igniterealtime.atlassian.net/browse/SMACK-541) - + XHTMLExtensionProvider relies on incorrect behavior of MXParser, + violating the contract of the XMLPullParser interface +- [SMACK-543](https://igniterealtime.atlassian.net/browse/SMACK-543) - + packet.Time is not thread-safe +- [SMACK-546](https://igniterealtime.atlassian.net/browse/SMACK-546) - + PubSub\'s Item needs to escape its XML payload +- [SMACK-548](https://igniterealtime.atlassian.net/browse/SMACK-548) - + PingManager notifies pingFailedListeners multiple times +- [SMACK-551](https://igniterealtime.atlassian.net/browse/SMACK-551) - + ChatManager throws NPE, when Message has no \'from\' attribute +- [SMACK-554](https://igniterealtime.atlassian.net/browse/SMACK-554) - + Memory leak in BookmarkManager +- [SMACK-555](https://igniterealtime.atlassian.net/browse/SMACK-555) - + VCardProvider should consider some elements as optional +- [SMACK-558](https://igniterealtime.atlassian.net/browse/SMACK-558) - + connect() must wait until the stream features have been parsed +- [SMACK-559](https://igniterealtime.atlassian.net/browse/SMACK-559) - + Roster entries without a group are not updated +- [SMACK-560](https://igniterealtime.atlassian.net/browse/SMACK-560) - + Race condition in PacketWriter +- [SMACK-567](https://igniterealtime.atlassian.net/browse/SMACK-567) - + XMPPConnection leaks listenerExecutor ExecutorService + +### Improvement + +- [SMACK-343](https://igniterealtime.atlassian.net/browse/SMACK-343) - + Make Smack jar an OSGi bundle. +- [SMACK-356](https://igniterealtime.atlassian.net/browse/SMACK-356) - + There is no way to reliably end a Chat and have a new one created. +- [SMACK-454](https://igniterealtime.atlassian.net/browse/SMACK-454) - + Follow XEP-0170 recommendation: Compression before Resource Binding +- [SMACK-459](https://igniterealtime.atlassian.net/browse/SMACK-459) - + Add option to configure the default identity in + ServiceDiscoveryManager +- [SMACK-465](https://igniterealtime.atlassian.net/browse/SMACK-465) - + Replace custom wrapped Throwable in XMPPException with + Exception.cause +- [SMACK-468](https://igniterealtime.atlassian.net/browse/SMACK-468) - + Don\'t throw an IOException in IBBStreams when the stream got closed + by the remote +- [SMACK-536](https://igniterealtime.atlassian.net/browse/SMACK-536) - + JUL Loggers should become final +- [SMACK-537](https://igniterealtime.atlassian.net/browse/SMACK-537) - + Move XMPP Ping code to smackx, add keep-alive functionality to + PingManager +- [SMACK-545](https://igniterealtime.atlassian.net/browse/SMACK-545) - + Change API to the style mentioned in Smack\'s Code Guidelines +- [SMACK-547](https://igniterealtime.atlassian.net/browse/SMACK-547) - + Consistent behavior for \"from\" attribute on outgoing stanzas +- [SMACK-556](https://igniterealtime.atlassian.net/browse/SMACK-556) - + Make ConnectionConfigration getters public +- [SMACK-557](https://igniterealtime.atlassian.net/browse/SMACK-557) - + Provide a MultiUserChat method to create \*or\* join a room +- [SMACK-568](https://igniterealtime.atlassian.net/browse/SMACK-568) - + Don\'t exclude groupchat messages without body element in + MultiUserChat MessageListeners + +### New Feature + +- [SMACK-53](https://igniterealtime.atlassian.net/browse/SMACK-53) - + Add support for XEP-0092: Software Version +- [SMACK-71](https://igniterealtime.atlassian.net/browse/SMACK-71) - + Create new FromFilter that checks for exact matching +- [SMACK-187](https://igniterealtime.atlassian.net/browse/SMACK-187) - + Add HTTP Binding support (BOSH / XEP-0124) +- [SMACK-265](https://igniterealtime.atlassian.net/browse/SMACK-265) - + Move to a newer build process with artifacts published to maven + central repo +- [SMACK-426](https://igniterealtime.atlassian.net/browse/SMACK-426) - + Improve XMPPException +- [SMACK-544](https://igniterealtime.atlassian.net/browse/SMACK-544) - + Add support for XEP-0079: Advanced Message Processing +- [SMACK-552](https://igniterealtime.atlassian.net/browse/SMACK-552) - + Add support for \"HTTP over XMPP transport\" aka. XEP-0332 + +### Task + +- [SMACK-371](https://igniterealtime.atlassian.net/browse/SMACK-371) - + Some MUC tasks are using stanza\'s as defined in an older version of + the spec. Fails to work on some servers. +- [SMACK-432](https://igniterealtime.atlassian.net/browse/SMACK-432) - + Code cleanup of deprecated methods +- [SMACK-446](https://igniterealtime.atlassian.net/browse/SMACK-446) - + Remove non-SASL authentication code + +## 3.4.1 -- 2014-02-09 + +### Bug + +- [SMACK-540](https://igniterealtime.atlassian.net/browse/SMACK-540) - + Memory leak in MultiUserChat + +## 3.4.0 -- 2014-02-02 + +### Bug Fixes + +- [SMACK-442](https://igniterealtime.atlassian.net/browse/SMACK-442) - + Manager\'s should also handle connectionClosedOnError() +- [SMACK-443](https://igniterealtime.atlassian.net/browse/SMACK-443) - + ReconnectionSuccessful listeners are invoked twice on reconnection + if connect() failed before +- [SMACK-452](https://igniterealtime.atlassian.net/browse/SMACK-452) - + PacketParserUtils.parseStreamError() is not aware of optional text + element and therefore failes to parse stream error\'s correctly. + Prevents ReconnectionManager from reconnecting. +- [SMACK-458](https://igniterealtime.atlassian.net/browse/SMACK-458) - + Smack\'s Managers should not remove itself when the connection is + closed or should re-add themselfs if the connection get reconnected +- [SMACK-462](https://igniterealtime.atlassian.net/browse/SMACK-462) - + Prevent duplicate manager instances by using the manager\'s + constructor in the ConnectionCreationListener\'s connectionCreated +- [SMACK-463](https://igniterealtime.atlassian.net/browse/SMACK-463) - + packet listeners silently fail when preceding listener caused + exception +- [SMACK-524](https://igniterealtime.atlassian.net/browse/SMACK-524) - + Use correct block-size definition for IBB transfers +- [SMACK-525](https://igniterealtime.atlassian.net/browse/SMACK-525) - + NPE in XMPPConnection.notifyConnectionError +- [SMACK-529](https://igniterealtime.atlassian.net/browse/SMACK-529) - + Add support for XEP-0280 \"Message Carbons\" +- [SMACK-530](https://igniterealtime.atlassian.net/browse/SMACK-530) - + DNSUtilTest requires an internet connection to work, it should be + moved to integration tests. + +### New Feature + +- [SMACK-286](https://igniterealtime.atlassian.net/browse/SMACK-286) - + Need to change ProviderManager to support loading smack.providers + from alternative locations +- [SMACK-387](https://igniterealtime.atlassian.net/browse/SMACK-387) - + Allow configuration of ChatManager to be able to allow message + handling to be customized. +- [SMACK-403](https://igniterealtime.atlassian.net/browse/SMACK-403) - + Add support for XEP-0297 \"Stanza Forwarding\" +- [SMACK-434](https://igniterealtime.atlassian.net/browse/SMACK-434) - + Create a project to contain non production ready implementations of + specifications + +### Improvement + +- [SMACK-343](https://igniterealtime.atlassian.net/browse/SMACK-343) - + Make Smack jar an OSGi bundle. +- [SMACK-381](https://igniterealtime.atlassian.net/browse/SMACK-381) - + Separate the configuration for smack extension related classes from + the smack jar. +- [SMACK-444](https://igniterealtime.atlassian.net/browse/SMACK-444) - + Allow \'null\' for TruststorePath and TruststorePassword in + ServerTrustManager +- [SMACK-456](https://igniterealtime.atlassian.net/browse/SMACK-456) - + Add the causing exception to the XMPPExceptions thrown in + XMPPConnection +- [SMACK-457](https://igniterealtime.atlassian.net/browse/SMACK-457) - + Remove unnecessary printStackTrace() in XMPPConnection +- [SMACK-460](https://igniterealtime.atlassian.net/browse/SMACK-460) - + ServiceDiscoveryManager should not use the constructor in + connectionCreated() +- [SMACK-461](https://igniterealtime.atlassian.net/browse/SMACK-461) - + Remove incorrect deprecated marker for + DiscoverInfo.Identity.setType() +- [SMACK-464](https://igniterealtime.atlassian.net/browse/SMACK-464) - + Make it clear that PacketListener\'s added with + XMPPConnection.addPacketListener() are only for received packets +- [SMACK-534](https://igniterealtime.atlassian.net/browse/SMACK-534) - + Convert all System.out and printStackTrace calls to use Java util + logging. +- [SMACK-339](https://igniterealtime.atlassian.net/browse/SMACK-339) - + Allow ConnectionListeners to be added before XMPPConnection is + connected. Currently throws exception +- [SMACK-373](https://igniterealtime.atlassian.net/browse/SMACK-373) - + Don\'t remove listeners after a disconnect() , keep state of + XMPPConnection between disconnect() and connect()/login() +- [SMACK-434](https://igniterealtime.atlassian.net/browse/SMACK-434) - + Create a project to contain non production ready implementations of + specifications +- [SMACK-526](https://igniterealtime.atlassian.net/browse/SMACK-526) - + Deprecate all PEP related classes. + +## 3.3.1 -- 2013-10-06 + +### Bug Fixes + +- [SMACK-428](https://igniterealtime.atlassian.net/browse/SMACK-428) - + RosterEntry overrides equals, but not hashcode. +- [SMACK-438](https://igniterealtime.atlassian.net/browse/SMACK-438) - + Possible NPE in + MultiUserChat.InvitationsMonitor.getInvitationsMonitor() +- [SMACK-441](https://igniterealtime.atlassian.net/browse/SMACK-441) - + Memory leak in KeepAliveManager +- [SMACK-447](https://igniterealtime.atlassian.net/browse/SMACK-447) - + Compression is not enabled for Java7ZlibInputOutputStream +- [SMACK-448](https://igniterealtime.atlassian.net/browse/SMACK-448) - + Java7ZlibInputOutputStream does not work. Deflater.DEFAULT_STRATEGY + is used as compression level when it should use + Deflater.DEFAULT_COMPRESSION +- [SMACK-450](https://igniterealtime.atlassian.net/browse/SMACK-450) - + VCard.load() throws null pointer exception if there is no VCard for + the user +- [SMACK-455](https://igniterealtime.atlassian.net/browse/SMACK-455) - + Multiple items doesn\`t not parse correctly in a pubsub message + +### New Feature + +- [SMACK-425](https://igniterealtime.atlassian.net/browse/SMACK-425) - + Collect (parser) Exceptions and unparseable stanzas. Provide a + callback method so that the user is notified about them if he wants + to + +### Improvement + +- [SMACK-369](https://igniterealtime.atlassian.net/browse/SMACK-369) - + Exceptions during login should get thrown back up to the caller. +- [SMACK-439](https://igniterealtime.atlassian.net/browse/SMACK-439) - + Improve documentation for MultiUserChat.InvitationsListener +- [SMACK-451](https://igniterealtime.atlassian.net/browse/SMACK-451) - + PingManager entry in META-INF/smack.providers is within Ad-Hoc + Command section +- [SMACK-431](https://igniterealtime.atlassian.net/browse/SMACK-431) - + Enable Entity Caps as default for new connections and write + extensions documentation html page +- [SMACK-405](https://igniterealtime.atlassian.net/browse/SMACK-405) - + Cleanup of redundant code in XMPPConnection.shutdown() + +## 3.3.0 -- 2013-05-04 + +### Bug Fixes + +- [SMACK-225](https://igniterealtime.atlassian.net/browse/SMACK-225) - + Improper handeling of DNS SRV records +- [SMACK-238](https://igniterealtime.atlassian.net/browse/SMACK-238) - + The vCard avatar type always return jpg +- [SMACK-270](https://igniterealtime.atlassian.net/browse/SMACK-270) - + Fix for a memory leak in MUC with MUC.finalize() +- [SMACK-278](https://igniterealtime.atlassian.net/browse/SMACK-278) - + Deadlock during Smack disconnect +- [SMACK-342](https://igniterealtime.atlassian.net/browse/SMACK-342) - + VCards causes ConcurrentModificationException +- [SMACK-344](https://igniterealtime.atlassian.net/browse/SMACK-344) - + Bug in SASL authentication mechanism when SRV records are being + used. +- [SMACK-351](https://igniterealtime.atlassian.net/browse/SMACK-351) - + Rework File Transfer +- [SMACK-352](https://igniterealtime.atlassian.net/browse/SMACK-352) - + Update the licensing headers in various files. +- [SMACK-355](https://igniterealtime.atlassian.net/browse/SMACK-355) - + IO Error if smack cant use port for local proxy +- [SMACK-371](https://igniterealtime.atlassian.net/browse/SMACK-371) - + Some MUC tasks are using stanza\'s as defined in an older version of + the spec. Fails to work on some servers. +- [SMACK-375](https://igniterealtime.atlassian.net/browse/SMACK-375) - + Node strings in the discovery info packets are not escaped as in the + other packets +- [SMACK-382](https://igniterealtime.atlassian.net/browse/SMACK-382) - + Prevent memory leak in AdHocCommandManager +- [SMACK-384](https://igniterealtime.atlassian.net/browse/SMACK-384) - + Endless waiting for connection to be established +- [SMACK-390](https://igniterealtime.atlassian.net/browse/SMACK-390) - + Smack login will fail if a bad delay packet is received +- [SMACK-392](https://igniterealtime.atlassian.net/browse/SMACK-392) - + In ant build, compile-test target doesn\'t work. +- [SMACK-394](https://igniterealtime.atlassian.net/browse/SMACK-394) - + Erroneous cast in IBBInputStream\'s read() method +- [SMACK-395](https://igniterealtime.atlassian.net/browse/SMACK-395) - + Socks5BytestreamManager\'s establishConnection() should still try to + use the local streamhost proxy if the server doesn\'t provide one +- [SMACK-404](https://igniterealtime.atlassian.net/browse/SMACK-404) - + Smack uses the wrong method to decode Base64 Strings +- [SMACK-413](https://igniterealtime.atlassian.net/browse/SMACK-413) - + VCardProvider incorrectly parses binary value of avatars +- [SMACK-415](https://igniterealtime.atlassian.net/browse/SMACK-415) - + ItemProvider relies on incorrect behavior of MXParser, violating the + contract of the XMLPullParser interface +- [SMACK-417](https://igniterealtime.atlassian.net/browse/SMACK-417) - + If both PacketReader and PacketWriter fail at the same time, + connectionClosedonError() is called two times + +### New Features + +- [SMACK-331](https://igniterealtime.atlassian.net/browse/SMACK-331) - + Add support for XEP-0184: Message Delivery Receipts +- [SMACK-345](https://igniterealtime.atlassian.net/browse/SMACK-345) - + Inproved detection of last activity +- [SMACK-361](https://igniterealtime.atlassian.net/browse/SMACK-361) - + Add support for XEP-0115 Entity Capabilities +- [SMACK-376](https://igniterealtime.atlassian.net/browse/SMACK-376) - + Setting a custom trust manager to control certificates from outside +- [SMACK-388](https://igniterealtime.atlassian.net/browse/SMACK-388) - + XEP-199 XMPP Ping support + +### Improvements + +- [SMACK-341](https://igniterealtime.atlassian.net/browse/SMACK-341) - + Update the PacketCollector and ConnectionDetachedPacketCollector to + use the java concurrent classes. +- [SMACK-358](https://igniterealtime.atlassian.net/browse/SMACK-358) - + Support additional properties for account creation in test cases. +- [SMACK-363](https://igniterealtime.atlassian.net/browse/SMACK-363) - + Code Cleanup +- [SMACK-377](https://igniterealtime.atlassian.net/browse/SMACK-377) - + avoid unnecessary DNS requests in XMPPconnection +- [SMACK-379](https://igniterealtime.atlassian.net/browse/SMACK-379) - + Sessions were removed from the specification but Smack still uses + them. Should be updated to reflect the spec changes. +- [SMACK-385](https://igniterealtime.atlassian.net/browse/SMACK-385) - + Reusing KeyStore in order to reduce memory usage +- [SMACK-389](https://igniterealtime.atlassian.net/browse/SMACK-389) - + Add java.util.zip.Deflater(In\|Out)putStream as Java7 API native + alternative to JZlib +- [SMACK-391](https://igniterealtime.atlassian.net/browse/SMACK-391) - + Improve date parsing in StringUtils and make + DelayInformationProvider use StringUtils for date parsing. +- [SMACK-412](https://igniterealtime.atlassian.net/browse/SMACK-412) - + Replace the whitespace ping with a XEP-0199 ping +- [SMACK-419](https://igniterealtime.atlassian.net/browse/SMACK-419) - + PacketWriter: Only flush the BufferedWriter if the packet queue is + empty +- [SMACK-423](https://igniterealtime.atlassian.net/browse/SMACK-423) - + Investigate whether unhandled packets should still parse the child + xml into a string as content +- [SMACK-430](https://igniterealtime.atlassian.net/browse/SMACK-430) - + Throw an exception if + FileTransferManager.createOutgoingFileTransfer() was used with a + bare JID + +## 3.2.2 -- 2011-12-23 + +### Bug Fixes + +- [SMACK-263](https://igniterealtime.atlassian.net/browse/SMACK-263) - + Set file info in all send\* methods +- [SMACK-322](https://igniterealtime.atlassian.net/browse/SMACK-322) - + NPE in XMPPConnection +- [SMACK-324](https://igniterealtime.atlassian.net/browse/SMACK-324) - + Investigate SASL issue with jabberd2 servers +- [SMACK-338](https://igniterealtime.atlassian.net/browse/SMACK-338) - + IBB filetransfer doesn\'t work as expected +- [SMACK-346](https://igniterealtime.atlassian.net/browse/SMACK-346) - + Bug in return code for rejection handling in FileTransferManager +- [SMACK-348](https://igniterealtime.atlassian.net/browse/SMACK-348) - + Documentation error - broken link +- [SMACK-349](https://igniterealtime.atlassian.net/browse/SMACK-349) - + Smack\'s IBB sends too much data in a packet +- [SMACK-350](https://igniterealtime.atlassian.net/browse/SMACK-350) - + Bytestream is not working in Spark 2.6.3 from XP to W7 +- [SMACK-353](https://igniterealtime.atlassian.net/browse/SMACK-353) - + Thread leak in the FaultTolerantNegotiator +- [SMACK-362](https://igniterealtime.atlassian.net/browse/SMACK-362) - + smack throw NoSuchElementException if the muc#roominfo_subject has + no values + +### Improvements + +- [SMACK-343](https://igniterealtime.atlassian.net/browse/SMACK-343) - + Make Smack jar an OSGi bundle. +- [SMACK-354](https://igniterealtime.atlassian.net/browse/SMACK-354) - + Provide milliseconds in timestamp colum debugwindow + +## 3.2.1 -- 2011-07-04 + +### Bug Fixes + +- [SMACK-129](https://igniterealtime.atlassian.net/browse/SMACK-129) - + MultiUserChat will Store Messages in its PacketCollector + irregardless of whether or not they are being read +- [SMACK-230](https://igniterealtime.atlassian.net/browse/SMACK-230) - + Disconnect Can Cause Null Pointer Exception +- [SMACK-273](https://igniterealtime.atlassian.net/browse/SMACK-273) - + Bug in RoomListenerMultiplexor.java +- [SMACK-329](https://igniterealtime.atlassian.net/browse/SMACK-329) - + XHTMLText uses improper format for br tag +- [SMACK-338](https://igniterealtime.atlassian.net/browse/SMACK-338) - + IBB filetransfer doesn\'t work as expected +- [SMACK-324](https://igniterealtime.atlassian.net/browse/SMACK-324) - + Investigate SASL issue with jabberd2 servers + +## 3.2.0 -- 2011-05-03 + +### New Features + +- [SMACK-272](https://igniterealtime.atlassian.net/browse/SMACK-272) - + Add support for pubsub (XEP-0060) +- [SMACK-296](https://igniterealtime.atlassian.net/browse/SMACK-296) - + Add support for XEP-0224: Attention +- [SMACK-319](https://igniterealtime.atlassian.net/browse/SMACK-319) - + Add common interfaces for SOCKS5 Bytestreams and In-Band Bytestreams + +### Improvements + +- [SMACK-137](https://igniterealtime.atlassian.net/browse/SMACK-137) - + File Transfer Settings +- [SMACK-156](https://igniterealtime.atlassian.net/browse/SMACK-156) - + Add the ability to register for roster events before logging in +- [SMACK-261](https://igniterealtime.atlassian.net/browse/SMACK-261) - + Minor Jingle cleanup to better support Jingle in Spark +- [SMACK-277](https://igniterealtime.atlassian.net/browse/SMACK-277) - + Update XMLUnit to the latest version +- [SMACK-282](https://igniterealtime.atlassian.net/browse/SMACK-282) - + Support SASL-related error conditions. +- [SMACK-283](https://igniterealtime.atlassian.net/browse/SMACK-283) - + Investigate why Jingle is connecting to stun.xten.net +- [SMACK-285](https://igniterealtime.atlassian.net/browse/SMACK-285) - + Add support for Nicks +- [SMACK-289](https://igniterealtime.atlassian.net/browse/SMACK-289) - + There is no way of retrieving items from a pubsub node when the user + has multiple subscriptions. +- [SMACK-294](https://igniterealtime.atlassian.net/browse/SMACK-294) - + Handle empty roster groups and no goups in the same way +- [SMACK-295](https://igniterealtime.atlassian.net/browse/SMACK-295) - + Fire reconnectionSuccessful event when session is established +- [SMACK-297](https://igniterealtime.atlassian.net/browse/SMACK-297) - + add configuration for local Socks5 proxy +- [SMACK-298](https://igniterealtime.atlassian.net/browse/SMACK-298) - + Respond to all incoming Socks5 bytestream requests +- [SMACK-299](https://igniterealtime.atlassian.net/browse/SMACK-299) - + Improve accepting of Socks5 bytestream requests +- [SMACK-300](https://igniterealtime.atlassian.net/browse/SMACK-300) - + improve local Socks5 proxy implemetation +- [SMACK-301](https://igniterealtime.atlassian.net/browse/SMACK-301) - + support for bytestream packets to query Socks5 proxy for network + address +- [SMACK-302](https://igniterealtime.atlassian.net/browse/SMACK-302) - + Improve establishing of Socks5 bytestreams +- [SMACK-303](https://igniterealtime.atlassian.net/browse/SMACK-303) - + integrate of the extracted Socks5 bytestream API in file transfer + API +- [SMACK-304](https://igniterealtime.atlassian.net/browse/SMACK-304) - + Extend the IQ API to create empty IQ results and IQ error response + packets +- [SMACK-307](https://igniterealtime.atlassian.net/browse/SMACK-307) - + Improve Message Parser Robustness and Message Body I18N +- [SMACK-309](https://igniterealtime.atlassian.net/browse/SMACK-309) - + Fully implement XEP-0047 In-Band Bytestreams +- [SMACK-310](https://igniterealtime.atlassian.net/browse/SMACK-310) - + Add Support for Localized Message Subjects + +### Bug Fixes + +- [SMACK-163](https://igniterealtime.atlassian.net/browse/SMACK-163) - + Fix NPE in RoomInfo when subject has not value +- [SMACK-207](https://igniterealtime.atlassian.net/browse/SMACK-207) - + Parsing of messages may disconnect Smack/Spark +- [SMACK-225](https://igniterealtime.atlassian.net/browse/SMACK-225) - + Improper handeling of DNS SRV records +- [SMACK-232](https://igniterealtime.atlassian.net/browse/SMACK-232) - + Better handling of Roster error +- [SMACK-243](https://igniterealtime.atlassian.net/browse/SMACK-243) - + Packet with wrong date format makes Smack to disconnect +- [SMACK-264](https://igniterealtime.atlassian.net/browse/SMACK-264) - + fix for NPE in SASLMechanism.java +- [SMACK-269](https://igniterealtime.atlassian.net/browse/SMACK-269) - + Smack 3.1.0 creates a new chat for every incoming message +- [SMACK-271](https://igniterealtime.atlassian.net/browse/SMACK-271) - + Deadlock in XMPPConnection while login and parsing stream features +- [SMACK-275](https://igniterealtime.atlassian.net/browse/SMACK-275) - + Patch: Fix for broken SASL DIGEST-MD5 implementation +- [SMACK-280](https://igniterealtime.atlassian.net/browse/SMACK-280) - + The authentification should use the XMPPConnection#sendPacket method + and work transparent with packets and packet listeners. +- [SMACK-288](https://igniterealtime.atlassian.net/browse/SMACK-288) - + The parsing of the result for a LeafNode.getItems() call is + incorrect. It creates a DefaultPacketExtension instead of an Item + for every other item in the result. +- [SMACK-290](https://igniterealtime.atlassian.net/browse/SMACK-290) - + Deadlock while getting Roster before it\'s initialized +- [SMACK-291](https://igniterealtime.atlassian.net/browse/SMACK-291) - + RosterGroup modifications should depend on roster push +- [SMACK-293](https://igniterealtime.atlassian.net/browse/SMACK-293) - + Support optional roster subscription attribute +- [SMACK-305](https://igniterealtime.atlassian.net/browse/SMACK-305) - + RosterEntry#getGroups causing a roster reload +- [SMACK-308](https://igniterealtime.atlassian.net/browse/SMACK-308) - + Multiple errors in pubsub GetItemsRequest +- [SMACK-312](https://igniterealtime.atlassian.net/browse/SMACK-312) - + Only fire RosterListener#entriesUpdated for RosterEntries that + changed +- [SMACK-327](https://igniterealtime.atlassian.net/browse/SMACK-327) - + getFeatures() method on DiscoverInfo is improperly set to be package + protected instead of public +- [SMACK-328](https://igniterealtime.atlassian.net/browse/SMACK-328) - + Number format exception while parsing dates. +- [SMACK-332](https://igniterealtime.atlassian.net/browse/SMACK-332) - + Smack 3.2.0b2 shows wrong version in Smack Dubugger Window +- [SMACK-334](https://igniterealtime.atlassian.net/browse/SMACK-334) - + Error in form for FileTransferNegotiator + +## 3.1.0 -- 2008-11-20 + +### New Features + +- [SMACK-142](https://igniterealtime.atlassian.net/browse/SMACK-142) - + Added support for Kerberos/NTLM. **(6 votes)** +- [SMACK-210](https://igniterealtime.atlassian.net/browse/SMACK-210) - + Added support for MD5 SASL. **(1 vote)** +- [SMACK-256](https://igniterealtime.atlassian.net/browse/SMACK-256) - + Added support for new sophisticated TLS mechanisms including + SmartCard and Apple\'s KeychainStore. +- [SMACK-242](https://igniterealtime.atlassian.net/browse/SMACK-242) - + Added support for JEP-50: Ad-hoc commands. +- [SMACK-251](https://igniterealtime.atlassian.net/browse/SMACK-251) - + Added support for XEP-0163: Personal Eventing Protocol. **(1 vote)** +- [SMACK-226](https://igniterealtime.atlassian.net/browse/SMACK-226) - + XMLConnection can now be used with an http/socks proxy. **(2 + votes)** +- [SMACK-254](https://igniterealtime.atlassian.net/browse/SMACK-254) - + Loading the Roster during login is now optional. +- [SMACK-255](https://igniterealtime.atlassian.net/browse/SMACK-255) - + Added ability to set mime type for avatar. +- [SMACK-235](https://igniterealtime.atlassian.net/browse/SMACK-235) - + Improved performance of Roster class. +- [SMACK-241](https://igniterealtime.atlassian.net/browse/SMACK-241) - + Updated Base64 implementation to match Openfire\'s. +- [SMACK-240](https://igniterealtime.atlassian.net/browse/SMACK-240) - + Updated Jingle implementation to newest version. +- [SMACK-246](https://igniterealtime.atlassian.net/browse/SMACK-246) - + Improve Jingle logging using commons-logging +- [SMACK-244](https://igniterealtime.atlassian.net/browse/SMACK-244) - + Updated JSTUN to 0.7.2. +- [SMACK-259](https://igniterealtime.atlassian.net/browse/SMACK-259) - + Updated XPP library to latest version. + +### Bug Fixes + +- [SMACK-231](https://igniterealtime.atlassian.net/browse/SMACK-231) - + IBB Outputstream was not being flushed before it was closed. +- [SMACK-236](https://igniterealtime.atlassian.net/browse/SMACK-236) - + Renamed stanza error \"unexpected-condition\" to + \"unexpected-request\". +- [SMACK-258](https://igniterealtime.atlassian.net/browse/SMACK-258) - + Fixed disconnection issue when parsing SASL success that contained a + payload. +- [SMACK-175](https://igniterealtime.atlassian.net/browse/SMACK-175) - + Fixed typo in RosterPacket.ItemStatus constant. +- [SMACK-260](https://igniterealtime.atlassian.net/browse/SMACK-260) - + Added handling of error presence packets + +## 3.0.3 -- 2007-05-31 + +### New Features + +- [SMACK-99](https://igniterealtime.atlassian.net/browse/SMACK-99) - + Added support for multiple message bodies and message body + languages. +- [SMACK-218](https://igniterealtime.atlassian.net/browse/SMACK-218) - + Implemented GSSAPI for single-sign on. + +### Bug Fixes + +- [SMACK-219](https://igniterealtime.atlassian.net/browse/SMACK-219) - + The getPresence method was not working correctly with offline + presence. +- [SMACK-224](https://igniterealtime.atlassian.net/browse/SMACK-224) - + SASL authenticion was using the XMPP domain instead of the FQDN. + +## 3.0.2 -- 2007-05-03 + +### Bug Fixes + +- [SMACK-212](https://igniterealtime.atlassian.net/browse/SMACK-212) - + Jingle can\'t establish session if only one side has a relay service +- [SMACK-213](https://igniterealtime.atlassian.net/browse/SMACK-213) - + RTP Bridge Resolver get wrong localhost address in certain + situations +- [SMACK-214](https://igniterealtime.atlassian.net/browse/SMACK-214) - + Presences with a negative priority of -1 are not sending the + priority to the server + +## 3.0.1 -- 2007-04-12 + +### Bug Fixes + +- [SMACK-211](https://igniterealtime.atlassian.net/browse/SMACK-211) - + Jingle ICE with relay sometimes closed sessions. +- Upgraded bundled version of JSTUN. + +## 3.0.0 -- 2007-03-31 + +### Important Changes + +- Java 5 is now required. +- Several API changes are not backwards compatible. In particular, + connection handling has been significantly updated, the GroupChat + class has been dropped in favor of the standardized MultiUserChat, + and the Chat class has an updated API. + +### New Features + +- [SMACK-74](https://igniterealtime.atlassian.net/browse/SMACK-74) - + Added support for unavailable presences with status text. **(4 + votes)** +- [SMACK-191](https://igniterealtime.atlassian.net/browse/SMACK-191) - + RosterListener API improvements. +- [SMACK-194](https://igniterealtime.atlassian.net/browse/SMACK-194) - + Roster.getPresence(String) now considers mode after priority to + determine the presence value to return. +- [SMACK-195](https://igniterealtime.atlassian.net/browse/SMACK-195) - + Added the ability to disconnect with a custom presence value (for + offline status). +- [SMACK-200](https://igniterealtime.atlassian.net/browse/SMACK-200) - + Added convenience methods to Presence class. +- [SMACK-31](https://igniterealtime.atlassian.net/browse/SMACK-31) - + Added support for privacy lists. **(4 votes)** +- [SMACK-94](https://igniterealtime.atlassian.net/browse/SMACK-94) - + Added support for last activity of online users. **(1 vote)** +- [SMACK-121](https://igniterealtime.atlassian.net/browse/SMACK-121) - + Added support for stream errors. +- [SMACK-136](https://igniterealtime.atlassian.net/browse/SMACK-136) - + Added support for XEP-0048: bookmark storage. +- [SMACK-144](https://igniterealtime.atlassian.net/browse/SMACK-144) - + Added bookmark manager for central bookmark management. +- [SMACK-150](https://igniterealtime.atlassian.net/browse/SMACK-150) - + Added support for handling node features in disco. +- [SMACK-167](https://igniterealtime.atlassian.net/browse/SMACK-167) - + Added support for XEP-0106: JID Escaping +- [SMACK-171](https://igniterealtime.atlassian.net/browse/SMACK-171) - + The presence of available contacts is now changed to offline when + the connection is closed. +- [SMACK-172](https://igniterealtime.atlassian.net/browse/SMACK-172) - + Added support for re-connection when the connection is abruptly + closed. +- [SMACK-182](https://igniterealtime.atlassian.net/browse/SMACK-182) - + ProviderManager is now pluggable (for Eclipse ECF). +- [SMACK-185](https://igniterealtime.atlassian.net/browse/SMACK-185) - + Added the workgroup API to Smack. +- [SMACK-206](https://igniterealtime.atlassian.net/browse/SMACK-206) - + Added the option to specify the username to use for the automated + test cases. +- [SMACK-208](https://igniterealtime.atlassian.net/browse/SMACK-208) - + Added a max queue size for outgoing packets to prevent memory issues + during extreme load. +- [SMACK-209](https://igniterealtime.atlassian.net/browse/SMACK-209) - + Initial Jingle support implemented. + +### Bug Fixes + +- [SMACK-6](https://igniterealtime.atlassian.net/browse/SMACK-6) - + Don\'t force use of collectors in Chat class. +- [SMACK-10](https://igniterealtime.atlassian.net/browse/SMACK-10) - + Flush pending packets before closing the connection. **(4 votes)** +- [SMACK-51](https://igniterealtime.atlassian.net/browse/SMACK-51) - + Use unique Thread names among connections. +- [SMACK-54](https://igniterealtime.atlassian.net/browse/SMACK-54) - + Add #equals and #hashCode to Occupant. +- [SMACK-86](https://igniterealtime.atlassian.net/browse/SMACK-86) - + Made presence checks case in-sensitive. +- [SMACK-93](https://igniterealtime.atlassian.net/browse/SMACK-93) - + XHTML provider wasn\'t handling some tags correctly. +- [SMACK-138](https://igniterealtime.atlassian.net/browse/SMACK-138) - + Added caching to file transfer negotiation operations. +- [SMACK-143](https://igniterealtime.atlassian.net/browse/SMACK-143) - + Updated XMPPError to be compliant with RFC3920. +- [SMACK-145](https://igniterealtime.atlassian.net/browse/SMACK-145) - + XHTML parsing could fail in some cases. +- [SMACK-146](https://igniterealtime.atlassian.net/browse/SMACK-146) - + DNS lookups were failing with some DNS servers. +- [SMACK-147](https://igniterealtime.atlassian.net/browse/SMACK-147) - + Removed invisibility presence mode. +- [SMACK-148](https://igniterealtime.atlassian.net/browse/SMACK-148) - + Socks 5 listening thread was not cleaning up correctly. **(2 + votes)** +- [SMACK-149](https://igniterealtime.atlassian.net/browse/SMACK-149) - + Fixed possible memory leaking in PacketReader. +- [SMACK-151](https://igniterealtime.atlassian.net/browse/SMACK-151) - + Now use getBytes(\"UTF-8\") instead of getBytes(). +- [SMACK-152](https://igniterealtime.atlassian.net/browse/SMACK-152) - + The FN field is duplicated when loading vCards from the server. +- [SMACK-153](https://igniterealtime.atlassian.net/browse/SMACK-153) - + Optimized performance by replacing StringBuffer with StringBuilder. +- [SMACK-154](https://igniterealtime.atlassian.net/browse/SMACK-154) - + Fixed roster test cases that were sometimes failing. +- [SMACK-155](https://igniterealtime.atlassian.net/browse/SMACK-155) - + Optimized MUC performance by reducing number of packet collectors + and listeners. +- [SMACK-158](https://igniterealtime.atlassian.net/browse/SMACK-158) - + FileTransfer isDone() method was returning true even when the + transfer was refused. +- [SMACK-159](https://igniterealtime.atlassian.net/browse/SMACK-159) - + Filenames were not escaped for file transfers. +- [SMACK-160](https://igniterealtime.atlassian.net/browse/SMACK-160) - + Now use stream:feature to discover registration support. +- [SMACK-161](https://igniterealtime.atlassian.net/browse/SMACK-161) - + Improved connection speed. +- [SMACK-162](https://igniterealtime.atlassian.net/browse/SMACK-162) - + Fixed NPE in SmackConfiguration. +- [SMACK-163](https://igniterealtime.atlassian.net/browse/SMACK-163) - + Fixed NPE in RoomInfo when subject was null. +- [SMACK-164](https://igniterealtime.atlassian.net/browse/SMACK-164) - + Contact name was not being escaped. +- [SMACK-165](https://igniterealtime.atlassian.net/browse/SMACK-165) - + Listeners were not being removed from PacketReader. +- [SMACK-166](https://igniterealtime.atlassian.net/browse/SMACK-166) - + Packet reader thread was freezing when parsing an error text with no + description. +- [SMACK-168](https://igniterealtime.atlassian.net/browse/SMACK-168) - + Fixed possible delay in PacketReader when negotiating TLS. +- [SMACK-173](https://igniterealtime.atlassian.net/browse/SMACK-173) - + Renamed ConnectionEstablishedListener to ConnectionCreationListener. +- [SMACK-176](https://igniterealtime.atlassian.net/browse/SMACK-176) - + Fixed incorrect property initialization. +- [SMACK-177](https://igniterealtime.atlassian.net/browse/SMACK-177) - + Removed synchronization from Roster. +- [SMACK-178](https://igniterealtime.atlassian.net/browse/SMACK-178) - + Added NodeInformation#getNodeIdentities() to return identities of + hosted nodes +- [SMACK-181](https://igniterealtime.atlassian.net/browse/SMACK-181) - + Improved parsing of certificates to get signed domains. +- [SMACK-183](https://igniterealtime.atlassian.net/browse/SMACK-183) - + Documentation fixes. +- [SMACK-184](https://igniterealtime.atlassian.net/browse/SMACK-184) - + Simplified XMPPConnection constructors. +- [SMACK-203](https://igniterealtime.atlassian.net/browse/SMACK-203) - + NULL thread IDs would cause an error inside of the Chat Manager. +- [SMACK-205](https://igniterealtime.atlassian.net/browse/SMACK-205) - + Fixed PacketReader concurrency problems. +- [SMACK-188](https://igniterealtime.atlassian.net/browse/SMACK-188) - + Resources are now closed after reading the keystore. +- [SMACK-189](https://igniterealtime.atlassian.net/browse/SMACK-189) - + The listener was remaining blocked forever in some cases. +- [SMACK-190](https://igniterealtime.atlassian.net/browse/SMACK-190) - + Exceptions while notifying packet reader listeners was stopping the + notification thread. +- [SMACK-192](https://igniterealtime.atlassian.net/browse/SMACK-192) - + Roster.getPresence(String) now forces use of the bare JID. +- [SMACK-193](https://igniterealtime.atlassian.net/browse/SMACK-193) - + New presence packets now default to a null presence mode. +- [SMACK-196](https://igniterealtime.atlassian.net/browse/SMACK-196) - + Now set closed to true at the start of the connection shutdown + method and not the end. +- [SMACK-197](https://igniterealtime.atlassian.net/browse/SMACK-197) - + The source build was failing. +- [SMACK-198](https://igniterealtime.atlassian.net/browse/SMACK-198) - + File transfer streams were not being closed properly in some cases. +- [SMACK-199](https://igniterealtime.atlassian.net/browse/SMACK-199) - + MultiUserChat invitation listeners are no longer removed on + disconnects. +- [SMACK-201](https://igniterealtime.atlassian.net/browse/SMACK-201) - + Roster no longer exposes that it implements ConnectionListener. + +## 2.2.1 -- 2006-06-12 + +- [SMACK-141](https://igniterealtime.atlassian.net/browse/SMACK-141) - + Fixed SSL exception while creating new XMPPConnections. **(1 vote)** +- [SMACK-127](https://igniterealtime.atlassian.net/browse/SMACK-127) - + Fixed incorrect file transfer progress. +- [SMACK-130](https://igniterealtime.atlassian.net/browse/SMACK-130) - + Fixed VCard escaping problem that was crashing connections. +- [SMACK-134](https://igniterealtime.atlassian.net/browse/SMACK-134) - + VCards were not being saved when avatar was the only element. +- [SMACK-131](https://igniterealtime.atlassian.net/browse/SMACK-131) - + Illegal XML characters are now properly escaped in the presence + status. +- [SMACK-133](https://igniterealtime.atlassian.net/browse/SMACK-133) - + Illegal XML characters are now properly escaped in groups names. +- [SMACK-132](https://igniterealtime.atlassian.net/browse/SMACK-132) - + Fixed IBB problem triggered when buffersize was increased. +- [SMACK-135](https://igniterealtime.atlassian.net/browse/SMACK-135) - + Moved to new Base64 implementation to fix line break issue in old + implementation. + +## 2.2.0 -- 2006-03-09 + +- [SMACK-122](https://igniterealtime.atlassian.net/browse/SMACK-122) - + Added support for JEP-96: File Transfer. **(1 vote)** +- [SMACK-72](https://igniterealtime.atlassian.net/browse/SMACK-72) - + Added support for JEP-47: In-Band Bytestreams. **(2 votes)** +- [SMACK-122](https://igniterealtime.atlassian.net/browse/SMACK-122) - + Added support for JEP-65: SOCKS5 Bytestreams. **(1 vote)** +- [SMACK-112](https://igniterealtime.atlassian.net/browse/SMACK-112) - + Added support for JEP-38 Stream Compression. +- [SMACK-117](https://igniterealtime.atlassian.net/browse/SMACK-117) - + Added support for JEP-33: Extended Stanza Addressing. +- [SMACK-27](https://igniterealtime.atlassian.net/browse/SMACK-27) - + Certification validation is now pluggable. +- [SMACK-118](https://igniterealtime.atlassian.net/browse/SMACK-118) - + Added methods to dynamically remove providers. +- [SMACK-125](https://igniterealtime.atlassian.net/browse/SMACK-125) - + Added support for deaf occupant in MUC rooms. +- [SMACK-109](https://igniterealtime.atlassian.net/browse/SMACK-109) - + Optimized client performance. **(1 vote)** +- [SMACK-113](https://igniterealtime.atlassian.net/browse/SMACK-113) - + Added support for choosing if TLS should be used or not. +- [SMACK-114](https://igniterealtime.atlassian.net/browse/SMACK-114) - + Added support for choosing if SASL should be used or not. +- [SMACK-123](https://igniterealtime.atlassian.net/browse/SMACK-123) - + A thread is no longer used for packet writer listeners. +- [SMACK-110](https://igniterealtime.atlassian.net/browse/SMACK-110) - + Resource binding and session establishment are now requested only if + the server offered them. +- [SMACK-111](https://igniterealtime.atlassian.net/browse/SMACK-111) - + Fixed concurrency issue with date formatter. +- [SMACK-116](https://igniterealtime.atlassian.net/browse/SMACK-116) - + Fixed vCard issues. +- [SMACK-119](https://igniterealtime.atlassian.net/browse/SMACK-119) - + Fixed AccessControlException when using vCard from an applet. +- [SMACK-120](https://igniterealtime.atlassian.net/browse/SMACK-120) - + Listener thread was not being shutdown properly. +- [SMACK-124](https://igniterealtime.atlassian.net/browse/SMACK-124) - + Parsing resource binding packets was requiring smackx.jar file to be + in the classpath. +- [SMACK-97](https://igniterealtime.atlassian.net/browse/SMACK-97) - + Fixed functional test failures in PresencePriorityTest and + RosterTest. diff --git a/NOTICE b/NOTICE index 2c4a2fd15..495ab3409 100644 --- a/NOTICE +++ b/NOTICE @@ -29,6 +29,7 @@ Chris Deering Christoph Fiehe Craig Hesling Damian Minkov +Dan Caseley Daniele Ricci Daniel Henninger Daniel Hintze @@ -44,6 +45,7 @@ Fernando Ramirez Florian Kimmann Florian Schmaus Francisco Vives +Frank Matheron Gaston Dombiak Georg Lukas Gilles Cornu @@ -64,6 +66,7 @@ Jay Kline Jeff Williams Jesus Fuentes John Haubrich +Jonathan Lennox Júlio Cesar Bueno Cotta Lars Noschinski Luca Stucchi @@ -82,6 +85,7 @@ Pete Matern Piotr Nosek Rajat Kumar Gupta Robin Collier +Simon Abykov Simon Schuster Son Goku Tairs Rzajevs @@ -98,4 +102,4 @@ V Lau Vyacheslav Blinov Wolf Posdorfer Xiaowei YAN -Yash Thakkar \ No newline at end of file +Yash Thakkar diff --git a/build.gradle b/build.gradle index a85f9370f..8ec8597eb 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { } dependencies { classpath 'org.kordamp.gradle:clirr-gradle-plugin:0.2.2' + classpath "biz.aQute.bnd:biz.aQute.bnd.gradle:6.0.0" } } @@ -147,7 +148,7 @@ allprojects { smackMinAndroidSdk = 19 junitVersion = '5.7.1' commonsIoVersion = '2.6' - bouncyCastleVersion = '1.68' + bouncyCastleVersion = '1.69' guavaVersion = '30.1-jre' mockitoVersion = '3.7.7' orgReflectionsVersion = '0.9.11' @@ -196,10 +197,6 @@ allprojects { repositories { mavenLocal() mavenCentral() - // Add OSS Sonatype Snapshot repository - maven { - url 'https://oss.sonatype.org/content/repositories/snapshots' - } } tasks.withType(JavaCompile) { @@ -450,6 +447,7 @@ subprojects { apply plugin: 'signing' apply plugin: 'checkstyle' apply plugin: 'org.kordamp.gradle.clirr' + apply plugin: 'biz.aQute.bnd.builder' checkstyle { toolVersion = '8.27' @@ -612,9 +610,15 @@ project(':smack-omemo').clirr.enabled = false project(':smack-omemo-signal').clirr.enabled = false subprojects*.jar { - manifest { - from sharedManifest - } + manifest { + from sharedManifest + } + bundle { + bnd( + '-removeheaders': 'Tool, Bnd-*', + '-exportcontents': '*', + ) + } } configure(subprojects - gplLicensedProjects) { diff --git a/documentation/developer/integrationtest.md b/documentation/developer/integrationtest.md index 60f3142b8..d351e71e1 100644 --- a/documentation/developer/integrationtest.md +++ b/documentation/developer/integrationtest.md @@ -86,6 +86,27 @@ debugger=console The framework will first load the properties file from `~/.config/smack-integration-test/properties` +### Running selected tests only + +Using `enabledTests` is is possible to run only selected tests. The +tests can be selected on a per class base or by specifying concrete +test methods. In the latter case, the methods must be qualified by a +(simple) class name. + +For example: + +```bash +$ gradle integrationTest -Dsinttest.enabledTests=SoftwareInfoIntegrationTest.test +``` + +will only run the `test()` method of `SoftwareInfoIntegrationTest`, whereas + +```bash +$ gradle integrationTest -Dsinttest.enabledTests=SoftwareInfoIntegrationTest +``` + +would run all tests defined in the `SoftwareInfoIntegrationTest` class. + Overview of the components -------------------------- diff --git a/resources/generate-notice-file b/resources/generate-notice-file new file mode 100755 index 000000000..60c66bbce --- /dev/null +++ b/resources/generate-notice-file @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +SMACK_DIR=$(realpath "${SCRIPT_DIR}/..") + +cd "${SMACK_DIR}" + +TEMPFILE=$(mktemp) + +cleanup() { + rm "${TEMPFILE}" +} +trap cleanup EXIT + +git shortlog -s |\ + cut -f2- |\ + grep -v '(no author)' |\ + grep '\w \w.*' |\ + sort \ + > "${TEMPFILE}" + +readonly NOTICE_FILE="${SMACK_DIR}/NOTICE" + +cat < "${NOTICE_FILE}" + Smack + + An open-source XMPP library + maintained by Florian Schmaus + + https://igniterealtime.org/projects/smack + + +Authors: + +EOF + +cat "${TEMPFILE}" >> "${NOTICE_FILE}" diff --git a/resources/get-contributors.sh b/resources/get-contributors.sh deleted file mode 100755 index c31236464..000000000 --- a/resources/get-contributors.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash - -git shortlog -s |\ - cut -f2- |\ - grep -v '(no author)' |\ - grep '\w \w.*' |\ - sort diff --git a/resources/releasedocs/changelog.html b/resources/releasedocs/changelog.html deleted file mode 100644 index 8d8e20366..000000000 --- a/resources/releasedocs/changelog.html +++ /dev/null @@ -1,1654 +0,0 @@ - - - - - Smack Changelog - - - - -
- - - - -
- -

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 -

-
    -
  • [SMACK-903] - StaxXmlPullParser.getNamespace() may throws IllegalArgumentException -
  • -
  • [SMACK-904] - XEP-0096 file transfer fails because of a hidden ClastCastException -
  • -
- -

4.4.1 -- 2021-03-03

- -

Bug -

-
    -
  • [SMACK-895] - BoBIQ#getIQChildElementBuilder throws NPE when the BoB data does not contain ‘max-age’. -
  • -
  • [SMACK-896] - BoBDataExtension is missing getter for BoBData and ContentId -
  • -
  • [SMACK-897] - DirectoryRosterStore.readEntry() should also catch IllegalArgumentException -
  • -
  • [SMACK-898] - AbstractProvider should also consider TypeVariable -
  • -
  • [SMACK-899] - NullPointerException in EntityCapsManager.addCapsExtension -
  • -
  • [SMACK-900] - NPE in DataForm.Builder.addItem() -
  • -
  • [SMACK-902] - DataFormProvider should retrieve the type of fields from <reported/> elements if possible -
  • -
- -

Improvement -

-
    -
  • [SMACK-901] - BoBDataExtension.from() should also allow IQs -
  • -
- -

4.4.0 -- 2020-12-06

- -

Bug -

-
    -
  • [SMACK-561] - Smack should not reply with multiple stream types after stream initiation is offered -
  • -
  • [SMACK-624] - AdHocCommandManager's session sweeping thread does never stop -
  • -
  • [SMACK-729] - Not all providers from smack-legacy.jar are loaded -
  • -
  • [SMACK-770] - There is no Bits of Binary Extension Element provider registered -
  • -
  • [SMACK-848] - Make MultiUserChat.leave() wait for response -
  • -
  • [SMACK-874] - PacketParserUtilsTest#invalidXMLInMessageBody() fails on non-english machines -
  • -
  • [SMACK-881] - Deadlock between reader and writer if Stream Mangement unacked stanza queue is full -
  • -
  • [SMACK-888] - MUC roomDestroyed() callback is not invoked -
  • -
- -

New Feature -

-
    -
  • [SMACK-257] - Add support for XEP-0118: User Tune -
  • -
  • [SMACK-636] - Add support for XEP-0319: Last User Interaction in Presence -
  • -
  • [SMACK-743] - Add support for XEP-0384: OMEMO Encryption -
  • -
  • [SMACK-801] - Update Smack to Java 8 -
  • -
  • [SMACK-824] - Add support for XEP-0221: Data Forms Media Element -
  • -
  • [SMACK-862] - Add support for XEP-0418: DNS Queries over XMPP (DoX) -
  • -
  • [SMACK-871] - Add support for XEP-0350: Data Forms Geolocation Element -
  • -
  • [SMACK-872] - Add support for XEP-0315: Data Forms XML Element -
  • -
  • [SMACK-878] - Add support for XEP-0328: JID Prep -
  • -
  • [SMACK-884] - Add support for XEP-0422: Message Fastening -
  • -
  • [SMACK-885] - Add support for XEP-0420 Stanza Content Encryption -
  • -
  • [SMACK-889] - Add support for XEP-0428: Fallback Indication -
  • -
- -

Improvement -

-
    -
  • [SMACK-591] - Replace XPP3 by SmackXmlPullParser (wrapping Stax's XmlStreamReader and XPP3 on Android) -
  • -
  • [SMACK-650] - Enable Java8's javadoc doclint -
  • -
  • [SMACK-651] - Perform sound cross-compilation: Use newer javac's --release feature -
  • -
  • [SMACK-718] - Prevent extremely long reply timeouts from being set -
  • -
  • [SMACK-821] - Make Forwarded a generic type -
  • -
  • [SMACK-822] - Add API for XEP-0313 § 6.2 Advanced configuration via Ad-Hoc commands -
  • -
  • [SMACK-825] - Discourage Stanza.getExtension(String, String) in favor of Stanza.getExtension(Class<E extends ExtensionElement>) -
  • -
  • [SMACK-826] - Add support for XEP-0373:" OpenPGP for XMPP" and XEP-0374: "OpenPGP for XMPP Instant Messaging" -
  • -
  • [SMACK-828] - Add support for XEP-0107: User Mood -
  • -
  • [SMACK-836] - Save a ServiceDiscoveryManager instance in a private field of MultiUserChatManger -
  • -
  • [SMACK-839] - Provider.parse() should not throw a generic Exception, but instead IOException and XmlPullParserException -
  • -
  • [SMACK-852] - Message thread and subject should be designed and implemented as ExtensionElements -
  • -
  • [SMACK-854] - Rename smack-java7 to smack-java8 -
  • -
  • [SMACK-866] - Remove all tabs from the source code and add checkstyle rule that enforces no-tabs -
  • -
  • [SMACK-867] - Extend HttpFileUploadManager by methods with InputStream parameter -
  • -
  • [SMACK-882] - Add support for MUC status code 333 -
  • -
  • [SMACK-883] - Add generic MUC callback for "participant left" caused by unavailable presences -
  • -
  • [SMACK-890] - Update Message Archive Management (XEP-0313) support to urn:xmpp:mam:2 -
  • -
  • [SMACK-892] - Smack performs unnecessary escaping in XML text -
  • -
- -

Task -

-
    -
  • [SMACK-750] - Raise Smack's minimum required Android SDK level to 19 (Android 4.4, Kit Kat, 2013-10) -
  • -
  • [SMACK-840] - Remove smack-compression-jzlib, as it is obsolete (Smack uses Java 7 de- and inflate API now) -
  • -
- -

4.3.4 -- 2019-05-27

- -

Bug -

-
    -
  • [SMACK-861] - Potential NPE in Roster.getPresencesInternal(BareJid) -
  • -
  • [SMACK-863] - ServiceDiscoveryManger does not use the main identity, causing setIdentity() to have no effect -
  • -
  • [SMACK-864] - Potential Denial of Service (DOS) by remote entities caused by unlimited threads for asynchronous operations -
  • -
  • [SMACK-865] - Some Manager.getInsanceFor() methods are missing the 'synchronized' keyword -
  • -
  • [SMACK-868] - XHTMLText.appendOpenBodyTag() produces invalid XML -
  • -
  • [SMACK-870] - TLS X.509 certificate verification should be performed with the ACE representation of the XMPP service domain when possible -
  • -
- -

Improvement -

-
    -
  • [SMACK-869] - Exceptions in async tasks should not go uncaught to the call stack to avoid program termination -
  • -
- -

4.3.3 -- 2019-03-14

- -

Bug -

-
    -
  • [SMACK-856] - Smack fails under JDK 11 because com.sun.jndi.dns.DnsContextFactory is not inaccessible -
  • -
- -

Improvement -

-
    -
  • [SMACK-858] - Dependency version specifier of jxmpp and MiniDNS include alpha/beta/... versions of the follow up version when Maven is used -
  • -
  • [SMACK-859] - MultiUserChat enter() should reset the timeout of the collector waiting for the final self presence to prevent timeouts for large MUCs -
  • -
- -

4.3.2 -- 2019-02-22

- -

Bug -

-
    -
  • [SMACK-842] - The RFC 3920 xml-not-well-formed error condition should be handled in stream error not a stanza error -
  • -
  • [SMACK-843] - ManManager.pagePrevious() pages into the wrong direction -
  • -
  • [SMACK-844] - Check if bounded unacknowledged stanzas queue is full before adding to it to avoid IllegalStateException -
  • -
  • [SMACK-845] - Ensure that IQ response 'to' address and ID are set correctly -
  • -
  • [SMACK-846] - XMPPTCPConnection does not wait for stream features after authentication if compression is disabled -
  • -
  • [SMACK-848] - Make MultiUserChat.leave() wait for response -
  • -
  • [SMACK-850] - DeliveryReceiptManager should not send receipts with messages of type 'groupchat' -
  • -
  • [SMACK-855] - XMPPTCPConnection sometimes has two writer threads running -
  • -
- -

Improvement -

-
    -
  • [SMACK-847] - Make TCP socket connection attempt interruptable -
  • -
  • [SMACK-849] - Smack Local SOCKS5 Proxy thread should be marked as daemon thread -
  • -
- -

4.3.1 -- 2018-10-14

- -

Bug -

-
    -
  • [SMACK-833] - XMLUtil.prettyFormatXml() throws on some Android devices -
  • -
- -

Improvement -

-
    -
  • [SMACK-829] - Disconnect BOSH client on shutdown -
  • -
  • [SMACK-838] - FormField.getFirstValue() throws IndexOutOfBoundsException if there are no values -
  • -
- -

4.3.0 -- 2018-08-02

- -

Bug -

-
    -
  • [SMACK-759] - PubSubManager.getLeafNode() throws PubSubAssertionError.DiscoInfoNodeAssertionError if node exists but its not a PubSub Node -
  • -
  • [SMACK-814] - NPE when using Node.getAffiliationsAsOwner() -
  • -
  • [SMACK-815] - XEP-0184: DeliveryReceipt requires ID, although the XEP defines it as optional attribute -
  • -
  • [SMACK-818] - EntityCapsManager sends presences with multiple CapsExtension causing disco#info lookup to fail -
  • -
  • [SMACK-819] - ConcurrentModification Exception in MultiUserChatManager.java -
  • -
  • [SMACK-820] - DNSUtil.setDaneProvider() does not set the DANE provider -
  • -
- -

Task -

-
    -
  • [SMACK-769] - Rename XMPPError to StanzaError -
  • -
  • [SMACK-776] - Remove deprecated reconnection callbacks in ConnectionListener -
  • -
- -

Improvement -

-
    -
  • [SMACK-761] - Adopt ChatStateManager to new Chat API (chat2) -
  • -
  • [SMACK-812] - Enable ModifierOrder checkstyle check -
  • -
  • [SMACK-816] - SimplePayload should infer the XML element name and namespace -
  • -
- -

4.2.4 -- 2018-04-15

- -

Bug -

-
    -
  • [SMACK-804] - ServiceAdministrationManager does not use correct form actions -
  • -
  • [SMACK-805] - ServiceDiscoveryManager.findService() only considers the first service by feature -
  • -
  • [SMACK-813] - Smack uses hostname instead of XMPP service name for SNI -
  • -
- -

New Feature -

-
    -
  • [SMACK-794] - Add support for XEP-0394: Message Markup -
  • -
  • [SMACK-795] - Add support for XEP-0382: Spoiler messages -
  • -
  • [SMACK-799] - Add support for XEP-0372: References -
  • -
  • [SMACK-800] - Add support for XEP-0392: Consistent Color Generation -
  • -
- -

Improvement -

-
    -
  • [SMACK-802] - Rename and deprecate: addPacketSendingListener(), removePacketSendingListener(), addPacketInterceptor() and removePacketInterceptor() -
  • -
  • [SMACK-809] - Make Roster's non-roster presence map second-level map bounded -
  • -
- -

4.2.3 -- 2018-02-07

- -

Bug -

-
    -
  • [SMACK-788] - NullPointerException if hostAddresses is null -
  • -
  • [SMACK-789] - AffiliationsExtension toXml() produces invalid XML -
  • -
  • [SMACK-790] - Some HTTP File Upload elements are not correctly parsed and serialized -
  • -
  • [SMACK-791] - NumberFormatException in IpAddressUtil.isIPv4LiteralAddress -
  • -
  • [SMACK-796] - SOCKS5 authentication erroneously uses 'user' when it should use 'passwd', causes authentication to fail -
  • -
- -

4.2.2 -- 2017-11-25

- -

Bug -

-
    -
  • [SMACK-775] - Create callback interface for ReconnectionManager -
  • -
  • [SMACK-778] - ReconnectionManager.reconnect() can throw NotConnectedException -
  • -
  • [SMACK-779] - smack-android erroneously depends on smack-omemo and smack-omemo-signal -
  • -
  • [SMACK-780] - PushNotificationManager's isSupported logic does query the server, whereas it should query the bare JID -
  • -
  • [SMACK-781] - MiniDnsResolver does not correctly handle the case when NOERROR is returned together with an empty answer section. -
  • -
  • [SMACK-782] - MultiUserChat does not remove the subject listener causing a memory leak -
  • -
  • [SMACK-783] - InvitationRejectionListener fires multiple times -
  • -
  • [SMACK-784] - StringUtils.numbersAndLetters has the numbers twice, resulting in a lower entropy -
  • -
  • [SMACK-785] - OfflineMessageManager.getMessages() does count the pending messages incorrectly, causing an unnecessary delay -
  • -
  • [SMACK-786] - Race condition when resuming a stream -
  • -
  • [SMACK-787] - Presence.getPriority() may return Integer.MIN_VALUE. -
  • -
- -

4.2.1 -- 2017-08-14

- -

Bug -

-
    -
  • [SMACK-749] - SCRAM-SHA-1 and SCRAM-SHA-1-PLUS SASL mechanisms have the same priority, causing SASL authentication failures -
  • -
  • [SMACK-755] - DIGEST-MD5 sometimes causes malformed request server response -
  • -
  • [SMACK-756] - IoTIsFriendResponse has invalid name and produces invalid XML -
  • -
  • [SMACK-759] - PubSubManager.getLeafNode() throws PubSubAssertionError.DiscoInfoNodeAssertionError if node exists but its not a PubSub Node -
  • -
  • [SMACK-764] - NPE in hashCode() in Occupant when jid is null -
  • -
  • [SMACK-766] - Smack possibly includes 'ask' attribute in roster items when sending requests -
  • -
  • [SMACK-768] - Smack throws NoResponse timeout when waiting for IQ although there was a response -
  • -
  • [SMACK-771] - XMPPTCPConnection should use KeyManagerFactory.getDefaultAlgorithm() instead of KeyManagerFactory.getInstance("sunX509"); -
  • -
  • [SMACK-772] - HostAddress must deal with 'fqdn' being null. -
  • -
  • [SMACK-773] - Allow roster pushes from our full JID for backwards compatibility -
  • -
  • [SMACK-774] - HTTP File Upload's SlotRequest metadata should be attributes not child elements -
  • -
- -

New Feature -

-
    -
  • [SMACK-746] - Add support for XEP-0380: Explicit Message Encryption -
  • -
  • [SMACK-758] - Add support for XEP-0334: Message Processing Hints -
  • -
  • [SMACK-760] - Smack does not allow custom extension elements in SM's <failed/> -
  • -
- -

Improvement -

-
    -
  • [SMACK-752] - XEP-0357 Push Notification enable IQ uses wrong form type: Should be 'submit' instead of 'form' -
  • -
  • [SMACK-754] - Allow MUC room subject changes from the MUCs bare JID -
  • -
  • [SMACK-777] - MamManager should use the user's bare JID to check if MAM is supported -
  • -
- -

4.2.0 -- 2017-03-10

- -

Sub-task -

-
    -
  • [SMACK-639] - Add support for pre-approved subscription requests (RFC 6121 § 3.4) -
  • -
- -

Bug -

-
    -
  • [SMACK-306] - loadRosterOnLogin has non-trivial side effect on getRoster -
  • -
  • [SMACK-416] - Refactor PEP to make it use the existing pubsub API. -
  • -
  • [SMACK-674] - PubSub Affiliation extension element is missing 'jid' attribute, and is using wrong element name 'subscription' -
  • -
  • [SMACK-682] - Add support for "XEP-0360: Nonzas (are not Stanzas)" -
  • -
  • [SMACK-683] - Using a Proxy with XMPPTCPConnection failes with "SocketException: Unconnected sockets not implemented" -
  • -
  • [SMACK-691] - Add support for MUCItem's Actor 'nick' -
  • -
  • [SMACK-705] - PubSub's Affiliation.getElementName() returns wrong name -
  • -
  • [SMACK-722] - SASL X-OAUTH2 implementation incorrectly performs Base64 encoding twice -
  • -
  • [SMACK-723] - Support "Caps Optimizations" (XEP-0115 § 8.4) -
  • -
  • [SMACK-724] - Do not re-use the Socket after connect() failed. -
  • -
  • [SMACK-725] - ReconnectionManager should handle AlreadyConnectedException and AlreadyLoggedInException not as failure -
  • -
  • [SMACK-741] - Ad-hoc command 'note' element 'type' attribute should be treated as optional -
  • -
  • [SMACK-745] - Memory leak in MultiUserChat -
  • -
- -

New Feature -

-
    -
  • [SMACK-366] - Add support for DNSSEC. -
  • -
  • [SMACK-610] - Add support for XEP-0080: User Location -
  • -
  • [SMACK-619] - Add roomDestroyed to MUC UserStatusListener -
  • -
  • [SMACK-625] - Add support for XEP-313: Message Archive Management -
  • -
  • [SMACK-675] - Add support for PubSub affiliation actions as owner -
  • -
  • [SMACK-677] - Add support for SASL 'authzid' (Authorization Identity) -
  • -
  • [SMACK-690] - Add support for DNS-Based Authentication of Named Entities (DANE, RFC 6698) -
  • -
  • [SMACK-731] - Add support for XEP-0191: Blocking Command -
  • -
  • [SMACK-732] - Smack should be able to handle "single equals sign" SASL responses -
  • -
  • [SMACK-740] - Add support for Multi-User Chat Light -
  • -
  • [SMACK-742] - Add support for XEP-0133: Service Administration -
  • -
  • [SMACK-747] - Add support for XEP-0363: HTTP File Upload -
  • -
- -

Task -

-
    -
  • [SMACK-638] - Call connection creation listeners from within AbstractXMPPConnection's constructor -
  • -
  • [SMACK-644] - Throw exception if account creation or password change is performed over insecure connections -
  • -
  • [SMACK-655] - Enable StreamManagement by default -
  • -
- -

Improvement -

-
    -
  • [SMACK-372] - Make package protected methods in PEPItem public -
  • -
  • [SMACK-572] - Rejoin MUC rooms after reconnect -
  • -
  • [SMACK-628] - Rework Roster handling with anonymous connections -
  • -
  • [SMACK-629] - Rework how Smack handles anonymous connections -
  • -
  • [SMACK-631] - Improve ParsingExceptionCallback, allow it to be a functional interface -
  • -
  • [SMACK-632] - Make Smack interruptible -
  • -
  • [SMACK-633] - Allow clean and graceful disconnects (stream closing) -
  • -
  • [SMACK-634] - Use jxmpp-jid, add Jid class to replace String's being used as JIDs -
  • -
  • [SMACK-646] - Add support for MUC roomnick rewrite -
  • -
  • [SMACK-647] - Don't automatically call login() on connect() if the connection was authenticated before -
  • -
  • [SMACK-648] - Improve MultiUserChat API -
  • -
  • [SMACK-657] - Rename RosterEntry.getStatus and RosterPacket.ItemStatus to ItemAskStatus -
  • -
  • [SMACK-663] - Roster should be fully loaded when Roster.getInstanceFor(XMPPConnection) is called with a authenticated connection -
  • -
  • [SMACK-665] - Rename 'serviceName' to 'xmppServiceDomain' -
  • -
  • [SMACK-666] - Typo in 'RosterEntries.rosterEntires()', change to 'RosterEntries.rosterEntries()' -
  • -
  • [SMACK-703] - Limit the stored presences of entities not in Roster -
  • -
  • [SMACK-704] - Pass down Message stanza in ChatStateListener -
  • -
  • [SMACK-711] - Improve the logging of TCP connection attempts. -
  • -
  • [SMACK-720] - Improve support for Tor and Hidden Services. -
  • -
  • [SMACK-721] - Report illegal Stream Management states to avoid OOM Exception -
  • -
  • [SMACK-727] - Add partial support for the IoT XEPs (XEP-0323, -0324, -0325, -0347) -
  • -
  • [SMACK-733] - Handle outgoing 'unavailable' Presences in Roster -
  • -
  • [SMACK-736] - Add support for Chat Markers (XEP-0333) -
  • -
  • [SMACK-737] - Add support for Bits of Binary (XEP-0231) -
  • -
  • [SMACK-738] - Add support for Push Notifications (XEP-0357) -
  • -
- -

4.1.9 -- 2016-11-19

- -

Bug -

-
    -
  • [SMACK-739] - Smack starts SASL step without TLS in case STARTTLS is stripped even if SecurityMode.Required is used -
  • -
  • [SMACK-735] - Smack sometimes sends invalid SCRAM-SHA1 nonce -
  • -
- -

4.1.8 -- 2016-07-30

- -

Bug -

-
    -
  • [SMACK-722] - SASL X-OAUTH2 implementation incorrectly performs Base64 encoding twice -
  • -
  • [SMACK-724] - Do not re-use the Socket after connect() failed. -
  • -
  • [SMACK-725] - ReconnectionManager should handle AlreadyConnectedException and AlreadyLoggedInException not as failure -
  • -
  • [SMACK-726] - 'purge' and 'remove' IQ of XEP-0013 must be of type 'set' -
  • -
- -

4.1.7 -- 2016-04-14

- -

Bug -

-
    -
  • [SMACK-712] - XMPPTCPConnection's setEnabledSSL(Protocols|Ciphers) has no effect -
  • -
  • [SMACK-716] - EntityTimeManager.getTime() does not set the recipients JID -
  • -
  • [SMACK-719] - XMPPError should use Locale.US in toUpperCase() -
  • -
- -

Improvement -

-
    -
  • [SMACK-715] - Add Roster.setRosterLoadedAtLoginDefault(boolean) -
  • -
- -

4.1.6 -- 2016-01-23

- -

Bug -

-
    -
  • [SMACK-705] - PubSub's Affiliation.getElementName() returns wrong name -
  • -
  • [SMACK-706] - Smack may sends <bind/> and <session/> twice if Stream Management is used and a previous SM state exists -
  • -
  • [SMACK-707] - Infinite loop of NullPointerExceptions in Socks5Proxy -
  • -
  • [SMACK-708] - DeliveryReceipt(Manager) should ensure that receipts (and requests) have an ID set -
  • -
  • [SMACK-709] - Don't request delivery receipts for messages without a body -
  • -
  • [SMACK-710] - SASL DIGEST-MD5 backslash must be quoted -
  • -
- -

4.1.5 -- 2015-11-22

- -

Bug -

-
    -
  • [SMACK-698] - Time creates invalid XML -
  • -
  • [SMACK-700] - Duplicate stanzas in unacknowledgedStanzas queue when stream is resumed -
  • -
  • [SMACK-702] - RejectedExecutionException in AbstractXMPPConnection.processPacket() causes connection Termination -
  • -
- -

4.1.4 -- 2015-09-14

- -

Bug -

-
    -
  • [SMACK-688] - Reset carbons state if session got not resumed or cleanly disconnected -
  • -
  • [SMACK-689] - PEPPubSub creates malformed XML -
  • -
  • [SMACK-693] - MultiUserChat's UserStatusListener is not getting triggered -
  • -
  • [SMACK-695] - JSON and GCM parser does an erroneous extra next() -
  • -
  • [SMACK-697] - PrivacyListManager should handle the case where not default and active list are currently set -
  • -
- -

Improvement -

-
    -
  • [SMACK-686] - Provide a hint that connect() needs to be called prior login() in NotConnectedException -
  • -
  • [SMACK-687] - Update to jxmpp 0.4.2 -
  • -
  • [SMACK-696] - Drop stream state after stream error -
  • -
- -

4.1.3 -- 2015-07-15

- -

Bug -

-
    -
  • [SMACK-679] - Memory leak in Socks5BytestreamManager. Should use weak map for 'managers' -
  • -
  • [SMACK-680] - XHTML bodies are un-escaped after parsing -
  • -
  • [SMACK-681] - Roster presence callbacks may not be invoked right after login -
  • -
- -

4.1.2 -- 2015-06-27

- -

Bug -

-
    -
  • [SMACK-664] - Invalid IQ error response to OfferRequestPacket and OfferRevokePacket -
  • -
  • [SMACK-668] - ReconnectionManager's value of 'attempts' is not reset after successful reconnection -
  • -
  • [SMACK-669] - Only add Entity Capabilities extension to available presences -
  • -
  • [SMACK-670] - SASLMechanism.authenticate should treat an empty byte array like 'null' byte array -
  • -
  • [SMACK-672] - Memory leak caused by RosterGroup declaring a strong reference to XMPPConnection -
  • -
  • [SMACK-673] - VCard API does not support all elements -
  • -
  • [SMACK-676] - ConcurrentModificationException in ServerPingWithAlarmManager -
  • -
  • [SMACK-678] - Login hangs if starttls advertised, but security is set to 'disabled' and compression is also advertised -
  • -
- -

Improvement -

-
    -
  • [SMACK-667] - Request Stream Mangement Acknowledgement after re-sending unack'ed stanzas after stream resumption -
  • -
  • [SMACK-671] - Don't disable Scoks5BytestreamManager on connection termination -
  • -
- -

4.1.1 -- 2015-05-09

- -

Bug -

-
    -
  • [SMACK-649] - DIGEST-MD5 challenge/response parsing must handle linear white spaces after the comma -
  • -
  • [SMACK-652] - SynchronizationPoint should use signalAll -
  • -
  • [SMACK-653] - Integer overflow if both client and server don't specify a max resumption time -
  • -
  • [SMACK-654] - isSmResumptionPossible() returns wrong values -
  • -
  • [SMACK-656] - DeliveryReceipts auto add should use packet interceptors and should not be requested for messages with ACKs. -
  • -
  • [SMACK-659] - Memory leak caused by RosterEntry declaring a strong reference to XMPPConnection -
  • -
  • [SMACK-660] - ReconnectionManager's RANDOM_INCREASING_DELAY is erroneously using a fixed value. -
  • -
  • [SMACK-661] - Add method to set ProxyInfo in ConnectionConfiguration.Builder -
  • -
  • [SMACK-662] - RosterEntry.setName() does not change the name -
  • -
- -

4.1.0 -- 2015-03-29

- -

Sub-task -

- - -

Bug -

-
    -
  • [SMACK-65] - Packet parsing should look for depth -
  • -
  • [SMACK-237] - Handle more vCard values (XEP-0054) -
  • -
  • [SMACK-383] - Allow the garbage collection of all object instances of a closed and unreferenced connection -
  • -
  • [SMACK-424] - Add a MultiUserChat.presenceChanged callback method to be informed if a presence within a MUC has changed (joined, leaved, status change) -
  • -
  • [SMACK-542] - MUC: RoomInfo should hold more data if the result contains a FORM_TYPE field -
  • -
  • [SMACK-549] - MUCUser#getStatus should be a List -
  • -
  • [SMACK-564] - Some tests fail with Java 8 -
  • -
  • [SMACK-570] - Smack does not support resourceparts which contain the '@' character. -
  • -
  • [SMACK-571] - Don't remove the MUC listeners after a disconnect() , keep state of Connection between disconnect() and connect()/login() -
  • -
  • [SMACK-573] - MessageEventManager treats error replies as message events -
  • -
  • [SMACK-583] - PacketListeners may not be invoked in delivery order -
  • -
  • [SMACK-585] - XMPPTCPConnection does not set 'host' and 'port' -
  • -
  • [SMACK-590] - Don't use IQReplyFilter for the bind set/result exchange -
  • -
  • [SMACK-597] - PingManager.getLastReceivedPong() always returns -1 -
  • -
  • [SMACK-604] - MUCUser must support multiple status codes -
  • -
  • [SMACK-620] - Smack should use a safe SAX parser, e.g. with entity reference expansion disabled -
  • -
  • [SMACK-635] - Typo DNSUtil.init() prevents DNS SRV lookups to fail in some cases -
  • -
  • [SMACK-643] - Smack should not set the service name to the vale of the 'from' attribute of the opening stream element received from the service -
  • -
- -

Improvement -

-
    -
  • [SMACK-340] - Should make the wait/timeouts on SASL authentication configurable. -
  • -
  • [SMACK-402] - Update obsolete "Message Delivery Receipts" support from (JEP|XEP)-0022 to XEP-0184 -
  • -
  • [SMACK-453] - Add support for all primitive types in IntrospectionProvider.decode() -
  • -
  • [SMACK-521] - Clear PacketWriters queue when the connection is shut down -
  • -
  • [SMACK-532] - Evaluate if its possible to guarantee the order of listeners by using a LinkedHashMap -
  • -
  • [SMACK-566] - Create public method that parses Strings/CharSequences to messages, IQs and presence instances -
  • -
  • [SMACK-587] - Subprojects should uses versions when importing the OSGi smack-core components -
  • -
  • [SMACK-595] - Add an API to send a stanza and wait asynchronously for a response -
  • -
  • [SMACK-599] - Provide string messages to all exceptions thrown by Smack -
  • -
  • [SMACK-600] - RoomInfo Class should add the information from the Identity element. -
  • -
  • [SMACK-608] - Add support for XMPP error conditions text -
  • -
  • [SMACK-622] - Add support for 'optional' in session stream features -
  • -
  • [SMACK-626] - Add support for 'ofrom' Extended Stanza Addressing type -
  • -
  • [SMACK-627] - Smack should allow null usernames under certain circumstances -
  • -
  • [SMACK-645] - Roster should not leak internal state e.g. presences -
  • -
- -

New Feature -

-
    -
  • [SMACK-234] - Add support for SASL EXTERNAL: PKI (Client SSL Cert) Support -
  • -
  • [SMACK-333] - Implement XEP-0198: Stream Management -
  • -
  • [SMACK-378] - Give access to the socket outside the XMPPconnection -
  • -
  • [SMACK-581] - Add support for "Result Set Management" (XEP-59) -
  • -
  • [SMACK-607] - Add support for XEP-0352: Client State Indication -
  • -
  • [SMACK-612] - Add support for XEP-0141: Data Forms Layout -
  • -
  • [SMACK-621] - Add support for XEP-0122: Data Forms Validation -
  • -
  • [SMACK-623] - Add API to retrieve the subscriptions of a PubSub node as owner -
  • -
- -

Task -

-
    -
  • [SMACK-365] - SmackConfiguration should only report errors if the file fails to load, not when it fails to load for a specific classloader. -
  • -
  • [SMACK-371] - Some MUC tasks are using stanza's as defined in an older version of the spec. Fails to work on some servers. -
  • -
  • [SMACK-569] - Move Message Event code to legacy subproject -
  • -
  • [SMACK-578] - Remove decorators for "Legacy Delayed Delivery" (XEP-91) in favor of Delayed Delivery (XEP-203) -
  • -
  • [SMACK-579] - FileTransferManager and FileTransferNegoiator should use WeakHashMaps and extend Manager -
  • -
  • [SMACK-582] - Change ReceiptReceivedListener.onReceiptReceived parameters -
  • -
  • [SMACK-637] - Move Roster and Chat code to new smack-im subproject -
  • -
- -

4.0.7 -- 2015-02-20

- -

Bug -

-
    -
  • [SMACK-635] - Typo DNSUtil.init() prevents DNS SRV lookups to fail in some cases -
  • -
  • [SMACK-643] - Smack should not set the service name to the vale of the 'from' attribute of the opening stream element received from the service -
  • -
- -

4.0.6 -- 2014-11-23

- -

Bug -

-
    -
  • [SMACK-616] - Smack should fallback to using host with default port if DNS SRV lookup fails -
  • -
  • [SMACK-617] - Message Digest in EntityCapsManager should be synchronized -
  • -
- -

4.0.5 -- 2014-10-22

- -

Bug -

-
    -
  • [SMACK-609] - PingManager.ping(String, long) does not respect timeout -
  • -
  • [SMACK-613] - Parsing exception causes infinite loop if the exception is not thrown -
  • -
- -

4.0.4 -- 2014-09-05

- -

Bug -

-
    -
  • [SMACK-596] - Smack should load roster before sending the initial presence -
  • -
  • [SMACK-598] - Smack should allow the empty string as content of message body element -
  • -
  • [SMACK-601] - PubSub ItemProvider does only process the outermost namespace definition when creating PayloadItems -
  • -
  • [SMACK-602] - PacketCollector must handle InterruptException -
  • -
  • [SMACK-603] - XMPPError.Condition.equals() should be null-safe -
  • -
- -

4.0.3 -- 2014-08-16

- -

Bug -

-
    -
  • [SMACK-589] - FormField.Option toXML() produces malformed XML -
  • -
  • [SMACK-592] - OfflineMessagesManager.getMessages() does send request before collector is set up and could leak collector -
  • -
  • [SMACK-594] - PrivateData Bookmarks.toXML() returns invalid XML -
  • -
- -

Improvement -

-
    -
  • [SMACK-539] - Verify ConnectionConfiguration parameters -
  • -
  • [SMACK-588] - Typo in org.jivesoftware.smackx.pubsub.ConfigureForm: s/isSubscibe/isSubscribe/ -
  • -
  • [SMACK-593] - Smack should prefer full flush over sync flush when using compression -
  • -
- -

4.0.2 -- 2014-07-27

- -

Improvement -

-
    -
  • [SMACK-576] - smack-resolver-javax should become a OSGi ServiceComponent -
  • -
  • [SMACK-586] - Extend API to configure a HostnameVerifier -
  • -
- -

4.0.1 -- 2014-07-20

- -

Sub-task -

-
    -
  • [SMACK-346] - Bug in return code for rejection handling in FileTransferManager -
  • -
- -

Bug -

-
    -
  • [SMACK-574] - Documentation still refers at some places to Connection -
  • -
  • [SMACK-575] - PingManager schedules pings after pingInterval when it should be use nextPingIn instead -
  • -
  • [SMACK-577] - Bookmarks and FormField toXml() methods do not properly escape XML -
  • -
  • [SMACK-583] - PacketListeners may not be invoked in delivery order -
  • -
- -

Improvement -

-
    -
  • [SMACK-576] - smack-resolver-javax should become a OSGi ServiceComponent -
  • -
- -

New Feature -

-
    -
  • [SMACK-580] - Add support for retrieving a PubSub node's affiliations -
  • -
- -

4.0.0 -- 2014-06-08

- -

Sub-task -

-
    -
  • [SMACK-399] - Add support for Roster Versioning (was XEP-0237, now in RFC 6121) -
  • -
  • [SMACK-400] - Change xml-not-well-formed to not-well-formed -
  • -
  • [SMACK-401] - Remove <invalid-id/> -
  • -
  • [SMACK-445] - XMPPError class is based on deprecated XEP-0086 -
  • -
- -

Bug -

-
    -
  • [SMACK-357] - Error in SASL authentication when SASL authzid parameter is null -
  • -
  • [SMACK-410] - Any valid SSL server certificate can be used to perform a man-in-the-middle attack -
  • -
  • [SMACK-411] - ServiceDiscoveryManager identities should be non-static and kept in a Set to allow multiple identities as per XEP-30 -
  • -
  • [SMACK-414] - Smack does not announce the support for XEP-54 aka vcard-temp -
  • -
  • [SMACK-427] - Typo in code - StreamInitiation.setSesssionID() -
  • -
  • [SMACK-467] - Don't use the default locale for machine-readable output, use Locale.US instead -
  • -
  • [SMACK-531] - Add missing namespace attribute to XHTML-IM body tags -
  • -
  • [SMACK-533] - Smack should prevent IQ response spoofing -
  • -
  • [SMACK-535] - jul.properties should only configure the 'org.igniterealtime' namespace -
  • -
  • [SMACK-538] - ParseRoster does not check the sender of the roster and for pending roster queries -
  • -
  • [SMACK-541] - XHTMLExtensionProvider relies on incorrect behavior of MXParser, violating the contract of the XMLPullParser interface -
  • -
  • [SMACK-543] - packet.Time is not thread-safe -
  • -
  • [SMACK-546] - PubSub's Item needs to escape its XML payload -
  • -
  • [SMACK-548] - PingManager notifies pingFailedListeners multiple times -
  • -
  • [SMACK-551] - ChatManager throws NPE, when Message has no 'from' attribute -
  • -
  • [SMACK-554] - Memory leak in BookmarkManager -
  • -
  • [SMACK-555] - VCardProvider should consider some elements as optional -
  • -
  • [SMACK-558] - connect() must wait until the stream features have been parsed -
  • -
  • [SMACK-559] - Roster entries without a group are not updated -
  • -
  • [SMACK-560] - Race condition in PacketWriter -
  • -
  • [SMACK-567] - XMPPConnection leaks listenerExecutor ExecutorService -
  • -
- -

Improvement -

-
    -
  • [SMACK-343] - Make Smack jar an OSGi bundle. -
  • -
  • [SMACK-356] - There is no way to reliably end a Chat and have a new one created. -
  • -
  • [SMACK-454] - Follow XEP-0170 recommendation: Compression before Resource Binding -
  • -
  • [SMACK-459] - Add option to configure the default identity in ServiceDiscoveryManager -
  • -
  • [SMACK-465] - Replace custom wrapped Throwable in XMPPException with Exception.cause -
  • -
  • [SMACK-468] - Don't throw an IOException in IBBStreams when the stream got closed by the remote -
  • -
  • [SMACK-536] - JUL Loggers should become final -
  • -
  • [SMACK-537] - Move XMPP Ping code to smackx, add keep-alive functionality to PingManager -
  • -
  • [SMACK-545] - Change API to the style mentioned in Smack's Code Guidelines -
  • -
  • [SMACK-547] - Consistent behavior for "from" attribute on outgoing stanzas -
  • -
  • [SMACK-556] - Make ConnectionConfigration getters public -
  • -
  • [SMACK-557] - Provide a MultiUserChat method to create *or* join a room -
  • -
  • [SMACK-568] - Don't exclude groupchat messages without body element in MultiUserChat MessageListeners -
  • -
- -

New Feature -

-
    -
  • [SMACK-53] - Add support for XEP-0092: Software Version -
  • -
  • [SMACK-71] - Create new FromFilter that checks for exact matching -
  • -
  • [SMACK-187] - Add HTTP Binding support (BOSH / XEP-0124) -
  • -
  • [SMACK-265] - Move to a newer build process with artifacts published to maven central repo -
  • -
  • [SMACK-426] - Improve XMPPException -
  • -
  • [SMACK-544] - Add support for XEP-0079: Advanced Message Processing -
  • -
  • [SMACK-552] - Add support for "HTTP over XMPP transport" aka. XEP-0332 -
  • -
- -

Task -

-
    -
  • [SMACK-371] - Some MUC tasks are using stanza's as defined in an older version of the spec. Fails to work on some servers. -
  • -
  • [SMACK-432] - Code cleanup of deprecated methods -
  • -
  • [SMACK-446] - Remove non-SASL authentication code -
  • -
- -

3.4.1 -- Feb 9, 2014

- -

Bug

-
    -
  • [SMACK-540] - Memory leak in MultiUserChat -
  • -
- -

3.4.0 -- Feb 2, 2014

- -

Bug Fixes

-
    -
  • [SMACK-442] - Manager's should also handle connectionClosedOnError()
  • -
  • [SMACK-443] - ReconnectionSuccessful listeners are invoked twice on reconnection if connect() failed before
  • -
  • [SMACK-452] - PacketParserUtils.parseStreamError() is not aware of optional text element and therefore failes to parse stream error's correctly. Prevents ReconnectionManager from reconnecting.
  • -
  • [SMACK-458] - Smack's Managers should not remove itself when the connection is closed or should re-add themselfs if the connection get reconnected
  • -
  • [SMACK-462] - Prevent duplicate manager instances by using the manager's constructor in the ConnectionCreationListener's connectionCreated
  • -
  • [SMACK-463] - packet listeners silently fail when preceding listener caused exception
  • -
  • [SMACK-524] - Use correct block-size definition for IBB transfers
  • -
  • [SMACK-525] - NPE in XMPPConnection.notifyConnectionError
  • -
  • [SMACK-529] - Add support for XEP-0280 "Message Carbons"
  • -
  • [SMACK-530] - DNSUtilTest requires an internet connection to work, it should be moved to integration tests.
  • -
- -

New Feature

-
    -
  • [SMACK-286] - Need to change ProviderManager to support loading smack.providers from alternative locations
  • -
  • [SMACK-387] - Allow configuration of ChatManager to be able to allow message handling to be customized.
  • -
  • [SMACK-403] - Add support for XEP-0297 "Stanza Forwarding"
  • -
  • [SMACK-434] - Create a project to contain non production ready implementations of specifications
  • -
- -

Improvement

-
    -
  • [SMACK-343] - Make Smack jar an OSGi bundle.
  • -
  • [SMACK-381] - Separate the configuration for smack extension related classes from the smack jar.
  • -
  • [SMACK-444] - Allow 'null' for TruststorePath and TruststorePassword in ServerTrustManager
  • -
  • [SMACK-456] - Add the causing exception to the XMPPExceptions thrown in XMPPConnection
  • -
  • [SMACK-457] - Remove unnecessary printStackTrace() in XMPPConnection
  • -
  • [SMACK-460] - ServiceDiscoveryManager should not use the constructor in connectionCreated()
  • -
  • [SMACK-461] - Remove incorrect deprecated marker for DiscoverInfo.Identity.setType()
  • -
  • [SMACK-464] - Make it clear that PacketListener's added with XMPPConnection.addPacketListener() are only for received packets
  • -
  • [SMACK-534] - Convert all System.out and printStackTrace calls to use Java util logging.
  • -
  • [SMACK-339] - Allow ConnectionListeners to be added before XMPPConnection is connected. Currently throws exception
  • -
  • [SMACK-373] - Don't remove listeners after a disconnect() , keep state of XMPPConnection between disconnect() and connect()/login()
  • -
  • [SMACK-434] - Create a project to contain non production ready implementations of specifications
  • -
  • [SMACK-526] - Deprecate all PEP related classes.
  • -
- -

3.3.1 -- Oct 6, 2013

- -

Bug Fixes

-
    -
  • [SMACK-428] - RosterEntry overrides equals, but not hashcode.
  • -
  • [SMACK-438] - Possible NPE in MultiUserChat.InvitationsMonitor.getInvitationsMonitor()
  • -
  • [SMACK-441] - Memory leak in KeepAliveManager
  • -
  • [SMACK-447] - Compression is not enabled for Java7ZlibInputOutputStream
  • -
  • [SMACK-448] - Java7ZlibInputOutputStream does not work. Deflater.DEFAULT_STRATEGY is used as compression level when it should use Deflater.DEFAULT_COMPRESSION
  • -
  • [SMACK-450] - VCard.load() throws null pointer exception if there is no VCard for the user
  • -
  • [SMACK-455] - Multiple items doesn`t not parse correctly in a pubsub message
  • -
- -

New Feature

-
    -
  • [SMACK-425] - Collect (parser) Exceptions and unparseable stanzas. Provide a callback method so that the user is notified about them if he wants to
  • -
- -

Improvement

-
    -
  • [SMACK-369] - Exceptions during login should get thrown back up to the caller.
  • -
  • [SMACK-439] - Improve documentation for MultiUserChat.InvitationsListener
  • -
  • [SMACK-451] - PingManager entry in META-INF/smack.providers is within Ad-Hoc Command section
  • -
  • [SMACK-431] - Enable Entity Caps as default for new connections and write extensions documentation html page
  • -
  • [SMACK-405] - Cleanup of redundant code in XMPPConnection.shutdown()
  • -
- -

3.3.0 -- May 4, 2013

- -

Bug Fixes

-
    -
  • [SMACK-225] - Improper handeling of DNS SRV records
  • -
  • [SMACK-238] - The vCard avatar type always return jpg
  • -
  • [SMACK-270] - Fix for a memory leak in MUC with MUC.finalize()
  • -
  • [SMACK-278] - Deadlock during Smack disconnect
  • -
  • [SMACK-342] - VCards causes ConcurrentModificationException
  • -
  • [SMACK-344] - Bug in SASL authentication mechanism when SRV records are being used.
  • -
  • [SMACK-351] - Rework File Transfer
  • -
  • [SMACK-352] - Update the licensing headers in various files.
  • -
  • [SMACK-355] - IO Error if smack cant use port for local proxy
  • -
  • [SMACK-371] - Some MUC tasks are using stanza's as defined in an older version of the spec. Fails to work on some servers.
  • -
  • [SMACK-375] - Node strings in the discovery info packets are not escaped as in the other packets
  • -
  • [SMACK-382] - Prevent memory leak in AdHocCommandManager
  • -
  • [SMACK-384] - Endless waiting for connection to be established
  • -
  • [SMACK-390] - Smack login will fail if a bad delay packet is received
  • -
  • [SMACK-392] - In ant build, compile-test target doesn't work.
  • -
  • [SMACK-394] - Erroneous cast in IBBInputStream's read() method
  • -
  • [SMACK-395] - Socks5BytestreamManager's establishConnection() should still try to use the local streamhost proxy if the server doesn't provide one
  • -
  • [SMACK-404] - Smack uses the wrong method to decode Base64 Strings
  • -
  • [SMACK-413] - VCardProvider incorrectly parses binary value of avatars
  • -
  • [SMACK-415] - ItemProvider relies on incorrect behavior of MXParser, violating the contract of the XMLPullParser interface
  • -
  • [SMACK-417] - If both PacketReader and PacketWriter fail at the same time, connectionClosedonError() is called two times
  • -
- -

New Features

-
    -
  • [SMACK-331] - Add support for XEP-0184: Message Delivery Receipts
  • -
  • [SMACK-345] - Inproved detection of last activity
  • -
  • [SMACK-361] - Add support for XEP-0115 Entity Capabilities
  • -
  • [SMACK-376] - Setting a custom trust manager to control certificates from outside
  • -
  • [SMACK-388] - XEP-199 XMPP Ping support
  • -
- -

Improvements

-
    -
  • [SMACK-341] - Update the PacketCollector and ConnectionDetachedPacketCollector to use the java concurrent classes.
  • -
  • [SMACK-358] - Support additional properties for account creation in test cases.
  • -
  • [SMACK-363] - Code Cleanup
  • -
  • [SMACK-377] - avoid unnecessary DNS requests in XMPPconnection
  • -
  • [SMACK-379] - Sessions were removed from the specification but Smack still uses them. Should be updated to reflect the spec changes.
  • -
  • [SMACK-385] - Reusing KeyStore in order to reduce memory usage
  • -
  • [SMACK-389] - Add java.util.zip.Deflater(In|Out)putStream as Java7 API native alternative to JZlib
  • -
  • [SMACK-391] - Improve date parsing in StringUtils and make DelayInformationProvider use StringUtils for date parsing.
  • -
  • [SMACK-412] - Replace the whitespace ping with a XEP-0199 ping
  • -
  • [SMACK-419] - PacketWriter: Only flush the BufferedWriter if the packet queue is empty
  • -
  • [SMACK-423] - Investigate whether unhandled packets should still parse the child xml into a string as content
  • -
  • [SMACK-430] - Throw an exception if FileTransferManager.createOutgoingFileTransfer() was used with a bare JID
  • -
- -

3.2.2 -- Dec. 23, 2011

- -

Bug Fixes

-
    -
  • [SMACK-263] - Set file info in all send* methods
  • -
  • [SMACK-322] - NPE in XMPPConnection
  • -
  • [SMACK-324] - Investigate SASL issue with jabberd2 servers
  • -
  • [SMACK-338] - IBB filetransfer doesn't work as expected
  • -
  • [SMACK-346] - Bug in return code for rejection handling in FileTransferManager
  • -
  • [SMACK-348] - Documentation error - broken link
  • -
  • [SMACK-349] - Smack's IBB sends too much data in a packet
  • -
  • [SMACK-350] - Bytestream is not working in Spark 2.6.3 from XP to W7
  • -
  • [SMACK-353] - Thread leak in the FaultTolerantNegotiator
  • -
  • [SMACK-362] - smack throw NoSuchElementException if the muc#roominfo_subject has no values
  • -
- -

Improvements

-
    -
  • [SMACK-343] - Make Smack jar an OSGi bundle.
  • -
  • [SMACK-354] - Provide milliseconds in timestamp colum debugwindow
  • -
- -

3.2.1 -- July 4, 2011

-

Bug Fixes

-
    -
  • [SMACK-129] - MultiUserChat will Store Messages in its PacketCollector irregardless of whether or not they are being read
  • -
  • [SMACK-230] - Disconnect Can Cause Null Pointer Exception
  • -
  • [SMACK-273] - Bug in RoomListenerMultiplexor.java
  • -
  • [SMACK-329] - XHTMLText uses improper format for br tag
  • -
  • [SMACK-338] - IBB filetransfer doesn't work as expected
  • -
  • [SMACK-324] - Investigate SASL issue with jabberd2 servers
  • -
- -

3.2.0 -- May 3, 2011

-

New Features

-
    -
  • [SMACK-272] - Add support for pubsub (XEP-0060)
  • -
  • [SMACK-296] - Add support for XEP-0224: Attention
  • -
  • [SMACK-319] - Add common interfaces for SOCKS5 Bytestreams and In-Band Bytestreams
  • -
- -

Improvements

-
    -
  • [SMACK-137] - File Transfer Settings
  • -
  • [SMACK-156] - Add the ability to register for roster events before logging in
  • -
  • [SMACK-261] - Minor Jingle cleanup to better support Jingle in Spark
  • -
  • [SMACK-277] - Update XMLUnit to the latest version
  • -
  • [SMACK-282] - Support SASL-related error conditions.
  • -
  • [SMACK-283] - Investigate why Jingle is connecting to stun.xten.net
  • -
  • [SMACK-285] - Add support for Nicks
  • -
  • [SMACK-289] - There is no way of retrieving items from a pubsub node when the user has multiple subscriptions.
  • -
  • [SMACK-294] - Handle empty roster groups and no goups in the same way
  • -
  • [SMACK-295] - Fire reconnectionSuccessful event when session is established
  • -
  • [SMACK-297] - add configuration for local Socks5 proxy
  • -
  • [SMACK-298] - Respond to all incoming Socks5 bytestream requests
  • -
  • [SMACK-299] - Improve accepting of Socks5 bytestream requests
  • -
  • [SMACK-300] - improve local Socks5 proxy implemetation
  • -
  • [SMACK-301] - support for bytestream packets to query Socks5 proxy for network address
  • -
  • [SMACK-302] - Improve establishing of Socks5 bytestreams
  • -
  • [SMACK-303] - integrate of the extracted Socks5 bytestream API in file transfer API
  • -
  • [SMACK-304] - Extend the IQ API to create empty IQ results and IQ error response packets
  • -
  • [SMACK-307] - Improve Message Parser Robustness and Message Body I18N
  • -
  • [SMACK-309] - Fully implement XEP-0047 In-Band Bytestreams
  • -
  • [SMACK-310] - Add Support for Localized Message Subjects
  • -
- -

Bug Fixes

-
    -
  • [SMACK-163] - Fix NPE in RoomInfo when subject has not value
  • -
  • [SMACK-207] - Parsing of messages may disconnect Smack/Spark
  • -
  • [SMACK-225] - Improper handeling of DNS SRV records
  • -
  • [SMACK-232] - Better handling of Roster error
  • -
  • [SMACK-243] - Packet with wrong date format makes Smack to disconnect
  • -
  • [SMACK-264] - fix for NPE in SASLMechanism.java
  • -
  • [SMACK-269] - Smack 3.1.0 creates a new chat for every incoming message
  • -
  • [SMACK-271] - Deadlock in XMPPConnection while login and parsing stream features
  • -
  • [SMACK-275] - Patch: Fix for broken SASL DIGEST-MD5 implementation
  • -
  • [SMACK-280] - The authentification should use the XMPPConnection#sendPacket method and work transparent with packets and packet listeners.
  • -
  • [SMACK-288] - The parsing of the result for a LeafNode.getItems() call is incorrect. It creates a DefaultPacketExtension instead of an Item for every other item in the result.
  • -
  • [SMACK-290] - Deadlock while getting Roster before it's initialized
  • -
  • [SMACK-291] - RosterGroup modifications should depend on roster push
  • -
  • [SMACK-293] - Support optional roster subscription attribute
  • -
  • [SMACK-305] - RosterEntry#getGroups causing a roster reload
  • -
  • [SMACK-308] - Multiple errors in pubsub GetItemsRequest
  • -
  • [SMACK-312] - Only fire RosterListener#entriesUpdated for RosterEntries that changed
  • -
  • [SMACK-327] - getFeatures() method on DiscoverInfo is improperly set to be package protected instead of public
  • -
  • [SMACK-328] - Number format exception while parsing dates.
  • -
  • [SMACK-332] - Smack 3.2.0b2 shows wrong version in Smack Dubugger Window
  • -
  • [SMACK-334] - Error in form for FileTransferNegotiator
  • -
- -

3.1.0 -- November 20, 2008

- -

New Features

-
    -
  • [SMACK-142] - Added support for Kerberos/NTLM. (6 votes)
  • -
  • [SMACK-210] - Added support for MD5 SASL. (1 vote)
  • -
  • [SMACK-256] - Added support for new sophisticated TLS mechanisms including SmartCard and Apple's KeychainStore.
  • -
  • [SMACK-242] - Added support for JEP-50: Ad-hoc commands.
  • -
  • [SMACK-251] - Added support for XEP-0163: Personal Eventing Protocol. (1 vote)
  • -
  • [SMACK-226] - XMLConnection can now be used with an http/socks proxy. (2 votes)
  • -
  • [SMACK-254] - Loading the Roster during login is now optional.
  • -
  • [SMACK-255] - Added ability to set mime type for avatar.
  • -
  • [SMACK-235] - Improved performance of Roster class.
  • -
  • [SMACK-241] - Updated Base64 implementation to match Openfire's.
  • -
  • [SMACK-240] - Updated Jingle implementation to newest version.
  • -
  • [SMACK-246] - Improve Jingle logging using commons-logging
  • -
  • [SMACK-244] - Updated JSTUN to 0.7.2.
  • -
  • [SMACK-259] - Updated XPP library to latest version.
  • -
- -

Bug Fixes

-
    -
  • [SMACK-231] - IBB Outputstream was not being flushed before it was closed.
  • -
  • [SMACK-236] - Renamed stanza error "unexpected-condition" to "unexpected-request".
  • -
  • [SMACK-258] - Fixed disconnection issue when parsing SASL success that contained a payload.
  • -
  • [SMACK-175] - Fixed typo in RosterPacket.ItemStatus constant.
  • -
  • [SMACK-260] - Added handling of error presence packets
  • -
- -

3.0.3 -- May 31, 2007

- -

New Features

-
    -
  • [SMACK-99] - Added support for multiple message bodies and message body languages.
  • -
  • [SMACK-218] - Implemented GSSAPI for single-sign on.
  • -
- -

Bug Fixes

-
    -
  • [SMACK-219] - The getPresence method was not working correctly with offline presence.
  • -
  • [SMACK-224] - SASL authenticion was using the XMPP domain instead of the FQDN.
  • -
- -

3.0.2 -- May 3, 2007

- -

Bug Fixes

-
    -
  • [SMACK-212] - Jingle can't establish session if only one side has a relay service
  • -
  • [SMACK-213] - RTP Bridge Resolver get wrong localhost address in certain situations
  • -
  • [SMACK-214] - Presences with a negative priority of -1 are not sending the priority to the server
  • -
- -

3.0.1 -- April 12, 2007

- -

Bug Fixes

-
    -
  • [SMACK-211] - Jingle ICE with relay sometimes closed sessions.
  • -
  • Upgraded bundled version of JSTUN.
  • -
- - -

3.0.0 -- March 31, 2007

- -

Important Changes

-
    -
  • Java 5 is now required.
  • -
  • Several API changes are not backwards compatible. In particular, connection handling has - been significantly updated, the GroupChat class has been dropped in favor of the standardized - MultiUserChat, and the Chat class has an updated API.
  • -
- -

New Features

-
    -
  • [SMACK-74] - Added support for unavailable presences with status text. (4 votes)
  • -
  • [SMACK-191] - RosterListener API improvements.
  • -
  • [SMACK-194] - Roster.getPresence(String) now considers mode after priority to determine the presence value to return.
  • -
  • [SMACK-195] - Added the ability to disconnect with a custom presence value (for offline status).
  • -
  • [SMACK-200] - Added convenience methods to Presence class.
  • -
  • [SMACK-31] - Added support for privacy lists. (4 votes)
  • -
  • [SMACK-94] - Added support for last activity of online users. (1 vote)
  • -
  • [SMACK-121] - Added support for stream errors.
  • -
  • [SMACK-136] - Added support for XEP-0048: bookmark storage.
  • -
  • [SMACK-144] - Added bookmark manager for central bookmark management.
  • -
  • [SMACK-150] - Added support for handling node features in disco.
  • -
  • [SMACK-167] - Added support for XEP-0106: JID Escaping
  • -
  • [SMACK-171] - The presence of available contacts is now changed to offline when the connection is closed.
  • -
  • [SMACK-172] - Added support for re-connection when the connection is abruptly closed.
  • -
  • [SMACK-182] - ProviderManager is now pluggable (for Eclipse ECF).
  • -
  • [SMACK-185] - Added the workgroup API to Smack.
  • -
  • [SMACK-206] - Added the option to specify the username to use for the automated test cases.
  • -
  • [SMACK-208] - Added a max queue size for outgoing packets to prevent memory issues during extreme load.
  • -
  • [SMACK-209] - Initial Jingle support implemented.
  • -
- -

Bug Fixes

-
    -
  • [SMACK-6] - Don't force use of collectors in Chat class.
  • -
  • [SMACK-10] - Flush pending packets before closing the connection. (4 votes)
  • -
  • [SMACK-51] - Use unique Thread names among connections.
  • -
  • [SMACK-54] - Add #equals and #hashCode to Occupant.
  • -
  • [SMACK-86] - Made presence checks case in-sensitive.
  • -
  • [SMACK-93] - XHTML provider wasn't handling some tags correctly.
  • -
  • [SMACK-138] - Added caching to file transfer negotiation operations.
  • -
  • [SMACK-143] - Updated XMPPError to be compliant with RFC3920.
  • -
  • [SMACK-145] - XHTML parsing could fail in some cases.
  • -
  • [SMACK-146] - DNS lookups were failing with some DNS servers.
  • -
  • [SMACK-147] - Removed invisibility presence mode.
  • -
  • [SMACK-148] - Socks 5 listening thread was not cleaning up correctly. (2 votes)
  • -
  • [SMACK-149] - Fixed possible memory leaking in PacketReader.
  • -
  • [SMACK-151] - Now use getBytes("UTF-8") instead of getBytes().
  • -
  • [SMACK-152] - The FN field is duplicated when loading vCards from the server.
  • -
  • [SMACK-153] - Optimized performance by replacing StringBuffer with StringBuilder.
  • -
  • [SMACK-154] - Fixed roster test cases that were sometimes failing.
  • -
  • [SMACK-155] - Optimized MUC performance by reducing number of packet collectors and listeners.
  • -
  • [SMACK-158] - FileTransfer isDone() method was returning true even when the transfer was refused.
  • -
  • [SMACK-159] - Filenames were not escaped for file transfers.
  • -
  • [SMACK-160] - Now use stream:feature to discover registration support.
  • -
  • [SMACK-161] - Improved connection speed.
  • -
  • [SMACK-162] - Fixed NPE in SmackConfiguration.
  • -
  • [SMACK-163] - Fixed NPE in RoomInfo when subject was null.
  • -
  • [SMACK-164] - Contact name was not being escaped.
  • -
  • [SMACK-165] - Listeners were not being removed from PacketReader.
  • -
  • [SMACK-166] - Packet reader thread was freezing when parsing an error text with no description.
  • -
  • [SMACK-168] - Fixed possible delay in PacketReader when negotiating TLS.
  • -
  • [SMACK-173] - Renamed ConnectionEstablishedListener to ConnectionCreationListener.
  • -
  • [SMACK-176] - Fixed incorrect property initialization.
  • -
  • [SMACK-177] - Removed synchronization from Roster.
  • -
  • [SMACK-178] - Added NodeInformation#getNodeIdentities() to return identities of hosted nodes
  • -
  • [SMACK-181] - Improved parsing of certificates to get signed domains.
  • -
  • [SMACK-183] - Documentation fixes.
  • -
  • [SMACK-184] - Simplified XMPPConnection constructors.
  • -
  • [SMACK-203] - NULL thread IDs would cause an error inside of the Chat Manager.
  • -
  • [SMACK-205] - Fixed PacketReader concurrency problems.
  • -
  • [SMACK-188] - Resources are now closed after reading the keystore.
  • -
  • [SMACK-189] - The listener was remaining blocked forever in some cases.
  • -
  • [SMACK-190] - Exceptions while notifying packet reader listeners was stopping the notification thread.
  • -
  • [SMACK-192] - Roster.getPresence(String) now forces use of the bare JID.
  • -
  • [SMACK-193] - New presence packets now default to a null presence mode.
  • -
  • [SMACK-196] - Now set closed to true at the start of the connection shutdown method and not the end.
  • -
  • [SMACK-197] - The source build was failing.
  • -
  • [SMACK-198] - File transfer streams were not being closed properly in some cases.
  • -
  • [SMACK-199] - MultiUserChat invitation listeners are no longer removed on disconnects.
  • -
  • [SMACK-201] - Roster no longer exposes that it implements ConnectionListener.
  • - -
- -

2.2.1 -- June 12, 2006

- -
    -
  • [SMACK-141] - Fixed SSL exception while creating new XMPPConnections. (1 vote)
  • -
  • [SMACK-127] - Fixed incorrect file transfer progress.
  • -
  • [SMACK-130] - Fixed VCard escaping problem that was crashing connections.
  • -
  • [SMACK-134] - VCards were not being saved when avatar was the only element.
  • -
  • [SMACK-131] - Illegal XML characters are now properly escaped in the presence status.
  • -
  • [SMACK-133] - Illegal XML characters are now properly escaped in groups names.
  • -
  • [SMACK-132] - Fixed IBB problem triggered when buffersize was increased.
  • -
  • [SMACK-135] - Moved to new Base64 implementation to fix line break issue in old implementation.
  • -
- -

2.2.0 -- March 9, 2006

-
    -
  • [SMACK-122] - Added support for JEP-96: File Transfer. (1 vote)
  • -
  • [SMACK-72] - Added support for JEP-47: In-Band Bytestreams. (2 votes)
  • -
  • [SMACK-122] - Added support for JEP-65: SOCKS5 Bytestreams. (1 vote)
  • -
  • [SMACK-112] - Added support for JEP-38 Stream Compression.
  • -
  • [SMACK-117] - Added support for JEP-33: Extended Stanza Addressing.
  • -
  • [SMACK-27] - Certification validation is now pluggable.
  • -
  • [SMACK-118] - Added methods to dynamically remove providers.
  • -
  • [SMACK-125] - Added support for deaf occupant in MUC rooms.
  • - -
  • [SMACK-109] - Optimized client performance. (1 vote)
  • -
  • [SMACK-113] - Added support for choosing if TLS should be used or not.
  • -
  • [SMACK-114] - Added support for choosing if SASL should be used or not.
  • -
  • [SMACK-123] - A thread is no longer used for packet writer listeners.
  • - -
  • [SMACK-110] - Resource binding and session establishment are now requested only if the server offered them.
  • -
  • [SMACK-111] - Fixed concurrency issue with date formatter.
  • -
  • [SMACK-116] - Fixed vCard issues.
  • -
  • [SMACK-119] - Fixed AccessControlException when using vCard from an applet.
  • -
  • [SMACK-120] - Listener thread was not being shutdown properly.
  • -
  • [SMACK-124] - Parsing resource binding packets was requiring smackx.jar file to be in the classpath.
  • -
  • [SMACK-97] - Fixed functional test failures in PresencePriorityTest and RosterTest.
  • -
- - - -
-
- - - diff --git a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java index c3d7109f8..76d8271ec 100644 --- a/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java +++ b/smack-bosh/src/main/java/org/jivesoftware/smack/bosh/XMPPBOSHConnection.java @@ -43,6 +43,7 @@ import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smack.util.CloseableUtil; import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; import org.igniterealtime.jbosh.AbstractBody; import org.igniterealtime.jbosh.BOSHClient; @@ -200,6 +201,14 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { + getHost() + ":" + getPort() + "."; throw new SmackException.SmackMessageException(errorMessage); } + + try { + XmlPullParser parser = PacketParserUtils.getParserFor( + ""); + onStreamOpen(parser); + } catch (XmlPullParserException | IOException e) { + throw new AssertionError("Failed to setup stream environment", e); + } } @Override @@ -511,7 +520,7 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection { parseAndProcessStanza(parser); break; case "features": - parseFeatures(parser); + parseFeaturesAndNotify(parser); break; case "error": // Some BOSH error isn't stream error. diff --git a/smack-core/build.gradle b/smack-core/build.gradle index fcc351120..f614ef812 100644 --- a/smack-core/build.gradle +++ b/smack-core/build.gradle @@ -1,3 +1,8 @@ +// Note that this is also declared in the main build.gradle for +// subprojects, but since evaluationDependsOnChildren is enabled we +// need to declare it here too to have bundle{bnd{...}} available +apply plugin: 'biz.aQute.bnd.builder' + description = """\ Smack core components.""" @@ -55,3 +60,11 @@ task createVersionResource(type: CreateFileTask) { } compileJava.dependsOn(createVersionResource) + +jar { + bundle { + bnd( + 'DynamicImport-Package': '*', + ) + } +} 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 33d306b32..61db14365 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java @@ -524,9 +524,6 @@ public abstract class AbstractXMPPConnection implements XMPPConnection { closingStreamReceived = false; streamId = null; - // The connection should not be connected nor marked as such prior calling connectInternal(). - assert !connected; - try { // Perform the actual connection to the XMPP service connectInternal(); 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 23e664e94..e8689d24f 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/ConnectionConfiguration.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2017-2020 Florian Schmaus. + * Copyright 2003-2007 Jive Software, 2017-2022 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Provider; +import java.security.SecureRandom; import java.security.Security; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; @@ -189,7 +190,7 @@ public abstract class ConnectionConfiguration { protected ConnectionConfiguration(Builder builder) { try { smackTlsContext = getSmackTlsContext(builder.dnssecMode, builder.sslContextFactory, - builder.customX509TrustManager, builder.keystoreType, builder.keystorePath, + builder.customX509TrustManager, builder.keyManagers, builder.sslContextSecureRandom, builder.keystoreType, builder.keystorePath, builder.callbackHandler, builder.pkcs11Library); } catch (UnrecoverableKeyException | KeyManagementException | NoSuchAlgorithmException | CertificateException | KeyStoreException | NoSuchProviderException | IOException | NoSuchMethodException @@ -252,7 +253,7 @@ public abstract class ConnectionConfiguration { } private static SmackTlsContext getSmackTlsContext(DnssecMode dnssecMode, SslContextFactory sslContextFactory, - X509TrustManager trustManager, String keystoreType, String keystorePath, + X509TrustManager trustManager, KeyManager[] keyManagers, SecureRandom secureRandom, String keystoreType, String keystorePath, CallbackHandler callbackHandler, String pkcs11Library) throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException, NoSuchProviderException, UnrecoverableKeyException, KeyManagementException, UnsupportedCallbackException, @@ -266,69 +267,10 @@ public abstract class ConnectionConfiguration { context = SSLContext.getInstance("TLS"); } - KeyStore ks = null; - PasswordCallback pcb = null; - KeyManager[] kms = null; - - if ("PKCS11".equals(keystoreType)) { - Constructor c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); - String pkcs11Config = "name = SmartCard\nlibrary = " + pkcs11Library; - ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8)); - Provider p = (Provider) c.newInstance(config); - Security.addProvider(p); - ks = KeyStore.getInstance("PKCS11", p); - pcb = new PasswordCallback("PKCS11 Password: ", false); - callbackHandler.handle(new Callback[] { pcb }); - ks.load(null, pcb.getPassword()); - } else if ("Apple".equals(keystoreType)) { - ks = KeyStore.getInstance("KeychainStore", "Apple"); - ks.load(null, null); - // pcb = new PasswordCallback("Apple Keychain",false); - // pcb.setPassword(null); - } else if (keystoreType != null) { - ks = KeyStore.getInstance(keystoreType); - if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) { - pcb = new PasswordCallback("Keystore Password: ", false); - callbackHandler.handle(new Callback[] { pcb }); - ks.load(new FileInputStream(keystorePath), pcb.getPassword()); - } else { - InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible(); - try { - // Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous - // 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702 - char[] password = "changeit".toCharArray(); - try { - ks.load(stream, password); - } finally { - CloseableUtil.maybeClose(stream); - } - } catch (IOException e) { - LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e); - - ks = KeyStore.getInstance("jks"); - // Open the stream again, so that we read it from the beginning. - stream = TLSUtils.getDefaultTruststoreStreamIfPossible(); - try { - ks.load(stream, null); - } finally { - CloseableUtil.maybeClose(stream); - } - } - } - } - - if (ks != null) { - String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm); - if (kmf != null) { - if (pcb == null) { - kmf.init(ks, null); - } else { - kmf.init(ks, pcb.getPassword()); - pcb.clearPassword(); - } - kms = kmf.getKeyManagers(); - } + // TODO: Remove the block below once we removed setKeystorePath(), setKeystoreType(), setCallbackHanlder() and + // setPKCS11Library() in the builder, and all related fields and the parameters of this function. + if (keyManagers == null) { + keyManagers = Builder.getKeyManagersFrom(keystoreType, keystorePath, callbackHandler, pkcs11Library); } SmackDaneVerifier daneVerifier = null; @@ -343,7 +285,7 @@ public abstract class ConnectionConfiguration { } // User requested DANE verification. - daneVerifier.init(context, kms, trustManager, null); + daneVerifier.init(context, keyManagers, trustManager, secureRandom); } else { final TrustManager[] trustManagers; if (trustManager != null) { @@ -354,7 +296,7 @@ public abstract class ConnectionConfiguration { trustManagers = null; } - context.init(kms, trustManagers, null); + context.init(keyManagers, trustManagers, secureRandom); } return new SmackTlsContext(context, daneVerifier); @@ -405,7 +347,7 @@ public abstract class ConnectionConfiguration { /** * Returns the TLS security mode used when making the connection. By default, - * the mode is {@link SecurityMode#ifpossible}. + * the mode is {@link SecurityMode#required}. * * @return the security mode. */ @@ -688,6 +630,8 @@ public abstract class ConnectionConfiguration { public abstract static class Builder, C extends ConnectionConfiguration> { private SecurityMode securityMode = SecurityMode.required; private DnssecMode dnssecMode = DnssecMode.disabled; + private KeyManager[] keyManagers; + private SecureRandom sslContextSecureRandom; private String keystorePath; private String keystoreType; private String pkcs11Library = "pkcs11.config"; @@ -942,7 +886,12 @@ public abstract class ConnectionConfiguration { * @param callbackHandler to obtain information, such as the password or * principal information during the SASL authentication. * @return a reference to this builder. + * @deprecated set a callback-handler aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or + * {@link #setKeyManagers(KeyManager[])}, created by + * {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead. */ + // TODO: Remove in Smack 4.6. + @Deprecated public B setCallbackHandler(CallbackHandler callbackHandler) { this.callbackHandler = callbackHandler; return getThis(); @@ -960,7 +909,7 @@ public abstract class ConnectionConfiguration { /** * Sets the TLS security mode used when making the connection. By default, - * the mode is {@link SecurityMode#ifpossible}. + * the mode is {@link SecurityMode#required}. * * @param securityMode the security mode. * @return a reference to this builder. @@ -970,6 +919,47 @@ public abstract class ConnectionConfiguration { return getThis(); } + /** + * Set the {@link KeyManager}s to initialize the {@link SSLContext} used by Smack to establish the XMPP connection. + * + * @param keyManagers an array of {@link KeyManager}s to initialize the {@link SSLContext} with. + * @return a reference to this builder. + * @since 4.4.5 + */ + public B setKeyManagers(KeyManager[] keyManagers) { + this.keyManagers = keyManagers; + return getThis(); + } + + /** + * Set the {@link KeyManager}s to initialize the {@link SSLContext} used by Smack to establish the XMPP connection. + * + * @param keyManager the {@link KeyManager}s to initialize the {@link SSLContext} with. + * @return a reference to this builder. + * @see #setKeyManagers(KeyManager[]) + * @since 4.4.5 + */ + public B setKeyManager(KeyManager keyManager) { + KeyManager[] keyManagers = new KeyManager[] { keyManager }; + return setKeyManagers(keyManagers); + } + + /** + * Set the {@link SecureRandom} used to initialize the {@link SSLContext} used by Smack to establish the XMPP + * connection. Note that you usually do not need (nor want) to set this. Because if the {@link SecureRandom} is + * not explicitly set, Smack will initialize the {@link SSLContext} with null as + * {@link SecureRandom} argument. And all sane {@link SSLContext} implementations will then select a safe secure + * random source by default. + * + * @param secureRandom the {@link SecureRandom} to initialize the {@link SSLContext} with. + * @return a reference to this builder. + * @since 4.4.5 + */ + public B setSslContextSecureRandom(SecureRandom secureRandom) { + this.sslContextSecureRandom = secureRandom; + return getThis(); + } + /** * Sets the path to the keystore file. The key store file contains the * certificates that may be used to authenticate the client to the server, @@ -977,7 +967,12 @@ public abstract class ConnectionConfiguration { * * @param keystorePath the path to the keystore file. * @return a reference to this builder. + * @deprecated set a keystore-path aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or + * {@link #setKeyManagers(KeyManager[])}, created by + * {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead. */ + // TODO: Remove in Smack 4.6. + @Deprecated public B setKeystorePath(String keystorePath) { this.keystorePath = keystorePath; return getThis(); @@ -988,7 +983,12 @@ public abstract class ConnectionConfiguration { * * @param keystoreType the keystore type. * @return a reference to this builder. + * @deprecated set a key-type aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or + * {@link #setKeyManagers(KeyManager[])}, created by + * {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead. */ + // TODO: Remove in Smack 4.6. + @Deprecated public B setKeystoreType(String keystoreType) { this.keystoreType = keystoreType; return getThis(); @@ -1000,7 +1000,12 @@ public abstract class ConnectionConfiguration { * * @param pkcs11Library the path to the PKCS11 library file. * @return a reference to this builder. + * @deprecated set a PKCS11-library aware {@link KeyManager} via {@link #setKeyManager(KeyManager)} or + * {@link #setKeyManagers(KeyManager[])}, created by + * {@link #getKeyManagersFrom(String, String, CallbackHandler, String)}, instead. */ + // TODO: Remove in Smack 4.6. + @Deprecated public B setPKCS11Library(String pkcs11Library) { this.pkcs11Library = pkcs11Library; return getThis(); @@ -1276,5 +1281,77 @@ public abstract class ConnectionConfiguration { public abstract C build(); protected abstract B getThis(); + + public static KeyManager[] getKeyManagersFrom(String keystoreType, String keystorePath, + CallbackHandler callbackHandler, String pkcs11Library) + throws NoSuchMethodException, SecurityException, ClassNotFoundException, KeyStoreException, + NoSuchProviderException, NoSuchAlgorithmException, CertificateException, IOException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, UnsupportedCallbackException, UnrecoverableKeyException { + KeyManager[] keyManagers = null; + KeyStore ks = null; + PasswordCallback pcb = null; + + if ("PKCS11".equals(keystoreType)) { + Constructor c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); + String pkcs11Config = "name = SmartCard\nlibrary = " + pkcs11Library; + ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8)); + Provider p = (Provider) c.newInstance(config); + Security.addProvider(p); + ks = KeyStore.getInstance("PKCS11", p); + pcb = new PasswordCallback("PKCS11 Password: ", false); + callbackHandler.handle(new Callback[] { pcb }); + ks.load(null, pcb.getPassword()); + } else if ("Apple".equals(keystoreType)) { + ks = KeyStore.getInstance("KeychainStore", "Apple"); + ks.load(null, null); + // pcb = new PasswordCallback("Apple Keychain",false); + // pcb.setPassword(null); + } else if (keystoreType != null) { + ks = KeyStore.getInstance(keystoreType); + if (callbackHandler != null && StringUtils.isNotEmpty(keystorePath)) { + pcb = new PasswordCallback("Keystore Password: ", false); + callbackHandler.handle(new Callback[] { pcb }); + ks.load(new FileInputStream(keystorePath), pcb.getPassword()); + } else { + InputStream stream = TLSUtils.getDefaultTruststoreStreamIfPossible(); + try { + // Note that PKCS12 keystores need a password one some Java platforms. Hence we try the famous + // 'changeit' here. See https://bugs.openjdk.java.net/browse/JDK-8194702 + char[] password = "changeit".toCharArray(); + try { + ks.load(stream, password); + } finally { + CloseableUtil.maybeClose(stream); + } + } catch (IOException e) { + LOGGER.log(Level.FINE, "KeyStore load() threw, attempting 'jks' fallback", e); + + ks = KeyStore.getInstance("jks"); + // Open the stream again, so that we read it from the beginning. + stream = TLSUtils.getDefaultTruststoreStreamIfPossible(); + try { + ks.load(stream, null); + } finally { + CloseableUtil.maybeClose(stream); + } + } + } + } + + if (ks != null) { + String keyManagerFactoryAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerFactoryAlgorithm); + if (kmf != null) { + if (pcb == null) { + kmf.init(ks, null); + } else { + kmf.init(ks, pcb.getPassword()); + pcb.clearPassword(); + } + keyManagers = kmf.getKeyManagers(); + } + } + + return keyManagers; + } } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java index 6cd744bf0..ddb64eb17 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackFuture.java @@ -170,16 +170,20 @@ public abstract class SmackFuture implements Future, return exception; } + private boolean callbacksInvoked; + protected final synchronized void maybeInvokeCallbacks() { - if (cancelled) { + if (cancelled || callbacksInvoked) { return; } if ((result != null || exception != null) && completionCallback != null) { + callbacksInvoked = true; completionCallback.accept(this); } if (result != null && successCallback != null) { + callbacksInvoked = true; AbstractXMPPConnection.asyncGo(new Runnable() { @Override public void run() { @@ -188,6 +192,7 @@ public abstract class SmackFuture implements Future, }); } else if (exception != null && exceptionCallback != null) { + callbacksInvoked = true; AbstractXMPPConnection.asyncGo(new Runnable() { @Override public void run() { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java b/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java index 75e1af8e9..67e68f1fd 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/SmackReactor.java @@ -221,11 +221,10 @@ public class SmackReactor { selectWait = 0; } else { selectWait = nextScheduledAction.getTimeToDueMillis(); - } - - if (selectWait < 0) { - // A scheduled action was just released and became ready to execute. - return; + if (selectWait <= 0) { + // A scheduled action was just released and became ready to execute. + return; + } } // Before we call select, we handle the pending the interest Ops. This will not block since no other diff --git a/smack-core/src/main/java/org/jivesoftware/smack/XMPPException.java b/smack-core/src/main/java/org/jivesoftware/smack/XMPPException.java index ce4c0a4f0..f7235fb48 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/XMPPException.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/XMPPException.java @@ -120,6 +120,16 @@ public abstract class XMPPException extends Exception { return error; } + /** + * Gets the stanza associated with this exception. + * + * @return the stanza from which this exception was created or {@code null} if the exception is not from a + * stanza. + */ + public Stanza getStanza() { + return stanza; + } + /** * Get the request which triggered the error response causing this exception. * 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 35144f73a..41bb29f59 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 @@ -186,6 +186,10 @@ public class StateDescriptorGraph { for (GraphVertex> successor : sortedSuccessors) { GraphVertex successorVertex = successorStateDescriptors.get(successor.element); + if (successorVertex == null) { + // The successor does not exist, probably because its module was not enabled. + continue; + } node.addOutgoingEdge(successorVertex); // Recurse further. diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/IQ.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/IQ.java index 2fcfe88fc..2c9cd0e51 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/IQ.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/IQ.java @@ -202,7 +202,7 @@ public abstract class IQ extends Stanza implements IqView { // Add the query section if there is one. IQChildElementXmlStringBuilder iqChildElement = getIQChildElementBuilder( - new IQChildElementXmlStringBuilder(this)); + new IQChildElementXmlStringBuilder(getChildElementName(), getChildElementNamespace(), null, xml.getXmlEnvironment())); // TOOD: Document the cases where iqChildElement is null but childElementName not. And if there are none, change // the logic. if (iqChildElement == null) { @@ -399,17 +399,16 @@ public abstract class IQ extends Stanza implements IqView { private boolean isEmptyElement; - private IQChildElementXmlStringBuilder(IQ iq) { - this(iq.getChildElementName(), iq.getChildElementNamespace()); + public IQChildElementXmlStringBuilder(ExtensionElement extensionElement, + XmlEnvironment enclosingXmlEnvironment) { + this(extensionElement.getElementName(), extensionElement.getNamespace(), extensionElement.getLanguage(), + enclosingXmlEnvironment); } - public IQChildElementXmlStringBuilder(ExtensionElement pe) { - this(pe.getElementName(), pe.getNamespace()); - } - - private IQChildElementXmlStringBuilder(String element, String namespace) { - prelude(element, namespace); - this.element = element; + private IQChildElementXmlStringBuilder(String elementName, String xmlNs, String xmlLang, + XmlEnvironment enclosingXmlEnvironment) { + super(elementName, xmlNs, xmlLang, enclosingXmlEnvironment); + this.element = elementName; } public void setEmptyElement() { diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaBuilder.java index 41265d633..f0c379fe8 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/StanzaBuilder.java @@ -184,6 +184,19 @@ public abstract class StanzaBuilder> implements Stanz return getThis(); } + public final B removeExtension(String elementName, String namespace) { + QName key = new QName(namespace, elementName); + extensionElements.remove(key); + return getThis(); + } + + public final B removeExtension(ExtensionElement extension) { + QName key = extension.getQName(); + List list = extensionElements.getAll(key); + list.remove(extension); + return getThis(); + } + public abstract Stanza build(); public abstract B getThis(); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/parsing/ExceptionThrowingCallbackWithHint.java b/smack-core/src/main/java/org/jivesoftware/smack/parsing/ExceptionThrowingCallbackWithHint.java index f7e91355d..b2b2c47ab 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/parsing/ExceptionThrowingCallbackWithHint.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/parsing/ExceptionThrowingCallbackWithHint.java @@ -34,7 +34,7 @@ public class ExceptionThrowingCallbackWithHint extends ExceptionThrowingCallback @Override public void handleUnparsableStanza(UnparseableStanza packetData) throws IOException { - LOGGER.warning("Parsing exception encountered." + LOGGER.warning("Parsing exception \"" + packetData.getParsingException().getMessage() + "\" encountered." + " This exception will be re-thrown, leading to a disconnect." + " You can change this behavior by setting a different ParsingExceptionCallback using setParsingExceptionCallback()." + " More information an be found in AbstractXMPPConnection's javadoc."); diff --git a/smack-core/src/main/java/org/jivesoftware/smack/provider/IQProvider.java b/smack-core/src/main/java/org/jivesoftware/smack/provider/IQProvider.java index ba8129032..069036588 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/provider/IQProvider.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/provider/IQProvider.java @@ -30,7 +30,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException; /** *

- * Deprecation Notice: This class is deprecated, use {@link IQProvider} instead. + * Deprecation Notice: This class is deprecated, use {@link IqProvider} instead. *

* An abstract class for parsing custom IQ packets. Each IQProvider must be registered with * the ProviderManager class for it to be used. Every implementation of this diff --git a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/SASLAnonymous.java b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/SASLAnonymous.java index fd99732fe..79b9eff48 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/SASLAnonymous.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/sasl/core/SASLAnonymous.java @@ -60,4 +60,8 @@ public class SASLAnonymous extends SASLMechanism { // SASL Anonymous is always successful :) } + @Override + public boolean requiresPassword() { + return false; + } } 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 24b73b8b0..408e00bfd 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 @@ -89,4 +89,11 @@ public class CollectionUtil { } return Collections.singletonList(element); } + + public static Set nullSafeUnmodifiableSet(Set set) { + if (set == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(set); + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/DoOnce.java b/smack-core/src/main/java/org/jivesoftware/smack/util/DoOnce.java new file mode 100644 index 000000000..2037f8802 --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/DoOnce.java @@ -0,0 +1,29 @@ +/** + * + * 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.smack.util; + +public final class DoOnce { + + private boolean done; + + public void once(Runnable run) { + if (done) return; + + done = true; + run.run(); + } +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java index 7729bea9d..6b4ef4241 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/PacketParserUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2019 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. @@ -76,9 +76,9 @@ public class PacketParserUtils { return getParserFor(new StringReader(stanza)); } - public static XmlPullParser getParserFor(InputStream inputStream) throws XmlPullParserException { + public static XmlPullParser getParserFor(InputStream inputStream) throws XmlPullParserException, IOException { InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); - return SmackXmlParser.newXmlParser(inputStreamReader); + return getParserFor(inputStreamReader); } public static XmlPullParser getParserFor(Reader reader) throws XmlPullParserException, IOException { @@ -501,6 +501,23 @@ public class PacketParserUtils { return parseIQ(parser, null); } + public static IqData parseIqData(XmlPullParser parser) throws XmppStringprepException { + final String id = parser.getAttributeValue("", "id"); + IqData iqData = StanzaBuilder.buildIqData(id); + + final Jid to = ParserUtils.getJidAttribute(parser, "to"); + iqData.to(to); + + final Jid from = ParserUtils.getJidAttribute(parser, "from"); + iqData.from(from); + + String typeString = parser.getAttributeValue("", "type"); + final IQ.Type type = IQ.Type.fromString(typeString); + iqData.ofType(type); + + return iqData; + } + /** * Parses an IQ packet. * @@ -518,18 +535,7 @@ public class PacketParserUtils { XmlEnvironment iqXmlEnvironment = XmlEnvironment.from(parser, outerXmlEnvironment); IQ iqPacket = null; StanzaError error = null; - - final String id = parser.getAttributeValue("", "id"); - IqData iqData = StanzaBuilder.buildIqData(id); - - final Jid to = ParserUtils.getJidAttribute(parser, "to"); - iqData.to(to); - - final Jid from = ParserUtils.getJidAttribute(parser, "from"); - iqData.from(from); - - final IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); - iqData.ofType(type); + IqData iqData = parseIqData(parser); outerloop: while (true) { XmlPullParser.Event eventType = parser.next(); @@ -549,8 +555,8 @@ public class PacketParserUtils { if (provider != null) { iqPacket = provider.parse(parser, iqData, outerXmlEnvironment); } - // Note that if we reach this code, it is guranteed that the result IQ contained a child element - // (RFC 6120 § 8.2.3 6) because otherwhise we would have reached the END_ELEMENT first. + // Note that if we reach this code, it is guaranteed that the result IQ contained a child element + // (RFC 6120 § 8.2.3 6) because otherwise we would have reached the END_ELEMENT first. else { // No Provider found for the IQ stanza, parse it to an UnparsedIQ instance // so that the content of the IQ can be examined later on @@ -571,7 +577,7 @@ public class PacketParserUtils { } // Decide what to do when an IQ packet was not understood if (iqPacket == null) { - switch (type) { + switch (iqData.getType()) { case error: // If an IQ packet wasn't created above, create an empty error IQ packet. iqPacket = new ErrorIQ(error); @@ -585,10 +591,10 @@ public class PacketParserUtils { } // Set basic values on the iq packet. - iqPacket.setStanzaId(id); - iqPacket.setTo(to); - iqPacket.setFrom(from); - iqPacket.setType(type); + iqPacket.setStanzaId(iqData.getStanzaId()); + iqPacket.setTo(iqData.getTo()); + iqPacket.setFrom(iqData.getFrom()); + iqPacket.setType(iqData.getType()); iqPacket.setError(error); return iqPacket; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java index 32b11496a..6c8912b14 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/ParserUtils.java @@ -78,6 +78,7 @@ public class ParserUtils { throws XmlPullParserException, IOException { XmlPullParser.Event event = parser.getEventType(); while (!(event == XmlPullParser.Event.END_ELEMENT && parser.getDepth() == depth)) { + assert event != XmlPullParser.Event.END_DOCUMENT; event = parser.next(); } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index 77bc402c3..5ebe4c116 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2016-2020 Florian Schmaus. + * Copyright 2003-2007 Jive Software, 2016-2021 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ package org.jivesoftware.smack.util; import java.io.IOException; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; @@ -605,4 +606,13 @@ public class StringUtils { String[] lines = input.split(PORTABLE_NEWLINE_REGEX); return Arrays.asList(lines); } + + public static List toStrings(Collection charSequences) { + List res = new ArrayList<>(charSequences.size()); + for (CharSequence cs : charSequences) { + String string = cs.toString(); + res.add(string); + } + return res; + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index ec0e11cad..21b192b4c 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -1,6 +1,6 @@ /** * - * Copyright 2014-2020 Florian Schmaus + * Copyright 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. @@ -52,11 +52,13 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { } public XmlStringBuilder(XmlElement element, XmlEnvironment enclosingXmlEnvironment) { - sb = new LazyStringBuilder(); - halfOpenElement(element); + this(element.getElementName(), element.getNamespace(), element.getLanguage(), enclosingXmlEnvironment); + } + + public XmlStringBuilder(String elementName, String xmlNs, String xmlLang, XmlEnvironment enclosingXmlEnvironment) { + sb = new LazyStringBuilder(); + halfOpenElement(elementName); - String xmlNs = element.getNamespace(); - String xmlLang = element.getLanguage(); if (enclosingXmlEnvironment == null) { xmlnsAttribute(xmlNs); xmllangAttribute(xmlLang); @@ -286,8 +288,7 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { public XmlStringBuilder attribute(String name, Enum value) { assert value != null; - // TODO: Should use toString() instead of name(). - attribute(name, value.name()); + attribute(name, value.toString()); return this; } diff --git a/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestUtil.java b/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestUtil.java index 57734b9c3..3f8df090c 100644 --- a/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestUtil.java +++ b/smack-core/src/testFixtures/java/org/jivesoftware/smack/test/util/SmackTestUtil.java @@ -1,6 +1,6 @@ /** * - * Copyright 2019-2021 Florian Schmaus + * Copyright 2019-2022 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,9 +28,13 @@ import java.util.function.Predicate; import javax.xml.namespace.QName; import org.jivesoftware.smack.packet.Element; +import org.jivesoftware.smack.packet.IqData; import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.provider.AbstractProvider; +import org.jivesoftware.smack.provider.IqProvider; import org.jivesoftware.smack.provider.Provider; import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smack.xml.XmlPullParserFactory; @@ -58,41 +62,56 @@ public class SmackTestUtil { } } - public static > E parse(CharSequence xml, Class

providerClass, XmlPullParserKind parserKind) + public static > E parse(CharSequence xml, Class

providerClass, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { P provider = providerClassToProvider(providerClass); return parse(xml, provider, parserKind); } - public static > E parse(InputStream inputStream, Class

providerClass, XmlPullParserKind parserKind) + public static > E parse(InputStream inputStream, Class

providerClass, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { P provider = providerClassToProvider(providerClass); return parse(inputStream, provider, parserKind); } - public static > E parse(Reader reader, Class

providerClass, XmlPullParserKind parserKind) + public static > E parse(Reader reader, Class

providerClass, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { P provider = providerClassToProvider(providerClass); return parse(reader, provider, parserKind); } - public static E parse(CharSequence xml, Provider provider, XmlPullParserKind parserKind) + public static E parse(CharSequence xml, AbstractProvider provider, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { String xmlString = xml.toString(); Reader reader = new StringReader(xmlString); return parse(reader, provider, parserKind); } - public static E parse(InputStream inputStream, Provider provider, XmlPullParserKind parserKind) + public static E parse(InputStream inputStream, AbstractProvider provider, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); return parse(inputStreamReader, provider, parserKind); } - public static E parse(Reader reader, Provider provider, XmlPullParserKind parserKind) + @SuppressWarnings("unchecked") + public static E parse(Reader reader, AbstractProvider abstractProvider, XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { XmlPullParser parser = getParserFor(reader, parserKind); - E element = provider.parse(parser); + + final E element; + if (abstractProvider instanceof Provider) { + Provider provider = (Provider) abstractProvider; + element = provider.parse(parser); + } else if (abstractProvider instanceof IqProvider) { + IqData iqData = PacketParserUtils.parseIqData(parser); + parser.next(); + ParserUtils.forwardToStartElement(parser); + IqProvider iqProvider = (IqProvider) abstractProvider; + element = (E) iqProvider.parse(parser, iqData); + } else { + throw new AssertionError(); + } + return element; } @@ -132,7 +151,7 @@ public class SmackTestUtil { } @SuppressWarnings("unchecked") - private static > P providerClassToProvider(Class

providerClass) { + private static > P providerClassToProvider(Class

providerClass) { P provider; try { diff --git a/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebugger.java b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebugger.java index c4a8c90dd..40d2e5263 100644 --- a/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebugger.java +++ b/smack-debug/src/main/java/org/jivesoftware/smackx/debugger/EnhancedDebugger.java @@ -34,7 +34,13 @@ import java.io.Reader; import java.io.Writer; import java.net.URL; import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; import java.util.Date; +import java.util.List; +import java.util.concurrent.PriorityBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -419,38 +425,11 @@ public class EnhancedDebugger extends SmackDebugger { // Create a special Reader that wraps the main Reader and logs data to the GUI. ObservableReader debugReader = new ObservableReader(reader); readerListener = new ReaderListener() { - @Override - public void read(final String str) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER && - !EnhancedDebuggerWindow.getInstance().isVisible()) { - // Do not add content if the parent is not visible - return; - } + private final PriorityBlockingQueue buffer = new PriorityBlockingQueue<>(); - int index = str.lastIndexOf(">"); - if (index != -1) { - if (receivedText.getLineCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) { - try { - receivedText.replaceRange("", 0, receivedText.getLineEndOffset(0)); - } - catch (BadLocationException e) { - LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e); - } - } - receivedText.append(str.substring(0, index + 1)); - receivedText.append(NEWLINE); - if (str.length() > index) { - receivedText.append(str.substring(index + 1)); - } - } - else { - receivedText.append(str); - } - } - }); + @Override + public void read(final String string) { + addBatched(string, buffer, receivedText); } }; debugReader.addReaderListener(readerListener); @@ -458,34 +437,11 @@ public class EnhancedDebugger extends SmackDebugger { // Create a special Writer that wraps the main Writer and logs data to the GUI. ObservableWriter debugWriter = new ObservableWriter(writer); writerListener = new WriterListener() { + private final PriorityBlockingQueue buffer = new PriorityBlockingQueue<>(); + @Override - public void write(final String str) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER && - !EnhancedDebuggerWindow.getInstance().isVisible()) { - // Do not add content if the parent is not visible - return; - } - - if (sentText.getLineCount() >= EnhancedDebuggerWindow.MAX_TABLE_ROWS) { - try { - sentText.replaceRange("", 0, sentText.getLineEndOffset(0)); - } - catch (BadLocationException e) { - LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e); - } - } - - sentText.append(str); - if (str.endsWith(">")) { - sentText.append(NEWLINE); - } - } - }); - - + public void write(final String string) { + addBatched(string, buffer, sentText); } }; debugWriter.addWriterListener(writerListener); @@ -497,6 +453,50 @@ public class EnhancedDebugger extends SmackDebugger { } + private static void addBatched(String string, PriorityBlockingQueue buffer, JTextArea jTextArea) { + buffer.add(string); + + SwingUtilities.invokeLater(() -> { + List linesToAdd = new ArrayList<>(); + String data; + Instant start = Instant.now(); + try { + // To reduce overhead/increase performance, try to process up to a certain amount of lines at the + // same time, when they arrive in rapid succession. + while (linesToAdd.size() < 50 + && Duration.between(start, Instant.now()).compareTo(Duration.ofMillis(100)) < 0 + && (data = buffer.poll(10, TimeUnit.MILLISECONDS)) != null) { + linesToAdd.add(data); + } + } catch (InterruptedException e) { + LOGGER.log(Level.FINER, "Interrupted wait-for-poll in addBatched(). Process all data now.", e); + } + + if (linesToAdd.isEmpty()) { + return; + } + + if (EnhancedDebuggerWindow.PERSISTED_DEBUGGER && !EnhancedDebuggerWindow.getInstance().isVisible()) { + // Do not add content if the parent is not visible + return; + } + + // Delete lines from the top, if lines to be added will exceed the maximum. + int linesToDelete = jTextArea.getLineCount() + linesToAdd.size() - EnhancedDebuggerWindow.MAX_TABLE_ROWS; + if (linesToDelete > 0) { + try { + jTextArea.replaceRange("", 0, jTextArea.getLineEndOffset(linesToDelete - 1)); + } catch (BadLocationException e) { + LOGGER.log(Level.SEVERE, "Error with line offset, MAX_TABLE_ROWS is set too low: " + + EnhancedDebuggerWindow.MAX_TABLE_ROWS, e); + } + } + + // Add the new content. + jTextArea.append(String.join(NEWLINE, linesToAdd)); + }); + } + private void addAdhocPacketPanel() { // Create UI elements for sending ad-hoc messages. final JTextArea adhocMessages = new JTextArea(); diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/carbons/CarbonManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/carbons/CarbonManager.java index c020df7cf..f2e055386 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/carbons/CarbonManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/carbons/CarbonManager.java @@ -154,8 +154,7 @@ public final class CarbonManager extends Manager { // because we also reset in authenticated() if the stream got not resumed, but for maximum correctness, // also reset here. enabled_state = false; - boolean removed = connection().removeSyncStanzaListener(carbonsListener); - assert removed; + connection().removeSyncStanzaListener(carbonsListener); } @Override public void authenticated(XMPPConnection connection, boolean resumed) { diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/AbstractHttpUploadException.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/AbstractHttpUploadException.java new file mode 100644 index 000000000..1eb51f2ca --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/AbstractHttpUploadException.java @@ -0,0 +1,101 @@ +/** + * + * Copyright 2022 Micha Kurvers + * + * 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.httpfileupload; + +import java.io.IOException; +import java.net.URL; + +import org.jivesoftware.smackx.httpfileupload.element.Slot; +/** + * An exception class to provide additional information in case of exceptions during file uploading. + * + */ +public abstract class AbstractHttpUploadException extends IOException { + + private static final long serialVersionUID = 1L; + private final long fileSize; + private final Slot slot; + + protected AbstractHttpUploadException(long fileSize, Slot slot, String message) { + this(fileSize, slot, message, null); + } + + protected AbstractHttpUploadException(long fileSize, Slot slot, String message, Throwable wrappedThrowable) { + super(message, wrappedThrowable); + this.fileSize = fileSize; + this.slot = slot; + } + + public long getFileSize() { + return fileSize; + } + + public URL getPutUrl() { + return slot.getPutUrl(); + } + + public Slot getSlot() { + return slot; + } + + /** + * Exception thrown when http response returned after upload is not 200. + */ + public static class HttpUploadErrorException extends AbstractHttpUploadException { + + private static final long serialVersionUID = 8494356028399474995L; + private final int httpStatus; + private final String responseMsg; + + public HttpUploadErrorException(int httpStatus, String responseMsg, long fileSize, Slot slot) { + super(fileSize, slot, "Error response " + httpStatus + " from server during file upload: " + + responseMsg + ", file size: " + fileSize + ", put URL: " + + slot.getPutUrl()); + this.httpStatus = httpStatus; + this.responseMsg = responseMsg; + } + + public int getHttpStatus() { + return httpStatus; + } + + public String getResponseMsg() { + return responseMsg; + } + + } + + /** + * Exception thrown when an unexpected exception occurred during the upload. + */ + public static class HttpUploadIOException extends AbstractHttpUploadException { + + private static final long serialVersionUID = 5940866318073349451L; + private final IOException wrappedIOException; + + public HttpUploadIOException(long fileSize, Slot slot, IOException cause) { + super(fileSize, slot, "Unexpected error occurred during file upload, file size: " + fileSize + + ", put Url: " + slot.getPutUrl(), cause); + this.wrappedIOException = cause; + } + + public IOException getCausingIOException() { + return this.wrappedIOException; + } + + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java index 3f98a5056..338d519aa 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java @@ -49,6 +49,8 @@ import org.jivesoftware.smack.proxy.ProxyInfo; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; +import org.jivesoftware.smackx.httpfileupload.AbstractHttpUploadException.HttpUploadErrorException; +import org.jivesoftware.smackx.httpfileupload.AbstractHttpUploadException.HttpUploadIOException; import org.jivesoftware.smackx.httpfileupload.UploadService.Version; import org.jivesoftware.smackx.httpfileupload.element.Slot; import org.jivesoftware.smackx.httpfileupload.element.SlotRequest; @@ -494,11 +496,12 @@ public final class HttpFileUploadManager extends Manager { case HttpURLConnection.HTTP_NO_CONTENT: break; default: - throw new IOException("Error response " + status + " from server during file upload: " - + urlConnection.getResponseMessage() + ", file size: " + fileSize + ", put URL: " - + putUrl); + throw new HttpUploadErrorException(status, urlConnection.getResponseMessage(), fileSize, slot); } } + catch (IOException e) { + throw new HttpUploadIOException(fileSize, slot, e); + } finally { urlConnection.disconnect(); } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index b0d43e117..546e0d79c 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -47,15 +47,17 @@ import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.commands.AdHocCommandManager; import org.jivesoftware.smackx.commands.RemoteCommand; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; import org.jivesoftware.smackx.disco.packet.DiscoverItems; import org.jivesoftware.smackx.forward.packet.Forwarded; -import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; +import org.jivesoftware.smackx.mam.element.MamElementFactory; import org.jivesoftware.smackx.mam.element.MamElements; import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension; import org.jivesoftware.smackx.mam.element.MamFinIQ; import org.jivesoftware.smackx.mam.element.MamPrefsIQ; import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior; import org.jivesoftware.smackx.mam.element.MamQueryIQ; +import org.jivesoftware.smackx.mam.element.MamVersion; import org.jivesoftware.smackx.mam.filter.MamResultFilter; import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.rsm.packet.RSMSet; @@ -225,6 +227,8 @@ public final class MamManager extends Manager { private final AdHocCommandManager adHocCommandManager; + private MamVersion mamVersion = null; + private MamManager(XMPPConnection connection, Jid archiveAddress) { super(connection); this.archiveAddress = archiveAddress; @@ -250,6 +254,52 @@ public final class MamManager extends Manager { return archiveAddress; } + /** + * Returns the MAM namespace used by this {@link MamManager}. If the archive does not support any MAM namespace + * supported by Smack, null is returned. + * + * @return the MAM namespace used by this manager, null if MAM is not supported + * @throws NoResponseException if there was no response from the remote entity. + * @throws XMPPErrorException if there was an XMPP error returned. + * @throws NotConnectedException if the XMPP connection is not connected. + * @throws InterruptedException if the calling thread was interrupted. + */ + public String getMamNamespace() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException { + MamVersion mamVersion = getSupportedMamVersionOrNull(); + return mamVersion == null ? null : mamVersion.getNamespace(); + } + + private MamVersion getSupportedMamVersionOrNull() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException { + if (mamVersion != null) { + return mamVersion; + } + + DiscoverInfo info = serviceDiscoveryManager.discoverInfo(getArchiveAddress()); + + // Enum values are always returned the order they are declared (see https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.9.3). + // We pick the first version supported by the server. + for (MamVersion v : MamVersion.values()) { + if (info.containsFeature(v.getNamespace())) { + mamVersion = v; + break; + } + } + + return mamVersion; + } + + private MamVersion getSupportedMamVersionOrThrow() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException { + MamVersion mamVersion = getSupportedMamVersionOrNull(); + if (mamVersion == null) { + throw new UnsupportedOperationException("Message Archive Management is not supported by " + getArchiveAddress()); + } + return mamVersion; + } + + private MamElementFactory getElementFactory() throws XMPPErrorException, NotConnectedException, NoResponseException, InterruptedException { + return getSupportedMamVersionOrThrow().newElementFactory(); + } + public static final class MamQueryArgs { private final String node; @@ -275,11 +325,11 @@ public final class MamManager extends Manager { private DataForm dataForm; - DataForm getDataForm() { + DataForm getDataForm(MamVersion version) { if (dataForm != null) { return dataForm; } - DataForm.Builder dataFormBuilder = getNewMamForm(); + DataForm.Builder dataFormBuilder = getNewMamForm(version); dataFormBuilder.addFields(formFields.values()); dataForm = dataFormBuilder.build(); return dataForm; @@ -472,9 +522,9 @@ public final class MamManager extends Manager { NotConnectedException, NotLoggedInException, InterruptedException { String queryId = StringUtils.secureUniqueRandomString(); String node = mamQueryArgs.node; - DataForm dataForm = mamQueryArgs.getDataForm(); + DataForm dataForm = mamQueryArgs.getDataForm(mamVersion); - MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, dataForm); + MamQueryIQ mamQueryIQ = getElementFactory().newQueryIQ(queryId, node, dataForm); mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setTo(archiveAddress); @@ -530,7 +580,7 @@ public final class MamManager extends Manager { throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { String queryId = StringUtils.secureUniqueRandomString(); - MamQueryIQ mamQueryIq = new MamQueryIQ(queryId, node, null); + MamQueryIQ mamQueryIq = getElementFactory().newQueryIQ(queryId, node, null); mamQueryIq.setTo(archiveAddress); MamQueryIQ mamResponseQueryIq = connection().sendIqRequestAndWaitForResponse(mamQueryIq); @@ -592,7 +642,7 @@ public final class MamManager extends Manager { private List page(RSMSet requestRsmSet) throws NoResponseException, XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException { String queryId = StringUtils.secureUniqueRandomString(); - MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, form); + MamQueryIQ mamQueryIQ = getElementFactory().newQueryIQ(queryId, node, form); mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setTo(archiveAddress); mamQueryIQ.addExtension(requestRsmSet); @@ -696,9 +746,7 @@ public final class MamManager extends Manager { * @see XEP-0313 § 7. Determining support */ public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - // Note that this may return 'null' but SDM's supportsFeature() does the right thing™ then. - Jid archiveAddress = getArchiveAddress(); - return serviceDiscoveryManager.supportsFeature(archiveAddress, MamElements.NAMESPACE); + return getSupportedMamVersionOrNull() != null; } public boolean isAdvancedConfigurationSupported() throws InterruptedException, XMPPException, SmackException { @@ -720,8 +768,8 @@ public final class MamManager extends Manager { throw new SmackException.FeatureNotSupportedException(ADVANCED_CONFIG_NODE, archiveAddress); } - private static DataForm.Builder getNewMamForm() { - FormField field = FormField.buildHiddenFormType(MamElements.NAMESPACE); + private static DataForm.Builder getNewMamForm(MamVersion version) { + FormField field = FormField.buildHiddenFormType(version.getNamespace()); DataForm.Builder form = DataForm.builder(); form.addField(field); return form; @@ -765,7 +813,7 @@ public final class MamManager extends Manager { */ public MamPrefsResult retrieveArchivingPreferences() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { - MamPrefsIQ mamPrefIQ = new MamPrefsIQ(); + MamPrefsIQ mamPrefIQ = getElementFactory().newPrefsIQ(); return queryMamPrefs(mamPrefIQ); } @@ -830,6 +878,7 @@ public final class MamManager extends Manager { public static final class MamPrefs { private final List alwaysJids; private final List neverJids; + private final MamVersion mamVersion; private DefaultBehavior defaultBehavior; private MamPrefs(MamPrefsResult mamPrefsResult) { @@ -837,6 +886,7 @@ public final class MamManager extends Manager { this.alwaysJids = new ArrayList<>(mamPrefsIq.getAlwaysJids()); this.neverJids = new ArrayList<>(mamPrefsIq.getNeverJids()); this.defaultBehavior = mamPrefsIq.getDefault(); + this.mamVersion = MamVersion.fromNamespace(mamPrefsIq.getNamespace()); } public void setDefaultBehavior(DefaultBehavior defaultBehavior) { @@ -856,7 +906,7 @@ public final class MamManager extends Manager { } private MamPrefsIQ constructMamPrefsIq() { - return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior); + return mamVersion.newElementFactory().newPrefsIQ(alwaysJids, neverJids, defaultBehavior); } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElementFactory.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElementFactory.java new file mode 100644 index 000000000..6b0017bbc --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElementFactory.java @@ -0,0 +1,94 @@ +/** + * + * Copyright © 2016-2021 Florian Schmaus and Frank Matheron + * + * 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.mam.element; + +import java.util.List; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smackx.forward.packet.Forwarded; +import org.jivesoftware.smackx.rsm.packet.RSMSet; +import org.jivesoftware.smackx.xdata.packet.DataForm; + +import org.jxmpp.jid.Jid; + +/** + * Factory that creates MAM objects. + * + * @since 4.5.0 + */ +public interface MamElementFactory { + + /** + * Creates a new {@link MamElementFactory} for the parser based on the namespace of the parser. + * @param parser the XML parser to retrieve the MAM namespace from + * @return the factory suitable for the MAM namespace + */ + static MamElementFactory forParser(XmlPullParser parser) { + String namespace = parser.getNamespace(); + return MamVersion.fromNamespace(namespace).newElementFactory(); + } + + /** + * Create a MAM result extension class. + * + * @param queryId id of the query + * @param id the message's archive UID + * @param forwarded the original message as it was received + * @return the result extension + */ + MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded forwarded); + + /** + * Create a MAM fin IQ class. + * + * @param queryId id of the query + * @param rsmSet the RSM set included in the {@code } + * @param complete true if the results returned by the server are complete (no further paging in needed) + * @param stable false if the results returned by the sever are unstable (e.g. they might later change in sequence or content) + * @return the fin IQ + */ + MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable); + + /** + * Create a new MAM preferences IQ. + * + * @param alwaysJids JIDs for which all messages are archived by default + * @param neverJids JIDs for which messages are never archived + * @param defaultBehavior default archive behavior + * @return the prefs IQ + */ + MamPrefsIQ newPrefsIQ(List alwaysJids, List neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior); + + /** + * Construct a new MAM {@code } IQ retrieval request (IQ type 'get'). + * + * @return the prefs IQ + */ + MamPrefsIQ newPrefsIQ(); + + /** + * Create a new MAM Query IQ. + * + * @param queryId id of the query + * @param node pubsub node id when querying a pubsub node, null when not querying a pubsub node + * @param dataForm the dataform containing the query parameters + * @return the query IQ + */ + MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm); + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java index 24c0753a6..6e3d7e982 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamElements.java @@ -18,15 +18,13 @@ package org.jivesoftware.smackx.mam.element; import java.util.List; -import javax.xml.namespace.QName; - import org.jivesoftware.smack.packet.Element; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.MessageView; +import org.jivesoftware.smack.packet.XmlElement; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; - import org.jivesoftware.smackx.forward.packet.Forwarded; import org.jxmpp.jid.Jid; @@ -41,8 +39,6 @@ import org.jxmpp.jid.Jid; */ public class MamElements { - public static final String NAMESPACE = "urn:xmpp:mam:2"; - /** * MAM result extension class. * @@ -50,18 +46,13 @@ public class MamElements { * Archive Management * */ - public static class MamResultExtension implements ExtensionElement { + public abstract static class MamResultExtension implements ExtensionElement { /** * result element. */ public static final String ELEMENT = "result"; - /** - * The qualified name of the MAM result extension element. - */ - public static final QName QNAME = new QName(NAMESPACE, ELEMENT); - /** * id of the result. */ @@ -77,20 +68,27 @@ public class MamElements { */ private String queryId; + protected final MamVersion version; + /** * MAM result extension constructor. * + * @param version TODO javadoc me please * @param queryId TODO javadoc me please * @param id TODO javadoc me please * @param forwarded TODO javadoc me please */ - public MamResultExtension(String queryId, String id, Forwarded forwarded) { + public MamResultExtension(MamVersion version, String queryId, String id, Forwarded forwarded) { if (StringUtils.isEmpty(id)) { throw new IllegalArgumentException("id must not be null or empty"); } if (forwarded == null) { throw new IllegalArgumentException("forwarded must no be null"); } + if (version == null) { + throw new IllegalArgumentException("version must not be null"); + } + this.version = version; this.id = id; this.forwarded = forwarded; this.queryId = queryId; @@ -130,7 +128,7 @@ public class MamElements { @Override public final String getNamespace() { - return NAMESPACE; + return version.getNamespace(); } @Override @@ -148,7 +146,13 @@ public class MamElements { } public static MamResultExtension from(MessageView message) { - return message.getExtension(MamResultExtension.class); + for (XmlElement extension : message.getExtensions()) { + if (extension instanceof MamResultExtension) { + return (MamResultExtension) extension; + } + } + + return null; } } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamFinIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamFinIQ.java index cd5b92669..91b2defbc 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamFinIQ.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamFinIQ.java @@ -35,11 +35,6 @@ public class MamFinIQ extends IQ { */ public static final String ELEMENT = "fin"; - /** - * the IQ NAMESPACE. - */ - public static final String NAMESPACE = MamElements.NAMESPACE; - /** * RSM set. */ @@ -63,13 +58,14 @@ public class MamFinIQ extends IQ { /** * MamFinIQ constructor. * + * @param version TODO javadoc me please * @param queryId TODO javadoc me please * @param rsmSet TODO javadoc me please * @param complete TODO javadoc me please * @param stable TODO javadoc me please */ - public MamFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) { - super(ELEMENT, NAMESPACE); + public MamFinIQ(MamVersion version, String queryId, RSMSet rsmSet, boolean complete, boolean stable) { + super(ELEMENT, version.getNamespace()); if (rsmSet == null) { throw new IllegalArgumentException("rsmSet must not be null"); } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamPrefsIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamPrefsIQ.java index 743847914..e5898f4c3 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamPrefsIQ.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamPrefsIQ.java @@ -46,11 +46,6 @@ public class MamPrefsIQ extends IQ { */ public static final String ELEMENT = "prefs"; - /** - * the IQ NAMESPACE. - */ - public static final String NAMESPACE = MamElements.NAMESPACE; - /** * list of always. */ @@ -68,9 +63,11 @@ public class MamPrefsIQ extends IQ { /** * Construct a new MAM {@code } IQ retrieval request (IQ type 'get'). + * + * @param version TODO javadoc me please * */ - public MamPrefsIQ() { - super(ELEMENT, NAMESPACE); + public MamPrefsIQ(MamVersion version) { + super(ELEMENT, version.getNamespace()); alwaysJids = null; neverJids = null; defaultBehavior = null; @@ -79,12 +76,13 @@ public class MamPrefsIQ extends IQ { /** * MAM preferences IQ constructor. * + * @param version TODO javadoc me please * @param alwaysJids TODO javadoc me please * @param neverJids TODO javadoc me please * @param defaultBehavior TODO javadoc me please */ - public MamPrefsIQ(List alwaysJids, List neverJids, DefaultBehavior defaultBehavior) { - super(ELEMENT, NAMESPACE); + public MamPrefsIQ(MamVersion version, List alwaysJids, List neverJids, DefaultBehavior defaultBehavior) { + super(ELEMENT, version.getNamespace()); setType(Type.set); this.alwaysJids = alwaysJids; this.neverJids = neverJids; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java index b41400505..3909b077c 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamQueryIQ.java @@ -35,11 +35,6 @@ public class MamQueryIQ extends IQ { */ public static final String ELEMENT = QUERY_ELEMENT; - /** - * the MAM query IQ NAMESPACE. - */ - public static final String NAMESPACE = MamElements.NAMESPACE; - private final String queryId; private final String node; private final DataForm dataForm; @@ -47,41 +42,45 @@ public class MamQueryIQ extends IQ { /** * MAM query IQ constructor. * + * @param version TODO javadoc me please * @param queryId TODO javadoc me please */ - public MamQueryIQ(String queryId) { - this(queryId, null, null); + public MamQueryIQ(MamVersion version, String queryId) { + this(version, queryId, null, null); setType(IQ.Type.get); } /** * MAM query IQ constructor. * + * @param version TODO javadoc me please * @param form TODO javadoc me please */ - public MamQueryIQ(DataForm form) { - this(null, null, form); + public MamQueryIQ(MamVersion version, DataForm form) { + this(version, null, null, form); } /** * MAM query IQ constructor. * + * @param version TODO javadoc me please * @param queryId TODO javadoc me please * @param form TODO javadoc me please */ - public MamQueryIQ(String queryId, DataForm form) { - this(queryId, null, form); + public MamQueryIQ(MamVersion version, String queryId, DataForm form) { + this(version, queryId, null, form); } /** * MAM query IQ constructor. * + * @param version TODO javadoc me please * @param queryId TODO javadoc me please * @param node TODO javadoc me please * @param dataForm TODO javadoc me please */ - public MamQueryIQ(String queryId, String node, DataForm dataForm) { - super(ELEMENT, NAMESPACE); + public MamQueryIQ(MamVersion version, String queryId, String node, DataForm dataForm) { + super(ELEMENT, version.getNamespace()); this.queryId = queryId; this.node = node; this.dataForm = dataForm; @@ -91,9 +90,9 @@ public class MamQueryIQ extends IQ { if (formType == null) { throw new IllegalArgumentException("If a data form is given it must posses a hidden form type field"); } - if (!formType.equals(MamElements.NAMESPACE)) { + if (!formType.equals(version.getNamespace())) { throw new IllegalArgumentException( - "Value of the hidden form type field must be '" + MamElements.NAMESPACE + "'"); + "Value of the hidden form type field must be '" + version.getNamespace() + "'"); } addExtension(dataForm); } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV1ElementFactory.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV1ElementFactory.java new file mode 100644 index 000000000..e87f79ca7 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV1ElementFactory.java @@ -0,0 +1,66 @@ +/** + * + * Copyright © 2016-2021 Florian Schmaus and Frank Matheron + * + * 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.mam.element; + +import java.util.List; +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.forward.packet.Forwarded; +import org.jivesoftware.smackx.rsm.packet.RSMSet; +import org.jivesoftware.smackx.xdata.packet.DataForm; + +import org.jxmpp.jid.Jid; + +class MamV1ElementFactory implements MamElementFactory { + + @Override + public MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded forwarded) { + return new MamV1ResultExtension(queryId, id, forwarded); + } + + @Override + public MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) { + return new MamFinIQ(MamVersion.MAM1, queryId, rsmSet, complete, stable); + } + + @Override + public MamPrefsIQ newPrefsIQ(List alwaysJids, List neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior) { + return new MamPrefsIQ(MamVersion.MAM1, alwaysJids, neverJids, defaultBehavior); + } + + @Override + public MamPrefsIQ newPrefsIQ() { + return new MamPrefsIQ(MamVersion.MAM1); + } + + @Override + public MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm) { + return new MamQueryIQ(MamVersion.MAM1, queryId, node, dataForm); + } + + public static class MamV1ResultExtension extends MamElements.MamResultExtension { + /** + * The qualified name of the MAM result extension element. + */ + public static final QName QNAME = new QName(MamVersion.MAM1.getNamespace(), ELEMENT); + + MamV1ResultExtension(String queryId, String id, Forwarded forwarded) { + super(MamVersion.MAM1, queryId, id, forwarded); + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV2ElementFactory.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV2ElementFactory.java new file mode 100644 index 000000000..a706f8632 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamV2ElementFactory.java @@ -0,0 +1,66 @@ +/** + * + * Copyright © 2016-2021 Florian Schmaus and Frank Matheron + * + * 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.mam.element; + +import java.util.List; +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.forward.packet.Forwarded; +import org.jivesoftware.smackx.rsm.packet.RSMSet; +import org.jivesoftware.smackx.xdata.packet.DataForm; + +import org.jxmpp.jid.Jid; + +class MamV2ElementFactory implements MamElementFactory { + + @Override + public MamElements.MamResultExtension newResultExtension(String queryId, String id, Forwarded forwarded) { + return new MamV2ResultExtension(queryId, id, forwarded); + } + + @Override + public MamFinIQ newFinIQ(String queryId, RSMSet rsmSet, boolean complete, boolean stable) { + return new MamFinIQ(MamVersion.MAM2, queryId, rsmSet, complete, stable); + } + + @Override + public MamPrefsIQ newPrefsIQ(List alwaysJids, List neverJids, MamPrefsIQ.DefaultBehavior defaultBehavior) { + return new MamPrefsIQ(MamVersion.MAM2, alwaysJids, neverJids, defaultBehavior); + } + + @Override + public MamPrefsIQ newPrefsIQ() { + return new MamPrefsIQ(MamVersion.MAM2); + } + + @Override + public MamQueryIQ newQueryIQ(String queryId, String node, DataForm dataForm) { + return new MamQueryIQ(MamVersion.MAM2, queryId, node, dataForm); + } + + public static class MamV2ResultExtension extends MamElements.MamResultExtension { + /** + * The qualified name of the MAM result extension element. + */ + public static final QName QNAME = new QName(MamVersion.MAM2.getNamespace(), ELEMENT); + + MamV2ResultExtension(String queryId, String id, Forwarded forwarded) { + super(MamVersion.MAM2, queryId, id, forwarded); + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamVersion.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamVersion.java new file mode 100644 index 000000000..50a7a211d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/element/MamVersion.java @@ -0,0 +1,69 @@ +/** + * + * Copyright © 2016-2021 Florian Schmaus and Frank Matheron + * + * 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.mam.element; + +/** + * MAM versions supported by Smack. + * + * @since 4.5.0 + */ +public enum MamVersion { + // Note that the order in which the enum values are defined, is also the order in which we attempt to find a + // supported version. The versions should therefore be listed in order of newest to oldest, so that Smack prefers + // using a newer version over an older version. + MAM2("urn:xmpp:mam:2") { + @Override + public MamElementFactory newElementFactory() { + return new MamV2ElementFactory(); + } + }, + MAM1("urn:xmpp:mam:1") { + @Override + public MamElementFactory newElementFactory() { + return new MamV1ElementFactory(); + } + }; + + private final String namespace; + + MamVersion(String namespace) { + this.namespace = namespace; + } + + /** + * Each MAM version is identified by its namespace. Returns the namespace for this MAM version. + * @return the namespace of the MAM version + */ + public String getNamespace() { + return namespace; + } + + /** + * Creates a new factory that creates IQ's and extension objects for this MAM version. + * @return the factory + */ + public abstract MamElementFactory newElementFactory(); + + public static MamVersion fromNamespace(String namespace) { + for (MamVersion v : MamVersion.values()) { + if (v.namespace.equals(namespace)) { + return v; + } + } + throw new IllegalArgumentException("Unsupported namespace: " + namespace); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamFinIQProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamFinIQProvider.java index 1af384165..92be55c0b 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamFinIQProvider.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/provider/MamFinIQProvider.java @@ -25,6 +25,7 @@ import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.mam.element.MamElementFactory; import org.jivesoftware.smackx.mam.element.MamFinIQ; import org.jivesoftware.smackx.rsm.packet.RSMSet; import org.jivesoftware.smackx.rsm.provider.RSMSetProvider; @@ -41,6 +42,7 @@ public class MamFinIQProvider extends IQProvider { @Override public MamFinIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { + MamElementFactory elementFactory = MamElementFactory.forParser(parser); String queryId = parser.getAttributeValue("", "queryid"); boolean complete = ParserUtils.getBooleanAttribute(parser, "complete", false); boolean stable = ParserUtils.getBooleanAttribute(parser, "stable", true); @@ -65,7 +67,7 @@ public class MamFinIQProvider extends IQProvider { } } - return new MamFinIQ(queryId, rsmSet, complete, stable); + return elementFactory.newFinIQ(queryId, rsmSet, complete, stable); } } 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 2b4795036..d4d0b7c46 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 @@ -25,6 +25,7 @@ import org.jivesoftware.smack.provider.IQProvider; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.mam.element.MamElementFactory; import org.jivesoftware.smackx.mam.element.MamPrefsIQ; import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior; @@ -41,19 +42,17 @@ import org.jxmpp.jid.impl.JidCreate; */ public class MamPrefsIQProvider extends IQProvider { + public static final MamPrefsIQProvider INSTANCE = new MamPrefsIQProvider(); + @Override public MamPrefsIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException { - String iqType = parser.getAttributeValue("", "type"); + MamElementFactory elementFactory = MamElementFactory.forParser(parser); String defaultBehaviorString = parser.getAttributeValue("", "default"); DefaultBehavior defaultBehavior = null; if (defaultBehaviorString != null) { defaultBehavior = DefaultBehavior.valueOf(defaultBehaviorString); } - if (iqType == null) { - iqType = "result"; - } - List alwaysJids = null; List neverJids = null; @@ -82,7 +81,7 @@ public class MamPrefsIQProvider extends IQProvider { } } - return new MamPrefsIQ(alwaysJids, neverJids, defaultBehavior); + return elementFactory.newPrefsIQ(alwaysJids, neverJids, defaultBehavior); } private static List iterateJids(XmlPullParser parser) throws XmlPullParserException, IOException { 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 d0971c5de..e6280a52d 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 @@ -24,6 +24,7 @@ import org.jivesoftware.smack.provider.IQProvider; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; +import org.jivesoftware.smackx.mam.element.MamElementFactory; import org.jivesoftware.smackx.mam.element.MamQueryIQ; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdata.provider.DataFormProvider; @@ -41,6 +42,7 @@ public class MamQueryIQProvider extends IQProvider { @Override public MamQueryIQ parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { + MamElementFactory elementFactory = MamElementFactory.forParser(parser); DataForm dataForm = null; String queryId = parser.getAttributeValue("", "queryid"); String node = parser.getAttributeValue("", "node"); @@ -68,7 +70,7 @@ public class MamQueryIQProvider extends IQProvider { } } - return new MamQueryIQ(queryId, node, dataForm); + return elementFactory.newQueryIQ(queryId, node, dataForm); } } 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 21012f873..c870e5c87 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 @@ -28,6 +28,7 @@ import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smackx.forward.packet.Forwarded; import org.jivesoftware.smackx.forward.provider.ForwardedProvider; +import org.jivesoftware.smackx.mam.element.MamElementFactory; import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension; /** @@ -43,6 +44,7 @@ public class MamResultProvider extends ExtensionElementProvider forwarded = null; String queryId = parser.getAttributeValue("", "queryid"); String id = parser.getAttributeValue("", "id"); @@ -69,7 +71,7 @@ public class MamResultProvider extends ExtensionElementProviderurn:xmpp:mam:2 org.jivesoftware.smackx.mam.provider.MamResultProvider + + prefs + urn:xmpp:mam:1 + org.jivesoftware.smackx.mam.provider.MamPrefsIQProvider + + + query + urn:xmpp:mam:1 + org.jivesoftware.smackx.mam.provider.MamQueryIQProvider + + + fin + urn:xmpp:mam:1 + org.jivesoftware.smackx.mam.provider.MamFinIQProvider + + + result + urn:xmpp:mam:1 + org.jivesoftware.smackx.mam.provider.MamResultProvider + diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java index 212c37e89..b5c974dce 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/FiltersTest.java @@ -23,7 +23,7 @@ import java.util.Date; import java.util.List; import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; -import org.jivesoftware.smackx.mam.element.MamElements; +import org.jivesoftware.smackx.mam.element.MamVersion; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.junit.jupiter.api.Test; @@ -35,7 +35,7 @@ public class FiltersTest extends MamTest { private static String getMamXMemberWith(List fieldsNames, List fieldsValues) { String xml = "" + "" + "" - + MamElements.NAMESPACE + "" + ""; + + MamVersion.MAM2.getNamespace() + "" + ""; for (int i = 0; i < fieldsNames.size() && i < fieldsValues.size(); i++) { xml += "" + "" + fieldsValues.get(i) + "" @@ -51,7 +51,7 @@ public class FiltersTest extends MamTest { Date date = new Date(); MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsSince(date).build(); - DataForm dataForm = mamQueryArgs.getDataForm(); + DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2); List fields = new ArrayList<>(); fields.add("start"); @@ -66,7 +66,7 @@ public class FiltersTest extends MamTest { Date date = new Date(); MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsBefore(date).build(); - DataForm dataForm = mamQueryArgs.getDataForm(); + DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2); List fields = new ArrayList<>(); fields.add("end"); @@ -81,7 +81,7 @@ public class FiltersTest extends MamTest { Jid jid = JidTestUtil.BARE_JID_1; MamQueryArgs mamQueryArgs = MamQueryArgs.builder().limitResultsToJid(jid).build(); - DataForm dataForm = mamQueryArgs.getDataForm(); + DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2); List fields = new ArrayList<>(); fields.add("with"); diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamPrefIQProviderTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamPrefIQProviderTest.java index cd5d4c0ca..956d2057b 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamPrefIQProviderTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamPrefIQProviderTest.java @@ -19,54 +19,62 @@ package org.jivesoftware.smackx.mam; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.IOException; import java.util.List; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.test.util.SmackTestUtil; +import org.jivesoftware.smack.test.util.SmackTestUtil.XmlPullParserKind; import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smackx.mam.element.MamPrefsIQ; import org.jivesoftware.smackx.mam.provider.MamPrefsIQProvider; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.jxmpp.jid.Jid; public class MamPrefIQProviderTest extends MamTest { - private static final String exampleMamPrefsIQ1 = "" + "" + private static final String exampleMamPrefsIQ1 = "" + "" + "romeo@montague.lit" + "" + "" - + "montague@montague.lit" + "" + "" + ""; + + "montague@montague.lit" + "" + ""; - private static final String exampleMamPrefsIQ2 = "" + "" + private static final String exampleMamPrefsIQ2 = "" + "" + "romeo@montague.lit" + "montague@montague.lit" + "" - + "" + "" + "" + ""; + + "" + "" + ""; - private static final String exampleMamPrefsIQ3 = "" + "" + "" - + ""; + private static final String exampleMamPrefsIQ3 = "" + ""; private static final String exampleMamPrefsResultIQ = "" + "" + "" + "romeo@montague.lit" + "" + "" + "sarasa@montague.lit" + "montague@montague.lit" + "" + "" + ""; - @Test - public void checkMamPrefsIQProvider() throws Exception { - XmlPullParser parser1 = PacketParserUtils.getParserFor(exampleMamPrefsIQ1); - MamPrefsIQ mamPrefIQ1 = new MamPrefsIQProvider().parse(parser1); + @ParameterizedTest + @EnumSource(value = SmackTestUtil.XmlPullParserKind.class) + public void checkMamPrefsIQProvider(XmlPullParserKind parserKind) + throws XmlPullParserException, IOException, SmackParsingException { + XmlPullParser parser1 = SmackTestUtil.getParserFor(exampleMamPrefsIQ1, parserKind); + MamPrefsIQ mamPrefIQ1 = MamPrefsIQProvider.INSTANCE.parse(parser1); assertEquals(IQ.Type.set, mamPrefIQ1.getType()); assertEquals(mamPrefIQ1.getAlwaysJids().get(0).toString(), "romeo@montague.lit"); assertEquals(mamPrefIQ1.getNeverJids().get(0).toString(), "montague@montague.lit"); - XmlPullParser parser2 = PacketParserUtils.getParserFor(exampleMamPrefsIQ2); - MamPrefsIQ mamPrefIQ2 = new MamPrefsIQProvider().parse(parser2); + XmlPullParser parser2 = SmackTestUtil.getParserFor(exampleMamPrefsIQ2, parserKind); + MamPrefsIQ mamPrefIQ2 = MamPrefsIQProvider.INSTANCE.parse(parser2); assertEquals(IQ.Type.set, mamPrefIQ2.getType()); assertEquals(mamPrefIQ2.getAlwaysJids().get(0).toString(), "romeo@montague.lit"); assertEquals(mamPrefIQ2.getAlwaysJids().get(1).toString(), "montague@montague.lit"); assertTrue(mamPrefIQ2.getNeverJids().isEmpty()); - XmlPullParser parser3 = PacketParserUtils.getParserFor(exampleMamPrefsIQ3); - MamPrefsIQ mamPrefIQ3 = new MamPrefsIQProvider().parse(parser3); + XmlPullParser parser3 = SmackTestUtil.getParserFor(exampleMamPrefsIQ3, parserKind); + MamPrefsIQ mamPrefIQ3 = MamPrefsIQProvider.INSTANCE.parse(parser3); assertEquals(IQ.Type.set, mamPrefIQ3.getType()); } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java index 33e9df1a3..b8e6727d5 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/MamTest.java @@ -23,6 +23,7 @@ import org.jivesoftware.smack.DummyConnection; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smackx.mam.element.MamVersion; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.junit.jupiter.api.BeforeAll; @@ -47,9 +48,9 @@ public class MamTest extends SmackTestSuite { protected DataForm getNewMamForm() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { - Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm"); + Method methodGetNewMamForm = MamManager.class.getDeclaredMethod("getNewMamForm", MamVersion.class); methodGetNewMamForm.setAccessible(true); - DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager); + DataForm.Builder dataFormBuilder = (DataForm.Builder) methodGetNewMamForm.invoke(mamManager, MamVersion.MAM2); return dataFormBuilder.build(); } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java index 9a3a4e61a..7d7a26966 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PagingTest.java @@ -22,6 +22,7 @@ import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.StreamOpen; import org.jivesoftware.smackx.mam.element.MamQueryIQ; +import org.jivesoftware.smackx.mam.element.MamVersion; import org.jivesoftware.smackx.rsm.packet.RSMSet; import org.jivesoftware.smackx.xdata.packet.DataForm; @@ -40,7 +41,7 @@ public class PagingTest extends MamTest { int max = 10; RSMSet rsmSet = new RSMSet(max); - MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm); + MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm); mamQueryIQ.setStanzaId("sarasa"); mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.addExtension(rsmSet); diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PreferencesTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PreferencesTest.java index 2e80871bc..5b7aa81a7 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PreferencesTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/PreferencesTest.java @@ -23,9 +23,9 @@ import java.util.List; import org.jivesoftware.smack.packet.StreamOpen; -import org.jivesoftware.smackx.mam.element.MamElements; import org.jivesoftware.smackx.mam.element.MamPrefsIQ; import org.jivesoftware.smackx.mam.element.MamPrefsIQ.DefaultBehavior; +import org.jivesoftware.smackx.mam.element.MamVersion; import org.junit.jupiter.api.Test; import org.jxmpp.jid.Jid; @@ -33,16 +33,16 @@ import org.jxmpp.jid.impl.JidCreate; public class PreferencesTest { - private static final String retrievePrefsStanzaExample = "" + "" + "" + ""; - private static final String updatePrefsStanzaExample = "" + "" + "" + "" + "romeo@montague.lit" + "other@montague.lit" + "" + "" + "montague@montague.lit" + "" + "" + ""; @Test public void checkRetrievePrefsStanza() throws Exception { - MamPrefsIQ mamPrefIQ = new MamPrefsIQ(); + MamPrefsIQ mamPrefIQ = MamVersion.MAM2.newElementFactory().newPrefsIQ(); mamPrefIQ.setStanzaId("sarasa"); assertEquals(mamPrefIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), retrievePrefsStanzaExample); } @@ -56,7 +56,7 @@ public class PreferencesTest { List neverJids = new ArrayList<>(); neverJids.add(JidCreate.from("montague@montague.lit")); - MamPrefsIQ mamPrefIQ = new MamPrefsIQ(alwaysJids, neverJids, DefaultBehavior.roster); + MamPrefsIQ mamPrefIQ = MamVersion.MAM2.newElementFactory().newPrefsIQ(alwaysJids, neverJids, DefaultBehavior.roster); mamPrefIQ.setStanzaId("sarasa"); assertEquals(mamPrefIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), updatePrefsStanzaExample); } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java index 3b378a516..85ac63a8f 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/QueryArchiveTest.java @@ -29,9 +29,9 @@ import org.jivesoftware.smack.packet.StreamOpen; import org.jivesoftware.smackx.delay.packet.DelayInformation; import org.jivesoftware.smackx.forward.packet.Forwarded; -import org.jivesoftware.smackx.mam.element.MamElements; import org.jivesoftware.smackx.mam.element.MamElements.MamResultExtension; import org.jivesoftware.smackx.mam.element.MamQueryIQ; +import org.jivesoftware.smackx.mam.element.MamVersion; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.junit.jupiter.api.Test; @@ -41,7 +41,7 @@ public class QueryArchiveTest extends MamTest { private static final String mamSimpleQueryIQ = "" + "" + "" + "" + "" - + MamElements.NAMESPACE + "" + "" + "" + "" + ""; + + MamVersion.MAM2.getNamespace() + "" + "" + "" + "" + ""; private static final String mamQueryResultExample = "" + "" @@ -54,7 +54,7 @@ public class QueryArchiveTest extends MamTest { @Test public void checkMamQueryIQ() throws Exception { DataForm dataForm = getNewMamForm(); - MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm); + MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm); mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setStanzaId("sarasa"); assertEquals(mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString(), mamSimpleQueryIQ); @@ -80,7 +80,7 @@ public class QueryArchiveTest extends MamTest { Forwarded forwarded = new Forwarded<>(forwardedMessage, delay); - message.addExtension(new MamResultExtension("g27", "34482-21985-73620", forwarded)); + message.addExtension(MamVersion.MAM2.newElementFactory().newResultExtension("g27", "34482-21985-73620", forwarded)); assertEquals(mamQueryResultExample, message.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java index 893b78a5f..4089a3296 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/ResultsLimitTest.java @@ -22,8 +22,8 @@ import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.StreamOpen; import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; -import org.jivesoftware.smackx.mam.element.MamElements; import org.jivesoftware.smackx.mam.element.MamQueryIQ; +import org.jivesoftware.smackx.mam.element.MamVersion; import org.jivesoftware.smackx.xdata.packet.DataForm; import org.junit.jupiter.api.Test; @@ -32,13 +32,13 @@ public class ResultsLimitTest extends MamTest { private static final String resultsLimitStanza = "" + "" + "" + "" + "" - + MamElements.NAMESPACE + "" + "" + "" + "" + + MamVersion.MAM2.getNamespace() + "" + "" + "" + "" + "10" + "" + "" + ""; @Test public void checkResultsLimit() throws Exception { DataForm dataForm = getNewMamForm(); - MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm); + MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId, dataForm); mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setStanzaId("sarasa"); diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java index f973fa719..3fb2c3928 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java @@ -22,8 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.jivesoftware.smack.packet.StreamOpen; import org.jivesoftware.smackx.mam.MamManager.MamQueryArgs; -import org.jivesoftware.smackx.mam.element.MamElements; import org.jivesoftware.smackx.mam.element.MamQueryIQ; +import org.jivesoftware.smackx.mam.element.MamVersion; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.packet.DataForm; @@ -32,18 +32,18 @@ import org.jxmpp.jid.JidTestUtil; public class RetrieveFormFieldsTest extends MamTest { - private static final String retrieveFormFieldStanza = "" + "" + "" + ""; private static final String additionalFieldsStanza = "" + "" - + "" + MamElements.NAMESPACE + "" + "" + + "" + MamVersion.MAM2.getNamespace() + "" + "" + "" + "Hi" + "" + "" + "one@exampleone.org" + "" + ""; @Test public void checkRetrieveFormFieldsStanza() throws Exception { - MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId); + MamQueryIQ mamQueryIQ = new MamQueryIQ(MamVersion.MAM2, queryId); mamQueryIQ.setStanzaId("sarasa"); assertEquals(retrieveFormFieldStanza, mamQueryIQ.toXML(StreamOpen.CLIENT_NAMESPACE).toString()); @@ -63,7 +63,7 @@ public class RetrieveFormFieldsTest extends MamTest { .withAdditionalFormField(field1) .withAdditionalFormField(field2) .build(); - DataForm dataForm = mamQueryArgs.getDataForm(); + DataForm dataForm = mamQueryArgs.getDataForm(MamVersion.MAM2); String dataFormResult = dataForm.toXML().toString(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java index 532ac9a11..008c941c6 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java @@ -153,7 +153,7 @@ public class DataPacketExtension implements ExtensionElement { @Override public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this)); + XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this, enclosingNamespace)); xml.closeElement(this); return xml; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java index 12be65fcb..66e221c49 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java @@ -615,9 +615,7 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream if (annouceLocalStreamHost) { // add local proxy on first position if exists List localProxies = getLocalStreamHost(); - if (localProxies != null) { - streamHosts.addAll(localProxies); - } + streamHosts.addAll(localProxies); } // query SOCKS5 proxies for network settings @@ -652,12 +650,14 @@ public final class Socks5BytestreamManager extends Manager implements Bytestream /** * Returns the stream host information of the local SOCKS5 proxy containing the IP address and - * the port or null if local SOCKS5 proxy is not running. + * the port. The returned list may be empty if the local SOCKS5 proxy is not running. * - * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy - * is not running + * @return the stream host information of the local SOCKS5 proxy */ public List getLocalStreamHost() { + // Ensure that the local SOCKS5 proxy is running (if enabled). + Socks5Proxy.getSocks5Proxy(); + List streamHosts = new ArrayList<>(); XMPPConnection connection = connection(); 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 034d0f9dd..7f481ab21 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 @@ -700,7 +700,7 @@ public final class EntityCapsManager extends Manager { for (FormField f : fs) { sb.append(f.getFieldName()); sb.append('<'); - formFieldValuesToCaps(f.getRawValues(), sb); + formFieldValuesToCaps(f.getRawValueCharSequences(), sb); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java index b37209ad4..20714db77 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java @@ -921,6 +921,10 @@ public final class ServiceDiscoveryManager extends Manager { return entityCapabilitiesChangedListeners.add(entityCapabilitiesChangedListener); } + public boolean removeEntityCapabilitiesChangedListener(EntityCapabilitiesChangedListener entityCapabilitiesChangedListener) { + return entityCapabilitiesChangedListeners.remove(entityCapabilitiesChangedListener); + } + private static final int RENEW_ENTITY_CAPS_DELAY_MILLIS = 25; private ScheduledAction renewEntityCapsScheduledAction; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java index b4d3f7554..abe18f630 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java @@ -258,6 +258,14 @@ public class DiscoverInfo extends IQ implements DiscoverInfoView { return features.contains(new Feature(feature)); } + public static boolean nullSafeContainsFeature(DiscoverInfo discoverInfo, CharSequence feature) { + if (discoverInfo == null) { + return false; + } + + return discoverInfo.containsFeature(feature); + } + @Override protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) { xml.optAttribute("node", getNode()); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java index 356cf52fb..5dda27842 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/formtypes/FormFieldRegistry.java @@ -68,6 +68,12 @@ public class FormFieldRegistry { } } + public static void register(String formType, FormField.Type fieldType, String... fieldNames) { + for (String fieldName : fieldNames) { + register(formType, fieldName, fieldType); + } + } + public static void register(String formType, String fieldName, FormField.Type fieldType) { StringUtils.requireNotNullNorEmpty(fieldName, "fieldName must be provided"); Objects.requireNonNull(fieldType); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java index 000765ec9..df08bace6 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleUtil.java @@ -53,7 +53,7 @@ public class JingleUtil { JingleContentDescription description, JingleContentTransport transport) { - Jingle.Builder jb = Jingle.getBuilder(); + Jingle.Builder jb = Jingle.builder(connection); jb.setAction(JingleAction.session_initiate) .setSessionId(sessionId) .setInitiator(connection.getUser()); @@ -118,7 +118,7 @@ public class JingleUtil { JingleContentDescription description, JingleContentTransport transport) { - Jingle.Builder jb = Jingle.getBuilder(); + Jingle.Builder jb = Jingle.builder(connection); jb.setResponder(connection.getUser()) .setAction(JingleAction.session_accept) .setSessionId(sessionId); @@ -153,7 +153,7 @@ public class JingleUtil { } public Jingle createSessionTerminate(FullJid recipient, String sessionId, JingleReason reason) { - Jingle.Builder jb = Jingle.getBuilder(); + Jingle.Builder jb = Jingle.builder(connection); jb.setAction(JingleAction.session_terminate) .setSessionId(sessionId) .setReason(reason); @@ -232,7 +232,7 @@ public class JingleUtil { public Jingle createSessionTerminateContentCancel(FullJid recipient, String sessionId, JingleContent.Creator contentCreator, String contentName) { - Jingle.Builder jb = Jingle.getBuilder(); + Jingle.Builder jb = Jingle.builder(connection); jb.setAction(JingleAction.session_terminate) .setSessionId(sessionId); @@ -314,7 +314,7 @@ public class JingleUtil { } public Jingle createSessionPing(FullJid recipient, String sessionId) { - Jingle.Builder jb = Jingle.getBuilder(); + Jingle.Builder jb = Jingle.builder(connection); jb.setSessionId(sessionId) .setAction(JingleAction.session_info); @@ -343,7 +343,7 @@ public class JingleUtil { public Jingle createTransportReplace(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Creator contentCreator, String contentName, JingleContentTransport transport) { - Jingle.Builder jb = Jingle.getBuilder(); + Jingle.Builder jb = Jingle.builder(connection); jb.setInitiator(initiator) .setSessionId(sessionId) .setAction(JingleAction.transport_replace); @@ -370,7 +370,7 @@ public class JingleUtil { public Jingle createTransportAccept(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Creator contentCreator, String contentName, JingleContentTransport transport) { - Jingle.Builder jb = Jingle.getBuilder(); + Jingle.Builder jb = Jingle.builder(connection); jb.setAction(JingleAction.transport_accept) .setInitiator(initiator) .setSessionId(sessionId); @@ -397,7 +397,7 @@ public class JingleUtil { public Jingle createTransportReject(FullJid recipient, FullJid initiator, String sessionId, JingleContent.Creator contentCreator, String contentName, JingleContentTransport transport) { - Jingle.Builder jb = Jingle.getBuilder(); + Jingle.Builder jb = Jingle.builder(connection); jb.setAction(JingleAction.transport_reject) .setInitiator(initiator) .setSessionId(sessionId); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java index 40acf187a..7598d1894 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/Jingle.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software, 2014-2017 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. @@ -21,7 +21,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.IqBuilder; +import org.jivesoftware.smack.packet.IqData; +import org.jivesoftware.smack.packet.id.StandardStanzaIdSource; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; @@ -65,9 +69,9 @@ public final class Jingle extends IQ { private final List contents; - private Jingle(String sessionId, JingleAction action, FullJid initiator, FullJid responder, JingleReason reason, + private Jingle(Builder builder, String sessionId, JingleAction action, FullJid initiator, FullJid responder, JingleReason reason, List contents) { - super(ELEMENT, NAMESPACE); + super(builder, ELEMENT, NAMESPACE); this.sessionId = StringUtils.requireNotNullNorEmpty(sessionId, "Jingle session ID must not be null"); this.action = Objects.requireNonNull(action, "Jingle action must not be null"); this.initiator = initiator; @@ -169,11 +173,31 @@ public final class Jingle extends IQ { return xml; } + /** + * Deprecated, do not use. + * + * @return a builder. + * @deprecated use {@link #builder(XMPPConnection)} instead. + */ + @Deprecated + // TODO: Remove in Smack 4.6. public static Builder getBuilder() { - return new Builder(); + return builder(StandardStanzaIdSource.DEFAULT.getNewStanzaId()); } - public static final class Builder { + public static Builder builder(XMPPConnection connection) { + return new Builder(connection); + } + + public static Builder builder(IqData iqData) { + return new Builder(iqData); + } + + public static Builder builder(String stanzaId) { + return new Builder(stanzaId); + } + + public static final class Builder extends IqBuilder { private String sid; private JingleAction action; @@ -186,7 +210,16 @@ public final class Jingle extends IQ { private List contents; - private Builder() { + Builder(IqData iqCommon) { + super(iqCommon); + } + + Builder(XMPPConnection connection) { + super(connection); + } + + Builder(String stanzaId) { + super(stanzaId); } public Builder setSessionId(String sessionId) { @@ -228,8 +261,14 @@ public final class Jingle extends IQ { return this; } + @Override public Jingle build() { - return new Jingle(sid, action, initiator, responder, reason, contents); + return new Jingle(this, sid, action, initiator, responder, reason, contents); + } + + @Override + public Builder getThis() { + return this; } } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java index bfc085f60..ab5680c1d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleContent.java @@ -145,6 +145,11 @@ public final class JingleContent implements XmlElement { xml.optAttribute(DISPOSITION_ATTRIBUTE_NAME, disposition); xml.attribute(NAME_ATTRIBUTE_NAME, name); xml.optAttribute(SENDERS_ATTRIBUTE_NAME, senders); + + if (description == null && transport == null) { + return xml.closeEmptyElement(); + } + xml.rightAngleBracket(); xml.optAppend(description); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java index 0f0c1d759..5e31244ca 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/element/JingleReason.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017-2021 Florian Schmaus + * Copyright 2017-2022 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,6 +34,7 @@ public class JingleReason implements XmlElement { public static final String ELEMENT = "reason"; public static final String NAMESPACE = Jingle.NAMESPACE; + public static final String TEXT_ELEMENT = "text"; public static AlternativeSession AlternativeSession(String sessionId) { return new AlternativeSession(sessionId); @@ -105,9 +106,17 @@ public class JingleReason implements XmlElement { } protected final Reason reason; + private final String text; + private final XmlElement element; public JingleReason(Reason reason) { + this(reason, null, null); + } + + public JingleReason(Reason reason, String text, XmlElement element) { this.reason = reason; + this.text = text; + this.element = element; } @Override @@ -120,12 +129,34 @@ public class JingleReason implements XmlElement { return NAMESPACE; } + /** + * An optional text that provides human-readable information about the reason for the action. + * + * @return a human-readable text with information regarding this reason or null. + * @since 4.4.5 + */ + public String getText() { + return text; + } + + /** + * An optional element that provides more detailed machine-readable information about the reason for the action. + * + * @return an element with machine-readable information about this reason or null. + * @since 4.4.5 + */ + public XmlElement getElement() { + return element; + } + @Override public XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) { XmlStringBuilder xml = new XmlStringBuilder(this, enclosingXmlEnvironment); xml.rightAngleBracket(); xml.emptyElement(reason); + xml.optElement(TEXT_ELEMENT, text); + xml.optAppend(element); xml.closeElement(this); return xml; @@ -142,7 +173,11 @@ public class JingleReason implements XmlElement { private final String sessionId; public AlternativeSession(String sessionId) { - super(Reason.alternative_session); + this(sessionId, null, null); + } + + public AlternativeSession(String sessionId, String text, XmlElement element) { + super(Reason.alternative_session, text, element); if (StringUtils.isNullOrEmpty(sessionId)) { throw new NullPointerException("SessionID must not be null or empty."); } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java index 8ff1e269b..40dfe9fc4 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/provider/JingleProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017-2019 Florian Schmaus + * Copyright 2017-2022 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,11 +19,14 @@ package org.jivesoftware.smackx.jingle.provider; import java.io.IOException; import java.util.logging.Logger; +import org.jivesoftware.smack.packet.IqData; import org.jivesoftware.smack.packet.StandardExtensionElement; +import org.jivesoftware.smack.packet.XmlElement; import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.parsing.SmackParsingException; import org.jivesoftware.smack.parsing.StandardExtensionElementProvider; -import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.provider.IqProvider; +import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.ParserUtils; import org.jivesoftware.smack.xml.XmlPullParser; import org.jivesoftware.smack.xml.XmlPullParserException; @@ -40,13 +43,13 @@ import org.jivesoftware.smackx.jingle.element.UnknownJingleContentTransport; import org.jxmpp.jid.FullJid; -public class JingleProvider extends IQProvider { +public class JingleProvider extends IqProvider { private static final Logger LOGGER = Logger.getLogger(JingleProvider.class.getName()); @Override - public Jingle parse(XmlPullParser parser, int initialDepth, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { - Jingle.Builder builder = Jingle.getBuilder(); + public Jingle parse(XmlPullParser parser, int initialDepth, IqData iqData, XmlEnvironment xmlEnvironment) throws XmlPullParserException, IOException, SmackParsingException { + Jingle.Builder builder = Jingle.builder(iqData); String actionString = parser.getAttributeValue("", Jingle.ACTION_ATTRIBUTE_NAME); if (actionString != null) { @@ -75,16 +78,7 @@ public class JingleProvider extends IQProvider { builder.addJingleContent(content); break; case JingleReason.ELEMENT: - parser.next(); - String reasonString = parser.getName(); - JingleReason reason; - if (reasonString.equals("alternative-session")) { - parser.next(); - String sid = parser.nextText(); - reason = new JingleReason.AlternativeSession(sid); - } else { - reason = new JingleReason(Reason.fromString(reasonString)); - } + JingleReason reason = parseJingleReason(parser); builder.setReason(reason); break; default: @@ -177,4 +171,57 @@ public class JingleProvider extends IQProvider { return builder.build(); } + + public static JingleReason parseJingleReason(XmlPullParser parser) + throws XmlPullParserException, IOException, SmackParsingException { + ParserUtils.assertAtStartTag(parser); + final int initialDepth = parser.getDepth(); + final String jingleNamespace = parser.getNamespace(); + + JingleReason.Reason reason = null; + XmlElement element = null; + String text = null; + + // 'sid' is only set if the reason is 'alternative-session'. + String sid = null; + + outerloop: while (true) { + XmlPullParser.TagEvent event = parser.nextTag(); + switch (event) { + case START_ELEMENT: + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (namespace.equals(jingleNamespace)) { + switch (elementName) { + case "text": + text = parser.nextText(); + break; + case "alternative-session": + parser.next(); + sid = parser.nextText(); + break; + default: + reason = Reason.fromString(elementName); + break; + } + } else { + element = PacketParserUtils.parseExtensionElement(elementName, namespace, parser, null); + } + break; + case END_ELEMENT: + if (parser.getDepth() == initialDepth) { + break outerloop; + } + break; + } + } + + JingleReason res; + if (sid != null) { + res = new JingleReason.AlternativeSession(sid, text, element); + } else { + res = new JingleReason(reason, text, element); + } + return res; + } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java index 5b348c5d3..8c91c8976 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/JingleTransportManager.java @@ -36,6 +36,10 @@ public abstract class JingleTransportManager i } public XMPPConnection getConnection() { + return connection(); + } + + public XMPPConnection connection() { return connection; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java index 06accc144..8211820fe 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/transports/jingle_s5b/JingleS5BTransportManager.java @@ -148,7 +148,7 @@ public final class JingleS5BTransportManager extends JingleTransportManager { + private static final DoOnce LOG_OBJECT_NOT_ENABLED = new DoOnce(); + private static final Logger LOGGER = Logger.getLogger(JivePropertiesExtensionProvider.class.getName()); /** @@ -113,7 +116,10 @@ public class JivePropertiesExtensionProvider extends ExtensionElementProvider LOGGER.severe( + "JavaObject is not enabled. Enable with JivePropertiesManager.setJavaObjectEnabled(true)") + ); } } if (name != null && value != null) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java index 8d941ee06..1735d0b4f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java @@ -78,6 +78,17 @@ public class MucConfigFormManager { */ public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret"; + /** + * The constant String {@value}. + */ + public static final String MUC_ROOMCONFIG_MODERATEDROOM = "muc#roomconfig_moderatedroom"; + + /** + * The constant String {@value}. + */ + public static final String MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM = "muc#roomconfig_publicroom"; + + private final MultiUserChat multiUserChat; private final FillableForm answerForm; private final List owners; @@ -151,6 +162,15 @@ public class MucConfigFormManager { return answerForm.hasField(MUC_ROOMCONFIG_MEMBERSONLY); } + /** + * Check if the room supports being moderated in the configuration. + * + * @return true if supported, false if not. + */ + public boolean supportsModeration() { + return answerForm.hasField(MUC_ROOMCONFIG_MODERATEDROOM); + } + /** * Make the room for members only. * @@ -176,6 +196,68 @@ public class MucConfigFormManager { return this; } + + /** + * Make the room moderated. + * + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager makeModerated() throws MucConfigurationNotSupportedException { + return setModerated(true); + } + + /** + * Set if the room is members only. Rooms are not members only per default. + * + * @param isModerated if the room should be moderated. + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager setModerated(boolean isModerated) throws MucConfigurationNotSupportedException { + if (!supportsModeration()) { + throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MODERATEDROOM); + } + answerForm.setAnswer(MUC_ROOMCONFIG_MODERATEDROOM, isModerated); + return this; + } + + + /** + * Make the room publicly searchable. + * + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager makePublic() throws MucConfigurationNotSupportedException { + return setPublic(true); + } + + /** + * Make the room hidden (not publicly searchable). + * + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager makeHidden() throws MucConfigurationNotSupportedException { + return setPublic(false); + } + + /** + * Set if the room is publicly searchable (i.e. visible via discovery requests to the MUC service). + * + * @param isPublic if the room should be publicly searchable. + * @return a reference to this object. + * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. + */ + public MucConfigFormManager setPublic(boolean isPublic) throws MucConfigurationNotSupportedException { + if (!supportsModeration()) { + throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM); + } + answerForm.setAnswer(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM, isPublic); + return this; + } + /** * Check if the room supports password protection. * 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 dba422d43..1064ee59d 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 @@ -205,12 +205,20 @@ public class MultiUserChat { if (from == null) { return; } - final EntityFullJid myRoomJID = myRoomJid; + final EntityFullJid myRoomJID = getMyRoomJid(); final boolean isUserStatusModification = presence.getFrom().equals(myRoomJID); final MUCUser mucUser = MUCUser.from(packet); switch (presence.getType()) { case available: + if (!processedReflectedSelfPresence + && mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110)) { + processedReflectedSelfPresence = true; + synchronized (this) { + notify(); + } + } + Presence oldPresence = occupantsMap.put(from, presence); if (oldPresence != null) { // Get the previous occupant's affiliation & role @@ -228,11 +236,6 @@ public class MultiUserChat { newAffiliation, isUserStatusModification, from); - } else if (mucUser.getStatus().contains(MUCUser.Status.PRESENCE_TO_SELF_110)) { - processedReflectedSelfPresence = true; - synchronized (this) { - notify(); - } } else { // A new occupant has joined the room for (ParticipantStatusListener listener : participantStatusListeners) { @@ -259,23 +262,24 @@ public class MultiUserChat { listener.left(from); } } + } - Destroy destroy = mucUser.getDestroy(); - // The room has been destroyed. - if (destroy != null) { - EntityBareJid alternateMucJid = destroy.getJid(); - final MultiUserChat alternateMuc; - if (alternateMucJid == null) { - alternateMuc = null; - } else { - alternateMuc = multiUserChatManager.getMultiUserChat(alternateMucJid); - } + Destroy destroy = mucUser.getDestroy(); + // The room has been destroyed. + if (destroy != null) { + EntityBareJid alternateMucJid = destroy.getJid(); + final MultiUserChat alternateMuc; + if (alternateMucJid == null) { + alternateMuc = null; + } else { + alternateMuc = multiUserChatManager.getMultiUserChat(alternateMucJid); + } - for (UserStatusListener listener : userStatusListeners) { - listener.roomDestroyed(alternateMuc, destroy.getReason()); - } + for (UserStatusListener listener : userStatusListeners) { + listener.roomDestroyed(alternateMuc, destroy.getReason()); } } + if (isUserStatusModification) { for (UserStatusListener listener : userStatusListeners) { listener.removed(mucUser, presence); @@ -728,7 +732,7 @@ public class MultiUserChat { * @return true if currently in the multi user chat room. */ public boolean isJoined() { - return myRoomJid != null; + return getMyRoomJid() != null; } /** @@ -764,7 +768,7 @@ public class MultiUserChat { // "if (!joined) return" because it should be always be possible to leave the room in case the instance's // state does not reflect the actual state. - final EntityFullJid myRoomJid = this.myRoomJid; + final EntityFullJid myRoomJid = getMyRoomJid(); if (myRoomJid == null) { throw new MucNotJoinedException(this); } @@ -793,11 +797,14 @@ public class MultiUserChat { StanzaFilter reflectedLeavePresenceFilter = new AndFilter(reflectedLeavePresenceFilters); - // Reset occupant information first so that we are assume that we left the room even if sendStanza() would - // throw. - userHasLeft(); - - Presence reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow(); + Presence reflectedLeavePresence; + try { + reflectedLeavePresence = connection.createStanzaCollectorAndSend(reflectedLeavePresenceFilter, leavePresence).nextResultOrThrow(); + } finally { + // Reset occupant information after we send the leave presence. This ensures that we only call userHasLeft() + // and reset the local MUC state after we successfully left the MUC (or if an exception occurred). + userHasLeft(); + } return reflectedLeavePresence; } @@ -1193,13 +1200,23 @@ public class MultiUserChat { * @return the nickname currently being used. */ public Resourcepart getNickname() { - final EntityFullJid myRoomJid = this.myRoomJid; + final EntityFullJid myRoomJid = getMyRoomJid(); if (myRoomJid == null) { return null; } return myRoomJid.getResourcepart(); } + /** + * Return the full JID of the user in the room, or null if the room is not joined. + * + * @return the full JID of the user in the room, or null. + * @since 4.5.0 + */ + public EntityFullJid getMyRoomJid() { + return myRoomJid; + } + /** * Changes the occupant's nickname to a new nickname within the room. Each room occupant * will receive two presence packets. One of type "unavailable" for the old nickname and one @@ -1256,7 +1273,7 @@ public class MultiUserChat { * @throws MucNotJoinedException if not joined to the Multi-User Chat. */ public void changeAvailabilityStatus(String status, Presence.Mode mode) throws NotConnectedException, InterruptedException, MucNotJoinedException { - final EntityFullJid myRoomJid = this.myRoomJid; + final EntityFullJid myRoomJid = getMyRoomJid(); if (myRoomJid == null) { throw new MucNotJoinedException(this); } @@ -2579,7 +2596,7 @@ public class MultiUserChat { } public boolean serviceSupportsStableIds() { - return mucServiceDiscoInfo.containsFeature(MultiUserChatConstants.STABLE_ID_FEATURE); + return DiscoverInfo.nullSafeContainsFeature(mucServiceDiscoInfo, MultiUserChatConstants.STABLE_ID_FEATURE); } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java index 3a6945f0f..ef8f3373c 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/FormNode.java @@ -38,11 +38,7 @@ public class FormNode extends NodeExtension { * @param submitForm The form */ public FormNode(FormNodeType formType, DataForm submitForm) { - super(formType.getNodeElement()); - - if (submitForm == null) - throw new IllegalArgumentException("Submit form cannot be null"); - configForm = submitForm; + this(formType, null, submitForm); } /** @@ -55,9 +51,6 @@ public class FormNode extends NodeExtension { */ public FormNode(FormNodeType formType, String nodeId, DataForm submitForm) { super(formType.getNodeElement(), nodeId); - - if (submitForm == null) - throw new IllegalArgumentException("Submit form cannot be null"); configForm = submitForm; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java index e7d8b0946..5c5d71dc7 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java @@ -35,6 +35,11 @@ import org.jivesoftware.smackx.xdata.packet.DataForm; public class FormNodeProvider extends EmbeddedExtensionProvider { @Override protected FormNode createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) { - return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), (DataForm) content.iterator().next()); + DataForm dataForm = null; + if (!content.isEmpty()) { + dataForm = (DataForm) content.get(0); + } + + return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), dataForm); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java index 4c7d0e7ce..5842fda91 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java @@ -50,24 +50,25 @@ public class ItemProvider extends ExtensionElementProvider { String xmlns = parser.getNamespace(); ItemNamespace itemNamespace = ItemNamespace.fromXmlns(xmlns); - XmlPullParser.Event tag = parser.next(); + XmlPullParser.TagEvent event = parser.nextTag(); + switch (event) { + case START_ELEMENT: + String payloadElemName = parser.getName(); + String payloadNS = parser.getNamespace(); - if (tag == XmlPullParser.Event.END_ELEMENT) { - return new Item(itemNamespace, id, node); - } - else { - String payloadElemName = parser.getName(); - String payloadNS = parser.getNamespace(); - - final ExtensionElementProvider extensionProvider = ProviderManager.getExtensionProvider(payloadElemName, payloadNS); - if (extensionProvider == null) { - // TODO: Should we use StandardExtensionElement in this case? And probably remove SimplePayload all together. - CharSequence payloadText = PacketParserUtils.parseElement(parser, true); - return new PayloadItem<>(itemNamespace, id, node, new SimplePayload(payloadText.toString())); - } - else { - return new PayloadItem<>(itemNamespace, id, node, extensionProvider.parse(parser)); - } + final ExtensionElementProvider extensionProvider = ProviderManager.getExtensionProvider(payloadElemName, payloadNS); + if (extensionProvider == null) { + // TODO: Should we use StandardExtensionElement in this case? And probably remove SimplePayload all together. + CharSequence payloadText = PacketParserUtils.parseElement(parser, true); + return new PayloadItem<>(itemNamespace, id, node, new SimplePayload(payloadText.toString())); + } + else { + return new PayloadItem<>(itemNamespace, id, node, extensionProvider.parse(parser)); + } + case END_ELEMENT: + return new Item(itemNamespace, id, node); + default: + throw new AssertionError("unknown: " + event); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/softwareinfo/form/SoftwareInfoForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/softwareinfo/form/SoftwareInfoForm.java index 1d6a7af4a..f2327face 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/softwareinfo/form/SoftwareInfoForm.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/softwareinfo/form/SoftwareInfoForm.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Aditya Borikar + * Copyright 2020 Aditya Borikar, 2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.util.List; import org.jivesoftware.smack.util.EqualsUtil; import org.jivesoftware.smack.util.HashCode; - +import org.jivesoftware.smackx.formtypes.FormFieldRegistry; import org.jivesoftware.smackx.mediaelement.element.MediaElement; import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.FormFieldChildElement; @@ -47,6 +47,11 @@ public final class SoftwareInfoForm extends FilledForm { public static final String SOFTWARE_VERSION = "software_version"; public static final String ICON = "icon"; + static { + FormFieldRegistry.register(FORM_TYPE, FormField.Type.text_single, + OS, OS_VERSION, SOFTWARE, SOFTWARE_VERSION); + } + private SoftwareInfoForm(DataForm dataForm) { super(dataForm); } 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 4b1c092b3..16ccc3c7f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractMultiFormField.java @@ -27,35 +27,26 @@ import org.jxmpp.util.XmppDateTime; public class AbstractMultiFormField extends FormField { - private final List values; - - private final List rawValues; + private final List values; protected AbstractMultiFormField(Builder builder) { super(builder); values = CollectionUtil.cloneAndSeal(builder.values); - rawValues = CollectionUtil.cloneAndSeal(builder.rawValues); } @Override - public final List getValues() { + public final List getRawValues() { return values; } - @Override - public final List getRawValues() { - return rawValues; - } - public abstract static class Builder> extends FormField.Builder { - private List values; - private List rawValues; + private List values; protected Builder(AbstractMultiFormField formField) { super(formField); - values = CollectionUtil.newListWith(formField.getValues()); + values = CollectionUtil.newListWith(formField.getRawValues()); } protected Builder(String fieldName, FormField.Type type) { @@ -65,7 +56,6 @@ public class AbstractMultiFormField extends FormField { private void ensureValuesAreInitialized() { if (values == null) { values = new ArrayList<>(); - rawValues = new ArrayList<>(); } } @@ -77,11 +67,13 @@ public class AbstractMultiFormField extends FormField { public abstract B addValue(CharSequence value); public B addValueVerbatim(CharSequence value) { + return addValueVerbatim(new Value(value)); + } + + public B addValueVerbatim(Value value) { ensureValuesAreInitialized(); - String valueString = value.toString(); - values.add(valueString); - rawValues.add(valueString); + values.add(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 4530d3968..9e57a294e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/AbstractSingleStringValueFormField.java @@ -74,8 +74,15 @@ public class AbstractSingleStringValueFormField extends SingleValueFormField { return setValue(value); } + public B setValue(Value value) { + this.value = value.getValue().toString(); + this.rawValue = value; + return getThis(); + } + public B setValue(CharSequence value) { - this.rawValue = this.value = value.toString(); + this.value = value.toString(); + rawValue = new Value(this.value); return getThis(); } 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 e87278ae9..6681e6408 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 @@ -35,7 +35,31 @@ public class BooleanFormField extends SingleValueFormField { return value.toString(); } - public Boolean getValueAsBoolean() { + /** + * Get the value of the booelan field. Note that, if no explicit boolean value is provided, in the form of "true", + * "false", "0", or "1", then the default value of a boolean field is false, according to + * XEP-0004 § 3.3. + * + * @return the boolean value of this form field. + */ + public boolean getValueAsBoolean() { + if (value == null) { + return false; + } + return value; + } + + /** + * Get the value of the boolean field or maybe null. Note that you usually want to use + * {@link #getValueAsBoolean()} instead of this method, as {@link #getValueAsBoolean()} considers the default value + * of boolean fields. That is, boolean form fields have the value false if not explicitly set to + * something else. + * + * @return the boolean value of this form field or null if no value was explicitly provided. + * @see #getValueAsBoolean() + * @since 4.4.5 + */ + public Boolean getValueAsBooleanOrNull() { return value; } @@ -71,17 +95,21 @@ public class BooleanFormField extends SingleValueFormField { @Deprecated // TODO: Remove in Smack 4.6. public Builder addValue(CharSequence value) { - return setValue(value); + return setValue(new Value(value)); } public Builder setValue(CharSequence value) { - rawValue = value.toString(); - boolean valueBoolean = ParserUtils.parseXmlBoolean(rawValue); - return setValue(valueBoolean); + return setValue(new Value(value)); + } + + public Builder setValue(Value value) { + this.value = ParserUtils.parseXmlBoolean(value.getValue().toString()); + rawValue = value; + return getThis(); } public Builder setValue(boolean value) { - rawValue = Boolean.toString(value); + rawValue = new Value(Boolean.toString(value)); this.value = value; return this; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java index 8f4e9aafd..a2ad8cb76 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 @@ -33,6 +33,7 @@ import org.jivesoftware.smack.util.CollectionUtil; import org.jivesoftware.smack.util.EqualsUtil; import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smack.util.MultiMap; +import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -269,9 +270,25 @@ public abstract class FormField implements XmlElement { * * @return a List of the default values or answered values of the question. */ - public abstract List getValues(); + public List getValues() { + return getRawValueCharSequences(); + } - public abstract List getRawValues(); + public abstract List getRawValues(); + + private transient List rawValueCharSequences; + + public final List getRawValueCharSequences() { + if (rawValueCharSequences == null) { + List rawValues = getRawValues(); + rawValueCharSequences = new ArrayList<>(rawValues.size()); + for (Value value : rawValues) { + rawValueCharSequences.add(value.value); + } + } + + return rawValueCharSequences; + } public boolean hasValueSet() { List values = getValues(); @@ -385,12 +402,15 @@ public abstract class FormField implements XmlElement { protected transient List extraXmlChildElements; + /** + * Populate @{link {@link #extraXmlChildElements}}. Note that this method may be overridden by subclasses. + */ protected void populateExtraXmlChildElements() { - List values = getValues(); + List values = getRawValues(); + // Note that we need to create a new ArrayList here, since subclasses may add to it by overriding + // populateExtraXmlChildElements. extraXmlChildElements = new ArrayList<>(values.size()); - for (CharSequence value : values) { - extraXmlChildElements.add(new Value(value)); - } + extraXmlChildElements.addAll(values); } @Override @@ -414,7 +434,8 @@ public abstract class FormField implements XmlElement { populateExtraXmlChildElements(); } - if (formFieldChildElements.isEmpty() && extraXmlChildElements == null) { + if (formFieldChildElements.isEmpty() + && (extraXmlChildElements == null || extraXmlChildElements.isEmpty())) { buf.closeEmptyElement(); } else { buf.rightAngleBracket(); @@ -580,7 +601,7 @@ public abstract class FormField implements XmlElement { * @return a reference to this builder. */ public B setLabel(String label) { - this.label = StringUtils.requireNotNullNorEmpty(label, "label must not be null or empty"); + this.label = Objects.requireNonNull(label, "label must not be null"); return getThis(); } 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 a5c869c18..39d8357af 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 @@ -23,13 +23,14 @@ import java.util.List; import org.jivesoftware.smack.util.CollectionUtil; import org.jxmpp.jid.Jid; -import org.jxmpp.jid.util.JidUtil; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; public final class JidMultiFormField extends FormField { private final List values; - private final List rawValues; + private final List rawValues; JidMultiFormField(Builder builder) { super(builder); @@ -43,7 +44,7 @@ public final class JidMultiFormField extends FormField { } @Override - public List getRawValues() { + public List getRawValues() { return rawValues; } @@ -54,7 +55,7 @@ public final class JidMultiFormField extends FormField { public static final class Builder extends FormField.Builder { private List values; - private List rawValues; + private List rawValues; private Builder(JidMultiFormField jidMultiFormField) { super(jidMultiFormField); @@ -79,27 +80,29 @@ public final class JidMultiFormField extends FormField { } public Builder addValue(Jid jid) { - return addValue(jid, null); - } - - public Builder addValue(Jid jid, String rawValue) { - if (rawValue == null) { - rawValue = jid.toString(); - } + Value value = new Value(jid); ensureValuesAreInitialized(); - values.add(jid); - rawValues.add(rawValue); + rawValues.add(value); + + return getThis(); + } + + public Builder addValue(Value value) throws XmppStringprepException { + Jid jid = JidCreate.from(value.getValue()); + + ensureValuesAreInitialized(); + values.add(jid); + rawValues.add(value); return this; } public Builder addValues(Collection jids) { - ensureValuesAreInitialized(); - - values.addAll(jids); - rawValues.addAll(JidUtil.toStringList(jids)); + for (Jid jid : jids) { + addValue(jid); + } return this; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/JidSingleFormField.java index 7948971a3..ec5ec4c70 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 @@ -17,6 +17,8 @@ package org.jivesoftware.smackx.xdata; import org.jxmpp.jid.Jid; +import org.jxmpp.jid.impl.JidCreate; +import org.jxmpp.stringprep.XmppStringprepException; public class JidSingleFormField extends SingleValueFormField { @@ -60,11 +62,13 @@ public class JidSingleFormField extends SingleValueFormField { public Builder setValue(Jid value, String rawValue) { this.value = value; - if (rawValue != null) { - this.rawValue = rawValue; - } else { - this.rawValue = value.toString(); - } + this.rawValue = new Value(value); + return getThis(); + } + + public Builder setValue(Value value) throws XmppStringprepException { + this.value = JidCreate.from(value.getValue()); + this.rawValue = value; return this; } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java index 796abd654..d4b389a96 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/SingleValueFormField.java @@ -23,7 +23,7 @@ import org.jivesoftware.smack.util.CollectionUtil; public abstract class SingleValueFormField extends FormField { - private final String rawValue; + private final Value rawValue; protected SingleValueFormField(Builder builder) { super(builder); @@ -38,24 +38,23 @@ public abstract class SingleValueFormField extends FormField { public abstract CharSequence getValue(); - public final String getRawValue() { + public final Value getRawValue() { return rawValue; } @Override - public final List getRawValues() { - String rawValue = getRawValue(); + public final List getRawValues() { + Value rawValue = getRawValue(); return CollectionUtil.emptyOrSingletonListFrom(rawValue); } @Override protected void populateExtraXmlChildElements() { - CharSequence value = getValue(); - if (value == null) { + if (rawValue == null) { return; } - extraXmlChildElements = Collections.singletonList(new Value(value)); + extraXmlChildElements = Collections.singletonList(rawValue); } public abstract static class Builder> @@ -65,11 +64,12 @@ public abstract class SingleValueFormField extends FormField { super(fieldName, type); } - protected Builder(FormField formField) { + protected Builder(SingleValueFormField formField) { super(formField); + rawValue = formField.getRawValue(); } - protected String rawValue; + protected Value rawValue; @Override protected void resetInternal() { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java index 493abd25a..043e9a1fe 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FormReader.java @@ -1,6 +1,6 @@ /** * - * Copyright 2020 Florian Schmaus + * Copyright 2020-2021 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import org.jivesoftware.smack.util.StringUtils; + import org.jivesoftware.smackx.xdata.AbstractMultiFormField; import org.jivesoftware.smackx.xdata.AbstractSingleStringValueFormField; import org.jivesoftware.smackx.xdata.BooleanFormField; @@ -54,7 +56,8 @@ public interface FormReader { return Collections.emptyList(); } AbstractMultiFormField multiFormField = formField.ifPossibleAs(AbstractMultiFormField.class); - return multiFormField.getValues(); + List charSequences = multiFormField.getValues(); + return StringUtils.toStrings(charSequences); } default Boolean readBoolean(String fieldName) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java index f6362660c..0be42d61f 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/provider/DataFormProvider.java @@ -49,9 +49,6 @@ import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdatalayout.packet.DataLayout; import org.jivesoftware.smackx.xdatalayout.provider.DataLayoutProvider; -import org.jxmpp.jid.Jid; -import org.jxmpp.jid.impl.JidCreate; - /** * The DataFormProvider parses DataForm packets. * @@ -237,9 +234,7 @@ public class DataFormProvider extends ExtensionElementProvider { case jid_multi: JidMultiFormField.Builder jidMultiBuilder = FormField.jidMultiBuilder(fieldName); for (FormField.Value value : values) { - String rawValue = value.getValue().toString(); - Jid jid = JidCreate.from(rawValue); - jidMultiBuilder.addValue(jid, rawValue); + jidMultiBuilder.addValue(value); } builder = jidMultiBuilder; break; @@ -247,9 +242,8 @@ public class DataFormProvider extends ExtensionElementProvider { ensureAtMostSingleValue(type, values); JidSingleFormField.Builder jidSingleBuilder = FormField.jidSingleBuilder(fieldName); if (!values.isEmpty()) { - String rawValue = values.get(0).getValue().toString(); - Jid jid = JidCreate.from(rawValue); - jidSingleBuilder.setValue(jid, rawValue); + FormField.Value value = values.get(0); + jidSingleBuilder.setValue(value); } builder = jidSingleBuilder; break; @@ -303,7 +297,7 @@ public class DataFormProvider extends ExtensionElementProvider { BooleanFormField.Builder builder = FormField.booleanBuilder(fieldName); ensureAtMostSingleValue(builder.getType(), values); if (values.size() == 1) { - String value = values.get(0).getValue().toString(); + FormField.Value value = values.get(0); builder.setValue(value); } return builder; diff --git a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml index d234bb5b3..5921821a1 100644 --- a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml +++ b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.xml @@ -20,5 +20,6 @@ org.jivesoftware.smackx.receipts.DeliveryReceiptManager org.jivesoftware.smackx.iqversion.VersionManager org.jivesoftware.smackx.caps.EntityCapsManager + org.jivesoftware.smackx.softwareinfo.form.SoftwareInfoForm diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java index af8426029..f8df83f83 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java @@ -67,7 +67,6 @@ public class AMPExtensionTest { AMPExtensionProvider ampProvider = new AMPExtensionProvider(); XmlPullParser parser = PacketParserUtils.getParserFor(INCORRECT_RECEIVING_STANZA_STREAM); - assertEquals(XmlPullParser.Event.START_ELEMENT, parser.next()); assertEquals(AMPExtension.ELEMENT, parser.getName()); ExtensionElement extension = ampProvider.parse(parser); @@ -85,7 +84,6 @@ public class AMPExtensionTest { AMPExtensionProvider ampProvider = new AMPExtensionProvider(); XmlPullParser parser = PacketParserUtils.getParserFor(CORRECT_SENDING_STANZA_STREAM); - assertEquals(XmlPullParser.Event.START_ELEMENT, parser.next()); assertEquals(AMPExtension.ELEMENT, parser.getName()); ExtensionElement extension = ampProvider.parse(parser); assertTrue(extension instanceof AMPExtension); diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java index 45904af4e..946ae95e8 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleContentTest.java @@ -75,8 +75,7 @@ public class JingleContentTest extends SmackTestSuite { assertEquals(content1.toXML().toString(), builder.build().toXML().toString()); String xml = - "" + - ""; + ""; assertEquals(xml, content1.toXML().toString()); } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleTest.java index c8ac6c926..2f8147947 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/JingleTest.java @@ -38,7 +38,7 @@ public class JingleTest extends SmackTestSuite { @Test public void emptyBuilderTest() { - Jingle.Builder builder = Jingle.getBuilder(); + Jingle.Builder builder = Jingle.builder("id"); assertThrows(IllegalArgumentException.class, () -> { builder.build(); }); @@ -48,7 +48,7 @@ public class JingleTest extends SmackTestSuite { public void onlySessionIdBuilderTest() { String sessionId = "testSessionId"; - Jingle.Builder builder = Jingle.getBuilder(); + Jingle.Builder builder = Jingle.builder("id"); builder.setSessionId(sessionId); assertThrows(IllegalArgumentException.class, () -> { builder.build(); @@ -59,7 +59,7 @@ public class JingleTest extends SmackTestSuite { public void parserTest() throws XmppStringprepException { String sessionId = "testSessionId"; - Jingle.Builder builder = Jingle.getBuilder(); + Jingle.Builder builder = Jingle.builder("id"); builder.setSessionId(sessionId); builder.setAction(JingleAction.session_initiate); diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/element/JingleTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/element/JingleTest.java new file mode 100644 index 000000000..ae198617f --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/element/JingleTest.java @@ -0,0 +1,48 @@ +/** + * + * 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.jingle.element; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.jivesoftware.smack.packet.StreamOpen; + +import org.junit.jupiter.api.Test; + +public class JingleTest { + + @Test + public void noRedundantNamespaceTest() { + Jingle.Builder jingleBuilder = Jingle.builder("test-id"); + jingleBuilder.setSessionId("MySession"); + jingleBuilder.setAction(JingleAction.content_accept); + + JingleContent.Builder jingleContentBuilder = JingleContent.getBuilder(); + jingleContentBuilder.setName("Hello world"); + jingleContentBuilder.setCreator(JingleContent.Creator.initiator); + + jingleBuilder.addJingleContent(jingleContentBuilder.build()); + Jingle iq = jingleBuilder.build(); + + String actualXml = iq.toXML(StreamOpen.CLIENT_NAMESPACE).toString(); + String expectedXml + = "" + + "" + + "" + + ""; + assertEquals(expectedXml, actualXml); + } +} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java index c17543148..9385a88f8 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/jingle/provider/JingleProviderTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2017 Florian Schmaus + * Copyright 2017-2022 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,23 +17,30 @@ package org.jivesoftware.smackx.jingle.provider; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; -import org.jivesoftware.smack.util.PacketParserUtils; -import org.jivesoftware.smack.xml.XmlPullParser; +import org.jivesoftware.smack.packet.StandardExtensionElement; +import org.jivesoftware.smack.packet.XmlElement; +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.test.util.SmackTestUtil; import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smackx.jingle.element.Jingle; import org.jivesoftware.smackx.jingle.element.JingleContentDescription; import org.jivesoftware.smackx.jingle.element.JingleContentTransport; +import org.jivesoftware.smackx.jingle.element.JingleReason; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; public class JingleProviderTest { - @Test - public void testParseUnknownJingleContentDescrption() throws Exception { + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testParseUnknownJingleContentDescrption(SmackTestUtil.XmlPullParserKind parserKind) + throws XmlPullParserException, IOException, SmackParsingException { final String unknownJingleContentDescriptionNamespace = "urn:xmpp:jingle:unknown-description:5"; final String unknownJingleContentDescription = // @formatter:off @@ -50,8 +57,8 @@ public class JingleProviderTest { "" + ""; // @formatter:on - XmlPullParser parser = createTestJingle(unknownJingleContentDescription); - Jingle jingle = (Jingle) PacketParserUtils.parseIQ(parser); + CharSequence xml = createTestJingle(unknownJingleContentDescription); + Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind); JingleContentDescription jingleContentDescription = jingle.getSoleContentOrThrow().getDescription(); @@ -59,8 +66,10 @@ public class JingleProviderTest { assertEquals(unknownJingleContentDescriptionNamespace, parsedUnknownJingleContentDescriptionNamespace); } - @Test - public void testParseUnknownJingleContentTransport() throws Exception { + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testParseUnknownJingleContentTransport(SmackTestUtil.XmlPullParserKind parserKind) + throws XmlPullParserException, IOException, SmackParsingException { final String unknownJingleContentTransportNamespace = "urn:xmpp:jingle:unknown-transport:foo:1"; final String unknownJingleContentTransport = // @formatter:off @@ -81,8 +90,8 @@ public class JingleProviderTest { " type='direct'/>" + ""; // @formatter:on - XmlPullParser parser = createTestJingle(unknownJingleContentTransport); - Jingle jingle = (Jingle) PacketParserUtils.parseIQ(parser); + CharSequence xml = createTestJingle(unknownJingleContentTransport); + Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind); JingleContentTransport jingleContentTransport = jingle.getSoleContentOrThrow().getTransport(); @@ -90,7 +99,38 @@ public class JingleProviderTest { assertEquals(unknownJingleContentTransportNamespace, parsedUnknownJingleContentTransportNamespace); } - private static XmlPullParser createTestJingle(String... childs) throws XmlPullParserException, IOException { + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void testReasonElementWithExtraElement(SmackTestUtil.XmlPullParserKind parserKind) + throws XmlPullParserException, IOException, SmackParsingException { + String xml = "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + Jingle jingle = SmackTestUtil.parse(xml, JingleProvider.class, parserKind); + JingleReason jingleReason = jingle.getReason(); + + assertEquals(JingleReason.Reason.success, jingleReason.asEnum()); + + XmlElement element = jingleReason.getElement(); + // TODO: Use JUnit 5.8's assertInstanceOf when possible + // assertInstanceOf(StandardExtesionElement.class, extraElement); + assertTrue(element instanceof StandardExtensionElement); + StandardExtensionElement extraElement = (StandardExtensionElement) element; + assertEquals("https://example.org", extraElement.getNamespace()); + assertEquals("bar", extraElement.getAttributes().get("foo")); + } + + private static CharSequence createTestJingle(String... childs) throws XmlPullParserException, IOException { StringBuilder sb = new StringBuilder(); sb.append(// @formatter:off "" + "" + + "action='session-initiate' " + + "initiator='romeo@montague.example/dr4hcr0st3lup4c' " + + "sid='851ba2'>" + "" // @formatter:on ); @@ -114,9 +154,6 @@ public class JingleProviderTest { // @formatter:on ); - String jingleStanza = sb.toString(); - - XmlPullParser parser = PacketParserUtils.getParserFor(jingleStanza); - return parser; + return sb; } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/provider/ItemProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/provider/ItemProviderTest.java new file mode 100644 index 000000000..e7348e920 --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/pubsub/provider/ItemProviderTest.java @@ -0,0 +1,48 @@ +/** + * + * 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.pubsub.provider; + +import java.io.IOException; + +import org.jivesoftware.smack.parsing.SmackParsingException; +import org.jivesoftware.smack.test.util.SmackTestUtil; +import org.jivesoftware.smack.xml.XmlPullParserException; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class ItemProviderTest { + + /** + * Check that {@link ItemProvider} is able to parse items which have whitespace before their Payload. + * + * @param parserKind the used parser Kind + * @throws XmlPullParserException if an XML pull parser exception occurs. + * @throws IOException if an IO exception occurs. + * @throws SmackParsingException if an Smack parsing exception occurs. + * @see SMACK-918 + */ + @ParameterizedTest + @EnumSource(SmackTestUtil.XmlPullParserKind.class) + public void whitespaceBeforeItemPayload(SmackTestUtil.XmlPullParserKind parserKind) throws XmlPullParserException, IOException, SmackParsingException { + String item = "" + + "\n " + + ""; + SmackTestUtil.parse(item, ItemProvider.class, parserKind); + } + +} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java index 67cd5dd89..d6d0232c9 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/FormFieldTest.java @@ -17,6 +17,8 @@ package org.jivesoftware.smackx.xdata; import static org.jivesoftware.smack.test.util.XmlAssertUtil.assertXmlSimilar; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Test; import org.jxmpp.jid.JidTestUtil; @@ -35,4 +37,18 @@ class FormFieldTest { assertXmlSimilar(expectedXml, xml); } + @Test + public void testEmptyLabel() { + TextSingleFormField.Builder builder = FormField.textSingleBuilder("type"); + builder.setLabel(""); + TextSingleFormField formField = builder.build(); + + assertEquals("", formField.getLabel()); + } + + @Test + public void testThrowExceptionWhenNullLabel() { + TextSingleFormField.Builder builder = FormField.textSingleBuilder("type"); + assertThrows(IllegalArgumentException.class, () -> builder.setLabel(null)); + } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/provider/DataFormProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/provider/DataFormProviderTest.java index 90093023c..3d901a8fc 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/provider/DataFormProviderTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/xdata/provider/DataFormProviderTest.java @@ -115,4 +115,33 @@ public class DataFormProviderTest { assertEquals(2, items.size()); } + @Test + public void testRetrieveFieldWithEmptyLabel() throws XmlPullParserException, IOException, SmackParsingException { + + String form = + "" + + " Advanced User Search" + + " The following fields are available for searching. Wildcard (*) characters are allowed as part of the query." + + " " + + " jabber:iq:search" + + " " + + " " + + " " + + " " + + " " + + " true" + + " " + + " " + + " true" + + " " + + " " + + " true" + + " " + + ""; + XmlPullParser parser = PacketParserUtils.getParserFor(form); + DataForm dataForm = DataFormProvider.INSTANCE.parse(parser); + FormField usernameFormField = dataForm.getField("FORM_TYPE"); + assertEquals(FormField.Type.hidden, usernameFormField.getType()); + assertEquals("", usernameFormField.getLabel()); + } } diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java index 67557c553..5505e4dda 100644 --- a/smack-extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java @@ -39,7 +39,6 @@ public class XHTMLExtensionProviderTest { public void parsesWell() throws IOException, XmlPullParserException { InputStream inputStream = getClass().getResourceAsStream(XHTML_EXTENSION_SAMPLE_RESOURCE_NAME); XmlPullParser parser = PacketParserUtils.getParserFor(inputStream); - parser.next(); XHTMLExtensionProvider provider = new XHTMLExtensionProvider(); ExtensionElement extension = provider.parse(parser, parser.getDepth(), null); diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java index 3d3198315..500894f07 100644 --- a/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/Roster.java @@ -469,11 +469,14 @@ public final class Roster extends Manager { @Override public void processException(Exception exception) { rosterState = RosterState.uninitialized; - Level logLevel; + Level logLevel = Level.SEVERE; if (exception instanceof NotConnectedException) { logLevel = Level.FINE; - } else { - logLevel = Level.SEVERE; + } else if (exception instanceof XMPPErrorException) { + Condition condition = ((XMPPErrorException) exception).getStanzaError().getCondition(); + if (condition == Condition.feature_not_implemented || condition == Condition.service_unavailable) { + logLevel = Level.FINE; + } } LOGGER.log(logLevel, "Exception reloading roster", exception); for (RosterLoadedListener listener : rosterLoadedListeners) { diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java index 593d5b847..f3b083611 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.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. @@ -85,6 +85,7 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest * @param action the action to perform. * @throws Exception in case of an exception. */ + @SuppressWarnings("ThreadPriorityCheck") protected void performActionAndWaitForPresence(XMPPConnection conA, XMPPConnection conB, ThrowingRunnable action) throws Exception { final SimpleResultSyncPoint presenceReceivedSyncPoint = new SimpleResultSyncPoint(); @@ -109,5 +110,8 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest } finally { conA.removeAsyncStanzaListener(presenceListener); } + + // TODO: Ugly hack to make tests using this method more reliable. Ideally no test would use this method. + Thread.yield(); } } diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java index 8135e3deb..700c12eff 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.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. @@ -19,8 +19,11 @@ package org.igniterealtime.smack.inttest; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.lang.reflect.Method; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -33,6 +36,7 @@ import javax.net.ssl.SSLContext; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.debugger.ConsoleDebugger; +import org.jivesoftware.smack.util.CollectionUtil; import org.jivesoftware.smack.util.Function; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.ParserUtils; @@ -101,8 +105,12 @@ public final class Configuration { public final Set enabledTests; + private final Map> enabledTestsMap; + public final Set disabledTests; + private final Map> disabledTestsMap; + public final String defaultConnectionNickname; public final Set enabledConnections; @@ -117,6 +125,13 @@ public final class Configuration { public final DnsResolver dnsResolver; + public enum CompatibilityMode { + standardsCompliant, + ejabberd, + } + + public final CompatibilityMode compatibilityMode; + private Configuration(Configuration.Builder builder) throws KeyManagementException, NoSuchAlgorithmException { service = Objects.requireNonNull(builder.service, "'service' must be set. Either via 'properties' files or via system property 'sinttest.service'."); @@ -162,8 +177,10 @@ public final class Configuration { this.accountTwoPassword = builder.accountTwoPassword; this.accountThreeUsername = builder.accountThreeUsername; this.accountThreePassword = builder.accountThreePassword; - this.enabledTests = builder.enabledTests; - this.disabledTests = builder.disabledTests; + this.enabledTests = CollectionUtil.nullSafeUnmodifiableSet(builder.enabledTests); + this.enabledTestsMap = convertTestsToMap(enabledTests); + this.disabledTests = CollectionUtil.nullSafeUnmodifiableSet(builder.disabledTests); + this.disabledTestsMap = convertTestsToMap(disabledTests); this.defaultConnectionNickname = builder.defaultConnectionNickname; this.enabledConnections = builder.enabledConnections; this.disabledConnections = builder.disabledConnections; @@ -192,6 +209,7 @@ public final class Configuration { this.verbose = builder.verbose; this.dnsResolver = builder.dnsResolver; + this.compatibilityMode = builder.compatibilityMode; } public boolean isAccountRegistrationPossible() { @@ -246,6 +264,8 @@ public final class Configuration { private DnsResolver dnsResolver = DnsResolver.minidns; + private CompatibilityMode compatibilityMode = CompatibilityMode.standardsCompliant; + private Builder() { } @@ -427,6 +447,20 @@ public final class Configuration { return setDnsResolver(dnsResolver); } + public Builder setCompatibilityMode(CompatibilityMode compatibilityMode) { + this.compatibilityMode = compatibilityMode; + return this; + } + + public Builder setCompatibilityMode(String compatibilityModeString) { + if (compatibilityModeString == null) { + return this; + } + + CompatibilityMode compatibilityMode = CompatibilityMode.valueOf(compatibilityModeString); + return setCompatibilityMode(compatibilityMode); + } + public Configuration build() throws KeyManagementException, NoSuchAlgorithmException { return new Configuration(this); } @@ -500,6 +534,8 @@ public final class Configuration { builder.setDnsResolver(properties.getProperty("dnsResolver")); + builder.setCompatibilityMode(properties.getProperty("compatibilityMode")); + return builder.build(); } @@ -551,4 +587,112 @@ public final class Configuration { }); } + private static Map> convertTestsToMap(Set tests) { + Map> res = new HashMap<>(); + for (String test : tests) { + String[] testParts = test.split("\\."); + if (testParts.length == 1) { + // The whole test specification does not contain a dot, assume it is a test class specification. + res.put(test, Collections.emptySet()); + continue; + } + + String lastTestPart = testParts[testParts.length - 1]; + if (lastTestPart.isEmpty()) { + throw new IllegalArgumentException("Invalid test specifier: " + test); + } + + char firstCharOfLastTestPart = lastTestPart.charAt(0); + if (!Character.isLowerCase(firstCharOfLastTestPart)) { + // The first character of the last test part is not lowercase, assume this is a fully qualified test + // class specification, e.g. org.foo.bar.TestClass. + res.put(test, Collections.emptySet()); + } + + // The first character of the last test part is lowercase, assume this is a test class *and* method name + // specification. + String testMethodName = lastTestPart; + int classPartsCount = testParts.length - 1; + String[] classParts = new String[classPartsCount]; + System.arraycopy(testParts, 0, classParts, 0, classPartsCount); + String testClass = String.join(".", classParts); + + res.compute(testClass, (k, v) -> { + if (v == null) { + v = new HashSet<>(); + } + v.add(testMethodName); + return v; + }); + } + return res; + } + + private static Set getKey(Class testClass, Map> testsMap) { + String className = testClass.getName(); + if (testsMap.containsKey(className)) { + return testsMap.get(className); + } + + String unqualifiedClassName = testClass.getSimpleName(); + if (testsMap.containsKey(unqualifiedClassName)) { + return testsMap.get(unqualifiedClassName); + } + + return null; + } + + private static boolean contains(Class testClass, Map> testsMap) { + Set enabledMethods = getKey(testClass, testsMap); + return enabledMethods != null; + } + + public boolean isClassEnabled(Class testClass) { + if (enabledTestsMap.isEmpty()) { + return true; + } + + return contains(testClass, enabledTestsMap); + } + + public boolean isClassDisabled(Class testClass) { + if (disabledTestsMap.isEmpty()) { + return false; + } + + return contains(testClass, disabledTestsMap); + } + + private static boolean contains(Method method, Map> testsMap) { + Class testClass = method.getDeclaringClass(); + Set methods = getKey(testClass, testsMap); + + if (methods == null) { + return false; + } + + if (methods.isEmpty()) { + return true; + } + + String methodName = method.getName(); + return methods.contains(methodName); + } + + public boolean isMethodEnabled(Method method) { + if (enabledTestsMap.isEmpty()) { + return true; + } + + return contains(method, enabledTestsMap); + } + + public boolean isMethodDisabled(Method method) { + if (disabledTestsMap.isEmpty()) { + return false; + } + + return contains(method, disabledTestsMap); + } + } 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 769146c6f..433386600 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 @@ -282,13 +282,13 @@ public class SmackIntegrationTestFramework { continue; } - if (config.enabledTests != null && !isInSet(testClass, config.enabledTests)) { + if (!config.isClassEnabled(testClass)) { DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is not enabled"); testRunResult.disabledTestClasses.add(disabledTestClass); continue; } - if (isInSet(testClass, config.disabledTests)) { + if (config.isClassDisabled(testClass)) { DisabledTestClass disabledTestClass = new DisabledTestClass(testClass, "Skipping test class " + testClassName + " because it is disalbed"); testRunResult.disabledTestClasses.add(disabledTestClass); continue; @@ -377,14 +377,13 @@ public class SmackIntegrationTestFramework { while (it.hasNext()) { final Method method = it.next(); final String methodName = method.getName(); - if (config.enabledTests != null && !(config.enabledTests.contains(methodName) - || isInSet(testClass, config.enabledTests))) { + if (!config.isMethodEnabled(method)) { DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is not enabled"); testRunResult.disabledTests.add(disabledTest); it.remove(); continue; } - if (config.disabledTests != null && config.disabledTests.contains(methodName)) { + if (config.isMethodDisabled(method)) { DisabledTest disabledTest = new DisabledTest(method, "Skipping test method " + methodName + " because it is disabled"); testRunResult.disabledTests.add(disabledTest); it.remove(); @@ -607,15 +606,6 @@ public class SmackIntegrationTestFramework { return (Exception) e; } - private static boolean isInSet(Class clz, Set classes) { - if (classes == null) { - return false; - } - final String className = clz.getName(); - final String unqualifiedClassName = clz.getSimpleName(); - return classes.contains(className) || classes.contains(unqualifiedClassName); - } - public static final class TestRunResult { /** diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/IntegrationTestRosterUtil.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/IntegrationTestRosterUtil.java index c03ba1850..4a6569dcf 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/IntegrationTestRosterUtil.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/util/IntegrationTestRosterUtil.java @@ -24,6 +24,7 @@ import org.jivesoftware.smack.SmackException.NotLoggedInException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.StanzaError; import org.jivesoftware.smack.roster.AbstractPresenceEventListener; import org.jivesoftware.smack.roster.PresenceEventListener; import org.jivesoftware.smack.roster.Roster; @@ -99,7 +100,16 @@ public class IntegrationTestRosterUtil { if (c2Entry == null) { return; } - roster.removeEntry(c2Entry); + try { + roster.removeEntry(c2Entry); + } catch (XMPPErrorException e) { + // Account for race conditions: server-sided, the item might already have been removed. + if (e.getStanzaError().getCondition() == StanzaError.Condition.item_not_found) { + // Trying to remove non-existing item. As it needs to be gone, this is fine. + return; + } + throw e; + } } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java index 32f1d32b3..7e149ff3b 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/geolocation/GeolocationIntegrationTest.java @@ -23,8 +23,9 @@ import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotLoggedInException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; -import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.geoloc.GeoLocationManager; import org.jivesoftware.smackx.geoloc.packet.GeoLocation; import org.jivesoftware.smackx.pep.PepEventListener; @@ -35,7 +36,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass; import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil; import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; -import org.jxmpp.jid.EntityBareJid; +import org.junit.jupiter.api.Assertions; import org.jxmpp.util.XmppDateTime; public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest { @@ -49,10 +50,21 @@ public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest { glm2 = GeoLocationManager.getInstanceFor(conTwo); } + @AfterClass + public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); + } + + /** + * Verifies that a notification is sent when a publication is received, assuming that notification filtering + * has been adjusted to allow for the notification to be delivered. + * + * @throws Exception if the test fails + */ @SmackIntegrationTest - public void test() throws TimeoutException, Exception { + public void testNotification() throws Exception { GeoLocation.Builder builder = GeoLocation.builder(); - GeoLocation geoLocation1 = builder.setAccuracy(23d) + GeoLocation data = builder.setAccuracy(23d) .setAlt(1000d) .setAltAccuracy(10d) .setArea("Delhi") @@ -77,31 +89,163 @@ public class GeolocationIntegrationTest extends AbstractSmackIntegrationTest { .build(); IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); - final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint(); - final PepEventListener geoLocationListener = new PepEventListener() { - @Override - public void onPepEvent(EntityBareJid jid, GeoLocation geoLocation, String id, Message message) { - if (geoLocation.equals(geoLocation1)) { - geoLocationReceived.signal(); - } else { - geoLocationReceived.signalFailure("Received non matching GeoLocation"); - } + final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint(); + + final PepEventListener geoLocationListener = (jid, geoLocation, id, message) -> { + if (geoLocation.equals(data)) { + geoLocationReceived.signal(); } }; - glm2.addGeoLocationListener(geoLocationListener); - try { - glm1.publishGeoLocation(geoLocation1); - geoLocationReceived.waitForResult(timeout); + // Register ConTwo's interest in receiving geolocation notifications, and wait for that interest to have been propagated. + registerListenerAndWait(glm2, ServiceDiscoveryManager.getInstanceFor(conTwo), geoLocationListener); + + // Publish the data. + glm1.publishGeoLocation(data); // for the purpose of this test, this needs not be blocking/use publishAndWait(); + + // Wait for the data to be received. + try { + Object result = geoLocationReceived.waitForResult(timeout); + + // Explicitly assert the success case. + Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not."); + } catch (TimeoutException e) { + Assertions.fail("Expected to receive a PEP notification, but did not."); + } } finally { - glm2.removeGeoLocationListener(geoLocationListener); + unregisterListener(glm2, geoLocationListener); } } - @AfterClass - public void unsubscribe() throws NotLoggedInException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); + /** + * Verifies that a notification for a previously sent publication is received as soon as notification filtering + * has been adjusted to allow for the notification to be delivered. + * + * @throws Exception if the test fails + */ + @SmackIntegrationTest + public void testNotificationAfterFilterChange() throws Exception { + GeoLocation.Builder builder = GeoLocation.builder(); + GeoLocation data = builder.setAccuracy(12d) + .setAlt(999d) + .setAltAccuracy(9d) + .setArea("Amsterdam") + .setBearing(9d) + .setBuilding("Test Building") + .setCountry("Netherlands") + .setCountryCode("NL") + .setDescription("My Description") + .setFloor("middle") + .setLat(25.098345d) + .setLocality("brilliant") + .setLon(77.992034) + .setPostalcode("110085") + .setRegion("North") + .setRoom("small") + .setSpeed(250.0d) + .setStreet("Wall Street") + .setText("Unit Testing GeoLocation 2") + .setTimestamp(XmppDateTime.parseDate("2007-02-19")) + .setTzo("+5:30") + .setUri(new URI("http://xmpp.org")) + .build(); + + IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); + + final SimpleResultSyncPoint geoLocationReceived = new SimpleResultSyncPoint(); + + final PepEventListener geoLocationListener = (jid, geoLocation, id, message) -> { + if (geoLocation.equals(data)) { + geoLocationReceived.signal(); + } + }; + + // TODO Ensure that pre-existing filtering notification excludes geolocation. + try { + // Publish the data + publishAndWait(glm1, ServiceDiscoveryManager.getInstanceFor(conOne), data); + + // Adds listener, which implicitly publishes a disco/info filter for geolocation notification. + registerListenerAndWait(glm2, ServiceDiscoveryManager.getInstanceFor(conTwo), geoLocationListener); + + // Wait for the data to be received. + try { + Object result = geoLocationReceived.waitForResult(timeout); + + // Explicitly assert the success case. + Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not."); + } catch (TimeoutException e) { + Assertions.fail("Expected to receive a PEP notification, but did not."); + } + } finally { + unregisterListener(glm2, geoLocationListener); + } + } + + /** + * Registers a listener for GeoLocation data. This implicitly publishes a CAPS update to include a notification + * filter for the geolocation node. This method blocks until the server has indicated that this update has been + * received. + * + * @param geoManager The GeoLocationManager instance for the connection that is expected to receive data. + * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data. + * @param listener A listener instance for GeoLocation data that is to be registered. + * + * @throws Exception if the test fails + */ + public void registerListenerAndWait(GeoLocationManager geoManager, ServiceDiscoveryManager discoManager, PepEventListener listener) throws Exception { + final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint(); + final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> { + if (info.containsFeature(GeoLocationManager.GEOLOCATION_NODE + "+notify")) { + notificationFilterReceived.signal(); + } + }; + + discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener); + try { + geoManager.addGeoLocationListener(listener); + notificationFilterReceived.waitForResult(timeout); + } finally { + discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener); + } + } + + /** + * The functionally reverse of {@link #registerListenerAndWait(GeoLocationManager, ServiceDiscoveryManager, PepEventListener)} + * with the difference of not being a blocking operation. + * + * @param geoManager The GeoLocationManager instance for the connection that was expected to receive data. + * @param listener A listener instance for GeoLocation data that is to be removed. + */ + public void unregisterListener(GeoLocationManager geoManager, PepEventListener listener) { + // Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API. + geoManager.removeGeoLocationListener(listener); + } + + /** + * Publish data using PEP, and block until the server has echoed the publication back to the publishing user. + * + * @param geoManager The GeoLocationManager instance for the connection that is expected to publish data. + * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data. + * @param data The data to be published. + * + * @throws Exception if the test fails + */ + public void publishAndWait(GeoLocationManager geoManager, ServiceDiscoveryManager discoManager, GeoLocation data) throws Exception { + final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint(); + final PepEventListener publicationEchoListener = (jid, geoLocation, id, message) -> { + if (geoLocation.equals(data)) { + publicationEchoReceived.signal(); + } + }; + try { + registerListenerAndWait(geoManager, discoManager, publicationEchoListener); + geoManager.addGeoLocationListener(publicationEchoListener); + geoManager.publishGeoLocation(data); + } finally { + geoManager.removeGeoLocationListener(publicationEchoListener); + } } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java index d2eff1080..1a83085dc 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java @@ -16,9 +16,13 @@ */ package org.jivesoftware.smackx.mood; +import java.util.concurrent.TimeoutException; + import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.mood.element.MoodElement; import org.jivesoftware.smackx.pep.PepEventListener; @@ -28,6 +32,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass; import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil; import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.junit.jupiter.api.Assertions; public class MoodIntegrationTest extends AbstractSmackIntegrationTest { @@ -40,32 +45,155 @@ public class MoodIntegrationTest extends AbstractSmackIntegrationTest { mm2 = MoodManager.getInstanceFor(conTwo); } - @SmackIntegrationTest - public void test() throws Exception { - IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); - - final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint(); - - final PepEventListener moodListener = (jid, moodElement, id, message) -> { - if (moodElement.getMood() == Mood.satisfied) { - moodReceived.signal(); - } - }; - mm2.addMoodListener(moodListener); - - try { - mm1.setMood(Mood.satisfied); - - moodReceived.waitForResult(timeout); - } finally { - mm2.removeMoodListener(moodListener); - } - } - @AfterClass public void unsubscribe() throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); } + + /** + * Verifies that a notification is sent when a publication is received, assuming that notification filtering + * has been adjusted to allow for the notification to be delivered. + * + * @throws Exception if the test fails + */ + @SmackIntegrationTest + public void testNotification() throws Exception { + Mood data = Mood.satisfied; + + IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); + + final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint(); + + final PepEventListener moodListener = (jid, moodElement, id, message) -> { + if (moodElement.getMood().equals(data)) { + moodReceived.signal(); + } + }; + + try { + // Register ConTwo's interest in receiving mood notifications, and wait for that interest to have been propagated. + registerListenerAndWait(mm2, ServiceDiscoveryManager.getInstanceFor(conTwo), moodListener); + + // Publish the data. + mm1.setMood(data); // for the purpose of this test, this needs not be blocking/use publishAndWait(); + + // Wait for the data to be received. + try { + moodReceived.waitForResult(timeout); + } catch (TimeoutException e) { + Assertions.fail("Expected to receive a PEP notification, but did not."); + } + } finally { + unregisterListener(mm2, moodListener); + } + } + + /** + * Verifies that a notification for a previously sent publication is received as soon as notification filtering + * has been adjusted to allow for the notification to be delivered. + * + * @throws Exception if the test fails + */ + @SmackIntegrationTest + public void testNotificationAfterFilterChange() throws Exception { + Mood data = Mood.cautious; + + IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); + + final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint(); + + final PepEventListener moodListener = (jid, moodElement, id, message) -> { + if (moodElement.getMood().equals(data)) { + moodReceived.signal(); + } + }; + + // TODO Ensure that pre-existing filtering notification excludes mood. + try { + // Publish the data + publishAndWait(mm1, ServiceDiscoveryManager.getInstanceFor(conOne), data); + + // Adds listener, which implicitly publishes a disco/info filter for mood notification. + registerListenerAndWait(mm2, ServiceDiscoveryManager.getInstanceFor(conTwo), moodListener); + + // Wait for the data to be received. + try { + Object result = moodReceived.waitForResult(timeout); + + // Explicitly assert the success case. + Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not."); + } catch (TimeoutException e) { + Assertions.fail("Expected to receive a PEP notification, but did not."); + } + } finally { + unregisterListener(mm2, moodListener); + } + } + + /** + * Registers a listener for User Tune data. This implicitly publishes a CAPS update to include a notification + * filter for the mood node. This method blocks until the server has indicated that this update has been + * received. + * + * @param moodManager The MoodManager instance for the connection that is expected to receive data. + * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data. + * @param listener A listener instance for Mood data that is to be registered. + * + * @throws Exception if the test fails + */ + public void registerListenerAndWait(MoodManager moodManager, ServiceDiscoveryManager discoManager, PepEventListener listener) throws Exception { + final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint(); + final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> { + if (info.containsFeature(MoodManager.MOOD_NODE + "+notify")) { + notificationFilterReceived.signal(); + } + }; + + discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener); + try { + moodManager.addMoodListener(listener); + notificationFilterReceived.waitForResult(timeout); + } finally { + discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener); + } + } + + /** + * The functionally reverse of {@link #registerListenerAndWait(MoodManager, ServiceDiscoveryManager, PepEventListener)} + * with the difference of not being a blocking operation. + * + * @param moodManager The MoodManager instance for the connection that was expected to receive data. + * @param listener A listener instance for Mood data that is to be removed. + */ + public void unregisterListener(MoodManager moodManager, PepEventListener listener) { + // Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API. + moodManager.removeMoodListener(listener); + } + + /** + * Publish data using PEP, and block until the server has echoed the publication back to the publishing user. + * + * @param moodManager The MoodManager instance for the connection that is expected to publish data. + * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data. + * @param data The data to be published. + * + * @throws Exception if the test fails + */ + public void publishAndWait(MoodManager moodManager, ServiceDiscoveryManager discoManager, Mood data) throws Exception { + final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint(); + final PepEventListener publicationEchoListener = (jid, moodElement, id, message) -> { + if (moodElement.getMood().equals(data)) { + publicationEchoReceived.signal(); + } + }; + try { + registerListenerAndWait(moodManager, discoManager, publicationEchoListener); + moodManager.addMoodListener(publicationEchoListener); + moodManager.setMood(data); + } finally { + moodManager.removeMoodListener(publicationEchoListener); + } + } } 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 597b767e6..5b2879c63 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 @@ -22,8 +22,6 @@ 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; @@ -57,9 +55,8 @@ public class AbstractMultiUserChatIntegrationTest extends AbstractSmackIntegrati if (services.isEmpty()) { throw new TestNotPossibleException("No MUC (XEP-45) service found"); } - else { - mucService = services.get(0); - } + + mucService = services.get(0); } /** @@ -90,18 +87,57 @@ 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 { - MultiUserChat.MucCreateConfigFormHandle handle = muc.create(Resourcepart.from(resourceName)); - if (handle != null) { - handle.makeInstant(); - } + static void createMuc(MultiUserChat muc, Resourcepart resourceName) throws + SmackException.NoResponseException, XMPPException.XMPPErrorException, + InterruptedException, MultiUserChatException.MucAlreadyJoinedException, + SmackException.NotConnectedException, + MultiUserChatException.MissingMucCreationAcknowledgeException, + MultiUserChatException.NotAMucServiceException { + muc.create(resourceName).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); + static void createMuc(MultiUserChat muc, String nickname) throws + XmppStringprepException, MultiUserChatException.MucAlreadyJoinedException, + XMPPException.XMPPErrorException, SmackException.NotConnectedException, + MultiUserChatException.MissingMucCreationAcknowledgeException, + SmackException.NoResponseException, InterruptedException, + MultiUserChatException.NotAMucServiceException { + createMuc(muc, Resourcepart.from(nickname)); + } + + static void createMembersOnlyMuc(MultiUserChat muc, Resourcepart resourceName) throws + SmackException.NoResponseException, XMPPException.XMPPErrorException, + InterruptedException, MultiUserChatException.MucAlreadyJoinedException, + SmackException.NotConnectedException, + MultiUserChatException.MissingMucCreationAcknowledgeException, + MultiUserChatException.MucConfigurationNotSupportedException, + MultiUserChatException.NotAMucServiceException { + muc.create(resourceName) + .getConfigFormManager() + .makeMembersOnly() + .submitConfigurationForm(); + } + + static void createModeratedMuc(MultiUserChat muc, Resourcepart resourceName) + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, + MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, + MultiUserChatException.MissingMucCreationAcknowledgeException, + MultiUserChatException.NotAMucServiceException, + MultiUserChatException.MucConfigurationNotSupportedException { + muc.create(resourceName) + .getConfigFormManager() + .makeModerated() + .submitConfigurationForm(); + } + + static void createHiddenMuc(MultiUserChat muc, Resourcepart resourceName) + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, InterruptedException, + MultiUserChatException.MucAlreadyJoinedException, SmackException.NotConnectedException, + MultiUserChatException.MissingMucCreationAcknowledgeException, MultiUserChatException.NotAMucServiceException, XmppStringprepException, + MultiUserChatException.MucConfigurationNotSupportedException { + muc.create(resourceName) + .getConfigFormManager() + .makeHidden() + .submitConfigurationForm(); } } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java new file mode 100644 index 000000000..279160c0f --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/muc/MultiUserChatEntityIntegrationTest.java @@ -0,0 +1,205 @@ +/** + * + * Copyright 2021 Dan Caseley + * + * 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.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Map; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.StanzaError; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; +import org.jivesoftware.smackx.disco.packet.DiscoverItems; + +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.TestNotPossibleException; +import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.EntityFullJid; +import org.jxmpp.jid.parts.Resourcepart; + +public class MultiUserChatEntityIntegrationTest extends AbstractMultiUserChatIntegrationTest { + + public MultiUserChatEntityIntegrationTest(SmackIntegrationTestEnvironment environment) + throws SmackException.NoResponseException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, InterruptedException, TestNotPossibleException { + super(environment); + } + + /** + * Asserts that a MUC service can have its features discovered + * + *

From XEP-0045 § 6.2:

+ *
+ * An entity may wish to discover if a service implements the Multi-User Chat protocol; in order to do so, it + * sends a service discovery information ("disco#info") query to the MUC service's JID. The service MUST return + * its identity and the features it supports. + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForDiscoveringFeatures() throws Exception { + DiscoverInfo info = mucManagerOne.getMucServiceDiscoInfo(mucManagerOne.getMucServiceDomains().get(0)); + assertTrue(info.getIdentities().size() > 0); + assertTrue(info.getFeatures().size() > 0); + } + + /** + * Asserts that a MUC Service lists its public rooms. + * + *

From XEP-0045 § 6.3:

+ *
+ * The service discovery items ("disco#items") protocol enables an entity to query a service for a list of + * associated items, which in the case of a chat service would consist of the specific chat rooms hosted by the + * service. The service SHOULD return a full list of the public rooms it hosts (i.e., not return any rooms that + * are hidden). + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForDiscoveringRooms() throws Exception { + EntityBareJid mucAddressPublic = getRandomRoom("smack-inttest-publicroom"); + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddressPublic); + + EntityBareJid mucAddressHidden = getRandomRoom("smack-inttest-hiddenroom"); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddressHidden); + + createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString)); + + Map rooms; + try { + createHiddenMuc(mucAsSeenByTwo, Resourcepart.from("two-" + randomString)); + rooms = mucManagerThree.getRoomsHostedBy(mucService); + } finally { + tryDestroy(mucAsSeenByOne); + tryDestroy(mucAsSeenByTwo); + } + + assertTrue(rooms.containsKey(mucAddressPublic)); + assertFalse(rooms.containsKey(mucAddressHidden)); + } + + /** + * Asserts that a MUC Service returns disco info for a room. + * + *

From XEP-0045 § 6.4:

+ *
+ * Using the disco#info protocol, an entity may also query a specific chat room for more detailed information + * about the room....The room MUST return its identity and SHOULD return the features it supports + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForDiscoveringRoomInfo() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoinfo"); + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString)); + + DiscoverInfo discoInfo; + try { + // Use SDM because mucManagerOne.getRoomInfo(mucAddress) might not use Disco + discoInfo = ServiceDiscoveryManager.getInstanceFor(conOne).discoverInfo(mucAddress); + } finally { + tryDestroy(mucAsSeenByOne); + } + + assertTrue(discoInfo.getIdentities().size() > 0); + assertTrue(discoInfo.getFeatures().size() > 0); + } + + /** + * Asserts that a MUC Service returns disco info for a room's items. + * + *

From XEP-0045 § 6.5:

+ *
+ * An entity MAY also query a specific chat room for its associated items. An implementation MAY return a list + * of existing occupants if that information is publicly available, or return no list at all if this information is + * kept private. + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForDiscoveringRoomItems() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoitems"); + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + createMuc(mucAsSeenByOne, Resourcepart.from("one-" + randomString)); + + DiscoverItems roomItems; + try { + roomItems = ServiceDiscoveryManager.getInstanceFor(conTwo).discoverItems(mucAddress); + } finally { + tryDestroy(mucAsSeenByOne); + } + + assertEquals(1, roomItems.getItems().size()); + } + + /** + * Asserts that a non-occupant receives a Bad Request error when attempting to query an occupant by their + * occupant JID. + * + *

From XEP-0045 § 6.6:

+ *
+ * If a non-occupant attempts to send a disco request to an address of the form <room@service/nick>, a MUC service + * MUST return a <bad-request/> error + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestForRejectingDiscoOnRoomOccupantByNonOccupant() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-discoitems"); + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); + createMuc(mucAsSeenByOne, nicknameOne); + final EntityFullJid mucAsSeenByOneUserJid = mucAsSeenByOne.getMyRoomJid(); + // Ensure that we do not invoke discoverItems() with null below. This should not happen, as the room JID should + // be non-null after we created and joined the room. But it can not hurt to explicitly test for it either. + if (mucAsSeenByOneUserJid == null) { + throw new AssertionError(); + } + + XMPPException.XMPPErrorException xe; + try { + xe = assertThrows(XMPPException.XMPPErrorException.class, + () -> ServiceDiscoveryManager.getInstanceFor(conTwo).discoverItems(mucAsSeenByOneUserJid)); + } finally { + tryDestroy(mucAsSeenByOne); + } + + final StanzaError.Condition expectedCondition; + switch (sinttestConfiguration.compatibilityMode) { + default: + expectedCondition = StanzaError.Condition.bad_request; + break; + case ejabberd: + expectedCondition = StanzaError.Condition.not_acceptable; + break; + } + assertEquals(xe.getStanzaError().getCondition(), expectedCondition); + } +} 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 966ec3e97..d26e22593 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 @@ -1,6 +1,6 @@ /** * - * Copyright 2021 Florian Schmaus + * Copyright 2021 Florian Schmaus, Dan Caseley * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.jivesoftware.smack.SmackException; @@ -35,6 +36,7 @@ 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.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; @@ -80,13 +82,14 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); 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 { + 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); + resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -128,13 +131,13 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); 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 { + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + mucAsSeenByOne.grantModerator(nicknameTwo); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -176,12 +179,12 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); createMuc(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - - mucAsSeenByOne.grantModerator(nicknameTwo); - mucAsSeenByOne.revokeModerator(nicknameTwo); try { + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + + mucAsSeenByOne.grantModerator(nicknameTwo); + mucAsSeenByOne.revokeModerator(nicknameTwo); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -223,14 +226,14 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); 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 { + 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); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -271,10 +274,10 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); createMuc(mucAsSeenByOne, "one-" + randomString); - final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); - mucAsSeenByTwo.join(nicknameTwo); - mucAsSeenByOne.revokeVoice(nicknameTwo); try { + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByOne.revokeVoice(nicknameTwo); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -316,13 +319,13 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); 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 { + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + mucAsSeenByOne.revokeVoice(nicknameTwo); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -364,12 +367,12 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); 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 { + 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()); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -412,13 +415,13 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); 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 { + 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()); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -459,13 +462,12 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); 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 { + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + mucAsSeenByTwo.join(nicknameTwo); + + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + mucAsSeenByOne.revokeAdmin(conTwo.getUser().asEntityBareJid()); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -507,14 +509,14 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs }); 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 { + 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()); resultSyncPoint.waitForResult(timeout); } finally { tryDestroy(mucAsSeenByOne); @@ -544,14 +546,14 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs 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 { + 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."); Presence kickPresence = resultSyncPoint.waitForResult(timeout); MUCUser mucUser = MUCUser.from(kickPresence); assertNotNull(mucUser); @@ -591,16 +593,16 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs 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 { + 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."); Presence kickPresence = resultSyncPoint.waitForResult(timeout); MUCUser mucUser = MUCUser.from(kickPresence); assertNotNull(mucUser); @@ -619,4 +621,299 @@ public class MultiUserChatRolesAffiliationsPrivilegesIntegrationTest extends Abs } + /** + * Asserts that an affiliation is persistent between visits to the room. + * + *

From XEP-0045 § 5.2:

+ *
+ * These affiliations are long-lived in that they persist across a user's visits to the room and are not affected + * by happenings in the room...Affiliations are granted, revoked, and maintained based on the user's bare JID, not + * the nick as with roles. + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestPersistentAffiliation() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + + createMuc(mucAsSeenByOne, nicknameOne); + try { + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + mucAsSeenByOne.grantOwnership(conTwo.getUser().asBareJid()); + mucAsSeenByOne.grantAdmin(conThree.getUser().asBareJid()); + + mucAsSeenByTwo.leave(); + mucAsSeenByThree.leave(); + Presence p2 = mucAsSeenByTwo.join(nicknameTwo); + Presence p3 = mucAsSeenByThree.join(nicknameThree); + assertEquals(MUCAffiliation.owner, MUCUser.from(p2).getItem().getAffiliation()); + assertEquals(MUCAffiliation.admin, MUCUser.from(p3).getItem().getAffiliation()); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a moderator cannot revoke voice from an owner + * + *

From XEP-0045 § 5.1.1:

+ *
+ * A moderator MUST NOT be able to revoke voice privileges from an admin or owner + *
+ * + *

From XEP-0045 § 8.4:

+ *
+ * A moderator MUST NOT be able to revoke voice from a user whose affiliation is at or above the moderator's level. + * In addition, a service MUST NOT allow the voice privileges of an admin or owner to be removed by anyone. If a + * moderator attempts to revoke voice privileges from such a user, the service MUST deny the request and return a + * <not-allowed/> error to the sender along with the offending item(s) + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestModeratorCannotRevokeVoiceFromOwner() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + + final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + + createModeratedMuc(mucAsSeenByOne, nicknameOne); + try { + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByOne.grantModerator(nicknameTwo); + XMPPException.XMPPErrorException xe = assertThrows(XMPPException.XMPPErrorException.class, + () -> mucAsSeenByTwo.revokeVoice(nicknameOne)); + assertEquals(xe.getStanzaError().getCondition().toString(), "not-allowed"); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a moderator cannot revoke moderator privileges from a moderator with a higher affiliation + * than themselves. + * + *

From XEP-0045 § 5.1.3 and §5.2.1:

+ *
+ * A moderator SHOULD NOT be allowed to revoke moderation privileges from someone with a higher affiliation than + * themselves (i.e., an unaffiliated moderator SHOULD NOT be allowed to revoke moderation privileges from an admin + * or an owner, and an admin SHOULD NOT be allowed to revoke moderation privileges from an owner) + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestModeratorCannotBeRevokedFromHigherAffiliation() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + + createModeratedMuc(mucAsSeenByOne, nicknameOne); + try { + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + mucAsSeenByOne.grantModerator(nicknameThree); + + // Admin cannot revoke from Owner + XMPPException.XMPPErrorException xe1 = assertThrows(XMPPException.XMPPErrorException.class, + () -> mucAsSeenByTwo.revokeModerator(nicknameOne)); + // Moderator cannot revoke from Admin + XMPPException.XMPPErrorException xe2 = assertThrows(XMPPException.XMPPErrorException.class, + () -> mucAsSeenByThree.revokeModerator(nicknameOne)); + // Moderator cannot revoke from Owner + XMPPException.XMPPErrorException xe3 = assertThrows(XMPPException.XMPPErrorException.class, + () -> mucAsSeenByThree.revokeModerator(nicknameTwo)); + assertEquals(xe1.getStanzaError().getCondition().toString(), "not-allowed"); + assertEquals(xe2.getStanzaError().getCondition().toString(), "not-allowed"); + assertEquals(xe3.getStanzaError().getCondition().toString(), "not-allowed"); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that an unmoderated room assigns the correct default roles for a given affiliation + * + *

From XEP-0045 § 5.1.2:

+ *
+ * ...the initial default roles that a service SHOULD set based on the user's affiliation... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestDefaultRoleForAffiliationInUnmoderatedRoom() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-unmoderatedroles"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + + createMuc(mucAsSeenByOne, nicknameOne); + try { + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + mucAsSeenByOne.addParticipantStatusListener(new ParticipantStatusListener() { + @Override + public void adminGranted(EntityFullJid participant) { + resultSyncPoint.signal("done"); + } + }); + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + resultSyncPoint.waitForResult(timeout); + + assertEquals(mucAsSeenByOne.getOccupantsCount(), 3); + assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant( + JidCreate.entityFullFrom(mucAddress, nicknameOne)).getRole()); + assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant( + JidCreate.entityFullFrom(mucAddress, nicknameTwo)).getRole()); + assertEquals(MUCRole.participant, mucAsSeenByOne.getOccupant( + JidCreate.entityFullFrom(mucAddress, nicknameThree)).getRole()); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a moderated room assigns the correct default roles for a given affiliation + * + *

From XEP-0045 § 5.1.2:

+ *
+ * ...the initial default roles that a service SHOULD set based on the user's affiliation... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestDefaultRoleForAffiliationInModeratedRoom() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-moderatedroles"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + + final ResultSyncPoint resultSyncPoint = new ResultSyncPoint<>(); + mucAsSeenByOne.addParticipantStatusListener(new ParticipantStatusListener() { + @Override + public void adminGranted(EntityFullJid participant) { + resultSyncPoint.signal("done"); + } + }); + + createModeratedMuc(mucAsSeenByOne, nicknameOne); + + final MUCRole threeRole; + switch (sinttestConfiguration.compatibilityMode) { + default: + threeRole = MUCRole.visitor; + break; + case ejabberd: + threeRole = MUCRole.participant; + break; + } + + try { + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + resultSyncPoint.waitForResult(timeout); + + assertEquals(mucAsSeenByOne.getOccupantsCount(), 3); + assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant( + JidCreate.entityFullFrom(mucAddress, nicknameOne)).getRole()); + assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant( + JidCreate.entityFullFrom(mucAddress, nicknameTwo)).getRole()); + assertEquals(threeRole, mucAsSeenByOne.getOccupant( + JidCreate.entityFullFrom(mucAddress, nicknameThree)).getRole()); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + + /** + * Asserts that a members-only room assigns the correct default roles for a given affiliation + * + *

From XEP-0045 § 5.1.2:

+ *
+ * ...the initial default roles that a service SHOULD set based on the user's affiliation... + *
+ * + * @throws Exception when errors occur + */ + @SmackIntegrationTest + public void mucTestDefaultRoleForAffiliationInMembersOnlyRoom() throws Exception { + EntityBareJid mucAddress = getRandomRoom("smack-inttest-membersonlyroles"); + + MultiUserChat mucAsSeenByOne = mucManagerOne.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByTwo = mucManagerTwo.getMultiUserChat(mucAddress); + MultiUserChat mucAsSeenByThree = mucManagerThree.getMultiUserChat(mucAddress); + + final Resourcepart nicknameOne = Resourcepart.from("one-" + randomString); + final Resourcepart nicknameTwo = Resourcepart.from("two-" + randomString); + final Resourcepart nicknameThree = Resourcepart.from("three-" + randomString); + + final EntityFullJid jidOne = JidCreate.entityFullFrom(mucAddress, nicknameOne); + final EntityFullJid jidTwo = JidCreate.entityFullFrom(mucAddress, nicknameTwo); + final EntityFullJid jidThree = JidCreate.entityFullFrom(mucAddress, nicknameThree); + + createMembersOnlyMuc(mucAsSeenByOne, nicknameOne); + + final ResultSyncPoint adminResultSyncPoint = new ResultSyncPoint<>(); + mucAsSeenByOne.addParticipantStatusListener(new ParticipantStatusListener() { + @Override + public void adminGranted(EntityFullJid participant) { + adminResultSyncPoint.signal("done"); + } + }); + + try { + mucAsSeenByOne.grantMembership(conTwo.getUser().asBareJid()); + mucAsSeenByOne.grantMembership(conThree.getUser().asBareJid()); + + mucAsSeenByTwo.join(nicknameTwo); + mucAsSeenByThree.join(nicknameThree); + mucAsSeenByOne.grantAdmin(conTwo.getUser().asBareJid()); + adminResultSyncPoint.waitForResult(timeout); + assertEquals(mucAsSeenByOne.getOccupantsCount(), 3); + assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant(jidOne).getRole()); + assertEquals(MUCRole.moderator, mucAsSeenByOne.getOccupant(jidTwo).getRole()); + assertEquals(MUCRole.participant, mucAsSeenByOne.getOccupant(jidThree).getRole()); + } finally { + tryDestroy(mucAsSeenByOne); + } + } + } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/softwareInfo/SoftwareInfoIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/softwareInfo/SoftwareInfoIntegrationTest.java index f289c96be..3bf111f53 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/softwareInfo/SoftwareInfoIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/softwareInfo/SoftwareInfoIntegrationTest.java @@ -73,10 +73,10 @@ public class SoftwareInfoIntegrationTest extends AbstractSmackIntegrationTest { .build(); SoftwareInfoForm softwareInfoForm = builder.setIcon(mediaElement) - .setOS("Windows") - .setOSVersion("XP") - .setSoftware("Exodus") - .setSoftwareVersion("0.9.1") + .setOS("Linux") + .setOSVersion("Debian") + .setSoftware("Gajim") + .setSoftwareVersion("1.4.0") .build(); return softwareInfoForm; } diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java index 0309f9af0..653bded72 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/usertune/UserTuneIntegrationTest.java @@ -17,12 +17,14 @@ package org.jivesoftware.smackx.usertune; import java.net.URI; +import java.util.concurrent.TimeoutException; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException.NotLoggedInException; import org.jivesoftware.smack.XMPPException; -import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.disco.EntityCapabilitiesChangedListener; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.pep.PepEventListener; import org.jivesoftware.smackx.usertune.element.UserTuneElement; @@ -32,7 +34,7 @@ import org.igniterealtime.smack.inttest.annotations.AfterClass; import org.igniterealtime.smack.inttest.annotations.SmackIntegrationTest; import org.igniterealtime.smack.inttest.util.IntegrationTestRosterUtil; import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; -import org.jxmpp.jid.EntityBareJid; +import org.junit.jupiter.api.Assertions; public class UserTuneIntegrationTest extends AbstractSmackIntegrationTest { @@ -45,46 +47,172 @@ public class UserTuneIntegrationTest extends AbstractSmackIntegrationTest { utm2 = UserTuneManager.getInstanceFor(conTwo); } - @SmackIntegrationTest - public void test() throws Exception { - URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#9"); - UserTuneElement.Builder builder = UserTuneElement.getBuilder(); - UserTuneElement userTuneElement1 = builder.setArtist("Yes") - .setLength(686) - .setRating(8) - .setSource("Yessongs") - .setTitle("Heart of the Sunrise") - .setTrack("3") - .setUri(uri) - .build(); - - IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); - - final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint(); - - final PepEventListener userTuneListener = new PepEventListener() { - @Override - public void onPepEvent(EntityBareJid jid, UserTuneElement userTuneElement, String id, Message message) { - if (userTuneElement.equals(userTuneElement1)) { - userTuneReceived.signal(); - } - } - }; - - utm2.addUserTuneListener(userTuneListener); - - try { - utm1.publishUserTune(userTuneElement1); - userTuneReceived.waitForResult(timeout); - } finally { - utm2.removeUserTuneListener(userTuneListener); - } - } - @AfterClass public void unsubscribe() throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { IntegrationTestRosterUtil.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); } + + /** + * Verifies that a notification is sent when a publication is received, assuming that notification filtering + * has been adjusted to allow for the notification to be delivered. + * + * @throws Exception if the test fails + */ + @SmackIntegrationTest + public void testNotification() throws Exception { + URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#9"); + UserTuneElement.Builder builder = UserTuneElement.getBuilder(); + UserTuneElement data = builder.setArtist("Yes") + .setLength(686) + .setRating(8) + .setSource("Yessongs") + .setTitle("Heart of the Sunrise") + .setTrack("3") + .setUri(uri) + .build(); + + IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); + + final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint(); + + final PepEventListener userTuneListener = (jid, userTune, id, message) -> { + if (userTune.equals(data)) { + userTuneReceived.signal(); + } + }; + + try { + // Register ConTwo's interest in receiving user tune notifications, and wait for that interest to have been propagated. + registerListenerAndWait(utm2, ServiceDiscoveryManager.getInstanceFor(conTwo), userTuneListener); + + // Publish the data. + utm1.publishUserTune(data); // for the purpose of this test, this needs not be blocking/use publishAndWait(); + + // Wait for the data to be received. + Object result = userTuneReceived.waitForResult(timeout); + + // Explicitly assert the success case. + Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not."); + } finally { + unregisterListener(utm2, userTuneListener); + } + } + + /** + * Verifies that a notification for a previously sent publication is received as soon as notification filtering + * has been adjusted to allow for the notification to be delivered. + * + * @throws Exception if the test fails + */ + @SmackIntegrationTest + public void testNotificationAfterFilterChange() throws Exception { + URI uri = new URI("http://www.yesworld.com/lyrics/Fragile.html#8"); + UserTuneElement.Builder builder = UserTuneElement.getBuilder(); + UserTuneElement data = builder.setArtist("No") + .setLength(306) + .setRating(3) + .setSource("NoSongs") + .setTitle("Sunrise of the Heart") + .setTrack("2") + .setUri(uri) + .build(); + + IntegrationTestRosterUtil.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); + + final SimpleResultSyncPoint userTuneReceived = new SimpleResultSyncPoint(); + + final PepEventListener userTuneListener = (jid, userTune, id, message) -> { + if (userTune.equals(data)) { + userTuneReceived.signal(); + } + }; + + // TODO Ensure that pre-existing filtering notification excludes userTune. + try { + // Publish the data + publishAndWait(utm1, ServiceDiscoveryManager.getInstanceFor(conOne), data); + + // Adds listener, which implicitly publishes a disco/info filter for userTune notification. + registerListenerAndWait(utm2, ServiceDiscoveryManager.getInstanceFor(conTwo), userTuneListener); + + // Wait for the data to be received. + try { + Object result = userTuneReceived.waitForResult(timeout); + + // Explicitly assert the success case. + Assertions.assertNotNull(result, "Expected to receive a PEP notification, but did not."); + } catch (TimeoutException e) { + Assertions.fail("Expected to receive a PEP notification, but did not."); + } + } finally { + unregisterListener(utm2, userTuneListener); + } + } + + /** + * Registers a listener for User Tune data. This implicitly publishes a CAPS update to include a notification + * filter for the usertune node. This method blocks until the server has indicated that this update has been + * received. + * + * @param userTuneManager The UserTuneManager instance for the connection that is expected to receive data. + * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data. + * @param listener A listener instance for UserTune data that is to be registered. + * + * @throws Exception if the test fails + */ + public void registerListenerAndWait(UserTuneManager userTuneManager, ServiceDiscoveryManager discoManager, PepEventListener listener) throws Exception { + final SimpleResultSyncPoint notificationFilterReceived = new SimpleResultSyncPoint(); + final EntityCapabilitiesChangedListener notificationFilterReceivedListener = info -> { + if (info.containsFeature(UserTuneManager.USERTUNE_NODE + "+notify")) { + notificationFilterReceived.signal(); + } + }; + + discoManager.addEntityCapabilitiesChangedListener(notificationFilterReceivedListener); + try { + userTuneManager.addUserTuneListener(listener); + notificationFilterReceived.waitForResult(timeout); + } finally { + discoManager.removeEntityCapabilitiesChangedListener(notificationFilterReceivedListener); + } + } + + /** + * The functionally reverse of {@link #registerListenerAndWait(UserTuneManager, ServiceDiscoveryManager, PepEventListener)} + * with the difference of not being a blocking operation. + * + * @param userTuneManager The UserTuneManager instance for the connection that was expected to receive data. + * @param listener A listener instance for UserTune data that is to be removed. + */ + public void unregisterListener(UserTuneManager userTuneManager, PepEventListener listener) { + // Does it make sense to have a method implementation that's one line? This is provided to allow for symmetry in the API. + userTuneManager.removeUserTuneListener(listener); + } + + /** + * Publish data using PEP, and block until the server has echoed the publication back to the publishing user. + * + * @param userTuneManager The UserTuneManager instance for the connection that is expected to publish data. + * @param discoManager The ServiceDiscoveryManager instance for the connection that is expected to publish data. + * @param data The data to be published. + * + * @throws Exception if the test fails + */ + public void publishAndWait(UserTuneManager userTuneManager, ServiceDiscoveryManager discoManager, UserTuneElement data) throws Exception { + final SimpleResultSyncPoint publicationEchoReceived = new SimpleResultSyncPoint(); + final PepEventListener publicationEchoListener = (jid, userTune, id, message) -> { + if (userTune.equals(data)) { + publicationEchoReceived.signal(); + } + }; + try { + registerListenerAndWait(userTuneManager, discoManager, publicationEchoListener); + userTuneManager.addUserTuneListener(publicationEchoListener); + userTuneManager.publishUserTune(data); + } finally { + userTuneManager.removeUserTuneListener(publicationEchoListener); + } + } } diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java index 65e004de2..712c4fb8c 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java @@ -118,7 +118,7 @@ public class RoomInvitation implements ExtensionElement { @Override public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this)); + XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this, enclosingNamespace)); xml.closeElement(this); return xml; } diff --git a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java index 2d6d95c58..726af6f65 100644 --- a/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java +++ b/smack-legacy/src/main/java/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java @@ -113,7 +113,7 @@ public class RoomTransfer implements ExtensionElement { @Override public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) { - XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this)); + XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this, enclosingNamespace)); xml.closeElement(this); return xml; } diff --git a/smack-openpgp/build.gradle b/smack-openpgp/build.gradle index 08d104afa..81c6accbd 100644 --- a/smack-openpgp/build.gradle +++ b/smack-openpgp/build.gradle @@ -8,7 +8,7 @@ dependencies { api project(':smack-extensions') api project(':smack-experimental') - api 'org.pgpainless:pgpainless-core:0.2.0' + api 'org.pgpainless:pgpainless-core:1.0.0-rc6' testImplementation "org.bouncycastle:bcprov-jdk15on:${bouncyCastleVersion}" diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java index e8b99e3ef..2fa3bbe77 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/crypto/PainlessOpenPgpProvider.java @@ -29,7 +29,6 @@ import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.stringencoder.Base64; - import org.jivesoftware.smackx.ox.OpenPgpContact; import org.jivesoftware.smackx.ox.OpenPgpMessage; import org.jivesoftware.smackx.ox.OpenPgpSelf; @@ -47,6 +46,7 @@ import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.util.io.Streams; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.DocumentSignatureType; +import org.pgpainless.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.DecryptionStream; import org.pgpainless.decryption_verification.MissingPublicKeyCallback; import org.pgpainless.decryption_verification.OpenPgpMetadata; @@ -89,7 +89,7 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { SigningOptions signOpts = new SigningOptions(); signOpts.addInlineSignature(getStore().getKeyRingProtector(), self.getSigningKeyRing(), - "xmpp:" + self.getJid().toString(), DocumentSignatureType.BINARY_DOCUMENT); + DocumentSignatureType.BINARY_DOCUMENT); EncryptionStream cipherStream = PGPainless.encryptAndOrSign() .onOutputStream(cipherText) @@ -209,10 +209,10 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { DecryptionStream cipherStream = PGPainless.decryptAndOrVerify() .onInputStream(cipherText) - .decryptWith(getStore().getKeyRingProtector(), self.getSecretKeys()) - .verifyWith(announcedPublicKeys) - .handleMissingPublicKeysWith(missingPublicKeyCallback) - .build(); + .withOptions(new ConsumerOptions() + .addDecryptionKeys(self.getSecretKeys(), getStore().getKeyRingProtector()) + .addVerificationCerts(announcedPublicKeys) + .setMissingCertificateCallback(missingPublicKeyCallback)); Streams.pipeAll(cipherStream, plainText); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java index 3c8483dfe..b1ad533fb 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java @@ -37,6 +37,7 @@ import org.bouncycastle.util.io.Streams; import org.jxmpp.jid.BareJid; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.DecryptionStream; import org.pgpainless.encryption_signing.EncryptionOptions; import org.pgpainless.encryption_signing.EncryptionStream; @@ -153,9 +154,8 @@ public class SecretKeyBackupHelper { try { DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() .onInputStream(encryptedIn) - .decryptWith(Passphrase.fromPassword(backupCode.toString())) - .doNotVerify() - .build(); + .withOptions(new ConsumerOptions() + .addDecryptionPassphrase(Passphrase.fromPassword(backupCode.toString()))); Streams.pipeAll(decryptionStream, plaintextOut); decryptionStream.close(); diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PainlessOpenPgpProviderTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PainlessOpenPgpProviderTest.java index f6b78493a..5f1210367 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PainlessOpenPgpProviderTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PainlessOpenPgpProviderTest.java @@ -47,29 +47,27 @@ import org.jivesoftware.smackx.ox.store.filebased.FileBasedOpenPgpStore; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.junit.AfterClass; -import org.junit.BeforeClass; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.JidTestUtil; +import org.pgpainless.key.OpenPgpFingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.util.KeyRingUtils; public class PainlessOpenPgpProviderTest extends SmackTestSuite { - private static final File storagePath; + private static File storagePath; private static final BareJid alice = JidTestUtil.BARE_JID_1; private static final BareJid bob = JidTestUtil.BARE_JID_2; - static { + @BeforeEach + @AfterEach + public void deletePath() throws IOException { storagePath = new File(org.apache.commons.io.FileUtils.getTempDirectory(), "smack-painlessprovidertest"); - } - - @BeforeClass - @AfterClass - public static void deletePath() throws IOException { org.apache.commons.io.FileUtils.deleteDirectory(storagePath); } @@ -142,7 +140,7 @@ public class PainlessOpenPgpProviderTest extends SmackTestSuite { // Decrypt and Verify decrypted = bobProvider.decryptAndOrVerify(bobConnection, encrypted.getElement(), bobSelf, aliceForBob); - OpenPgpV4Fingerprint decryptionFingerprint = decrypted.getMetadata().getDecryptionFingerprint(); + OpenPgpFingerprint decryptionFingerprint = decrypted.getMetadata().getDecryptionKey().getFingerprint(); assertTrue(bobSelf.getSecretKeys().contains(decryptionFingerprint.getKeyId())); assertTrue(decrypted.getMetadata().containsVerifiedSignatureFrom(alicePubKeys)); @@ -162,9 +160,9 @@ public class PainlessOpenPgpProviderTest extends SmackTestSuite { decrypted = bobProvider.decryptAndOrVerify(bobConnection, encrypted.getElement(), bobSelf, aliceForBob); - decryptionFingerprint = decrypted.getMetadata().getDecryptionFingerprint(); + decryptionFingerprint = decrypted.getMetadata().getDecryptionKey().getFingerprint(); assertTrue(bobSelf.getSecretKeys().contains(decryptionFingerprint.getKeyId())); - assertTrue(decrypted.getMetadata().getVerifiedSignatureKeyFingerprints().isEmpty()); + assertTrue(decrypted.getMetadata().getVerifiedSignatures().isEmpty()); assertEquals(OpenPgpMessage.State.crypt, decrypted.getState()); CryptElement decryptedCrypt = (CryptElement) decrypted.getOpenPgpContentElement(); @@ -182,7 +180,7 @@ public class PainlessOpenPgpProviderTest extends SmackTestSuite { decrypted = bobProvider.decryptAndOrVerify(bobConnection, encrypted.getElement(), bobSelf, aliceForBob); - assertNull(decrypted.getMetadata().getDecryptionFingerprint()); + assertNull(decrypted.getMetadata().getDecryptionKey()); assertTrue(decrypted.getMetadata().containsVerifiedSignatureFrom(alicePubKeys)); assertEquals(OpenPgpMessage.State.sign, decrypted.getState()); 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 13363cd58..7854aedce 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 @@ -155,7 +155,7 @@ public class OXInstantMessagingManagerTest extends SmackTestSuite { assertTrue(metadata.isSigned() && metadata.isEncrypted()); // Check, if one of Bobs keys was used for decryption - assertNotNull(bobSelf.getSigningKeyRing().getPublicKey(metadata.getDecryptionFingerprint().getKeyId())); + assertNotNull(bobSelf.getSigningKeyRing().getPublicKey(metadata.getDecryptionKey().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 diff --git a/smack-openpgp/src/test/resources/logback-test.xml b/smack-openpgp/src/test/resources/logback-test.xml new file mode 100644 index 000000000..c77b1b2f7 --- /dev/null +++ b/smack-openpgp/src/test/resources/logback-test.xml @@ -0,0 +1,21 @@ + + + + System.out + + %blue(%-5level) %green(%logger{35}) - %msg %n + + + + + System.err + + %blue(%-5level) %green(%logger{35}) - %msg %n + + + + + + + + \ No newline at end of file diff --git a/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java index 210e94ab9..bdef7d673 100644 --- a/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java +++ b/smack-sasl-javax/src/main/java/org/jivesoftware/smack/sasl/javax/SASLGSSAPIMechanism.java @@ -66,4 +66,8 @@ public class SASLGSSAPIMechanism extends SASLJavaXMechanism { return new SASLGSSAPIMechanism(); } + @Override + public boolean requiresPassword() { + return false; + } } diff --git a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java index c0549895a..1403a78df 100644 --- a/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java +++ b/smack-tcp/src/main/java/org/jivesoftware/smack/tcp/XMPPTCPConnection.java @@ -48,6 +48,7 @@ import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import org.jivesoftware.smack.AbstractXMPPConnection; import org.jivesoftware.smack.ConnectionConfiguration; @@ -712,9 +713,11 @@ public class XMPPTCPConnection extends AbstractXMPPConnection { SmackTlsContext smackTlsContext = getSmackTlsContext(); Socket plain = socket; + int port = plain.getPort(); + String xmppServiceDomainString = config.getXMPPServiceDomain().toString(); + SSLSocketFactory sslSocketFactory = smackTlsContext.sslContext.getSocketFactory(); // Secure the plain connection - socket = smackTlsContext.sslContext.getSocketFactory().createSocket(plain, - config.getXMPPServiceDomain().toString(), plain.getPort(), true); + socket = sslSocketFactory.createSocket(plain, xmppServiceDomainString, port, true); final SSLSocket sslSocket = (SSLSocket) socket; // Immediately set the enabled SSL protocols and ciphers. See SMACK-712 why this is diff --git a/smack-xmlparser-stax/build.gradle b/smack-xmlparser-stax/build.gradle index 66cf94388..b25ce2e9a 100644 --- a/smack-xmlparser-stax/build.gradle +++ b/smack-xmlparser-stax/build.gradle @@ -1,3 +1,8 @@ +// Note that this is also declared in the main build.gradle for +// subprojects, but since evaluationDependsOnChildren is enabled we +// need to declare it here too to have bundle{bnd{...}} available +apply plugin: 'biz.aQute.bnd.builder' + description = """\ Smack XML parser using Stax.""" @@ -5,3 +10,13 @@ dependencies { api project(':smack-xmlparser') //testCompile project(path: ":smack-xmlparser", configuration: "testRuntime") } + +jar { + bundle { + bnd( + // see http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.loader.html + 'Require-Capability': 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)"', + 'Provide-Capability': "osgi.serviceloader;osgi.serviceloader=org.jivesoftware.smack.xml.XmlPullParserFactory;register:=org.jivesoftware.smack.xml.stax.StaxXmlPullParserFactory", + ) + } +} diff --git a/smack-xmlparser-xpp3/build.gradle b/smack-xmlparser-xpp3/build.gradle index a0afd7c4a..f0a9f56c6 100644 --- a/smack-xmlparser-xpp3/build.gradle +++ b/smack-xmlparser-xpp3/build.gradle @@ -1,3 +1,8 @@ +// Note that this is also declared in the main build.gradle for +// subprojects, but since evaluationDependsOnChildren is enabled we +// need to declare it here too to have bundle{bnd{...}} available +apply plugin: 'biz.aQute.bnd.builder' + description = """\ Smack XML parser using XPP3.""" @@ -11,3 +16,13 @@ dependencies { api project(':smack-xmlparser') //testCompile project(path: ":smack-xmlparser", configuration: "testRuntime") } + +jar { + bundle { + bnd( + // see http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.loader.html + 'Require-Capability': 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)"', + 'Provide-Capability': "osgi.serviceloader;osgi.serviceloader=org.jivesoftware.smack.xml.XmlPullParserFactory;register:=org.jivesoftware.smack.xml.xpp3.Xpp3XmlPullParserFactory", + ) + } +} diff --git a/smack-xmlparser/build.gradle b/smack-xmlparser/build.gradle index 46318a504..513a2b153 100644 --- a/smack-xmlparser/build.gradle +++ b/smack-xmlparser/build.gradle @@ -1,2 +1,16 @@ +// Note that this is also declared in the main build.gradle for +// subprojects, but since evaluationDependsOnChildren is enabled we +// need to declare it here too to have bundle{bnd{...}} available +apply plugin: 'biz.aQute.bnd.builder' + description = """\ Smack XML parser fundamentals""" + +jar { + bundle { + bnd( + // see http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.loader.html + 'Require-Capability': 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)"', + ) + } +} diff --git a/version b/version index 07b8ab2a4..891d39303 100644 --- a/version +++ b/version @@ -1 +1 @@ -4.5.0-alpha1-SNAPSHOT +4.5.0-alpha2-SNAPSHOT