Smack 4.2.0

-----BEGIN PGP SIGNATURE-----
 
 iQGTBAABCgB9FiEEl3UFnzoh3OFr5PuuIjmn6PWFIFIFAljDKBtfFIAAAAAALgAo
 aXNzdWVyLWZwckBub3RhdGlvbnMub3BlbnBncC5maWZ0aGhvcnNlbWFuLm5ldDk3
 NzUwNTlGM0EyMURDRTE2QkU0RkJBRTIyMzlBN0U4RjU4NTIwNTIACgkQIjmn6PWF
 IFJB7Qf6AlkwpzMqq1g18jzEBFVX/3Sk2QWivEY7t3EhGuSguan2VIfd1fL0P85Q
 vLBm6Pw93haIxHXKRUKc8DINwP9yuRMvUotCN2hYVgfqfByHGhDCJLTNZ9atncL5
 JToptfhdRy6kgljVZPtpXOMXKBvaO3QOuTuC5cmz8PlidsYw0yUnliPLQ36uPRWX
 eaEXXbgmkjJh35WjsaafD/uM86OCqZahfvEf3e8bkPzdAayd0OKU67+v0ArA9P2E
 CiRU5vfco/vt2Qo41aLLIEOjSFfVX6Xh/pXxfQvInMAxies0KRLi5vonOmfrWRmi
 uIblzcYRXCSaZSgVN2yF8KzmF4pzcw==
 =qETn
 -----END PGP SIGNATURE-----

Merge tag '4.2.0'

Smack 4.2.0
This commit is contained in:
Florian Schmaus 2017-03-10 23:35:06 +01:00
commit 1a93b448db
51 changed files with 1996 additions and 218 deletions

View File

@ -10,7 +10,6 @@ cache:
- $HOME/.m2 - $HOME/.m2
before_install: before_install:
- export JAVA_OPTS="-XX:MaxPermSize=512M"
- export GRADLE_VERSION=2.12 - export GRADLE_VERSION=2.12
- wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-all.zip - wget https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-all.zip
- unzip -q gradle-${GRADLE_VERSION}-all.zip - unzip -q gradle-${GRADLE_VERSION}-all.zip

View File

@ -3,20 +3,23 @@ Smack
[![Build Status](https://travis-ci.org/igniterealtime/Smack.svg?branch=master)](https://travis-ci.org/igniterealtime/Smack) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smackxmpp/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smackxmpp) [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/thing/3480125) [![Visit our IRC channel](https://kiwiirc.com/buttons/irc.freenode.net/smack.png)](https://kiwiirc.com/client/irc.freenode.net/smack) [![Build Status](https://travis-ci.org/igniterealtime/Smack.svg?branch=master)](https://travis-ci.org/igniterealtime/Smack) [![Coverage Status](https://coveralls.io/repos/igniterealtime/Smack/badge.svg)](https://coveralls.io/r/igniterealtime/Smack) [![Project Stats](https://www.openhub.net/p/smackxmpp/widgets/project_thin_badge.gif)](https://www.openhub.net/p/smackxmpp) [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/thing/3480125) [![Visit our IRC channel](https://kiwiirc.com/buttons/irc.freenode.net/smack.png)](https://kiwiirc.com/client/irc.freenode.net/smack)
Instructions how to use Smack in your Java or Android project are provided in the [Smack 4.1 Readme and Upgrade Guide](https://github.com/igniterealtime/Smack/wiki/Smack-4.1-Readme-and-Upgrade-Guide).
About About
----- -----
[Smack] is an open source, highly modular, easy to use, XMPP client library written in Java for JVMs and Android. [Smack] is an open source, highly modular, easy to use, XMPP client library written in Java for Java SE compatible JVMs and Android.
A pure Java library, it can be embedded into your applications to create anything from a full XMPP client to simple XMPP integrations such as sending notification messages and presence-enabling devices. A pure Java library, it can be embedded into your applications to create anything from a full XMPP instant messaging client to simple XMPP integrations such as sending notification messages and presence-enabling devices.
Smack and XMPP allows you to easily exchange data, in various ways e.g. fire-and-forget, publish-subscribe, between human and non-human endpoints (M2M, IoT, …). Smack and XMPP allows you to easily exchange data, in various ways e.g. fire-and-forget, publish-subscribe, between human and non-human endpoints (M2M, IoT, …).
Confused? Have a look at the [Overview](documentation/overview.md). Confused? Have a look at the [Overview](documentation/overview.md).
[Smack] - an [Ignite Realtime] community project. [Smack] - an [Ignite Realtime] community project.
Getting started
---------------
Instructions how to use Smack in your Java or Android project are provided in the [Smack 4.2 Readme and Upgrade Guide](https://github.com/igniterealtime/Smack/wiki/Smack-4.2-Readme-and-Upgrade-Guide).
Bug Reporting Bug Reporting
------------- -------------
@ -48,7 +51,7 @@ The guidelines also contain development quickstart instructions.
Resources Resources
--------- ---------
- Bug Tracker: http://issues.igniterealtime.org/browse/SMACK - Bug Tracker: https://issues.igniterealtime.org/browse/SMACK
- JaCoCo Coverage Reports: https://www.igniterealtime.org/builds/smack/dailybuilds/jacoco/html/ - JaCoCo Coverage Reports: https://www.igniterealtime.org/builds/smack/dailybuilds/jacoco/html/
- Nightly Builds: http://www.igniterealtime.org/downloads/nightly_smack.jsp - Nightly Builds: http://www.igniterealtime.org/downloads/nightly_smack.jsp
- Nightly Javadoc: http://www.igniterealtime.org/builds/smack/dailybuilds/javadoc/ - Nightly Javadoc: http://www.igniterealtime.org/builds/smack/dailybuilds/javadoc/
@ -57,6 +60,7 @@ Resources
- Dev Forum: http://community.igniterealtime.org/community/developers/smack - Dev Forum: http://community.igniterealtime.org/community/developers/smack
- Maven Releases: https://oss.sonatype.org/content/repositories/releases/org/igniterealtime/smack/ - Maven Releases: https://oss.sonatype.org/content/repositories/releases/org/igniterealtime/smack/
- Maven Snapshots: https://oss.sonatype.org/content/repositories/snapshots/org/igniterealtime/smack/ - Maven Snapshots: https://oss.sonatype.org/content/repositories/snapshots/org/igniterealtime/smack/
- Nightly Unique Maven Snapshots: https://igniterealtime.org/repo/
Donate Donate
------ ------

View File

@ -118,7 +118,7 @@ allprojects {
// Some systems may not have set their platform default // Some systems may not have set their platform default
// converter to 'utf8', but we use unicode in our source // converter to 'utf8', but we use unicode in our source
// files. Therefore ensure that javac uses unicode // files. Therefore ensure that javac uses unicode
options.encoding = "utf8" options.encoding = 'UTF-8'
options.compilerArgs = [ options.compilerArgs = [
'-Xlint:all', '-Xlint:all',
// Set '-options' because a non-java7 javac will emit a // Set '-options' because a non-java7 javac will emit a
@ -164,6 +164,7 @@ allprojects {
} }
tasks.withType(Javadoc) { tasks.withType(Javadoc) {
options.charSet = "UTF-8" options.charSet = "UTF-8"
options.encoding = 'UTF-8'
} }
// Pin the errorprone version to prevent "unsupported major.minor // Pin the errorprone version to prevent "unsupported major.minor
@ -301,6 +302,8 @@ subprojects {
archives sourcesJar archives sourcesJar
archives javadocJar archives javadocJar
archives testJar archives testJar
// See http://stackoverflow.com/a/21946676/194894
testRuntime testJar
} }
uploadArchives { uploadArchives {

View File

@ -91,6 +91,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental
| [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. | | [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. |
| Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. | | Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. |
| [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. | | [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. |
| HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. |
| [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. | | [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. |
| Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers | | Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers |

View File

@ -141,6 +141,140 @@ hr {
<div id="pageBody"> <div id="pageBody">
<h2>4.2.0 -- <span style="font-weight: normal;">2017-03-10</span></h2>
<h2> Sub-task
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-639'>SMACK-639</a>] - Add support for pre-approved subscription requests (RFC 6121 § 3.4)
</li>
</ul>
<h2> Bug
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-306'>SMACK-306</a>] - loadRosterOnLogin has non-trivial side effect on getRoster
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-416'>SMACK-416</a>] - Refactor PEP to make it use the existing pubsub API.
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-674'>SMACK-674</a>] - PubSub Affiliation extension element is missing &#39;jid&#39; attribute, and is using wrong element name &#39;subscription&#39;
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-682'>SMACK-682</a>] - Add support for &quot;XEP-0360: Nonzas (are not Stanzas)&quot;
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-683'>SMACK-683</a>] - Using a Proxy with XMPPTCPConnection failes with &quot;SocketException: Unconnected sockets not implemented&quot;
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-691'>SMACK-691</a>] - Add support for MUCItem&#39;s Actor &#39;nick&#39;
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-705'>SMACK-705</a>] - PubSub&#39;s Affiliation.getElementName() returns wrong name
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-722'>SMACK-722</a>] - SASL X-OAUTH2 implementation incorrectly performs Base64 encoding twice
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-723'>SMACK-723</a>] - Support &quot;Caps Optimizations&quot; (XEP-0115 § 8.4)
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-724'>SMACK-724</a>] - Do not re-use the Socket after connect() failed.
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-725'>SMACK-725</a>] - ReconnectionManager should handle AlreadyConnectedException and AlreadyLoggedInException not as failure
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-741'>SMACK-741</a>] - Ad-hoc command &#39;note&#39; element &#39;type&#39; attribute should be treated as optional
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-745'>SMACK-745</a>] - Memory leak in MultiUserChat
</li>
</ul>
<h2> New Feature
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-366'>SMACK-366</a>] - Add support for DNSSEC.
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-610'>SMACK-610</a>] - Add support for XEP-0080: User Location
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-619'>SMACK-619</a>] - Add roomDestroyed to MUC UserStatusListener
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-625'>SMACK-625</a>] - Add support for XEP-313: Message Archive Management
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-675'>SMACK-675</a>] - Add support for PubSub affiliation actions as owner
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-677'>SMACK-677</a>] - Add support for SASL &#39;authzid&#39; (Authorization Identity)
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-690'>SMACK-690</a>] - Add support for DNS-Based Authentication of Named Entities (DANE, RFC 6698)
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-731'>SMACK-731</a>] - Add support for XEP-0191: Blocking Command
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-732'>SMACK-732</a>] - Smack should be able to handle &quot;single equals sign&quot; SASL responses
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-740'>SMACK-740</a>] - Add support for Multi-User Chat Light
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-742'>SMACK-742</a>] - Add support for XEP-0133: Service Administration
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-747'>SMACK-747</a>] - Add support for XEP-0363: HTTP File Upload
</li>
</ul>
<h2> Task
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-638'>SMACK-638</a>] - Call connection creation listeners from within AbstractXMPPConnection&#39;s constructor
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-644'>SMACK-644</a>] - Throw exception if account creation or password change is performed over insecure connections
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-655'>SMACK-655</a>] - Enable StreamManagement by default
</li>
</ul>
<h2> Improvement
</h2>
<ul>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-372'>SMACK-372</a>] - Make package protected methods in PEPItem public
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-572'>SMACK-572</a>] - Rejoin MUC rooms after reconnect
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-628'>SMACK-628</a>] - Rework Roster handling with anonymous connections
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-629'>SMACK-629</a>] - Rework how Smack handles anonymous connections
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-631'>SMACK-631</a>] - Improve ParsingExceptionCallback, allow it to be a functional interface
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-632'>SMACK-632</a>] - Make Smack interruptible
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-633'>SMACK-633</a>] - Allow clean and graceful disconnects (stream closing)
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-634'>SMACK-634</a>] - Use jxmpp-jid, add Jid class to replace String&#39;s being used as JIDs
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-646'>SMACK-646</a>] - Add support for MUC roomnick rewrite
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-647'>SMACK-647</a>] - Don&#39;t automatically call login() on connect() if the connection was authenticated before
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-648'>SMACK-648</a>] - Improve MultiUserChat API
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-657'>SMACK-657</a>] - Rename RosterEntry.getStatus and RosterPacket.ItemStatus to ItemAskStatus
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-663'>SMACK-663</a>] - Roster should be fully loaded when Roster.getInstanceFor(XMPPConnection) is called with a authenticated connection
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-665'>SMACK-665</a>] - Rename &#39;serviceName&#39; to &#39;xmppServiceDomain&#39;
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-666'>SMACK-666</a>] - Typo in &#39;RosterEntries.rosterEntires()&#39;, change to &#39;RosterEntries.rosterEntries()&#39;
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-703'>SMACK-703</a>] - Limit the stored presences of entities not in Roster
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-704'>SMACK-704</a>] - Pass down Message stanza in ChatStateListener
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-711'>SMACK-711</a>] - Improve the logging of TCP connection attempts.
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-720'>SMACK-720</a>] - Improve support for Tor and Hidden Services.
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-721'>SMACK-721</a>] - Report illegal Stream Management states to avoid OOM Exception
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-727'>SMACK-727</a>] - Add partial support for the IoT XEPs (XEP-0323, -0324, -0325, -0347)
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-733'>SMACK-733</a>] - Handle outgoing &#39;unavailable&#39; Presences in Roster
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-736'>SMACK-736</a>] - Add support for Chat Markers (XEP-0333)
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-737'>SMACK-737</a>] - Add support for Bits of Binary (XEP-0231)
</li>
<li>[<a href='https://issues.igniterealtime.org/browse/SMACK-738'>SMACK-738</a>] - Add support for Push Notifications (XEP-0357)
</li>
</ul>
<h2>4.1.9 -- <span style="font-weight: normal;">2016-11-19</span></h2> <h2>4.1.9 -- <span style="font-weight: normal;">2016-11-19</span></h2>

View File

@ -38,6 +38,7 @@ import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.Nonza; import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.SASLFailure;
import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success; import org.jivesoftware.smack.sasl.packet.SaslStreamElements.Success;
import org.jivesoftware.smack.util.PacketParserUtils; import org.jivesoftware.smack.util.PacketParserUtils;
@ -529,7 +530,13 @@ public class XMPPBOSHConnection extends AbstractXMPPConnection {
} }
break; break;
case "error": case "error":
throw new StreamErrorException(PacketParserUtils.parseStreamError(parser)); //Some bosh error isn't stream error.
if ("urn:ietf:params:xml:ns:xmpp-streams".equals(parser.getNamespace(null))) {
throw new StreamErrorException(PacketParserUtils.parseStreamError(parser));
} else {
XMPPError.Builder builder = PacketParserUtils.parseError(parser);
throw new XMPPException.XMPPErrorException(null, builder.build());
}
} }
break; break;
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2014-2016 Florian Schmaus * Copyright 2014-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import java.security.SecureRandom;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random;
import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.CallbackHandler;
@ -41,7 +42,12 @@ public abstract class ScramMechanism extends SASLMechanism {
private static final byte[] SERVER_KEY_BYTES = toBytes("Server Key"); private static final byte[] SERVER_KEY_BYTES = toBytes("Server Key");
private static final byte[] ONE = new byte[] { 0, 0, 0, 1 }; private static final byte[] ONE = new byte[] { 0, 0, 0, 1 };
private static final SecureRandom RANDOM = new SecureRandom(); private static final ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() {
@Override
protected SecureRandom initialValue() {
return new SecureRandom();
}
};
private static final Cache<String, Keys> CACHE = new LruCache<String, Keys>(10); private static final Cache<String, Keys> CACHE = new LruCache<String, Keys>(10);
@ -292,8 +298,9 @@ public abstract class ScramMechanism extends SASLMechanism {
String getRandomAscii() { String getRandomAscii() {
int count = 0; int count = 0;
char[] randomAscii = new char[RANDOM_ASCII_BYTE_COUNT]; char[] randomAscii = new char[RANDOM_ASCII_BYTE_COUNT];
final Random random = SECURE_RANDOM.get();
while (count < RANDOM_ASCII_BYTE_COUNT) { while (count < RANDOM_ASCII_BYTE_COUNT) {
int r = RANDOM.nextInt(128); int r = random.nextInt(128);
char c = (char) r; char c = (char) r;
// RFC 5802 § 5.1 specifies 'r:' to exclude the ',' character and to be only printable ASCII characters // RFC 5802 § 5.1 specifies 'r:' to exclude the ',' character and to be only printable ASCII characters
if (!isPrintableNonCommaAsciiChar(c)) { if (!isPrintableNonCommaAsciiChar(c)) {

View File

@ -235,4 +235,19 @@ public class ParserUtils {
return uri; return uri;
} }
public static String getRequiredAttribute(XmlPullParser parser, String name) throws IOException {
String value = parser.getAttributeValue("", name);
if (StringUtils.isNullOrEmpty(value)) {
throw new IOException("Attribute " + name + " is null or empty (" + value + ')');
}
return value;
}
public static String getRequiredNextText(XmlPullParser parser) throws XmlPullParserException, IOException {
String text = parser.nextText();
if (StringUtils.isNullOrEmpty(text)) {
throw new IOException("Next text is null or empty (" + text + ')');
}
return text;
}
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2003-2007 Jive Software, 2016 Florian Schmaus. * Copyright 2003-2007 Jive Software, 2016-2017 Florian Schmaus.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -271,7 +271,12 @@ public class StringUtils {
* The Random class is not considered to be cryptographically secure, so * The Random class is not considered to be cryptographically secure, so
* only use these random Strings for low to medium security applications. * only use these random Strings for low to medium security applications.
*/ */
private static final Random randGen = new Random(); private static final ThreadLocal<Random> randGen = new ThreadLocal<Random>() {
@Override
protected Random initialValue() {
return new Random();
}
};
/** /**
* Array of numbers and letters of mixed case. Numbers appear in the list * Array of numbers and letters of mixed case. Numbers appear in the list
@ -299,15 +304,22 @@ public class StringUtils {
if (length < 1) { if (length < 1) {
return null; return null;
} }
final Random random = randGen.get();
// Create a char buffer to put random letters and numbers in. // Create a char buffer to put random letters and numbers in.
char [] randBuffer = new char[length]; char [] randBuffer = new char[length];
for (int i=0; i<randBuffer.length; i++) { for (int i=0; i<randBuffer.length; i++) {
randBuffer[i] = numbersAndLetters[randGen.nextInt(numbersAndLetters.length)]; randBuffer[i] = numbersAndLetters[random.nextInt(numbersAndLetters.length)];
} }
return new String(randBuffer); return new String(randBuffer);
} }
private static final SecureRandom SECURE_RANDOM = new SecureRandom(); private static final ThreadLocal<SecureRandom> SECURE_RANDOM = new ThreadLocal<SecureRandom>() {
@Override
protected SecureRandom initialValue() {
return new SecureRandom();
}
};
public static String randomString(final int length) { public static String randomString(final int length) {
if (length < 1) { if (length < 1) {
@ -315,7 +327,7 @@ public class StringUtils {
} }
byte[] randomBytes = new byte[length]; byte[] randomBytes = new byte[length];
SECURE_RANDOM.nextBytes(randomBytes); SECURE_RANDOM.get().nextBytes(randomBytes);
char[] randomChars = new char[length]; char[] randomChars = new char[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
randomChars[i] = getPrintableChar(randomBytes[i]); randomChars[i] = getPrintableChar(randomBytes[i]);

View File

@ -9,4 +9,5 @@ dependencies {
compile project(':smack-extensions') compile project(':smack-extensions')
testCompile project(path: ":smack-core", configuration: "testRuntime") testCompile project(path: ":smack-core", configuration: "testRuntime")
testCompile project(path: ":smack-core", configuration: "archives") testCompile project(path: ":smack-core", configuration: "archives")
testCompile project(path: ":smack-extensions", configuration: "testRuntime")
} }

View File

@ -0,0 +1,463 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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 org.jivesoftware.smack.AbstractConnectionListener;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.httpfileupload.UploadService.Version;
import org.jivesoftware.smackx.httpfileupload.element.Slot;
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest_V0_2;
import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.DomainBareJid;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
/**
* A manager for XEP-0363: HTTP File Upload.
*
* @author Grigory Fedorov
* @author Florian Schmaus
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
public final class HttpFileUploadManager extends Manager {
public static final String NAMESPACE = "urn:xmpp:http:upload:0";
public static final String NAMESPACE_0_2 = "urn:xmpp:http:upload";
private static final Logger LOGGER = Logger.getLogger(HttpFileUploadManager.class.getName());
static {
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@Override
public void connectionCreated(XMPPConnection connection) {
getInstanceFor(connection);
}
});
}
private static final Map<XMPPConnection, HttpFileUploadManager> INSTANCES = new WeakHashMap<>();
private UploadService defaultUploadService;
private SSLSocketFactory tlsSocketFactory;
/**
* Obtain the HttpFileUploadManager responsible for a connection.
*
* @param connection the connection object.
* @return a HttpFileUploadManager instance
*/
public static synchronized HttpFileUploadManager getInstanceFor(XMPPConnection connection) {
HttpFileUploadManager httpFileUploadManager = INSTANCES.get(connection);
if (httpFileUploadManager == null) {
httpFileUploadManager = new HttpFileUploadManager(connection);
INSTANCES.put(connection, httpFileUploadManager);
}
return httpFileUploadManager;
}
private HttpFileUploadManager(XMPPConnection connection) {
super(connection);
connection.addConnectionListener(new AbstractConnectionListener() {
@Override
public void authenticated(XMPPConnection connection, boolean resumed) {
// No need to reset the cache if the connection got resumed.
if (resumed) {
return;
}
try {
discoverUploadService();
} catch (XMPPException.XMPPErrorException | SmackException.NotConnectedException
| SmackException.NoResponseException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Error during discovering HTTP File Upload service", e);
}
}
});
}
private static UploadService uploadServiceFrom(DiscoverInfo discoverInfo) {
assert(containsHttpFileUploadNamespace(discoverInfo));
UploadService.Version version;
if (discoverInfo.containsFeature(NAMESPACE)) {
version = Version.v0_3;
} else if (discoverInfo.containsFeature(NAMESPACE_0_2)) {
version = Version.v0_2;
} else {
throw new AssertionError();
}
DomainBareJid address = discoverInfo.getFrom().asDomainBareJid();
DataForm dataForm = DataForm.from(discoverInfo);
if (dataForm == null) {
return new UploadService(address, version);
}
FormField field = dataForm.getField("max-file-size");
if (field == null) {
return new UploadService(address, version);
}
List<String> values = field.getValues();
if (values.isEmpty()) {
return new UploadService(address, version);
}
Long maxFileSize = Long.valueOf(values.get(0));
return new UploadService(address, version, maxFileSize);
}
/**
* Discover upload service.
*
* Called automatically when connection is authenticated.
*
* Note that this is a synchronous call -- Smack must wait for the server response.
*
* @return true if upload service was discovered
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException
* @throws InterruptedException
* @throws SmackException.NoResponseException
*/
public boolean discoverUploadService() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException,
InterruptedException, SmackException.NoResponseException {
ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection());
List<DiscoverInfo> servicesDiscoverInfo = sdm
.findServicesDiscoverInfo(NAMESPACE, true, true);
if (servicesDiscoverInfo.isEmpty()) {
servicesDiscoverInfo = sdm.findServicesDiscoverInfo(NAMESPACE_0_2, true, true);
if (servicesDiscoverInfo.isEmpty()) {
return false;
}
}
DiscoverInfo discoverInfo = servicesDiscoverInfo.get(0);
defaultUploadService = uploadServiceFrom(discoverInfo);
return true;
}
/**
* Check if upload service was discovered.
*
* @return true if upload service was discovered
*/
public boolean isUploadServiceDiscovered() {
return defaultUploadService != null;
}
/**
* Get default upload service if it was discovered.
*
* @return upload service JID or null if not available
*/
public UploadService getDefaultUploadService() {
return defaultUploadService;
}
/**
* Request slot and uploaded file to HTTP file upload service.
*
* You don't need to request slot and upload file separately, this method will do both.
* Note that this is a synchronous call -- Smack must wait for the server response.
*
* @param file file to be uploaded
* @return public URL for sharing uploaded file
* @throws InterruptedException
* @throws XMPPException.XMPPErrorException
* @throws SmackException
* @throws IOException in case of HTTP upload errors
*/
public URL uploadFile(File file) throws InterruptedException, XMPPException.XMPPErrorException,
SmackException, IOException {
return uploadFile(file, null);
}
/**
* Request slot and uploaded file to HTTP file upload service with progress callback.
*
* You don't need to request slot and upload file separately, this method will do both.
* Note that this is a synchronous call -- Smack must wait for the server response.
*
* @param file file to be uploaded
* @param listener upload progress listener of null
* @return public URL for sharing uploaded file
*
* @throws InterruptedException
* @throws XMPPException.XMPPErrorException
* @throws SmackException
* @throws IOException
*/
public URL uploadFile(File file, UploadProgressListener listener) throws InterruptedException,
XMPPException.XMPPErrorException, SmackException, IOException {
if (!file.isFile()) {
throw new FileNotFoundException("The path " + file.getAbsolutePath() + " is not a file");
}
final Slot slot = requestSlot(file.getName(), file.length(), "application/octet-stream");
uploadFile(file, slot, listener);
return slot.getGetUrl();
}
/**
* Request a new upload slot from default upload service (if discovered). When you get slot you should upload file
* to PUT URL and share GET URL. Note that this is a synchronous call -- Smack must wait for the server response.
*
* @param filename name of file to be uploaded
* @param fileSize file size in bytes.
* @return file upload Slot in case of success
* @throws IllegalArgumentException if fileSize is less than or equal to zero or greater than the maximum size
* supported by the service.
* @throws InterruptedException
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NotConnectedException
* @throws SmackException.NoResponseException
*/
public Slot requestSlot(String filename, long fileSize) throws InterruptedException,
XMPPException.XMPPErrorException, SmackException {
return requestSlot(filename, fileSize, null, null);
}
/**
* Request a new upload slot with optional content type from default upload service (if discovered).
*
* When you get slot you should upload file to PUT URL and share GET URL.
* Note that this is a synchronous call -- Smack must wait for the server response.
*
* @param filename name of file to be uploaded
* @param fileSize file size in bytes.
* @param contentType file content-type or null
* @return file upload Slot in case of success
* @throws IllegalArgumentException if fileSize is less than or equal to zero or greater than the maximum size
* supported by the service.
* @throws SmackException.NotConnectedException
* @throws InterruptedException
* @throws XMPPException.XMPPErrorException
* @throws SmackException.NoResponseException
*/
public Slot requestSlot(String filename, long fileSize, String contentType) throws SmackException,
InterruptedException, XMPPException.XMPPErrorException {
return requestSlot(filename, fileSize, contentType, null);
}
/**
* Request a new upload slot with optional content type from custom upload service.
*
* When you get slot you should upload file to PUT URL and share GET URL.
* Note that this is a synchronous call -- Smack must wait for the server response.
*
* @param filename name of file to be uploaded
* @param fileSize file size in bytes.
* @param contentType file content-type or null
* @param uploadServiceAddress the address of the upload service to use or null for default one
* @return file upload Slot in case of success
* @throws IllegalArgumentException if fileSize is less than or equal to zero or greater than the maximum size
* supported by the service.
* @throws SmackException
* @throws InterruptedException
* @throws XMPPException.XMPPErrorException
*/
public Slot requestSlot(String filename, long fileSize, String contentType, DomainBareJid uploadServiceAddress)
throws SmackException, InterruptedException, XMPPException.XMPPErrorException {
final XMPPConnection connection = connection();
final UploadService defaultUploadService = this.defaultUploadService;
// The upload service we are going to use.
UploadService uploadService;
if (uploadServiceAddress == null) {
uploadService = defaultUploadService;
} else {
if (defaultUploadService != null && defaultUploadService.getAddress().equals(uploadServiceAddress)) {
// Avoid performing a service discovery if we already know about the given service.
uploadService = defaultUploadService;
} else {
DiscoverInfo discoverInfo = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(uploadServiceAddress);
if (!containsHttpFileUploadNamespace(discoverInfo)) {
throw new IllegalArgumentException("There is no HTTP upload service running at the given address '"
+ uploadServiceAddress + '\'');
}
uploadService = uploadServiceFrom(discoverInfo);
}
}
if (uploadService == null) {
throw new SmackException("No upload service specified and also none discovered.");
}
if (!uploadService.acceptsFileOfSize(fileSize)) {
throw new IllegalArgumentException(
"Requested file size " + fileSize + " is greater than max allowed size " + uploadService.getMaxFileSize());
}
SlotRequest slotRequest;
switch (uploadService.getVersion()) {
case v0_3:
slotRequest = new SlotRequest(uploadService.getAddress(), filename, fileSize, contentType);
break;
case v0_2:
slotRequest = new SlotRequest_V0_2(uploadService.getAddress(), filename, fileSize, contentType);
break;
default:
throw new AssertionError();
}
return connection.createStanzaCollectorAndSend(slotRequest).nextResultOrThrow();
}
public void setTlsContext(SSLContext tlsContext) {
if (tlsContext == null) {
return;
}
this.tlsSocketFactory = tlsContext.getSocketFactory();
}
public void useTlsSettingsFrom(ConnectionConfiguration connectionConfiguration) {
SSLContext sslContext = connectionConfiguration.getCustomSSLContext();
setTlsContext(sslContext);
}
private void uploadFile(final File file, final Slot slot, UploadProgressListener listener) throws IOException {
final long fileSize = file.length();
// TODO Remove once Smack's minimum Android API level is 19 or higher. See also comment below.
if (fileSize >= Integer.MAX_VALUE) {
throw new IllegalArgumentException("File size " + fileSize + " must be less than " + Integer.MAX_VALUE);
}
final int fileSizeInt = (int) fileSize;
// Construct the FileInputStream first to make sure we can actually read the file.
final FileInputStream fis = new FileInputStream(file);
final URL putUrl = slot.getPutUrl();
final HttpURLConnection urlConnection = (HttpURLConnection) putUrl.openConnection();
urlConnection.setRequestMethod("PUT");
urlConnection.setUseCaches(false);
urlConnection.setDoOutput(true);
// TODO Change to using fileSize once Smack's minimum Android API level is 19 or higher.
urlConnection.setFixedLengthStreamingMode(fileSizeInt);
urlConnection.setRequestProperty("Content-Type", "application/octet-stream;");
for (Entry<String, String> header : slot.getHeaders().entrySet()) {
urlConnection.setRequestProperty(header.getKey(), header.getValue());
}
final SSLSocketFactory tlsSocketFactory = this.tlsSocketFactory;
if (tlsSocketFactory != null && urlConnection instanceof HttpsURLConnection) {
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
httpsUrlConnection.setSSLSocketFactory(tlsSocketFactory);
}
try {
OutputStream outputStream = urlConnection.getOutputStream();
long bytesSend = 0;
if (listener != null) {
listener.onUploadProgress(0, fileSize);
}
BufferedInputStream inputStream = new BufferedInputStream(fis);
// TODO Factor in extra static method (and re-use e.g. in bytestream code).
byte[] buffer = new byte[4096];
int bytesRead;
try {
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
bytesSend += bytesRead;
if (listener != null) {
listener.onUploadProgress(bytesSend, fileSize);
}
}
}
finally {
try {
inputStream.close();
}
catch (IOException e) {
LOGGER.log(Level.WARNING, "Exception while closing input stream", e);
}
try {
outputStream.close();
}
catch (IOException e) {
LOGGER.log(Level.WARNING, "Exception while closing output stream", e);
}
}
int status = urlConnection.getResponseCode();
switch (status) {
case HttpURLConnection.HTTP_OK:
case HttpURLConnection.HTTP_CREATED:
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);
}
}
finally {
urlConnection.disconnect();
}
}
private static boolean containsHttpFileUploadNamespace(DiscoverInfo discoverInfo) {
return discoverInfo.containsFeature(NAMESPACE) || discoverInfo.containsFeature(NAMESPACE_0_2);
}
}

View File

@ -0,0 +1,32 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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;
/**
* Callback interface to get upload progress.
*/
public interface UploadProgressListener {
/**
* Callback for displaying upload progress.
*
* @param uploadedBytes the number of bytes uploaded at the moment
* @param totalBytes the total number of bytes to be uploaded
*/
void onUploadProgress(long uploadedBytes, long totalBytes);
}

View File

@ -0,0 +1,66 @@
/**
*
* Copyright © 2017 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.httpfileupload;
import org.jivesoftware.smack.util.Objects;
import org.jxmpp.jid.DomainBareJid;
public class UploadService {
enum Version {
v0_2,
v0_3,
};
private final DomainBareJid address;
private final Version version;
private final Long maxFileSize;
UploadService(DomainBareJid address, Version version) {
this(address, version, null);
}
UploadService(DomainBareJid address, Version version, Long maxFileSize) {
this.address = Objects.requireNonNull(address);
this.version = version;
this.maxFileSize = maxFileSize;
}
public DomainBareJid getAddress() {
return address;
}
public Version getVersion() {
return version;
}
public boolean hasMaxFileSizeLimit() {
return maxFileSize != null;
}
public Long getMaxFileSize() {
return maxFileSize;
}
public boolean acceptsFileOfSize(long size) {
if (!hasMaxFileSizeLimit()) {
return true;
}
return size <= maxFileSize;
}
}

View File

@ -0,0 +1,76 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.element;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.util.XmlStringBuilder;
/**
* File Too Large error extension.
*
* @author Grigory Fedorov
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
public class FileTooLargeError implements ExtensionElement {
public static final String ELEMENT = "file-too-large";
public static final String NAMESPACE = SlotRequest.NAMESPACE;
private final long maxFileSize;
private final String namespace;
public FileTooLargeError(long maxFileSize) {
this(maxFileSize, NAMESPACE);
}
protected FileTooLargeError(long maxFileSize, String namespace) {
this.maxFileSize = maxFileSize;
this.namespace = namespace;
}
public long getMaxFileSize() {
return maxFileSize;
}
@Override
public String getElementName() {
return ELEMENT;
}
@Override
public String getNamespace() {
return namespace;
}
@Override
public XmlStringBuilder toXML() {
XmlStringBuilder xml = new XmlStringBuilder(this);
xml.rightAngleBracket();
xml.element("max-file-size", String.valueOf(maxFileSize));
xml.closeElement(this);
return xml;
}
public static FileTooLargeError from(IQ iq) {
XMPPError error = iq.getError();
if (error == null) {
return null;
}
return error.getExtension(ELEMENT, NAMESPACE);
}
}

View File

@ -0,0 +1,30 @@
/**
*
* Copyright 2017 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.httpfileupload.element;
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
public class FileTooLargeError_V0_2 extends FileTooLargeError {
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE_0_2;
public FileTooLargeError_V0_2(long maxFileSize) {
super(maxFileSize, NAMESPACE);
}
}

View File

@ -0,0 +1,84 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.element;
import org.jivesoftware.smack.packet.IQ;
import java.net.URL;
import java.util.Collections;
import java.util.Map;
/**
* Slot responded by upload service.
*
* @author Grigory Fedorov
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
public class Slot extends IQ {
public static final String ELEMENT = "slot";
public static final String NAMESPACE = SlotRequest.NAMESPACE;
private final URL putUrl;
private final URL getUrl;
private final Map<String, String> headers;
public Slot(URL putUrl, URL getUrl) {
this(putUrl, getUrl, null);
}
public Slot(URL putUrl, URL getUrl, Map<String, String> headers) {
this(putUrl, getUrl, headers, NAMESPACE);
}
protected Slot(URL putUrl, URL getUrl, Map<String, String> headers, String namespace) {
super(ELEMENT, namespace);
setType(Type.result);
this.putUrl = putUrl;
this.getUrl = getUrl;
if (headers == null) {
this.headers = Collections.emptyMap();
} else {
this.headers = Collections.unmodifiableMap(headers);
}
}
public URL getPutUrl() {
return putUrl;
}
public URL getGetUrl() {
return getUrl;
}
public Map<String, String> getHeaders() {
return headers;
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.rightAngleBracket();
xml.element("put", putUrl.toString());
xml.element("get", getUrl.toString());
for (Map.Entry<String, String> entry : getHeaders().entrySet()) {
xml.openElement("header").attribute(entry.getKey(), entry.getValue());
}
return xml;
}
}

View File

@ -0,0 +1,89 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.element;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
import org.jxmpp.jid.DomainBareJid;
/**
* Upload slot request.
* @author Grigory Fedorov
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
public class SlotRequest extends IQ {
public static final String ELEMENT = "request";
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE;
private final String filename;
private final long size;
private final String contentType;
public SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size) {
this(uploadServiceAddress, filename, size, null);
}
/**
* Create new slot request.
*
* @param uploadServiceAddress the XMPP address of the service to request the slot from.
* @param filename name of file
* @param size size of file in bytes
* @param contentType file content type or null
* @throws IllegalArgumentException if size is less than or equal to zero
*/
public SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size, String contentType) {
this(uploadServiceAddress, filename, size, contentType, NAMESPACE);
}
protected SlotRequest(DomainBareJid uploadServiceAddress, String filename, long size, String contentType, String namespace) {
super(ELEMENT, namespace);
if (size <= 0) {
throw new IllegalArgumentException("File fileSize must be greater than zero.");
}
this.filename = filename;
this.size = size;
this.contentType = contentType;
setType(Type.get);
setTo(uploadServiceAddress);
}
public String getFilename() {
return filename;
}
public long getSize() {
return size;
}
public String getContentType() {
return contentType;
}
@Override
protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
xml.rightAngleBracket();
xml.element("filename", filename);
xml.element("size", String.valueOf(size));
xml.optElement("content-type", contentType);
return xml;
}
}

View File

@ -0,0 +1,42 @@
/**
*
* Copyright 2017 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.httpfileupload.element;
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
import org.jxmpp.jid.DomainBareJid;
public class SlotRequest_V0_2 extends SlotRequest {
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE_0_2;
public SlotRequest_V0_2(DomainBareJid uploadServiceAddress, String filename, long size) {
this(uploadServiceAddress, filename, size, null);
}
/**
* Create new slot request.
*
* @param uploadServiceAddress the XMPP address of the service to request the slot from.
* @param filename name of file
* @param size size of file in bytes
* @param contentType file content type or null
* @throws IllegalArgumentException if size is less than or equal to zero
*/
public SlotRequest_V0_2(DomainBareJid uploadServiceAddress, String filename, long size, String contentType) {
super(uploadServiceAddress, filename, size, contentType, NAMESPACE);
}
}

View File

@ -0,0 +1,31 @@
/**
*
* Copyright 2017 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.httpfileupload.element;
import java.net.URL;
import org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager;
public class Slot_V0_2 extends Slot {
public static final String NAMESPACE = HttpFileUploadManager.NAMESPACE_0_2;
public Slot_V0_2(URL putUrl, URL getUrl) {
super(putUrl, getUrl, null, NAMESPACE);
}
}

View File

@ -0,0 +1,24 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.
*/
/**
* IQ stanzas and extensions for XEP-0363: HTTP File Upload.
*
* @author Grigory Fedorov
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
package org.jivesoftware.smackx.httpfileupload.element;

View File

@ -0,0 +1,24 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.
*/
/**
* Smack's API for XEP-0363: HTTP File Upload.
*
* @author Grigory Fedorov
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
package org.jivesoftware.smackx.httpfileupload;

View File

@ -0,0 +1,66 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.provider;
import org.jivesoftware.smack.provider.ExtensionElementProvider;
import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError;
import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError_V0_2;
import org.xmlpull.v1.XmlPullParser;
/**
* Provider for File Too Large error extension.
*
* @author Grigory Fedorov
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
public class FileTooLargeErrorProvider extends ExtensionElementProvider<FileTooLargeError> {
@Override
public FileTooLargeError parse(XmlPullParser parser, int initialDepth) throws Exception {
final String namespace = parser.getNamespace();
Long maxFileSize = null;
outerloop: while(true) {
int event = parser.next();
switch (event) {
case XmlPullParser.START_TAG:
String name = parser.getName();
switch(name) {
case "max-file-size":
maxFileSize = Long.valueOf(parser.nextText());
break;
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
switch (namespace) {
case FileTooLargeError.NAMESPACE:
return new FileTooLargeError(maxFileSize);
case FileTooLargeError_V0_2.NAMESPACE:
return new FileTooLargeError_V0_2(maxFileSize);
default:
throw new AssertionError();
}
}
}

View File

@ -0,0 +1,87 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.provider;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.provider.IQProvider;
import org.jivesoftware.smack.util.ParserUtils;
import org.jivesoftware.smackx.httpfileupload.element.Slot;
import org.jivesoftware.smackx.httpfileupload.element.Slot_V0_2;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* Provider for Slot.
*
* @author Grigory Fedorov
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
public class SlotProvider extends IQProvider<Slot> {
@Override
public Slot parse(XmlPullParser parser, int initialDepth) throws XmlPullParserException, IOException, SmackException {
final String namespace = parser.getNamespace();
URL putUrl = null;
URL getUrl = null;
Map<String, String> headers = null;
outerloop: while (true) {
int event = parser.next();
switch (event) {
case XmlPullParser.START_TAG:
String name = parser.getName();
switch(name) {
case "put":
putUrl = new URL(parser.nextText());
break;
case "get":
getUrl = new URL(parser.nextText());
break;
case "header":
String headerName = ParserUtils.getRequiredAttribute(parser, "name");
String headerValue = ParserUtils.getRequiredNextText(parser);
if (headers == null) {
headers = new HashMap<>();
}
headers.put(headerName, headerValue);
break;
}
break;
case XmlPullParser.END_TAG:
if (parser.getDepth() == initialDepth) {
break outerloop;
}
break;
}
}
switch (namespace) {
case Slot.NAMESPACE:
return new Slot(putUrl, getUrl, headers);
case Slot_V0_2.NAMESPACE:
return new Slot_V0_2(putUrl, getUrl);
default:
throw new AssertionError();
}
}
}

View File

@ -0,0 +1,24 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.
*/
/**
* Providers for XEP-0363: HTTP File Upload.
*
* @author Grigory Fedorov
* @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
*/
package org.jivesoftware.smackx.httpfileupload.provider;

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright © 2016 Florian Schmaus and Fernando Ramirez * Copyright © 2016-2017 Florian Schmaus, Fernando Ramirez
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ package org.jivesoftware.smackx.mam;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -25,10 +26,10 @@ import java.util.WeakHashMap;
import org.jivesoftware.smack.ConnectionCreationListener; import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager; import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.StanzaCollector;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.NotLoggedInException; import org.jivesoftware.smack.SmackException.NotLoggedInException;
import org.jivesoftware.smack.StanzaCollector;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry; import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.XMPPException.XMPPErrorException;
@ -47,6 +48,8 @@ import org.jivesoftware.smackx.mam.filter.MamResultFilter;
import org.jivesoftware.smackx.rsm.packet.RSMSet; import org.jivesoftware.smackx.rsm.packet.RSMSet;
import org.jivesoftware.smackx.xdata.FormField; import org.jivesoftware.smackx.xdata.FormField;
import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.jxmpp.util.XmppDateTime; import org.jxmpp.util.XmppDateTime;
@ -70,7 +73,7 @@ public final class MamManager extends Manager {
}); });
} }
private static final Map<XMPPConnection, MamManager> INSTANCES = new WeakHashMap<>(); private static final Map<XMPPConnection, Map<Jid, MamManager>> INSTANCES = new WeakHashMap<>();
/** /**
* Get the singleton instance of MamManager. * Get the singleton instance of MamManager.
@ -78,19 +81,29 @@ public final class MamManager extends Manager {
* @param connection * @param connection
* @return the instance of MamManager * @return the instance of MamManager
*/ */
public static synchronized MamManager getInstanceFor(XMPPConnection connection) { public static MamManager getInstanceFor(XMPPConnection connection) {
MamManager mamManager = INSTANCES.get(connection); return getInstanceFor(connection, null);
}
if (mamManager == null) { public static synchronized MamManager getInstanceFor(XMPPConnection connection, Jid archiveAddress) {
mamManager = new MamManager(connection); Map<Jid, MamManager> managers = INSTANCES.get(connection);
INSTANCES.put(connection, mamManager); if (managers == null) {
managers = new HashMap<>();
INSTANCES.put(connection, managers);
}
MamManager mamManager = managers.get(archiveAddress);
if (mamManager == null) {
mamManager = new MamManager(connection, archiveAddress);
managers.put(archiveAddress, mamManager);
} }
return mamManager; return mamManager;
} }
private MamManager(XMPPConnection connection) { private final Jid archiveAddress;
private MamManager(XMPPConnection connection, Jid archiveAddress) {
super(connection); super(connection);
this.archiveAddress = archiveAddress;
} }
/** /**
@ -106,7 +119,7 @@ public final class MamManager extends Manager {
*/ */
public MamQueryResult queryArchive(Integer max) throws NoResponseException, XMPPErrorException, public MamQueryResult queryArchive(Integer max) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException { NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(max, null, null, null, null); return queryArchive(null, max, null, null, null, null);
} }
/** /**
@ -122,7 +135,7 @@ public final class MamManager extends Manager {
*/ */
public MamQueryResult queryArchive(Jid withJid) throws NoResponseException, XMPPErrorException, public MamQueryResult queryArchive(Jid withJid) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException { NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, null, null, withJid, null); return queryArchive(null, null, null, null, withJid, null);
} }
/** /**
@ -142,7 +155,7 @@ public final class MamManager extends Manager {
*/ */
public MamQueryResult queryArchive(Date start, Date end) throws NoResponseException, XMPPErrorException, public MamQueryResult queryArchive(Date start, Date end) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException { NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, start, end, null, null); return queryArchive(null, null, start, end, null, null);
} }
/** /**
@ -158,7 +171,7 @@ public final class MamManager extends Manager {
*/ */
public MamQueryResult queryArchive(List<FormField> additionalFields) throws NoResponseException, XMPPErrorException, public MamQueryResult queryArchive(List<FormField> additionalFields) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException { NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, null, null, null, additionalFields); return queryArchive(null, null, null, null, null, additionalFields);
} }
/** /**
@ -175,7 +188,7 @@ public final class MamManager extends Manager {
*/ */
public MamQueryResult queryArchiveWithStartDate(Date start) throws NoResponseException, XMPPErrorException, public MamQueryResult queryArchiveWithStartDate(Date start) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException { NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, start, null, null, null); return queryArchive(null, null, start, null, null, null);
} }
/** /**
@ -192,9 +205,10 @@ public final class MamManager extends Manager {
*/ */
public MamQueryResult queryArchiveWithEndDate(Date end) throws NoResponseException, XMPPErrorException, public MamQueryResult queryArchiveWithEndDate(Date end) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException { NotConnectedException, InterruptedException, NotLoggedInException {
return queryArchive(null, null, end, null, null); return queryArchive(null, null, null, end, null, null);
} }
/** /**
* Query archive applying filters: max count, start date, end date, from/to * Query archive applying filters: max count, start date, end date, from/to
* JID and with additional fields. * JID and with additional fields.
@ -214,6 +228,32 @@ public final class MamManager extends Manager {
public MamQueryResult queryArchive(Integer max, Date start, Date end, Jid withJid, List<FormField> additionalFields) public MamQueryResult queryArchive(Integer max, Date start, Date end, Jid withJid, List<FormField> additionalFields)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException,
NotLoggedInException { NotLoggedInException {
return queryArchive(null, max, start, end, withJid, additionalFields);
}
/**
* Query an message archive like a MUC archive or a pubsub node archive, addressed by an archiveAddress, applying
* filters: max count, start date, end date, from/to JID and with additional fields. When archiveAddress is null the
* default, the server will be requested.
*
* @param node The Pubsub node name, can be null
* @param max
* @param start
* @param end
* @param withJid
* @param additionalFields
* @return the MAM query result
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
*/
public MamQueryResult queryArchive(String node, Integer max, Date start, Date end, Jid withJid,
List<FormField> additionalFields)
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException,
NotLoggedInException {
DataForm dataForm = null; DataForm dataForm = null;
String queryId = UUID.randomUUID().toString(); String queryId = UUID.randomUUID().toString();
@ -225,8 +265,9 @@ public final class MamManager extends Manager {
addAdditionalFields(additionalFields, dataForm); addAdditionalFields(additionalFields, dataForm);
} }
MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, dataForm); MamQueryIQ mamQueryIQ = new MamQueryIQ(queryId, node, dataForm);
mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
addResultsLimit(max, mamQueryIQ); addResultsLimit(max, mamQueryIQ);
return queryArchive(mamQueryIQ); return queryArchive(mamQueryIQ);
@ -290,9 +331,31 @@ public final class MamManager extends Manager {
* @throws NotLoggedInException * @throws NotLoggedInException
*/ */
public MamQueryResult page(DataForm dataForm, RSMSet rsmSet) throws NoResponseException, XMPPErrorException, public MamQueryResult page(DataForm dataForm, RSMSet rsmSet) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException {
return page(null, dataForm, rsmSet);
}
/**
* Returns a page of the archive.
*
* @param node The Pubsub node name, can be null
* @param dataForm
* @param rsmSet
* @return the MAM query result
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
*/
public MamQueryResult page(String node, DataForm dataForm, RSMSet rsmSet)
throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException, NotLoggedInException { NotConnectedException, InterruptedException, NotLoggedInException {
MamQueryIQ mamQueryIQ = new MamQueryIQ(UUID.randomUUID().toString(), dataForm); MamQueryIQ mamQueryIQ = new MamQueryIQ(UUID.randomUUID().toString(), node, dataForm);
mamQueryIQ.setType(IQ.Type.set); mamQueryIQ.setType(IQ.Type.set);
mamQueryIQ.setTo(archiveAddress);
mamQueryIQ.addExtension(rsmSet); mamQueryIQ.addExtension(rsmSet);
return queryArchive(mamQueryIQ); return queryArchive(mamQueryIQ);
} }
@ -315,7 +378,7 @@ public final class MamManager extends Manager {
XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException {
RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet(); RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet();
RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getLast(), RSMSet.PageDirection.after); RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getLast(), RSMSet.PageDirection.after);
return page(mamQueryResult.form, requestRsmSet); return page(mamQueryResult, requestRsmSet);
} }
/** /**
@ -336,14 +399,24 @@ public final class MamManager extends Manager {
XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException { XMPPErrorException, NotConnectedException, InterruptedException, NotLoggedInException {
RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet(); RSMSet previousResultRsmSet = mamQueryResult.mamFin.getRSMSet();
RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getFirst(), RSMSet.PageDirection.before); RSMSet requestRsmSet = new RSMSet(count, previousResultRsmSet.getFirst(), RSMSet.PageDirection.before);
return page(mamQueryResult.form, requestRsmSet); return page(mamQueryResult, requestRsmSet);
}
private MamQueryResult page(MamQueryResult mamQueryResult, RSMSet requestRsmSet) throws NoResponseException,
XMPPErrorException, NotConnectedException, NotLoggedInException, InterruptedException {
ensureMamQueryResultMatchesThisManager(mamQueryResult);
return page(mamQueryResult.node, mamQueryResult.form, requestRsmSet);
} }
/** /**
* Obtain page before the first message saved (specific chat). * Obtain page before the first message saved (specific chat).
* <p>
* Note that the messageUid is the XEP-0313 UID and <b>not</> the stanza ID of the message.
* </p>
* *
* @param chatJid * @param chatJid
* @param firstMessageId * @param messageUid the UID of the message of which messages before should be received.
* @param max * @param max
* @return the MAM query result * @return the MAM query result
* @throws XMPPErrorException * @throws XMPPErrorException
@ -352,19 +425,22 @@ public final class MamManager extends Manager {
* @throws InterruptedException * @throws InterruptedException
* @throws NoResponseException * @throws NoResponseException
*/ */
public MamQueryResult pageBefore(Jid chatJid, String firstMessageId, int max) throws XMPPErrorException, public MamQueryResult pageBefore(Jid chatJid, String messageUid, int max) throws XMPPErrorException,
NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException { NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException {
RSMSet rsmSet = new RSMSet(null, firstMessageId, -1, -1, null, max, null, -1); RSMSet rsmSet = new RSMSet(null, messageUid, -1, -1, null, max, null, -1);
DataForm dataForm = getNewMamForm(); DataForm dataForm = getNewMamForm();
addWithJid(chatJid, dataForm); addWithJid(chatJid, dataForm);
return page(dataForm, rsmSet); return page(null, dataForm, rsmSet);
} }
/** /**
* Obtain page after the last message saved (specific chat). * Obtain page after the last message saved (specific chat).
* <p>
* Note that the messageUid is the XEP-0313 UID and <b>not</> the stanza ID of the message.
* </p>
* *
* @param chatJid * @param chatJid
* @param lastMessageId * @param messageUid the UID of the message of which messages after should be received.
* @param max * @param max
* @return the MAM query result * @return the MAM query result
* @throws XMPPErrorException * @throws XMPPErrorException
@ -373,12 +449,12 @@ public final class MamManager extends Manager {
* @throws InterruptedException * @throws InterruptedException
* @throws NoResponseException * @throws NoResponseException
*/ */
public MamQueryResult pageAfter(Jid chatJid, String lastMessageId, int max) throws XMPPErrorException, public MamQueryResult pageAfter(Jid chatJid, String messageUid, int max) throws XMPPErrorException,
NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException { NotLoggedInException, NotConnectedException, InterruptedException, NoResponseException {
RSMSet rsmSet = new RSMSet(lastMessageId, null, -1, -1, null, max, null, -1); RSMSet rsmSet = new RSMSet(messageUid, null, -1, -1, null, max, null, -1);
DataForm dataForm = getNewMamForm(); DataForm dataForm = getNewMamForm();
addWithJid(chatJid, dataForm); addWithJid(chatJid, dataForm);
return page(dataForm, rsmSet); return page(null, dataForm, rsmSet);
} }
/** /**
@ -409,9 +485,27 @@ public final class MamManager extends Manager {
* @throws NotLoggedInException * @throws NotLoggedInException
*/ */
public List<FormField> retrieveFormFields() throws NoResponseException, XMPPErrorException, NotConnectedException, public List<FormField> retrieveFormFields() throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException, NotLoggedInException {
return retrieveFormFields(null);
}
/**
* Get the form fields supported by the server.
*
* @param node The Pubsub node name, can be null
* @return the list of form fields.
* @throws NoResponseException
* @throws XMPPErrorException
* @throws NotConnectedException
* @throws InterruptedException
* @throws NotLoggedInException
*/
public List<FormField> retrieveFormFields(String node)
throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException, NotLoggedInException { InterruptedException, NotLoggedInException {
String queryId = UUID.randomUUID().toString(); String queryId = UUID.randomUUID().toString();
MamQueryIQ mamQueryIq = new MamQueryIQ(queryId); MamQueryIQ mamQueryIq = new MamQueryIQ(queryId, node, null);
mamQueryIq.setTo(archiveAddress);
MamQueryIQ mamResponseQueryIq = connection().createStanzaCollectorAndSend(mamQueryIq).nextResultOrThrow(); MamQueryIQ mamResponseQueryIq = connection().createStanzaCollectorAndSend(mamQueryIq).nextResultOrThrow();
@ -445,7 +539,7 @@ public final class MamManager extends Manager {
forwardedMessages.add(mamResultExtension.getForwarded()); forwardedMessages.add(mamResultExtension.getForwarded());
} }
return new MamQueryResult(forwardedMessages, mamFinIQ, DataForm.from(mamQueryIq)); return new MamQueryResult(forwardedMessages, mamFinIQ, mamQueryIq.getNode(), DataForm.from(mamQueryIq));
} }
/** /**
@ -455,15 +549,41 @@ public final class MamManager extends Manager {
public final static class MamQueryResult { public final static class MamQueryResult {
public final List<Forwarded> forwardedMessages; public final List<Forwarded> forwardedMessages;
public final MamFinIQ mamFin; public final MamFinIQ mamFin;
private final String node;
private final DataForm form; private final DataForm form;
private MamQueryResult(List<Forwarded> forwardedMessages, MamFinIQ mamFin, DataForm form) { private MamQueryResult(List<Forwarded> forwardedMessages, MamFinIQ mamFin, String node, DataForm form) {
this.forwardedMessages = forwardedMessages; this.forwardedMessages = forwardedMessages;
this.mamFin = mamFin; this.mamFin = mamFin;
this.node = node;
this.form = form; this.form = form;
} }
} }
private void ensureMamQueryResultMatchesThisManager(MamQueryResult mamQueryResult) {
EntityFullJid localAddress = connection().getUser();
EntityBareJid localBareAddress = null;
if (localAddress != null) {
localBareAddress = localAddress.asEntityBareJid();
}
boolean isLocalUserArchive = archiveAddress == null || archiveAddress.equals(localBareAddress);
Jid finIqFrom = mamQueryResult.mamFin.getFrom();
if (finIqFrom != null) {
if (finIqFrom.equals(archiveAddress) || (isLocalUserArchive && finIqFrom.equals(localBareAddress))) {
return;
}
throw new IllegalArgumentException("The given MamQueryResult is from the MAM archive '" + finIqFrom
+ "' whereas this MamManager is responsible for '" + archiveAddress + '\'');
}
else if (!isLocalUserArchive) {
throw new IllegalArgumentException(
"The given MamQueryResult is from the local entity (user) MAM archive, whereas this MamManager is responsible for '"
+ archiveAddress + '\'');
}
}
/** /**
* Returns true if Message Archive Management is supported by the server. * Returns true if Message Archive Management is supported by the server.
* *

View File

@ -108,6 +108,15 @@ public class MamQueryIQ extends IQ {
return queryId; return queryId;
} }
/**
* Get the Node name.
*
* @return the node
*/
public String getNode() {
return node;
}
/** /**
* Get the data form. * Get the data form.
* *

View File

@ -232,5 +232,27 @@
<namespace>urn:xmpp:chat-markers:0</namespace> <namespace>urn:xmpp:chat-markers:0</namespace>
<className>org.jivesoftware.smackx.chat_markers.provider.AcknowledgedProvider</className> <className>org.jivesoftware.smackx.chat_markers.provider.AcknowledgedProvider</className>
</extensionProvider> </extensionProvider>
<!-- XEP-0363: HTTP File Upload -->
<iqProvider>
<elementName>slot</elementName>
<namespace>urn:xmpp:http:upload:0</namespace>
<className>org.jivesoftware.smackx.httpfileupload.provider.SlotProvider</className>
</iqProvider>
<extensionProvider>
<elementName>file-too-large</elementName>
<namespace>urn:xmpp:http:upload:0</namespace>
<className>org.jivesoftware.smackx.httpfileupload.provider.FileTooLargeErrorProvider</className>
</extensionProvider>
<iqProvider>
<elementName>slot</elementName>
<namespace>urn:xmpp:http:upload</namespace>
<className>org.jivesoftware.smackx.httpfileupload.provider.SlotProvider</className>
</iqProvider>
<extensionProvider>
<elementName>file-too-large</elementName>
<namespace>urn:xmpp:http:upload</namespace>
<className>org.jivesoftware.smackx.httpfileupload.provider.FileTooLargeErrorProvider</className>
</extensionProvider>
</smackProviders> </smackProviders>

View File

@ -4,5 +4,6 @@
<className>org.jivesoftware.smackx.iot.data.IoTDataManager</className> <className>org.jivesoftware.smackx.iot.data.IoTDataManager</className>
<className>org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager</className> <className>org.jivesoftware.smackx.iot.discovery.IoTDiscoveryManager</className>
<className>org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager</className> <className>org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager</className>
<className>org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager</className>
</startupClasses> </startupClasses>
</smack> </smack>

View File

@ -0,0 +1,39 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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 org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError;
import org.junit.Assert;
import org.junit.Test;
public class FileTooLargeErrorCreateTest {
String fileTooLargeErrorExtensionExample
= "<file-too-large xmlns='urn:xmpp:http:upload:0'>"
+ "<max-file-size>20000</max-file-size>"
+ "</file-too-large>";
@Test
public void checkFileTooLargeErrorExtensionCreation() {
FileTooLargeError fileTooLargeError = new FileTooLargeError(20000);
Assert.assertEquals(20000, fileTooLargeError.getMaxFileSize());
Assert.assertEquals(fileTooLargeErrorExtensionExample, fileTooLargeError.toXML().toString());
}
}

View File

@ -0,0 +1,46 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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 org.jivesoftware.smackx.httpfileupload.element.Slot;
import org.junit.Assert;
import org.junit.Test;
import java.net.MalformedURLException;
import java.net.URL;
public class SlotCreateTest {
String testSlot
= "<slot xmlns='urn:xmpp:http:upload:0'>"
+ "<put>https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</put>"
+ "<get>https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</get>"
+ "</slot>";
@Test
public void checkSlotRequestCreation() throws MalformedURLException {
Slot slot = new Slot(new URL("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
new URL("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"));
Assert.assertEquals(new URL("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
slot.getPutUrl());
Assert.assertEquals(new URL("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
slot.getGetUrl());
Assert.assertEquals(testSlot, slot.getChildElementXML().toString());
}
}

View File

@ -0,0 +1,72 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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 org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
import org.junit.Assert;
import org.junit.Test;
import org.jxmpp.jid.JidTestUtil;
import org.jxmpp.stringprep.XmppStringprepException;
public class SlotRequestCreateTest {
String testRequest
= "<request xmlns='urn:xmpp:http:upload:0'>"
+ "<filename>my_juliet.png</filename>"
+ "<size>23456</size>"
+ "<content-type>image/jpeg</content-type>"
+ "</request>";
String testRequestWithoutContentType
= "<request xmlns='urn:xmpp:http:upload:0'>"
+ "<filename>my_romeo.png</filename>"
+ "<size>52523</size>"
+ "</request>";
@Test
public void checkSlotRequestCreation() throws XmppStringprepException {
SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", 23456, "image/jpeg");
Assert.assertEquals("my_juliet.png", slotRequest.getFilename());
Assert.assertEquals(23456, slotRequest.getSize());
Assert.assertEquals("image/jpeg", slotRequest.getContentType());
Assert.assertEquals(testRequest, slotRequest.getChildElementXML().toString());
}
@Test
public void checkSlotRequestCreationWithoutContentType() throws XmppStringprepException {
SlotRequest slotRequest = new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_romeo.png", 52523);
Assert.assertEquals("my_romeo.png", slotRequest.getFilename());
Assert.assertEquals(52523, slotRequest.getSize());
Assert.assertEquals(null, slotRequest.getContentType());
Assert.assertEquals(testRequestWithoutContentType, slotRequest.getChildElementXML().toString());
}
@Test(expected = IllegalArgumentException.class)
public void checkSlotRequestCreationNegativeSize() {
new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", -23456, "image/jpeg");
}
@Test(expected = IllegalArgumentException.class)
public void checkSlotRequestCreationZeroSize() {
new SlotRequest(JidTestUtil.DOMAIN_BARE_JID_1, "my_juliet.png", 0, "image/jpeg");
}
}

View File

@ -0,0 +1,58 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smackx.httpfileupload.element.FileTooLargeError;
import org.junit.Assert;
import org.junit.Test;
public class FileTooLargeErrorProviderTest {
/**
* Example 7. Alternative response by the upload service if the file size was too large
* @see <a href="http://xmpp.org/extensions/xep-0363.html#errors">XEP-0363: HTTP File Upload 5. Error conditions</a>
*/
String slotErrorFileToLarge
= "<iq from='upload.montague.tld' "
+ "id='step_03' "
+ "to='romeo@montague.tld/garden' "
+ "type='error'>"
+ "<request xmlns='urn:xmpp:http:upload:0'>"
+ "<filename>my_juliet.png</filename>"
+ "<size>23456</size>"
+ "</request>"
+ "<error type='modify'>"
+ "<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas' />"
+ "<text xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>File too large. The maximum file size is 20000 bytes</text>"
+ "<file-too-large xmlns='urn:xmpp:http:upload:0'>"
+ "<max-file-size>20000</max-file-size>"
+ "</file-too-large>"
+ "</error>"
+ "</iq>";
@Test
public void checkSlotErrorFileToLarge() throws Exception {
IQ fileTooLargeErrorIQ = PacketParserUtils.parseStanza(slotErrorFileToLarge);
Assert.assertEquals(IQ.Type.error, fileTooLargeErrorIQ.getType());
FileTooLargeError fileTooLargeError = FileTooLargeError.from(fileTooLargeErrorIQ);
Assert.assertEquals(20000, fileTooLargeError.getMaxFileSize());
}
}

View File

@ -0,0 +1,55 @@
/**
*
* Copyright © 2017 Grigory Fedorov
*
* 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.provider;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smackx.httpfileupload.element.Slot;
import org.junit.Assert;
import org.junit.Test;
import java.net.URL;
public class SlotProviderTest {
/**
* Example 6. The upload service responds with a slot
* @see <a href="http://xmpp.org/extensions/xep-0363.html#request">XEP-0363: HTTP File Upload 4. Requesting a slot</a>
*/
String slotExample
= "<iq from='upload.montague.tld' "
+ "id='step_03' "
+ "to='romeo@montague.tld/garden' "
+ "type='result'>"
+ "<slot xmlns='urn:xmpp:http:upload:0'>"
+ "<put>https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</put>"
+ "<get>https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png</get>"
+ "</slot>"
+ "</iq>";
@Test
public void checkSlotProvider() throws Exception {
Slot slot = PacketParserUtils.parseStanza(slotExample);
Assert.assertEquals(IQ.Type.result, slot.getType());
Assert.assertEquals(new URL("https://upload.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
slot.getPutUrl());
Assert.assertEquals(new URL("https://download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/my_juliet.png"),
slot.getGetUrl());
}
}

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2016 Fernando Ramirez * Copyright 2016-2017 Fernando Ramirez
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,17 +16,16 @@
*/ */
package org.jivesoftware.smackx.mam; package org.jivesoftware.smackx.mam;
import static org.mockito.Mockito.mock;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smackx.ExperimentalInitializerTest; import org.jivesoftware.smackx.InitExtensions;
import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jivesoftware.smackx.xdata.packet.DataForm;
import org.junit.Before; import org.junit.Before;
public class MamTest extends ExperimentalInitializerTest { public class MamTest extends InitExtensions {
protected XMPPConnection connection; protected XMPPConnection connection;
protected String queryId; protected String queryId;
@ -35,7 +34,7 @@ public class MamTest extends ExperimentalInitializerTest {
@Before @Before
public void setup() { public void setup() {
// mock connection // mock connection
connection = mock(XMPPConnection.class); connection = new DummyConnection();
// test query id // test query id
queryId = "testid"; queryId = "testid";

View File

@ -43,7 +43,10 @@ import org.jivesoftware.smack.filter.FromMatchesFilter;
import org.jivesoftware.smack.filter.MessageTypeFilter; import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.MessageWithSubjectFilter; import org.jivesoftware.smack.filter.MessageWithSubjectFilter;
import org.jivesoftware.smack.filter.NotFilter; import org.jivesoftware.smack.filter.NotFilter;
import org.jivesoftware.smack.filter.OrFilter;
import org.jivesoftware.smack.filter.PresenceTypeFilter;
import org.jivesoftware.smack.filter.StanzaFilter; import org.jivesoftware.smack.filter.StanzaFilter;
import org.jivesoftware.smack.filter.StanzaIdFilter;
import org.jivesoftware.smack.filter.StanzaExtensionFilter; import org.jivesoftware.smack.filter.StanzaExtensionFilter;
import org.jivesoftware.smack.filter.StanzaTypeFilter; import org.jivesoftware.smack.filter.StanzaTypeFilter;
import org.jivesoftware.smack.filter.ToMatchesFilter; import org.jivesoftware.smack.filter.ToMatchesFilter;
@ -327,9 +330,17 @@ public class MultiUserChat {
messageCollector = connection.createStanzaCollector(fromRoomGroupchatFilter); messageCollector = connection.createStanzaCollector(fromRoomGroupchatFilter);
// Wait for a presence packet back from the server. // Wait for a presence packet back from the server.
// Use a bare JID filter, since the room may rewrite the nickname. // @formatter:off
StanzaFilter responseFilter = new AndFilter(FromMatchesFilter.createBare(getRoom()), new StanzaTypeFilter( StanzaFilter responseFilter = new AndFilter(StanzaTypeFilter.PRESENCE,
Presence.class), MUCUserStatusCodeFilter.STATUS_110_PRESENCE_TO_SELF); new OrFilter(
// We use a bare JID filter for positive responses, since the MUC service/room may rewrite the nickname.
new AndFilter(FromMatchesFilter.createBare(getRoom()), MUCUserStatusCodeFilter.STATUS_110_PRESENCE_TO_SELF),
// In case there is an error reply, we match on an error presence with the same stanza id and from the full
// JID we send the join presence to.
new AndFilter(FromMatchesFilter.createFull(joinPresence.getTo()), new StanzaIdFilter(joinPresence), PresenceTypeFilter.ERROR)
)
);
// @formatter:on
Presence presence; Presence presence;
try { try {
presence = connection.createStanzaCollectorAndSend(responseFilter, joinPresence).nextResultOrThrow(conf.getTimeout()); presence = connection.createStanzaCollectorAndSend(responseFilter, joinPresence).nextResultOrThrow(conf.getTimeout());

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2012-2015 Florian Schmaus * Copyright 2012-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,11 +16,10 @@
*/ */
package org.jivesoftware.smackx.ping; package org.jivesoftware.smackx.ping;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
@ -105,8 +104,7 @@ public final class PingManager extends Manager {
defaultPingInterval = interval; defaultPingInterval = interval;
} }
private final Set<PingFailedListener> pingFailedListeners = Collections private final Set<PingFailedListener> pingFailedListeners = new CopyOnWriteArraySet<>();
.synchronizedSet(new HashSet<PingFailedListener>());
private final ScheduledExecutorService executorService; private final ScheduledExecutorService executorService;

View File

@ -66,6 +66,7 @@ import org.jivesoftware.smack.roster.rosterstore.RosterStore;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jxmpp.jid.BareJid; import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.Jid; import org.jxmpp.jid.Jid;
import org.jxmpp.jid.FullJid; import org.jxmpp.jid.FullJid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
@ -1674,9 +1675,16 @@ public final class Roster extends Manager {
final XMPPConnection connection = connection(); final XMPPConnection connection = connection();
RosterPacket rosterPacket = (RosterPacket) iqRequest; RosterPacket rosterPacket = (RosterPacket) iqRequest;
EntityFullJid localAddress = connection.getUser();
if (localAddress == null) {
LOGGER.warning("Ignoring roster push " + iqRequest + " while " + connection
+ " has no bound resource. This may be a server bug.");
return null;
}
// Roster push (RFC 6121, 2.1.6) // Roster push (RFC 6121, 2.1.6)
// A roster push with a non-empty from not matching our address MUST be ignored // A roster push with a non-empty from not matching our address MUST be ignored
EntityBareJid jid = connection.getUser().asEntityBareJid(); EntityBareJid jid = localAddress.asEntityBareJid();
Jid from = rosterPacket.getFrom(); Jid from = rosterPacket.getFrom();
if (from != null && !from.equals(jid)) { if (from != null && !from.equals(jid)) {
LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + jid + "' from='" + from LOGGER.warning("Ignoring roster push with a non matching 'from' ourJid='" + jid + "' from='" + from

View File

@ -16,10 +16,16 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Random; import java.util.Random;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.net.ssl.HttpsURLConnection;
import org.jivesoftware.smack.StanzaCollector; import org.jivesoftware.smack.StanzaCollector;
import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
@ -37,9 +43,12 @@ public abstract class AbstractSmackIntTest {
protected final long timeout; protected final long timeout;
protected AbstractSmackIntTest(String testRunId, long timeout) { protected final Configuration sinttestConfiguration;
protected AbstractSmackIntTest(String testRunId, Configuration configuration) {
this.testRunId = testRunId; this.testRunId = testRunId;
this.timeout = timeout; this.sinttestConfiguration = configuration;
this.timeout = configuration.replyTimeout;
} }
protected void performActionAndWaitUntilStanzaReceived(Runnable action, XMPPConnection connection, StanzaFilter filter) protected void performActionAndWaitUntilStanzaReceived(Runnable action, XMPPConnection connection, StanzaFilter filter)
@ -71,4 +80,19 @@ public abstract class AbstractSmackIntTest {
protected interface Condition { protected interface Condition {
boolean evaluate() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException; boolean evaluate() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException;
} }
protected File createNewTempFile() throws IOException {
File file = File.createTempFile("smack-integration-test-" + testRunId + "-temp-file", null);
file.deleteOnExit();
return file;
}
protected HttpURLConnection getHttpUrlConnectionFor(URL url) throws IOException {
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
if (sinttestConfiguration.tlsContext != null && urlConnection instanceof HttpsURLConnection) {
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
httpsUrlConnection.setSSLSocketFactory(sinttestConfiguration.tlsContext.getSocketFactory());
}
return urlConnection;
}
} }

View File

@ -41,7 +41,7 @@ public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest
protected final XMPPConnection connection; protected final XMPPConnection connection;
public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) { public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment.testRunId, environment.configuration.replyTimeout); super(environment.testRunId, environment.configuration);
this.connection = this.conOne = environment.conOne; this.connection = this.conOne = environment.conOne;
this.conTwo = environment.conTwo; this.conTwo = environment.conTwo;
this.conThree = environment.conThree; this.conThree = environment.conThree;

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2016 Florian Schmaus * Copyright 2015-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,14 +19,10 @@ package org.igniterealtime.smack.inttest;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import javax.net.ssl.SSLContext;
import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration; import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.DomainBareJid;
import eu.geekplace.javapinning.java7.Java7Pinning;
public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest { public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest {
private final SmackIntegrationTestEnvironment environment; private final SmackIntegrationTestEnvironment environment;
@ -39,7 +35,7 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
protected final DomainBareJid service; protected final DomainBareJid service;
public AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) { public AbstractSmackLowLevelIntegrationTest(SmackIntegrationTestEnvironment environment) {
super(environment.testRunId, environment.configuration.replyTimeout); super(environment.testRunId, environment.configuration);
this.environment = environment; this.environment = environment;
this.configuration = environment.configuration; this.configuration = environment.configuration;
this.service = configuration.service; this.service = configuration.service;
@ -47,9 +43,8 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
public final XMPPTCPConnectionConfiguration.Builder getConnectionConfiguration() throws KeyManagementException, NoSuchAlgorithmException { public final XMPPTCPConnectionConfiguration.Builder getConnectionConfiguration() throws KeyManagementException, NoSuchAlgorithmException {
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder(); XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
if (configuration.serviceTlsPin != null) { if (configuration.tlsContext != null) {
SSLContext sc = Java7Pinning.forPin(configuration.serviceTlsPin); builder.setCustomSSLContext(configuration.tlsContext);
builder.setCustomSSLContext(sc);
} }
builder.setSecurityMode(configuration.securityMode); builder.setSecurityMode(configuration.securityMode);
builder.setXmppDomain(service); builder.setXmppDomain(service);

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2016 Florian Schmaus * Copyright 2015-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,6 +19,8 @@ package org.igniterealtime.smack.inttest;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
@ -26,6 +28,8 @@ import java.util.Map.Entry;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import javax.net.ssl.SSLContext;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.Objects;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
@ -33,6 +37,8 @@ import org.jxmpp.jid.DomainBareJid;
import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
import eu.geekplace.javapinning.java7.Java7Pinning;
public final class Configuration { public final class Configuration {
public enum AccountRegistration { public enum AccountRegistration {
@ -45,6 +51,8 @@ public final class Configuration {
public final String serviceTlsPin; public final String serviceTlsPin;
public final SSLContext tlsContext;
public final SecurityMode securityMode; public final SecurityMode securityMode;
public final int replyTimeout; public final int replyTimeout;
@ -78,10 +86,16 @@ public final class Configuration {
private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout, private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername, boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests, String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests,
Set<String> testPackages, String adminAccountUsername, String adminAccountPassword) { Set<String> testPackages, String adminAccountUsername, String adminAccountPassword)
throws KeyManagementException, NoSuchAlgorithmException {
this.service = Objects.requireNonNull(service, this.service = Objects.requireNonNull(service,
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'."); "'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
this.serviceTlsPin = serviceTlsPin; this.serviceTlsPin = serviceTlsPin;
if (serviceTlsPin != null) {
tlsContext = Java7Pinning.forPin(serviceTlsPin);
} else {
tlsContext = null;
}
this.securityMode = securityMode; this.securityMode = securityMode;
if (replyTimeout > 0) { if (replyTimeout > 0) {
this.replyTimeout = replyTimeout; this.replyTimeout = replyTimeout;
@ -257,7 +271,7 @@ public final class Configuration {
return this; return this;
} }
public Configuration build() { public Configuration build() throws KeyManagementException, NoSuchAlgorithmException {
return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername, return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername,
accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests, accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests,
testPackages, adminAccountUsername, adminAccountPassword); testPackages, adminAccountUsername, adminAccountPassword);
@ -266,7 +280,8 @@ public final class Configuration {
private static final String SINTTEST = "sinttest."; private static final String SINTTEST = "sinttest.";
public static Configuration newConfiguration() throws IOException { public static Configuration newConfiguration()
throws IOException, KeyManagementException, NoSuchAlgorithmException {
Properties properties = new Properties(); Properties properties = new Properties();
File propertiesFile = findPropertiesFile(); File propertiesFile = findPropertiesFile();

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015-2016 Florian Schmaus * Copyright 2015-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -43,8 +43,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.net.ssl.SSLContext;
import org.igniterealtime.smack.inttest.IntTestUtil.UsernameAndPassword; import org.igniterealtime.smack.inttest.IntTestUtil.UsernameAndPassword;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.SmackConfiguration;
@ -64,8 +62,6 @@ import org.reflections.scanners.MethodParameterScanner;
import org.reflections.scanners.SubTypesScanner; import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner; import org.reflections.scanners.TypeAnnotationsScanner;
import eu.geekplace.javapinning.java7.Java7Pinning;
public class SmackIntegrationTestFramework { public class SmackIntegrationTestFramework {
private static final Logger LOGGER = Logger.getLogger(SmackIntegrationTestFramework.class.getName()); private static final Logger LOGGER = Logger.getLogger(SmackIntegrationTestFramework.class.getName());
@ -553,9 +549,8 @@ public class SmackIntegrationTestFramework {
.setResource(middlefix + '-' + testRunResult.testRunId) .setResource(middlefix + '-' + testRunResult.testRunId)
.setSecurityMode(config.securityMode); .setSecurityMode(config.securityMode);
// @formatter:on // @formatter:on
if (StringUtils.isNotEmpty(config.serviceTlsPin)) { if (config.tlsContext != null) {
SSLContext sc = Java7Pinning.forPin(config.serviceTlsPin); builder.setCustomSSLContext(config.tlsContext);
builder.setCustomSSLContext(sc);
} }
XMPPTCPConnection connection = new XMPPTCPConnection(builder.build()); XMPPTCPConnection connection = new XMPPTCPConnection(builder.build());
connection.connect(); connection.connect();
@ -581,9 +576,8 @@ public class SmackIntegrationTestFramework {
SmackException, IOException, XMPPException { SmackException, IOException, XMPPException {
Configuration config = environment.configuration; Configuration config = environment.configuration;
XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder(); XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
if (config.serviceTlsPin != null) { if (config.tlsContext != null) {
SSLContext sc = Java7Pinning.forPin(config.serviceTlsPin); builder.setCustomSSLContext(config.tlsContext);
builder.setCustomSSLContext(sc);
} }
builder.setSecurityMode(config.securityMode); builder.setSecurityMode(config.securityMode);
builder.setXmppDomain(config.service); builder.setXmppDomain(config.service);

View File

@ -1,21 +0,0 @@
/**
*
* Copyright 2015 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.
*/
/**
* TODO describe me.
*/
package org.jivesoftware.smackx.filetransfer;

View File

@ -0,0 +1 @@
../../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/package-info.java

View File

@ -0,0 +1,105 @@
/**
*
* Copyright 2017 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.httpfileupload;
import static org.junit.Assert.assertArrayEquals;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTest;
import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment;
import org.igniterealtime.smack.inttest.TestNotPossibleException;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
public class HttpFileUploadIntegrationTest extends AbstractSmackIntegrationTest {
private static final int FILE_SIZE = 1024*128;
private final HttpFileUploadManager hfumOne;
public HttpFileUploadIntegrationTest(SmackIntegrationTestEnvironment environment) throws XMPPErrorException,
NotConnectedException, NoResponseException, InterruptedException, TestNotPossibleException {
super(environment);
hfumOne = HttpFileUploadManager.getInstanceFor(conOne);
if (!hfumOne.discoverUploadService()) {
throw new TestNotPossibleException(
"HttpFileUploadManager was unable to discover a HTTP File Upload service");
}
UploadService uploadService = hfumOne.getDefaultUploadService();
if (!uploadService.acceptsFileOfSize(FILE_SIZE)) {
throw new TestNotPossibleException("The upload service at " + uploadService.getAddress()
+ " does not accept files of size " + FILE_SIZE
+ ". It only accepts files with a maximum size of " + uploadService.getMaxFileSize());
}
hfumOne.setTlsContext(environment.configuration.tlsContext);
}
@SmackIntegrationTest
public void httpFileUploadTest() throws FileNotFoundException, IOException, XMPPErrorException, InterruptedException, SmackException {
final int fileSize = FILE_SIZE;
File file = createNewTempFile();
FileOutputStream fos = new FileOutputStream(file.getCanonicalPath());
byte[] upBytes;
try {
upBytes = new byte[fileSize];
INSECURE_RANDOM.nextBytes(upBytes);
fos.write(upBytes);
}
finally {
fos.close();
}
URL getUrl = hfumOne.uploadFile(file, new UploadProgressListener() {
@Override
public void onUploadProgress(long uploadedBytes, long totalBytes) {
double progress = uploadedBytes / totalBytes;
LOGGER.fine("HTTP File Upload progress " + progress + "% (" + uploadedBytes + '/' + totalBytes + ')');
}
});
HttpURLConnection urlConnection = getHttpUrlConnectionFor(getUrl);
ByteArrayOutputStream baos = new ByteArrayOutputStream(fileSize);
byte[] buffer = new byte[4096];
int n;
try {
InputStream is = new BufferedInputStream(urlConnection.getInputStream());
while ((n = is.read(buffer)) != -1) {
baos.write(buffer, 0, n);
}
}
finally {
urlConnection.disconnect();
}
byte[] downBytes = baos.toByteArray();
assertArrayEquals(upBytes, downBytes);
}
}

View File

@ -0,0 +1 @@
../../../../../../../../smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/package-info.java

View File

@ -1,21 +0,0 @@
/**
*
* Copyright 2015 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.
*/
/**
* TODO describe me.
*/
package org.jivesoftware.smackx.iot;

View File

@ -0,0 +1 @@
../../../../../../../../smack-experimental/src/main/java/org/jivesoftware/smackx/iot/package-info.java

View File

@ -1,21 +0,0 @@
/**
*
* Copyright 2015 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.
*/
/**
* TODO describe me.
*/
package org.jivesoftware.smackx.iqversion;

View File

@ -0,0 +1 @@
../../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/iqversion/package-info.java

View File

@ -1,21 +0,0 @@
/**
*
* Copyright 2015 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.
*/
/**
* TODO describe me.
*/
package org.jivesoftware.smackx.muc;

View File

@ -0,0 +1 @@
../../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/muc/package-info.java

View File

@ -1,21 +0,0 @@
/**
*
* Copyright 2015 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.
*/
/**
* TODO describe me.
*/
package org.jivesoftware.smackx;

View File

@ -0,0 +1 @@
../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/package-info.java

View File

@ -1,21 +0,0 @@
/**
*
* Copyright 2015 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.
*/
/**
* TODO describe me.
*/
package org.jivesoftware.smackx.ping;

View File

@ -0,0 +1 @@
../../../../../../../../smack-extensions/src/main/java/org/jivesoftware/smackx/ping/package-info.java

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2017 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,11 +16,16 @@
*/ */
package org.igniterealtime.smack.inttest; package org.igniterealtime.smack.inttest;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import org.jxmpp.jid.JidTestUtil; import org.jxmpp.jid.JidTestUtil;
public class SmackIntegrationTestUnitTestUtil { public class SmackIntegrationTestUnitTestUtil {
public static DummySmackIntegrationTestFramework getFrameworkForUnitTest(Class<? extends AbstractSmackIntTest> unitTest) { public static DummySmackIntegrationTestFramework getFrameworkForUnitTest(
Class<? extends AbstractSmackIntTest> unitTest)
throws KeyManagementException, NoSuchAlgorithmException {
// @formatter:off // @formatter:off
Configuration configuration = Configuration.builder() Configuration configuration = Configuration.builder()
.setService(JidTestUtil.DOMAIN_BARE_JID_1) .setService(JidTestUtil.DOMAIN_BARE_JID_1)