mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-11-30 02:02:06 +01:00
SMACK-361 Added support for Entity Capabilities.
git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/branches/smack_3_3_0@13560 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
parent
1cdb86989a
commit
21be8c55ee
33 changed files with 2395 additions and 88 deletions
|
@ -30,8 +30,11 @@
|
||||||
|
|
||||||
<!-- Port of the local Socks5 proxy -->
|
<!-- Port of the local Socks5 proxy -->
|
||||||
<packetCollectorSize>10000</packetCollectorSize>
|
<packetCollectorSize>10000</packetCollectorSize>
|
||||||
|
|
||||||
<!-- Default interval (seconds) in which the Ping Manager sends ping request to the server (30 minutes) -->
|
<!-- Default interval (seconds) in which the Ping Manager sends ping request to the server (30 minutes) -->
|
||||||
<defaultPingInterval>1800</defaultPingInterval>
|
<defaultPingInterval>1800</defaultPingInterval>
|
||||||
|
|
||||||
|
<!-- Automatic enable Entity Caps (XEP-0115) for new connections -->
|
||||||
|
<autoEnableEntityCaps>false</autoEnableEntityCaps>
|
||||||
|
|
||||||
</smack>
|
</smack>
|
||||||
|
|
|
@ -678,4 +678,11 @@
|
||||||
<namespace>urn:xmpp:receipts</namespace>
|
<namespace>urn:xmpp:receipts</namespace>
|
||||||
<className>org.jivesoftware.smackx.receipts.DeliveryReceiptRequest$Provider</className>
|
<className>org.jivesoftware.smackx.receipts.DeliveryReceiptRequest$Provider</className>
|
||||||
</extensionProvider>
|
</extensionProvider>
|
||||||
|
|
||||||
|
<!-- XEP-0115 Entity Capabilities -->
|
||||||
|
<extensionProvider>
|
||||||
|
<elementName>c</elementName>
|
||||||
|
<namespace>http://jabber.org/protocol/caps</namespace>
|
||||||
|
<className>org.jivesoftware.smackx.entitycaps.provider.CapsExtensionProvider</className>
|
||||||
|
</extensionProvider>
|
||||||
</smackProviders>
|
</smackProviders>
|
||||||
|
|
|
@ -204,6 +204,11 @@ public abstract class Connection {
|
||||||
*/
|
*/
|
||||||
protected final ConnectionConfiguration config;
|
protected final ConnectionConfiguration config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the Caps Node information for the used XMPP service (i.e. the XMPP server)
|
||||||
|
*/
|
||||||
|
private String serviceCapsNode;
|
||||||
|
|
||||||
protected XMPPInputOutputStream compressionHandler;
|
protected XMPPInputOutputStream compressionHandler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -795,7 +800,29 @@ public abstract class Connection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the servers Entity Caps node
|
||||||
|
*
|
||||||
|
* Connection holds this information in order to avoid a dependency to
|
||||||
|
* smackx where EntityCapsManager lives from smack.
|
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
*/
|
||||||
|
protected void setServiceCapsNode(String node) {
|
||||||
|
serviceCapsNode = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the servers Entity Caps node
|
||||||
|
*
|
||||||
|
* Connection holds this information in order to avoid a dependency to
|
||||||
|
* smackx where EntityCapsManager lives from smack.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getServiceCapsNode() {
|
||||||
|
return serviceCapsNode;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper class to associate a packet filter with a listener.
|
* A wrapper class to associate a packet filter with a listener.
|
||||||
|
|
|
@ -393,6 +393,19 @@ class PacketReader {
|
||||||
// The server requires the client to bind a resource to the stream
|
// The server requires the client to bind a resource to the stream
|
||||||
connection.getSASLAuthentication().bindingRequired();
|
connection.getSASLAuthentication().bindingRequired();
|
||||||
}
|
}
|
||||||
|
// Set the entity caps node for the server if one is send
|
||||||
|
// See http://xmpp.org/extensions/xep-0115.html#stream
|
||||||
|
else if (parser.getName().equals("c")) {
|
||||||
|
String node = parser.getAttributeValue(null, "node");
|
||||||
|
String ver = parser.getAttributeValue(null, "ver");
|
||||||
|
if (ver != null && node != null) {
|
||||||
|
String capsNode = node + "#" + ver;
|
||||||
|
// In order to avoid a dependency from smack to smackx
|
||||||
|
// we have to set the services caps node in the connection
|
||||||
|
// and not directly in the EntityCapsManager
|
||||||
|
connection.setServiceCapsNode(capsNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (parser.getName().equals("session")) {
|
else if (parser.getName().equals("session")) {
|
||||||
// The server supports sessions
|
// The server supports sessions
|
||||||
connection.getSASLAuthentication().sessionsSupported();
|
connection.getSASLAuthentication().sessionsSupported();
|
||||||
|
|
|
@ -63,6 +63,11 @@ public final class SmackConfiguration {
|
||||||
*/
|
*/
|
||||||
private static int defaultPingInterval = 1800; // 30 min (30*60)
|
private static int defaultPingInterval = 1800; // 30 min (30*60)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This automatically enables EntityCaps for new connections if it is set to true
|
||||||
|
*/
|
||||||
|
private static boolean autoEnableEntityCaps = false;
|
||||||
|
|
||||||
private SmackConfiguration() {
|
private SmackConfiguration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,6 +120,9 @@ public final class SmackConfiguration {
|
||||||
else if (parser.getName().equals("defaultPingInterval")) {
|
else if (parser.getName().equals("defaultPingInterval")) {
|
||||||
defaultPingInterval = parseIntProperty(parser, defaultPingInterval);
|
defaultPingInterval = parseIntProperty(parser, defaultPingInterval);
|
||||||
}
|
}
|
||||||
|
else if (parser.getName().equals("autoEnableEntityCaps")) {
|
||||||
|
autoEnableEntityCaps = Boolean.parseBoolean(parser.nextText());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
eventType = parser.next();
|
eventType = parser.next();
|
||||||
}
|
}
|
||||||
|
@ -329,6 +337,23 @@ public final class SmackConfiguration {
|
||||||
SmackConfiguration.defaultPingInterval = defaultPingInterval;
|
SmackConfiguration.defaultPingInterval = defaultPingInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if Entity Caps are enabled as default for every new connection
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean autoEnableEntityCaps() {
|
||||||
|
return autoEnableEntityCaps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set if Entity Caps are enabled or disabled for every new connection
|
||||||
|
*
|
||||||
|
* @param true if Entity Caps should be auto enabled, false if not
|
||||||
|
*/
|
||||||
|
public static void setAutoEnableEntityCaps(boolean b) {
|
||||||
|
autoEnableEntityCaps = b;
|
||||||
|
}
|
||||||
|
|
||||||
private static void parseClassToLoad(XmlPullParser parser) throws Exception {
|
private static void parseClassToLoad(XmlPullParser parser) throws Exception {
|
||||||
String className = parser.nextText();
|
String className = parser.nextText();
|
||||||
// Attempt to load the class so that the class can get initialized
|
// Attempt to load the class so that the class can get initialized
|
||||||
|
|
|
@ -43,6 +43,14 @@ public abstract class IQ extends Packet {
|
||||||
|
|
||||||
private Type type = Type.GET;
|
private Type type = Type.GET;
|
||||||
|
|
||||||
|
public IQ() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IQ(IQ iq) {
|
||||||
|
super(iq);
|
||||||
|
type = iq.getType();
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Returns the type of the IQ packet.
|
* Returns the type of the IQ packet.
|
||||||
*
|
*
|
||||||
|
|
|
@ -90,6 +90,22 @@ public abstract class Packet {
|
||||||
private final Map<String,Object> properties = new HashMap<String, Object>();
|
private final Map<String,Object> properties = new HashMap<String, Object>();
|
||||||
private XMPPError error = null;
|
private XMPPError error = null;
|
||||||
|
|
||||||
|
public Packet() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Packet(Packet p) {
|
||||||
|
packetID = p.getPacketID();
|
||||||
|
to = p.getTo();
|
||||||
|
from = p.getFrom();
|
||||||
|
xmlns = p.xmlns;
|
||||||
|
error = p.error;
|
||||||
|
|
||||||
|
// Copy extensions
|
||||||
|
for (PacketExtension pe : p.getExtensions()) {
|
||||||
|
addExtension(pe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the unique ID of the packet. The returned value could be <tt>null</tt> when
|
* Returns the unique ID of the packet. The returned value could be <tt>null</tt> when
|
||||||
* ID_NOT_AVAILABLE was set as the packet's id.
|
* ID_NOT_AVAILABLE was set as the packet's id.
|
||||||
|
@ -247,14 +263,25 @@ public abstract class Packet {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a packet extension to the packet.
|
* Adds a packet extension to the packet. Does nothing if extension is null.
|
||||||
*
|
*
|
||||||
* @param extension a packet extension.
|
* @param extension a packet extension.
|
||||||
*/
|
*/
|
||||||
public void addExtension(PacketExtension extension) {
|
public void addExtension(PacketExtension extension) {
|
||||||
|
if (extension == null) return;
|
||||||
packetExtensions.add(extension);
|
packetExtensions.add(extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a collection of packet extensions to the packet. Does nothing if extensions is null.
|
||||||
|
*
|
||||||
|
* @param extensions a collection of packet extensions
|
||||||
|
*/
|
||||||
|
public void addExtensions(Collection<PacketExtension> extensions) {
|
||||||
|
if (extensions == null) return;
|
||||||
|
packetExtensions.addAll(extensions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a packet extension from the packet.
|
* Removes a packet extension from the packet.
|
||||||
*
|
*
|
||||||
|
@ -266,7 +293,7 @@ public abstract class Packet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the packet property with the specified name or <tt>null</tt> if the
|
* Returns the packet property with the specified name or <tt>null</tt> if the
|
||||||
* property doesn't exist. Property values that were orginally primitives will
|
* property doesn't exist. Property values that were originally primitives will
|
||||||
* be returned as their object equivalent. For example, an int property will be
|
* be returned as their object equivalent. For example, an int property will be
|
||||||
* returned as an Integer, a double as a Double, etc.
|
* returned as an Integer, a double as a Double, etc.
|
||||||
*
|
*
|
||||||
|
@ -456,4 +483,4 @@ public abstract class Packet {
|
||||||
result = 31 * result + (error != null ? error.hashCode() : 0);
|
result = 31 * result + (error != null ? error.hashCode() : 0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
187
source/org/jivesoftware/smack/util/Base32Encoder.java
Normal file
187
source/org/jivesoftware/smack/util/Base32Encoder.java
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
/**
|
||||||
|
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base32 string encoding is useful for when filenames case-insensitive filesystems are encoded.
|
||||||
|
* Base32 representation takes roughly 20% more space then Base64.
|
||||||
|
*
|
||||||
|
* @author Florian Schmaus
|
||||||
|
* Based on code by Brian Wellington (bwelling@xbill.org)
|
||||||
|
* @see <a href="http://en.wikipedia.org/wiki/Base32">Base32 Wikipedia entry<a>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class Base32Encoder implements StringEncoder {
|
||||||
|
|
||||||
|
private static Base32Encoder instance;
|
||||||
|
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ2345678";
|
||||||
|
|
||||||
|
private Base32Encoder() {
|
||||||
|
// Use getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Base32Encoder getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new Base32Encoder();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String decode(String str) {
|
||||||
|
ByteArrayOutputStream bs = new ByteArrayOutputStream();
|
||||||
|
byte[] raw = str.getBytes();
|
||||||
|
for (int i = 0; i < raw.length; i++) {
|
||||||
|
char c = (char) raw[i];
|
||||||
|
if (!Character.isWhitespace(c)) {
|
||||||
|
c = Character.toUpperCase(c);
|
||||||
|
bs.write((byte) c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (bs.size() % 8 != 0)
|
||||||
|
bs.write('8');
|
||||||
|
|
||||||
|
byte[] in = bs.toByteArray();
|
||||||
|
|
||||||
|
bs.reset();
|
||||||
|
DataOutputStream ds = new DataOutputStream(bs);
|
||||||
|
|
||||||
|
for (int i = 0; i < in.length / 8; i++) {
|
||||||
|
short[] s = new short[8];
|
||||||
|
int[] t = new int[5];
|
||||||
|
|
||||||
|
int padlen = 8;
|
||||||
|
for (int j = 0; j < 8; j++) {
|
||||||
|
char c = (char) in[i * 8 + j];
|
||||||
|
if (c == '8')
|
||||||
|
break;
|
||||||
|
s[j] = (short) ALPHABET.indexOf(in[i * 8 + j]);
|
||||||
|
if (s[j] < 0)
|
||||||
|
return null;
|
||||||
|
padlen--;
|
||||||
|
}
|
||||||
|
int blocklen = paddingToLen(padlen);
|
||||||
|
if (blocklen < 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// all 5 bits of 1st, high 3 (of 5) of 2nd
|
||||||
|
t[0] = (s[0] << 3) | s[1] >> 2;
|
||||||
|
// lower 2 of 2nd, all 5 of 3rd, high 1 of 4th
|
||||||
|
t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4);
|
||||||
|
// lower 4 of 4th, high 4 of 5th
|
||||||
|
t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F);
|
||||||
|
// lower 1 of 5th, all 5 of 6th, high 2 of 7th
|
||||||
|
t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3);
|
||||||
|
// lower 3 of 7th, all of 8th
|
||||||
|
t[4] = ((s[6] & 0x07) << 5) | s[7];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (int j = 0; j < blocklen; j++)
|
||||||
|
ds.writeByte((byte) (t[j] & 0xFF));
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(bs.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String encode(String str) {
|
||||||
|
byte[] b = str.getBytes();
|
||||||
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
for (int i = 0; i < (b.length + 4) / 5; i++) {
|
||||||
|
short s[] = new short[5];
|
||||||
|
int t[] = new int[8];
|
||||||
|
|
||||||
|
int blocklen = 5;
|
||||||
|
for (int j = 0; j < 5; j++) {
|
||||||
|
if ((i * 5 + j) < b.length)
|
||||||
|
s[j] = (short) (b[i * 5 + j] & 0xFF);
|
||||||
|
else {
|
||||||
|
s[j] = 0;
|
||||||
|
blocklen--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int padlen = lenToPadding(blocklen);
|
||||||
|
|
||||||
|
// convert the 5 byte block into 8 characters (values 0-31).
|
||||||
|
|
||||||
|
// upper 5 bits from first byte
|
||||||
|
t[0] = (byte) ((s[0] >> 3) & 0x1F);
|
||||||
|
// lower 3 bits from 1st byte, upper 2 bits from 2nd.
|
||||||
|
t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03));
|
||||||
|
// bits 5-1 from 2nd.
|
||||||
|
t[2] = (byte) ((s[1] >> 1) & 0x1F);
|
||||||
|
// lower 1 bit from 2nd, upper 4 from 3rd
|
||||||
|
t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F));
|
||||||
|
// lower 4 from 3rd, upper 1 from 4th.
|
||||||
|
t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01));
|
||||||
|
// bits 6-2 from 4th
|
||||||
|
t[5] = (byte) ((s[3] >> 2) & 0x1F);
|
||||||
|
// lower 2 from 4th, upper 3 from 5th;
|
||||||
|
t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07));
|
||||||
|
// lower 5 from 5th;
|
||||||
|
t[7] = (byte) (s[4] & 0x1F);
|
||||||
|
|
||||||
|
// write out the actual characters.
|
||||||
|
for (int j = 0; j < t.length - padlen; j++) {
|
||||||
|
char c = ALPHABET.charAt(t[j]);
|
||||||
|
os.write(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new String(os.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int lenToPadding(int blocklen) {
|
||||||
|
switch (blocklen) {
|
||||||
|
case 1:
|
||||||
|
return 6;
|
||||||
|
case 2:
|
||||||
|
return 4;
|
||||||
|
case 3:
|
||||||
|
return 3;
|
||||||
|
case 4:
|
||||||
|
return 1;
|
||||||
|
case 5:
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int paddingToLen(int padlen) {
|
||||||
|
switch (padlen) {
|
||||||
|
case 6:
|
||||||
|
return 1;
|
||||||
|
case 4:
|
||||||
|
return 2;
|
||||||
|
case 3:
|
||||||
|
return 3;
|
||||||
|
case 1:
|
||||||
|
return 4;
|
||||||
|
case 0:
|
||||||
|
return 5;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
source/org/jivesoftware/smack/util/Base64Encoder.java
Normal file
44
source/org/jivesoftware/smack/util/Base64Encoder.java
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Florian Schmaus
|
||||||
|
*/
|
||||||
|
public class Base64Encoder implements StringEncoder {
|
||||||
|
|
||||||
|
private static Base64Encoder instance;
|
||||||
|
|
||||||
|
private Base64Encoder() {
|
||||||
|
// Use getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Base64Encoder getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new Base64Encoder();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encode(String s) {
|
||||||
|
return Base64.encodeBytes(s.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String decode(String s) {
|
||||||
|
return new String(Base64.decode(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
38
source/org/jivesoftware/smack/util/StringEncoder.java
Normal file
38
source/org/jivesoftware/smack/util/StringEncoder.java
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* All rights reserved. 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Florian Schmaus
|
||||||
|
*/
|
||||||
|
package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
|
// TODO move StringEncoder, Base64Encoder and Base32Encoder to smack.util
|
||||||
|
|
||||||
|
public interface StringEncoder {
|
||||||
|
/**
|
||||||
|
* Encodes an string to another representation
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String encode(String string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes an string back to it's initial representation
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String decode(String string);
|
||||||
|
}
|
|
@ -42,17 +42,22 @@ import org.jivesoftware.smackx.packet.DataForm;
|
||||||
* Depending of the form's type different operations are available. For example, it's only possible
|
* Depending of the form's type different operations are available. For example, it's only possible
|
||||||
* to set answers if the form is of type "submit".
|
* to set answers if the form is of type "submit".
|
||||||
*
|
*
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0004.html">XEP-0004 Data Forms</a>
|
||||||
|
*
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public class Form {
|
public class Form {
|
||||||
|
|
||||||
public static final String TYPE_FORM = "form";
|
public static final String TYPE_FORM = "form";
|
||||||
public static final String TYPE_SUBMIT = "submit";
|
public static final String TYPE_SUBMIT = "submit";
|
||||||
public static final String TYPE_CANCEL = "cancel";
|
public static final String TYPE_CANCEL = "cancel";
|
||||||
public static final String TYPE_RESULT = "result";
|
public static final String TYPE_RESULT = "result";
|
||||||
|
|
||||||
|
public static final String NAMESPACE = "jabber:x:data";
|
||||||
|
public static final String ELEMENT = "x";
|
||||||
|
|
||||||
private DataForm dataForm;
|
private DataForm dataForm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new ReportedData if the packet is used for gathering data and includes an
|
* Returns a new ReportedData if the packet is used for gathering data and includes an
|
||||||
* extension that matches the elementName and namespace "x","jabber:x:data".
|
* extension that matches the elementName and namespace "x","jabber:x:data".
|
||||||
|
|
|
@ -299,6 +299,26 @@ public class FormField {
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (obj == this)
|
||||||
|
return true;
|
||||||
|
if (obj.getClass() != getClass())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
FormField other = (FormField) obj;
|
||||||
|
|
||||||
|
String thisXml = toXML();
|
||||||
|
String otherXml = other.toXML();
|
||||||
|
|
||||||
|
if (thisXml.equals(otherXml)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the available option of a given FormField.
|
* Represents the available option of a given FormField.
|
||||||
*
|
*
|
||||||
|
@ -354,5 +374,27 @@ public class FormField {
|
||||||
buf.append("</option>");
|
buf.append("</option>");
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (obj == this)
|
||||||
|
return true;
|
||||||
|
if (obj.getClass() != getClass())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Option other = (Option) obj;
|
||||||
|
|
||||||
|
if (!value.equals(other.value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String thisLabel = label == null ? "" : label;
|
||||||
|
String otherLabel = other.label == null ? "" : other.label;
|
||||||
|
|
||||||
|
if (!thisLabel.equals(otherLabel))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
package org.jivesoftware.smackx;
|
package org.jivesoftware.smackx;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
||||||
import org.jivesoftware.smackx.packet.DiscoverItems;
|
import org.jivesoftware.smackx.packet.DiscoverItems;
|
||||||
|
|
||||||
|
@ -36,7 +37,7 @@ import java.util.List;
|
||||||
* @author Gaston Dombiak
|
* @author Gaston Dombiak
|
||||||
*/
|
*/
|
||||||
public interface NodeInformationProvider {
|
public interface NodeInformationProvider {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of the Items {@link org.jivesoftware.smackx.packet.DiscoverItems.Item}
|
* Returns a list of the Items {@link org.jivesoftware.smackx.packet.DiscoverItems.Item}
|
||||||
* defined in the node. For example, the MUC protocol specifies that an XMPP client should
|
* defined in the node. For example, the MUC protocol specifies that an XMPP client should
|
||||||
|
@ -65,4 +66,10 @@ public interface NodeInformationProvider {
|
||||||
*/
|
*/
|
||||||
public abstract List<DiscoverInfo.Identity> getNodeIdentities();
|
public abstract List<DiscoverInfo.Identity> getNodeIdentities();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of the packet extensions defined in the node.
|
||||||
|
*
|
||||||
|
* @return a list of the packet extensions defined in the node.
|
||||||
|
*/
|
||||||
|
public abstract List<PacketExtension> getNodePacketExtensions();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,11 @@ import org.jivesoftware.smack.filter.PacketIDFilter;
|
||||||
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smack.packet.XMPPError;
|
import org.jivesoftware.smack.packet.XMPPError;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
|
||||||
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
|
||||||
import org.jivesoftware.smackx.packet.DiscoverItems;
|
import org.jivesoftware.smackx.packet.DiscoverItems;
|
||||||
import org.jivesoftware.smackx.packet.DataForm;
|
import org.jivesoftware.smackx.packet.DataForm;
|
||||||
|
|
||||||
|
@ -47,8 +50,13 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
*/
|
*/
|
||||||
public class ServiceDiscoveryManager {
|
public class ServiceDiscoveryManager {
|
||||||
|
|
||||||
private static String identityName = "Smack";
|
private static final String DEFAULT_IDENTITY_NAME = "Smack";
|
||||||
private static String identityType = "pc";
|
private static final String DEFAULT_IDENTITY_CATEGORY = "client";
|
||||||
|
private static final String DEFAULT_IDENTITY_TYPE = "pc";
|
||||||
|
|
||||||
|
private static List<DiscoverInfo.Identity> identities = new LinkedList<DiscoverInfo.Identity>();
|
||||||
|
|
||||||
|
private EntityCapsManager capsManager;
|
||||||
|
|
||||||
private static Map<Connection, ServiceDiscoveryManager> instances =
|
private static Map<Connection, ServiceDiscoveryManager> instances =
|
||||||
new ConcurrentHashMap<Connection, ServiceDiscoveryManager>();
|
new ConcurrentHashMap<Connection, ServiceDiscoveryManager>();
|
||||||
|
@ -66,6 +74,7 @@ public class ServiceDiscoveryManager {
|
||||||
new ServiceDiscoveryManager(connection);
|
new ServiceDiscoveryManager(connection);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
identities.add(new Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, DEFAULT_IDENTITY_TYPE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,6 +86,7 @@ public class ServiceDiscoveryManager {
|
||||||
*/
|
*/
|
||||||
public ServiceDiscoveryManager(Connection connection) {
|
public ServiceDiscoveryManager(Connection connection) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +108,12 @@ public class ServiceDiscoveryManager {
|
||||||
* in a disco request.
|
* in a disco request.
|
||||||
*/
|
*/
|
||||||
public static String getIdentityName() {
|
public static String getIdentityName() {
|
||||||
return identityName;
|
DiscoverInfo.Identity identity = identities.get(0);
|
||||||
|
if (identity != null) {
|
||||||
|
return identity.getName();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,7 +124,9 @@ public class ServiceDiscoveryManager {
|
||||||
* in a disco request.
|
* in a disco request.
|
||||||
*/
|
*/
|
||||||
public static void setIdentityName(String name) {
|
public static void setIdentityName(String name) {
|
||||||
identityName = name;
|
DiscoverInfo.Identity identity = identities.remove(0);
|
||||||
|
identity = new DiscoverInfo.Identity(DEFAULT_IDENTITY_CATEGORY, name, DEFAULT_IDENTITY_TYPE);
|
||||||
|
identities.add(identity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,7 +138,12 @@ public class ServiceDiscoveryManager {
|
||||||
* disco request.
|
* disco request.
|
||||||
*/
|
*/
|
||||||
public static String getIdentityType() {
|
public static String getIdentityType() {
|
||||||
return identityType;
|
DiscoverInfo.Identity identity = identities.get(0);
|
||||||
|
if (identity != null) {
|
||||||
|
return identity.getType();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,7 +155,22 @@ public class ServiceDiscoveryManager {
|
||||||
* disco request.
|
* disco request.
|
||||||
*/
|
*/
|
||||||
public static void setIdentityType(String type) {
|
public static void setIdentityType(String type) {
|
||||||
identityType = type;
|
DiscoverInfo.Identity identity = identities.get(0);
|
||||||
|
if (identity != null) {
|
||||||
|
identity.setType(type);
|
||||||
|
} else {
|
||||||
|
identity = new DiscoverInfo.Identity(DEFAULT_IDENTITY_CATEGORY, DEFAULT_IDENTITY_NAME, type);
|
||||||
|
identities.add(identity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all identities of this client as unmodifiable Collection
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<DiscoverInfo.Identity> getIdentities() {
|
||||||
|
return Collections.unmodifiableList(identities);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,13 +227,10 @@ public class ServiceDiscoveryManager {
|
||||||
NodeInformationProvider nodeInformationProvider =
|
NodeInformationProvider nodeInformationProvider =
|
||||||
getNodeInformationProvider(discoverItems.getNode());
|
getNodeInformationProvider(discoverItems.getNode());
|
||||||
if (nodeInformationProvider != null) {
|
if (nodeInformationProvider != null) {
|
||||||
// Specified node was found
|
// Specified node was found, add node items
|
||||||
List<DiscoverItems.Item> items = nodeInformationProvider.getNodeItems();
|
response.addItems(nodeInformationProvider.getNodeItems());
|
||||||
if (items != null) {
|
// Add packet extensions
|
||||||
for (DiscoverItems.Item item : items) {
|
response.addExtensions(nodeInformationProvider.getNodePacketExtensions());
|
||||||
response.addItem(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if(discoverItems.getNode() != null) {
|
} else if(discoverItems.getNode() != null) {
|
||||||
// Return <item-not-found/> error since client doesn't contain
|
// Return <item-not-found/> error since client doesn't contain
|
||||||
// the specified node
|
// the specified node
|
||||||
|
@ -222,22 +256,12 @@ public class ServiceDiscoveryManager {
|
||||||
response.setTo(discoverInfo.getFrom());
|
response.setTo(discoverInfo.getFrom());
|
||||||
response.setPacketID(discoverInfo.getPacketID());
|
response.setPacketID(discoverInfo.getPacketID());
|
||||||
response.setNode(discoverInfo.getNode());
|
response.setNode(discoverInfo.getNode());
|
||||||
// Add the client's identity and features only if "node" is null
|
// Add the client's identity and features only if "node" is null
|
||||||
|
// and if the request was not send to a node. If Entity Caps are
|
||||||
|
// enabled the client's identity and features are may also added
|
||||||
|
// if the right node is chosen
|
||||||
if (discoverInfo.getNode() == null) {
|
if (discoverInfo.getNode() == null) {
|
||||||
// Set this client identity
|
addDiscoverInfoTo(response);
|
||||||
DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client",
|
|
||||||
getIdentityName());
|
|
||||||
identity.setType(getIdentityType());
|
|
||||||
response.addIdentity(identity);
|
|
||||||
// Add the registered features to the response
|
|
||||||
synchronized (features) {
|
|
||||||
for (Iterator<String> it = getFeatures(); it.hasNext();) {
|
|
||||||
response.addFeature(it.next());
|
|
||||||
}
|
|
||||||
if (extendedInfo != null) {
|
|
||||||
response.addExtension(extendedInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Disco#info was sent to a node. Check if we have information of the
|
// Disco#info was sent to a node. Check if we have information of the
|
||||||
|
@ -246,20 +270,11 @@ public class ServiceDiscoveryManager {
|
||||||
getNodeInformationProvider(discoverInfo.getNode());
|
getNodeInformationProvider(discoverInfo.getNode());
|
||||||
if (nodeInformationProvider != null) {
|
if (nodeInformationProvider != null) {
|
||||||
// Node was found. Add node features
|
// Node was found. Add node features
|
||||||
List<String> features = nodeInformationProvider.getNodeFeatures();
|
response.addFeatures(nodeInformationProvider.getNodeFeatures());
|
||||||
if (features != null) {
|
|
||||||
for(String feature : features) {
|
|
||||||
response.addFeature(feature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add node identities
|
// Add node identities
|
||||||
List<DiscoverInfo.Identity> identities =
|
response.addIdentities(nodeInformationProvider.getNodeIdentities());
|
||||||
nodeInformationProvider.getNodeIdentities();
|
// Add packet extensions
|
||||||
if (identities != null) {
|
response.addExtensions(nodeInformationProvider.getNodePacketExtensions());
|
||||||
for (DiscoverInfo.Identity identity : identities) {
|
|
||||||
response.addIdentity(identity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Return <item-not-found/> error since specified node was not found
|
// Return <item-not-found/> error since specified node was not found
|
||||||
|
@ -274,6 +289,26 @@ public class ServiceDiscoveryManager {
|
||||||
connection.addPacketListener(packetListener, packetFilter);
|
connection.addPacketListener(packetListener, packetFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add discover info response data.
|
||||||
|
*
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0030.html#info-basic">XEP-30 Basic Protocol; Example 2</a>
|
||||||
|
*
|
||||||
|
* @param response the discover info response packet
|
||||||
|
*/
|
||||||
|
public void addDiscoverInfoTo(DiscoverInfo response) {
|
||||||
|
// First add the identities of the connection
|
||||||
|
response.addIdentities(identities);
|
||||||
|
|
||||||
|
// Add the registered features to the response
|
||||||
|
synchronized (features) {
|
||||||
|
for (Iterator<String> it = getFeatures(); it.hasNext();) {
|
||||||
|
response.addFeature(it.next());
|
||||||
|
}
|
||||||
|
response.addExtension(extendedInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the NodeInformationProvider responsible for providing information
|
* Returns the NodeInformationProvider responsible for providing information
|
||||||
* (ie items) related to a given node or <tt>null</null> if none.<p>
|
* (ie items) related to a given node or <tt>null</null> if none.<p>
|
||||||
|
@ -334,6 +369,17 @@ public class ServiceDiscoveryManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the supported features by this XMPP entity.
|
||||||
|
*
|
||||||
|
* @return a copy of the List on the supported features by this XMPP entity.
|
||||||
|
*/
|
||||||
|
public List<String> getFeaturesList() {
|
||||||
|
synchronized (features) {
|
||||||
|
return new LinkedList<String>(features);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers that a new feature is supported by this XMPP entity. When this client is
|
* Registers that a new feature is supported by this XMPP entity. When this client is
|
||||||
* queried for its information the registered features will be answered.<p>
|
* queried for its information the registered features will be answered.<p>
|
||||||
|
@ -348,6 +394,7 @@ public class ServiceDiscoveryManager {
|
||||||
public void addFeature(String feature) {
|
public void addFeature(String feature) {
|
||||||
synchronized (features) {
|
synchronized (features) {
|
||||||
features.add(feature);
|
features.add(feature);
|
||||||
|
renewEntityCapsVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,6 +409,7 @@ public class ServiceDiscoveryManager {
|
||||||
public void removeFeature(String feature) {
|
public void removeFeature(String feature) {
|
||||||
synchronized (features) {
|
synchronized (features) {
|
||||||
features.remove(feature);
|
features.remove(feature);
|
||||||
|
renewEntityCapsVersion();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,10 +442,36 @@ public class ServiceDiscoveryManager {
|
||||||
*/
|
*/
|
||||||
public void setExtendedInfo(DataForm info) {
|
public void setExtendedInfo(DataForm info) {
|
||||||
extendedInfo = info;
|
extendedInfo = info;
|
||||||
|
renewEntityCapsVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the dataform containing extended service discovery information
|
* Returns the data form that is set as extended information for this Service Discovery instance (XEP-0128)
|
||||||
|
*
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0128.html">XEP-128: Service Discovery Extensions</a>
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public DataForm getExtendedInfo() {
|
||||||
|
return extendedInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data form as List of PacketExtensions, or null if no data form is set.
|
||||||
|
* This representation is needed by some classes (e.g. EntityCapsManager, NodeInformationProvider)
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<PacketExtension> getExtendedInfoAsList() {
|
||||||
|
List<PacketExtension> res = null;
|
||||||
|
if (extendedInfo != null) {
|
||||||
|
res = new ArrayList<PacketExtension>(1);
|
||||||
|
res.add(extendedInfo);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the data form containing extended service discovery information
|
||||||
* from the information returned by this XMPP entity.<p>
|
* from the information returned by this XMPP entity.<p>
|
||||||
*
|
*
|
||||||
* Since no packet is actually sent to the server it is safe to perform this
|
* Since no packet is actually sent to the server it is safe to perform this
|
||||||
|
@ -405,17 +479,45 @@ public class ServiceDiscoveryManager {
|
||||||
*/
|
*/
|
||||||
public void removeExtendedInfo() {
|
public void removeExtendedInfo() {
|
||||||
extendedInfo = null;
|
extendedInfo = null;
|
||||||
|
renewEntityCapsVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the discovered information of a given XMPP entity addressed by its JID.
|
* Returns the discovered information of a given XMPP entity addressed by its JID.
|
||||||
|
* Use null as entityID to query the server
|
||||||
*
|
*
|
||||||
* @param entityID the address of the XMPP entity.
|
* @param entityID the address of the XMPP entity or null.
|
||||||
* @return the discovered information.
|
* @return the discovered information.
|
||||||
* @throws XMPPException if the operation failed for some reason.
|
* @throws XMPPException if the operation failed for some reason.
|
||||||
*/
|
*/
|
||||||
public DiscoverInfo discoverInfo(String entityID) throws XMPPException {
|
public DiscoverInfo discoverInfo(String entityID) throws XMPPException {
|
||||||
return discoverInfo(entityID, null);
|
if (entityID == null)
|
||||||
|
return discoverInfo(null, null);
|
||||||
|
|
||||||
|
// Check if the have it cached in the Entity Capabilities Manager
|
||||||
|
DiscoverInfo info = EntityCapsManager.getDiscoverInfoByUser(entityID);
|
||||||
|
|
||||||
|
if (info != null) {
|
||||||
|
// We were able to retrieve the information from Entity Caps and
|
||||||
|
// avoided a disco request, hurray!
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get the newest node#version if it's known, otherwise null is
|
||||||
|
// returned
|
||||||
|
EntityCapsManager.NodeVerHash nvh = EntityCapsManager.getNodeVerHashByJid(entityID);
|
||||||
|
|
||||||
|
// Discover by requesting the information from the remote entity
|
||||||
|
// Note that wee need to use NodeVer as argument for Node if it exists
|
||||||
|
info = discoverInfo(entityID, nvh != null ? nvh.getNodeVer() : null);
|
||||||
|
|
||||||
|
// If the node version is known, store the new entry.
|
||||||
|
if (nvh != null) {
|
||||||
|
if (EntityCapsManager.verifyDiscvoerInfoVersion(nvh.getVer(), nvh.getHash(), info))
|
||||||
|
EntityCapsManager.addDiscoverInfoByNode(nvh.getNodeVer(), info);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -423,8 +525,11 @@ public class ServiceDiscoveryManager {
|
||||||
* note attribute. Use this message only when trying to query information which is not
|
* note attribute. Use this message only when trying to query information which is not
|
||||||
* directly addressable.
|
* directly addressable.
|
||||||
*
|
*
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0030.html#info-basic">XEP-30 Basic Protocol</a>
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0030.html#info-nodes">XEP-30 Info Nodes</a>
|
||||||
|
*
|
||||||
* @param entityID the address of the XMPP entity.
|
* @param entityID the address of the XMPP entity.
|
||||||
* @param node the attribute that supplements the 'jid' attribute.
|
* @param node the optional attribute that supplements the 'jid' attribute.
|
||||||
* @return the discovered information.
|
* @return the discovered information.
|
||||||
* @throws XMPPException if the operation failed for some reason.
|
* @throws XMPPException if the operation failed for some reason.
|
||||||
*/
|
*/
|
||||||
|
@ -471,7 +576,7 @@ public class ServiceDiscoveryManager {
|
||||||
* directly addressable.
|
* directly addressable.
|
||||||
*
|
*
|
||||||
* @param entityID the address of the XMPP entity.
|
* @param entityID the address of the XMPP entity.
|
||||||
* @param node the attribute that supplements the 'jid' attribute.
|
* @param node the optional attribute that supplements the 'jid' attribute.
|
||||||
* @return the discovered items.
|
* @return the discovered items.
|
||||||
* @throws XMPPException if the operation failed for some reason.
|
* @throws XMPPException if the operation failed for some reason.
|
||||||
*/
|
*/
|
||||||
|
@ -513,8 +618,21 @@ public class ServiceDiscoveryManager {
|
||||||
*/
|
*/
|
||||||
public boolean canPublishItems(String entityID) throws XMPPException {
|
public boolean canPublishItems(String entityID) throws XMPPException {
|
||||||
DiscoverInfo info = discoverInfo(entityID);
|
DiscoverInfo info = discoverInfo(entityID);
|
||||||
return info.containsFeature("http://jabber.org/protocol/disco#publish");
|
return canPublishItems(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the server supports publishing of items. A client may wish to publish items
|
||||||
|
* to the server so that the server can provide items associated to the client. These items will
|
||||||
|
* be returned by the server whenever the server receives a disco request targeted to the bare
|
||||||
|
* address of the client (i.e. user@host.com).
|
||||||
|
*
|
||||||
|
* @param DiscoverInfo the discover info packet to check.
|
||||||
|
* @return true if the server supports publishing of items.
|
||||||
|
*/
|
||||||
|
public static boolean canPublishItems(DiscoverInfo info) {
|
||||||
|
return info.containsFeature("http://jabber.org/protocol/disco#publish");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Publishes new items to a parent entity. The item elements to publish MUST have at least
|
* Publishes new items to a parent entity. The item elements to publish MUST have at least
|
||||||
|
@ -565,4 +683,26 @@ public class ServiceDiscoveryManager {
|
||||||
throw new XMPPException(result.getError());
|
throw new XMPPException(result.getError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Entity Capabilities
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the ServiceDiscoveryManager with an EntityCapsManger
|
||||||
|
* that speeds up certain lookups
|
||||||
|
* @param manager
|
||||||
|
*/
|
||||||
|
public void setEntityCapsManager(EntityCapsManager manager) {
|
||||||
|
capsManager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the Entity Capabilities Verification String
|
||||||
|
* if EntityCaps is enabled
|
||||||
|
*/
|
||||||
|
private void renewEntityCapsVersion() {
|
||||||
|
if (capsManager != null && capsManager.entityCapsEnabled())
|
||||||
|
capsManager.updateLocalEntityCaps();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smack.packet.XMPPError;
|
import org.jivesoftware.smack.packet.XMPPError;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
import org.jivesoftware.smackx.Form;
|
import org.jivesoftware.smackx.Form;
|
||||||
|
@ -181,12 +182,16 @@ public class AdHocCommandManager {
|
||||||
public List<DiscoverInfo.Identity> getNodeIdentities() {
|
public List<DiscoverInfo.Identity> getNodeIdentities() {
|
||||||
List<DiscoverInfo.Identity> answer = new ArrayList<DiscoverInfo.Identity>();
|
List<DiscoverInfo.Identity> answer = new ArrayList<DiscoverInfo.Identity>();
|
||||||
DiscoverInfo.Identity identity = new DiscoverInfo.Identity(
|
DiscoverInfo.Identity identity = new DiscoverInfo.Identity(
|
||||||
"automation", name);
|
"automation", name, "command-node");
|
||||||
identity.setType("command-node");
|
|
||||||
answer.add(identity);
|
answer.add(identity);
|
||||||
return answer;
|
return answer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PacketExtension> getNodePacketExtensions() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +324,11 @@ public class AdHocCommandManager {
|
||||||
public List<Identity> getNodeIdentities() {
|
public List<Identity> getNodeIdentities() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PacketExtension> getNodePacketExtensions() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// The packet listener and the filter for processing some AdHoc Commands
|
// The packet listener and the filter for processing some AdHoc Commands
|
||||||
|
|
713
source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java
Normal file
713
source/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java
Normal file
|
@ -0,0 +1,713 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2009 Jonas Ådahl.
|
||||||
|
* Copyright 2011-2013 Florian Schmaus
|
||||||
|
*
|
||||||
|
* All rights reserved. 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.entitycaps;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.Connection;
|
||||||
|
import org.jivesoftware.smack.ConnectionCreationListener;
|
||||||
|
import org.jivesoftware.smack.ConnectionListener;
|
||||||
|
import org.jivesoftware.smack.PacketInterceptor;
|
||||||
|
import org.jivesoftware.smack.PacketListener;
|
||||||
|
import org.jivesoftware.smack.SmackConfiguration;
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
|
import org.jivesoftware.smack.filter.NotFilter;
|
||||||
|
import org.jivesoftware.smack.filter.PacketFilter;
|
||||||
|
import org.jivesoftware.smack.filter.AndFilter;
|
||||||
|
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||||
|
import org.jivesoftware.smack.filter.PacketExtensionFilter;
|
||||||
|
import org.jivesoftware.smack.util.Base64;
|
||||||
|
import org.jivesoftware.smack.util.Cache;
|
||||||
|
import org.jivesoftware.smackx.Form;
|
||||||
|
import org.jivesoftware.smackx.FormField;
|
||||||
|
import org.jivesoftware.smackx.NodeInformationProvider;
|
||||||
|
import org.jivesoftware.smackx.ServiceDiscoveryManager;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.cache.EntityCapsPersistentCache;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.packet.CapsExtension;
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
||||||
|
import org.jivesoftware.smackx.packet.DataForm;
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverInfo.Feature;
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverItems.Item;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps track of entity capabilities.
|
||||||
|
*
|
||||||
|
* @author Florian Schmaus
|
||||||
|
*/
|
||||||
|
public class EntityCapsManager {
|
||||||
|
|
||||||
|
public static final String NAMESPACE = "http://jabber.org/protocol/caps";
|
||||||
|
public static final String ELEMENT = "c";
|
||||||
|
|
||||||
|
private static final String ENTITY_NODE = "http://www.igniterealtime.org/projects/smack";
|
||||||
|
private static final Map<String, MessageDigest> SUPPORTED_HASHES = new HashMap<String, MessageDigest>();
|
||||||
|
|
||||||
|
protected static EntityCapsPersistentCache persistentCache;
|
||||||
|
|
||||||
|
private static Map<Connection, EntityCapsManager> instances = Collections
|
||||||
|
.synchronizedMap(new WeakHashMap<Connection, EntityCapsManager>());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of (node + '#" + hash algorithm) to DiscoverInfo data
|
||||||
|
*/
|
||||||
|
protected static Map<String, DiscoverInfo> caps = new Cache<String, DiscoverInfo>(1000, -1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of Full JID -> DiscoverInfo/null. In case of c2s connection the
|
||||||
|
* key is formed as user@server/resource (resource is required) In case of
|
||||||
|
* link-local connection the key is formed as user@host (no resource) In
|
||||||
|
* case of a server or component the key is formed as domain
|
||||||
|
*/
|
||||||
|
protected static Map<String, NodeVerHash> jidCaps = new Cache<String, NodeVerHash>(10000, -1);
|
||||||
|
|
||||||
|
static {
|
||||||
|
Connection.addConnectionCreationListener(new ConnectionCreationListener() {
|
||||||
|
public void connectionCreated(Connection connection) {
|
||||||
|
if (connection instanceof XMPPConnection)
|
||||||
|
new EntityCapsManager(connection);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
MessageDigest sha1MessageDigest = MessageDigest.getInstance("SHA-1");
|
||||||
|
SUPPORTED_HASHES.put("sha-1", sha1MessageDigest);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WeakReference<Connection> weakRefConnection;
|
||||||
|
private ServiceDiscoveryManager sdm;
|
||||||
|
private boolean entityCapsEnabled;
|
||||||
|
private String currentCapsVersion;
|
||||||
|
private boolean presenceSend = false;
|
||||||
|
private Queue<String> lastLocalCapsVersions = new ConcurrentLinkedQueue<String>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add DiscoverInfo to the database.
|
||||||
|
*
|
||||||
|
* @param nodeVer
|
||||||
|
* The node and verification String (e.g.
|
||||||
|
* "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w=").
|
||||||
|
* @param info
|
||||||
|
* DiscoverInfo for the specified node.
|
||||||
|
*/
|
||||||
|
public static void addDiscoverInfoByNode(String nodeVer, DiscoverInfo info) {
|
||||||
|
caps.put(nodeVer, info);
|
||||||
|
|
||||||
|
if (persistentCache != null)
|
||||||
|
persistentCache.addDiscoverInfoByNodePersistent(nodeVer, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Node version (node#ver) of a JID. Returns a String or null if
|
||||||
|
* EntiyCapsManager does not have any information.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* the user (Full JID)
|
||||||
|
* @return the node version (node#ver) or null
|
||||||
|
*/
|
||||||
|
public static String getNodeVersionByJid(String jid) {
|
||||||
|
NodeVerHash nvh = jidCaps.get(jid);
|
||||||
|
if (nvh != null) {
|
||||||
|
return nvh.nodeVer;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NodeVerHash getNodeVerHashByJid(String jid) {
|
||||||
|
return jidCaps.get(jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the discover info given a user name. The discover info is returned if
|
||||||
|
* the user has a node#ver associated with it and the node#ver has a
|
||||||
|
* discover info associated with it.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* user name (Full JID)
|
||||||
|
* @return the discovered info
|
||||||
|
*/
|
||||||
|
public static DiscoverInfo getDiscoverInfoByUser(String user) {
|
||||||
|
NodeVerHash nvh = jidCaps.get(user);
|
||||||
|
if (nvh == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return getDiscoveryInfoByNodeVer(nvh.nodeVer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve DiscoverInfo for a specific node.
|
||||||
|
*
|
||||||
|
* @param nodeVer
|
||||||
|
* The node name (e.g.
|
||||||
|
* "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w=").
|
||||||
|
* @return The corresponding DiscoverInfo or null if none is known.
|
||||||
|
*/
|
||||||
|
public static DiscoverInfo getDiscoveryInfoByNodeVer(String nodeVer) {
|
||||||
|
DiscoverInfo info = caps.get(nodeVer);
|
||||||
|
if (info != null)
|
||||||
|
info = new DiscoverInfo(info);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the persistent cache implementation
|
||||||
|
*
|
||||||
|
* @param cache
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void setPersistentCache(EntityCapsPersistentCache cache) throws IOException {
|
||||||
|
if (persistentCache != null)
|
||||||
|
throw new IllegalStateException("Entity Caps Persistent Cache was already set");
|
||||||
|
persistentCache = cache;
|
||||||
|
persistentCache.replay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum Cache size for the JID to nodeVer Cache
|
||||||
|
*
|
||||||
|
* @param maxCacheSize
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static void setJidCapsMaxCacheSize(int maxCacheSize) {
|
||||||
|
((Cache) jidCaps).setMaxCacheSize(maxCacheSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum Cache size for the nodeVer to DiscoverInfo Cache
|
||||||
|
*
|
||||||
|
* @param maxCacheSize
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static void setCapsMaxCacheSize(int maxCacheSize) {
|
||||||
|
((Cache) caps).setMaxCacheSize(maxCacheSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EntityCapsManager(Connection connection) {
|
||||||
|
this.weakRefConnection = new WeakReference<Connection>(connection);
|
||||||
|
this.sdm = ServiceDiscoveryManager.getInstanceFor(connection);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
Connection connection = weakRefConnection.get();
|
||||||
|
instances.put(connection, this);
|
||||||
|
|
||||||
|
connection.addConnectionListener(new ConnectionListener() {
|
||||||
|
public void connectionClosed() {
|
||||||
|
// Unregister this instance since the connection has been closed
|
||||||
|
presenceSend = false;
|
||||||
|
instances.remove(weakRefConnection.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void connectionClosedOnError(Exception e) {
|
||||||
|
presenceSend = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reconnectionFailed(Exception e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reconnectingIn(int seconds) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reconnectionSuccessful() {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This calculates the local entity caps version
|
||||||
|
updateLocalEntityCaps();
|
||||||
|
|
||||||
|
if (SmackConfiguration.autoEnableEntityCaps())
|
||||||
|
enableEntityCaps();
|
||||||
|
|
||||||
|
PacketFilter packetFilter = new AndFilter(new PacketTypeFilter(Presence.class), new PacketExtensionFilter(
|
||||||
|
ELEMENT, NAMESPACE));
|
||||||
|
connection.addPacketListener(new PacketListener() {
|
||||||
|
// Listen for remote presence stanzas with the caps extension
|
||||||
|
// If we receive such a stanza, record the JID and nodeVer
|
||||||
|
@Override
|
||||||
|
public void processPacket(Packet packet) {
|
||||||
|
if (!entityCapsEnabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
CapsExtension ext = (CapsExtension) packet.getExtension(EntityCapsManager.ELEMENT,
|
||||||
|
EntityCapsManager.NAMESPACE);
|
||||||
|
|
||||||
|
String hash = ext.getHash().toLowerCase();
|
||||||
|
if (!SUPPORTED_HASHES.containsKey(hash))
|
||||||
|
return;
|
||||||
|
|
||||||
|
String from = packet.getFrom();
|
||||||
|
String node = ext.getNode();
|
||||||
|
String ver = ext.getVer();
|
||||||
|
|
||||||
|
jidCaps.put(from, new NodeVerHash(node, ver, hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
}, packetFilter);
|
||||||
|
|
||||||
|
packetFilter = new AndFilter(new PacketTypeFilter(Presence.class), new NotFilter(new PacketExtensionFilter(
|
||||||
|
ELEMENT, NAMESPACE)));
|
||||||
|
connection.addPacketListener(new PacketListener() {
|
||||||
|
@Override
|
||||||
|
public void processPacket(Packet packet) {
|
||||||
|
// always remove the JID from the map, even if entityCaps are
|
||||||
|
// disabled
|
||||||
|
String from = packet.getFrom();
|
||||||
|
jidCaps.remove(from);
|
||||||
|
}
|
||||||
|
}, packetFilter);
|
||||||
|
|
||||||
|
packetFilter = new PacketTypeFilter(Presence.class);
|
||||||
|
connection.addPacketSendingListener(new PacketListener() {
|
||||||
|
@Override
|
||||||
|
public void processPacket(Packet packet) {
|
||||||
|
presenceSend = true;
|
||||||
|
}
|
||||||
|
}, packetFilter);
|
||||||
|
|
||||||
|
// Intercept presence packages and add caps data when intended.
|
||||||
|
// XEP-0115 specifies that a client SHOULD include entity capabilities
|
||||||
|
// with every presence notification it sends.
|
||||||
|
PacketFilter capsPacketFilter = new PacketTypeFilter(Presence.class);
|
||||||
|
PacketInterceptor packetInterceptor = new PacketInterceptor() {
|
||||||
|
public void interceptPacket(Packet packet) {
|
||||||
|
if (!entityCapsEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CapsExtension caps = new CapsExtension(ENTITY_NODE, getCapsVersion(), "sha-1");
|
||||||
|
packet.addExtension(caps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
connection.addPacketInterceptor(packetInterceptor, capsPacketFilter);
|
||||||
|
// It's important to do this as last action. Since it changes the
|
||||||
|
// behavior of the SDM in some ways
|
||||||
|
sdm.setEntityCapsManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized EntityCapsManager getInstanceFor(Connection connection) {
|
||||||
|
// For testing purposed forbid EntityCaps for non XMPPConnections
|
||||||
|
// it may work on BOSH connections too
|
||||||
|
if (!(connection instanceof XMPPConnection))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (SUPPORTED_HASHES.size() <= 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
EntityCapsManager entityCapsManager = instances.get(connection);
|
||||||
|
|
||||||
|
if (entityCapsManager == null) {
|
||||||
|
entityCapsManager = new EntityCapsManager(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return entityCapsManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableEntityCaps() {
|
||||||
|
// Add Entity Capabilities (XEP-0115) feature node.
|
||||||
|
sdm.addFeature(NAMESPACE);
|
||||||
|
updateLocalEntityCaps();
|
||||||
|
entityCapsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void disableEntityCaps() {
|
||||||
|
entityCapsEnabled = false;
|
||||||
|
sdm.removeFeature(NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean entityCapsEnabled() {
|
||||||
|
return entityCapsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a record telling what entity caps node a user has.
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
* the user (Full JID)
|
||||||
|
*/
|
||||||
|
public void removeUserCapsNode(String user) {
|
||||||
|
jidCaps.remove(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get our own caps version. The version depends on the enabled features. A
|
||||||
|
* caps version looks like '66/0NaeaBKkwk85efJTGmU47vXI='
|
||||||
|
*
|
||||||
|
* @return our own caps version
|
||||||
|
*/
|
||||||
|
public String getCapsVersion() {
|
||||||
|
return currentCapsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the local entity's NodeVer (e.g.
|
||||||
|
* "http://www.igniterealtime.org/projects/smack/#66/0NaeaBKkwk85efJTGmU47vXI=
|
||||||
|
* )
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getLocalNodeVer() {
|
||||||
|
return ENTITY_NODE + '#' + getCapsVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if Entity Caps are supported by a given JID
|
||||||
|
*
|
||||||
|
* @param jid
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean areEntityCapsSupported(String jid) {
|
||||||
|
if (jid == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
DiscoverInfo result = sdm.discoverInfo(jid);
|
||||||
|
return result.containsFeature(NAMESPACE);
|
||||||
|
} catch (XMPPException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if Entity Caps are supported by the local service/server
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean areEntityCapsSupportedByServer() {
|
||||||
|
return areEntityCapsSupported(weakRefConnection.get().getServiceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the local user Entity Caps information with the data provided
|
||||||
|
*
|
||||||
|
* If we are connected and there was already a presence send, another
|
||||||
|
* presence is send to inform others about your new Entity Caps node string.
|
||||||
|
*
|
||||||
|
* @param discoverInfo
|
||||||
|
* the local users discover info (mostly the service discovery
|
||||||
|
* features)
|
||||||
|
* @param identityType
|
||||||
|
* the local users identity type
|
||||||
|
* @param identityName
|
||||||
|
* the local users identity name
|
||||||
|
* @param extendedInfo
|
||||||
|
* the local users extended info
|
||||||
|
*/
|
||||||
|
public void updateLocalEntityCaps() {
|
||||||
|
Connection connection = weakRefConnection.get();
|
||||||
|
|
||||||
|
DiscoverInfo discoverInfo = new DiscoverInfo();
|
||||||
|
discoverInfo.setType(IQ.Type.RESULT);
|
||||||
|
discoverInfo.setNode(getLocalNodeVer());
|
||||||
|
if (connection != null)
|
||||||
|
discoverInfo.setFrom(connection.getUser());
|
||||||
|
sdm.addDiscoverInfoTo(discoverInfo);
|
||||||
|
|
||||||
|
currentCapsVersion = generateVerificationString(discoverInfo, "sha-1");
|
||||||
|
addDiscoverInfoByNode(ENTITY_NODE + '#' + currentCapsVersion, discoverInfo);
|
||||||
|
if (lastLocalCapsVersions.size() > 10) {
|
||||||
|
String oldCapsVersion = lastLocalCapsVersions.poll();
|
||||||
|
sdm.removeNodeInformationProvider(ENTITY_NODE + '#' + oldCapsVersion);
|
||||||
|
}
|
||||||
|
lastLocalCapsVersions.add(currentCapsVersion);
|
||||||
|
|
||||||
|
caps.put(currentCapsVersion, discoverInfo);
|
||||||
|
if (connection != null)
|
||||||
|
jidCaps.put(connection.getUser(), new NodeVerHash(ENTITY_NODE, currentCapsVersion, "sha-1"));
|
||||||
|
|
||||||
|
sdm.setNodeInformationProvider(ENTITY_NODE + '#' + currentCapsVersion, new NodeInformationProvider() {
|
||||||
|
List<String> features = sdm.getFeaturesList();
|
||||||
|
List<Identity> identities = new LinkedList<Identity>(ServiceDiscoveryManager.getIdentities());
|
||||||
|
List<PacketExtension> packetExtensions = sdm.getExtendedInfoAsList();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Item> getNodeItems() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getNodeFeatures() {
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Identity> getNodeIdentities() {
|
||||||
|
return identities;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PacketExtension> getNodePacketExtensions() {
|
||||||
|
return packetExtensions;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send an empty presence, and let the packet intercepter
|
||||||
|
// add a <c/> node to it.
|
||||||
|
// See http://xmpp.org/extensions/xep-0115.html#advertise
|
||||||
|
// We only send a presence packet if there was already one send
|
||||||
|
// to respect ConnectionConfiguration.isSendPresence()
|
||||||
|
if (connection != null && connection.isAuthenticated() && presenceSend) {
|
||||||
|
Presence presence = new Presence(Presence.Type.available);
|
||||||
|
connection.sendPacket(presence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify DisoverInfo and Caps Node as defined in XEP-0115 5.4 Processing
|
||||||
|
* Method
|
||||||
|
*
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0115.html#ver-proc">XEP-0115
|
||||||
|
* 5.4 Processing Method</a>
|
||||||
|
*
|
||||||
|
* @param capsNode
|
||||||
|
* the caps node (i.e. node#ver)
|
||||||
|
* @param info
|
||||||
|
* @return true if it's valid and should be cache, false if not
|
||||||
|
*/
|
||||||
|
public static boolean verifyDiscvoerInfoVersion(String ver, String hash, DiscoverInfo info) {
|
||||||
|
// step 3.3 check for duplicate identities
|
||||||
|
if (info.containsDuplicateIdentities())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// step 3.4 check for duplicate features
|
||||||
|
if (info.containsDuplicateFeatures())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// step 3.5 check for well-formed packet extensions
|
||||||
|
if (verifyPacketExtensions(info))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String calculatedVer = generateVerificationString(info, hash);
|
||||||
|
|
||||||
|
if (!ver.equals(calculatedVer))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param info
|
||||||
|
* @return true if the packet extensions is ill-formed
|
||||||
|
*/
|
||||||
|
protected static boolean verifyPacketExtensions(DiscoverInfo info) {
|
||||||
|
List<FormField> foundFormTypes = new LinkedList<FormField>();
|
||||||
|
for (Iterator<PacketExtension> i = info.getExtensions().iterator(); i.hasNext();) {
|
||||||
|
PacketExtension pe = i.next();
|
||||||
|
if (pe.getNamespace().equals(Form.NAMESPACE)) {
|
||||||
|
DataForm df = (DataForm) pe;
|
||||||
|
for (Iterator<FormField> it = df.getFields(); it.hasNext();) {
|
||||||
|
FormField f = it.next();
|
||||||
|
if (f.getVariable().equals("FORM_TYPE")) {
|
||||||
|
for (FormField fft : foundFormTypes) {
|
||||||
|
if (f.equals(fft))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
foundFormTypes.add(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a XEP-115 Verification String
|
||||||
|
*
|
||||||
|
* @see <a href="http://xmpp.org/extensions/xep-0115.html#ver">XEP-115
|
||||||
|
* Verification String</a>
|
||||||
|
*
|
||||||
|
* @param discoverInfo
|
||||||
|
* @param hash
|
||||||
|
* the used hash function
|
||||||
|
* @return The generated verification String or null if the hash is not
|
||||||
|
* supported
|
||||||
|
*/
|
||||||
|
protected static String generateVerificationString(DiscoverInfo discoverInfo, String hash) {
|
||||||
|
MessageDigest md = SUPPORTED_HASHES.get(hash.toLowerCase());
|
||||||
|
if (md == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
DataForm extendedInfo = (DataForm) discoverInfo.getExtension(Form.ELEMENT, Form.NAMESPACE);
|
||||||
|
|
||||||
|
// 1. Initialize an empty string S ('sb' in this method).
|
||||||
|
StringBuilder sb = new StringBuilder(); // Use StringBuilder as we don't
|
||||||
|
// need thread-safe StringBuffer
|
||||||
|
|
||||||
|
// 2. Sort the service discovery identities by category and then by
|
||||||
|
// type and then by xml:lang
|
||||||
|
// (if it exists), formatted as CATEGORY '/' [TYPE] '/' [LANG] '/'
|
||||||
|
// [NAME]. Note that each slash is included even if the LANG or
|
||||||
|
// NAME is not included (in accordance with XEP-0030, the category and
|
||||||
|
// type MUST be included.
|
||||||
|
SortedSet<DiscoverInfo.Identity> sortedIdentities = new TreeSet<DiscoverInfo.Identity>();
|
||||||
|
;
|
||||||
|
for (Iterator<DiscoverInfo.Identity> it = discoverInfo.getIdentities(); it.hasNext();)
|
||||||
|
sortedIdentities.add(it.next());
|
||||||
|
|
||||||
|
// 3. For each identity, append the 'category/type/lang/name' to S,
|
||||||
|
// followed by the '<' character.
|
||||||
|
for (Iterator<DiscoverInfo.Identity> it = sortedIdentities.iterator(); it.hasNext();) {
|
||||||
|
DiscoverInfo.Identity identity = it.next();
|
||||||
|
sb.append(identity.getCategory());
|
||||||
|
sb.append("/");
|
||||||
|
sb.append(identity.getType());
|
||||||
|
sb.append("/");
|
||||||
|
sb.append(identity.getLanguage() == null ? "" : identity.getLanguage());
|
||||||
|
sb.append("/");
|
||||||
|
sb.append(identity.getName() == null ? "" : identity.getName());
|
||||||
|
sb.append("<");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Sort the supported service discovery features.
|
||||||
|
SortedSet<String> features = new TreeSet<String>();
|
||||||
|
for (Iterator<Feature> it = discoverInfo.getFeatures(); it.hasNext();)
|
||||||
|
features.add(it.next().getVar());
|
||||||
|
|
||||||
|
// 5. For each feature, append the feature to S, followed by the '<'
|
||||||
|
// character
|
||||||
|
for (String f : features) {
|
||||||
|
sb.append(f);
|
||||||
|
sb.append("<");
|
||||||
|
}
|
||||||
|
|
||||||
|
// only use the data form for calculation is it has a hidden FORM_TYPE
|
||||||
|
// field
|
||||||
|
// see XEP-0115 5.4 step 3.6
|
||||||
|
if (extendedInfo != null && extendedInfo.hasHiddenFromTypeField()) {
|
||||||
|
synchronized (extendedInfo) {
|
||||||
|
// 6. If the service discovery information response includes
|
||||||
|
// XEP-0128 data forms, sort the forms by the FORM_TYPE (i.e.,
|
||||||
|
// by the XML character data of the <value/> element).
|
||||||
|
SortedSet<FormField> fs = new TreeSet<FormField>(new Comparator<FormField>() {
|
||||||
|
public int compare(FormField f1, FormField f2) {
|
||||||
|
return f1.getVariable().compareTo(f2.getVariable());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
FormField ft = null;
|
||||||
|
|
||||||
|
for (Iterator<FormField> i = extendedInfo.getFields(); i.hasNext();) {
|
||||||
|
FormField f = i.next();
|
||||||
|
if (!f.getVariable().equals("FORM_TYPE")) {
|
||||||
|
fs.add(f);
|
||||||
|
} else {
|
||||||
|
ft = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add FORM_TYPE values
|
||||||
|
if (ft != null) {
|
||||||
|
formFieldValuesToCaps(ft.getValues(), sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. 3. For each field other than FORM_TYPE:
|
||||||
|
// 1. Append the value of the "var" attribute, followed by the
|
||||||
|
// '<' character.
|
||||||
|
// 2. Sort values by the XML character data of the <value/>
|
||||||
|
// element.
|
||||||
|
// 3. For each <value/> element, append the XML character data,
|
||||||
|
// followed by the '<' character.
|
||||||
|
for (FormField f : fs) {
|
||||||
|
sb.append(f.getVariable());
|
||||||
|
sb.append("<");
|
||||||
|
formFieldValuesToCaps(f.getValues(), sb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 8. Ensure that S is encoded according to the UTF-8 encoding (RFC
|
||||||
|
// 3269).
|
||||||
|
// 9. Compute the verification string by hashing S using the algorithm
|
||||||
|
// specified in the 'hash' attribute (e.g., SHA-1 as defined in RFC
|
||||||
|
// 3174).
|
||||||
|
// The hashed data MUST be generated with binary output and
|
||||||
|
// encoded using Base64 as specified in Section 4 of RFC 4648
|
||||||
|
// (note: the Base64 output MUST NOT include whitespace and MUST set
|
||||||
|
// padding bits to zero).
|
||||||
|
byte[] digest = md.digest(sb.toString().getBytes());
|
||||||
|
return Base64.encodeBytes(digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void formFieldValuesToCaps(Iterator<String> i, StringBuilder sb) {
|
||||||
|
SortedSet<String> fvs = new TreeSet<String>();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
fvs.add(i.next());
|
||||||
|
}
|
||||||
|
for (String fv : fvs) {
|
||||||
|
sb.append(fv);
|
||||||
|
sb.append("<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NodeVerHash {
|
||||||
|
private String node;
|
||||||
|
private String hash;
|
||||||
|
private String ver;
|
||||||
|
private String nodeVer;
|
||||||
|
|
||||||
|
NodeVerHash(String node, String ver, String hash) {
|
||||||
|
this.node = node;
|
||||||
|
this.ver = ver;
|
||||||
|
this.hash = hash;
|
||||||
|
nodeVer = node + "#" + ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNodeVer() {
|
||||||
|
return nodeVer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVer() {
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
source/org/jivesoftware/smackx/entitycaps/cache/EntityCapsPersistentCache.java
vendored
Normal file
38
source/org/jivesoftware/smackx/entitycaps/cache/EntityCapsPersistentCache.java
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* All rights reserved. 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.entitycaps.cache;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
||||||
|
|
||||||
|
public interface EntityCapsPersistentCache {
|
||||||
|
/**
|
||||||
|
* Add an DiscoverInfo to the persistent Cache
|
||||||
|
*
|
||||||
|
* @param node
|
||||||
|
* @param info
|
||||||
|
*/
|
||||||
|
abstract void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replay the Caches data into EntityCapsManager
|
||||||
|
*/
|
||||||
|
abstract void replay() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty the Cache
|
||||||
|
*/
|
||||||
|
abstract void emptyCache();
|
||||||
|
}
|
193
source/org/jivesoftware/smackx/entitycaps/cache/SimpleDirectoryPersistentCache.java
vendored
Normal file
193
source/org/jivesoftware/smackx/entitycaps/cache/SimpleDirectoryPersistentCache.java
vendored
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2011 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.entitycaps.cache;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smack.provider.IQProvider;
|
||||||
|
import org.jivesoftware.smack.util.Base64Encoder;
|
||||||
|
import org.jivesoftware.smack.util.StringEncoder;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
||||||
|
import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
|
||||||
|
import org.xmlpull.mxp1.MXParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple implementation of an EntityCapsPersistentCache that uses a directory
|
||||||
|
* to store the Caps information for every known node. Every node is represented
|
||||||
|
* by an file.
|
||||||
|
*
|
||||||
|
* @author Florian Schmaus
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SimpleDirectoryPersistentCache implements EntityCapsPersistentCache {
|
||||||
|
|
||||||
|
private File cacheDir;
|
||||||
|
private StringEncoder stringEncoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SimpleDirectoryPersistentCache Object. Make sure that the
|
||||||
|
* cacheDir exists and that it's an directory.
|
||||||
|
*
|
||||||
|
* If your cacheDir is case insensitive then make sure to set the
|
||||||
|
* StringEncoder to Base32.
|
||||||
|
*
|
||||||
|
* @param cacheDir
|
||||||
|
*/
|
||||||
|
public SimpleDirectoryPersistentCache(File cacheDir) {
|
||||||
|
this(cacheDir, Base64Encoder.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SimpleDirectoryPersistentCache Object. Make sure that the
|
||||||
|
* cacheDir exists and that it's an directory.
|
||||||
|
*
|
||||||
|
* If your cacheDir is case insensitive then make sure to set the
|
||||||
|
* StringEncoder to Base32.
|
||||||
|
*
|
||||||
|
* @param cacheDir
|
||||||
|
* @param stringEncoder
|
||||||
|
*/
|
||||||
|
public SimpleDirectoryPersistentCache(File cacheDir, StringEncoder stringEncoder) {
|
||||||
|
if (!cacheDir.exists())
|
||||||
|
throw new IllegalStateException("Cache directory \"" + cacheDir + "\" does not exist");
|
||||||
|
if (!cacheDir.isDirectory())
|
||||||
|
throw new IllegalStateException("Cache directory \"" + cacheDir + "\" is not a directory");
|
||||||
|
|
||||||
|
this.cacheDir = cacheDir;
|
||||||
|
this.stringEncoder = stringEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info) {
|
||||||
|
String filename = stringEncoder.encode(node);
|
||||||
|
File nodeFile = new File(cacheDir, filename);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (nodeFile.createNewFile())
|
||||||
|
writeInfoToFile(nodeFile, info);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replay() throws IOException {
|
||||||
|
File[] files = cacheDir.listFiles();
|
||||||
|
for (File f : files) {
|
||||||
|
String node = stringEncoder.decode(f.getName());
|
||||||
|
DiscoverInfo info = restoreInfoFromFile(f);
|
||||||
|
if (info == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
EntityCapsManager.addDiscoverInfoByNode(node, info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void emptyCache() {
|
||||||
|
File[] files = cacheDir.listFiles();
|
||||||
|
for (File f : files) {
|
||||||
|
f.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the DiscoverInfo packet to an file
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @param info
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private static void writeInfoToFile(File file, DiscoverInfo info) throws IOException {
|
||||||
|
DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
|
||||||
|
try {
|
||||||
|
dos.writeUTF(info.toXML());
|
||||||
|
} finally {
|
||||||
|
dos.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to restore an DiscoverInfo packet from a file.
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private static DiscoverInfo restoreInfoFromFile(File file) throws IOException {
|
||||||
|
DataInputStream dis = new DataInputStream(new FileInputStream(file));
|
||||||
|
String fileContent = null;
|
||||||
|
String id;
|
||||||
|
String from;
|
||||||
|
String to;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileContent = dis.readUTF();
|
||||||
|
} finally {
|
||||||
|
dis.close();
|
||||||
|
}
|
||||||
|
if (fileContent == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Reader reader = new StringReader(fileContent);
|
||||||
|
XmlPullParser parser;
|
||||||
|
try {
|
||||||
|
parser = new MXParser();
|
||||||
|
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
|
||||||
|
parser.setInput(reader);
|
||||||
|
} catch (XmlPullParserException xppe) {
|
||||||
|
xppe.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiscoverInfo iqPacket;
|
||||||
|
IQProvider provider = new DiscoverInfoProvider();
|
||||||
|
|
||||||
|
// Parse the IQ, we only need the id
|
||||||
|
try {
|
||||||
|
parser.next();
|
||||||
|
id = parser.getAttributeValue("", "id");
|
||||||
|
from = parser.getAttributeValue("", "from");
|
||||||
|
to = parser.getAttributeValue("", "to");
|
||||||
|
parser.next();
|
||||||
|
} catch (XmlPullParserException e1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
iqPacket = (DiscoverInfo) provider.parseIQ(parser);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
iqPacket.setPacketID(id);
|
||||||
|
iqPacket.setFrom(from);
|
||||||
|
iqPacket.setTo(to);
|
||||||
|
iqPacket.setType(IQ.Type.RESULT);
|
||||||
|
return iqPacket;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2009 Jonas Ådahl.
|
||||||
|
* Copyright 2011-2013 Florian Schmaus
|
||||||
|
*
|
||||||
|
* All rights reserved. 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.entitycaps.packet;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
|
||||||
|
|
||||||
|
public class CapsExtension implements PacketExtension {
|
||||||
|
|
||||||
|
private String node, ver, hash;
|
||||||
|
|
||||||
|
public CapsExtension() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public CapsExtension(String node, String version, String hash) {
|
||||||
|
this.node = node;
|
||||||
|
this.ver = version;
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getElementName() {
|
||||||
|
return EntityCapsManager.ELEMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNamespace() {
|
||||||
|
return EntityCapsManager.NAMESPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNode() {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNode(String node) {
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVer() {
|
||||||
|
return ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVer(String ver) {
|
||||||
|
this.ver = ver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHash() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHash(String hash) {
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* <c xmlns='http://jabber.org/protocol/caps'
|
||||||
|
* hash='sha-1'
|
||||||
|
* node='http://code.google.com/p/exodus'
|
||||||
|
* ver='QgayPKawpkPSDYmwT/WM94uAlu0='/>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public String toXML() {
|
||||||
|
String xml = "<" + EntityCapsManager.ELEMENT + " xmlns=\"" + EntityCapsManager.NAMESPACE + "\" " +
|
||||||
|
"hash=\"" + hash + "\" " +
|
||||||
|
"node=\"" + node + "\" " +
|
||||||
|
"ver=\"" + ver + "\"/>";
|
||||||
|
|
||||||
|
return xml;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2009 Jonas Ådahl.
|
||||||
|
* Copyright 2011-2013 Florian Schmaus
|
||||||
|
*
|
||||||
|
* All rights reserved. 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.entitycaps.provider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.packet.CapsExtension;
|
||||||
|
|
||||||
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
public class CapsExtensionProvider implements PacketExtensionProvider {
|
||||||
|
|
||||||
|
public PacketExtension parseExtension(XmlPullParser parser) throws XmlPullParserException, IOException,
|
||||||
|
XMPPException {
|
||||||
|
String hash = null;
|
||||||
|
String version = null;
|
||||||
|
String node = null;
|
||||||
|
if (parser.getEventType() == XmlPullParser.START_TAG
|
||||||
|
&& parser.getName().equalsIgnoreCase(EntityCapsManager.ELEMENT)) {
|
||||||
|
hash = parser.getAttributeValue(null, "hash");
|
||||||
|
version = parser.getAttributeValue(null, "ver");
|
||||||
|
node = parser.getAttributeValue(null, "node");
|
||||||
|
} else {
|
||||||
|
throw new XMPPException("Malformed Caps element");
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.next();
|
||||||
|
|
||||||
|
if (!(parser.getEventType() == XmlPullParser.END_TAG
|
||||||
|
&& parser.getName().equalsIgnoreCase(EntityCapsManager.ELEMENT))) {
|
||||||
|
throw new XMPPException("Malformed nested Caps element");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hash != null && version != null && node != null) {
|
||||||
|
return new CapsExtension(node, version, hash);
|
||||||
|
} else {
|
||||||
|
throw new XMPPException("Caps elment with missing attributes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,7 @@ import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.packet.Message;
|
import org.jivesoftware.smack.packet.Message;
|
||||||
import org.jivesoftware.smack.packet.Packet;
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
import org.jivesoftware.smack.packet.Presence;
|
import org.jivesoftware.smack.packet.Presence;
|
||||||
import org.jivesoftware.smack.packet.Registration;
|
import org.jivesoftware.smack.packet.Registration;
|
||||||
import org.jivesoftware.smackx.Form;
|
import org.jivesoftware.smackx.Form;
|
||||||
|
@ -133,6 +134,11 @@ public class MultiUserChat {
|
||||||
public List<DiscoverInfo.Identity> getNodeIdentities() {
|
public List<DiscoverInfo.Identity> getNodeIdentities() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PacketExtension> getNodePacketExtensions() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
package org.jivesoftware.smackx.packet;
|
package org.jivesoftware.smackx.packet;
|
||||||
|
|
||||||
import org.jivesoftware.smack.packet.PacketExtension;
|
import org.jivesoftware.smack.packet.PacketExtension;
|
||||||
|
import org.jivesoftware.smackx.Form;
|
||||||
import org.jivesoftware.smackx.FormField;
|
import org.jivesoftware.smackx.FormField;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -123,11 +124,11 @@ public class DataForm implements PacketExtension {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getElementName() {
|
public String getElementName() {
|
||||||
return "x";
|
return Form.ELEMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNamespace() {
|
public String getNamespace() {
|
||||||
return "jabber:x:data";
|
return Form.NAMESPACE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,6 +196,21 @@ public class DataForm implements PacketExtension {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this DataForm has at least one FORM_TYPE field which is
|
||||||
|
* hidden. This method is used for sanity checks.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean hasHiddenFromTypeField() {
|
||||||
|
boolean found = false;
|
||||||
|
for (FormField f : fields) {
|
||||||
|
if (f.getVariable().equals("FORM_TYPE") && f.getType() != null && f.getType().equals("hidden"))
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
public String toXML() {
|
public String toXML() {
|
||||||
StringBuilder buf = new StringBuilder();
|
StringBuilder buf = new StringBuilder();
|
||||||
buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
|
buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append(
|
||||||
|
|
|
@ -23,8 +23,10 @@ package org.jivesoftware.smackx.packet;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
@ -45,6 +47,36 @@ public class DiscoverInfo extends IQ {
|
||||||
private final List<Identity> identities = new CopyOnWriteArrayList<Identity>();
|
private final List<Identity> identities = new CopyOnWriteArrayList<Identity>();
|
||||||
private String node;
|
private String node;
|
||||||
|
|
||||||
|
public DiscoverInfo() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy constructor
|
||||||
|
*
|
||||||
|
* @param d
|
||||||
|
*/
|
||||||
|
public DiscoverInfo(DiscoverInfo d) {
|
||||||
|
super(d);
|
||||||
|
|
||||||
|
// Set node
|
||||||
|
setNode(d.getNode());
|
||||||
|
|
||||||
|
// Copy features
|
||||||
|
synchronized (d.features) {
|
||||||
|
for (Feature f : d.features) {
|
||||||
|
addFeature(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy identities
|
||||||
|
synchronized (d.identities) {
|
||||||
|
for (Identity i : d.identities) {
|
||||||
|
addIdentity(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new feature to the discovered information.
|
* Adds a new feature to the discovered information.
|
||||||
*
|
*
|
||||||
|
@ -54,6 +86,18 @@ public class DiscoverInfo extends IQ {
|
||||||
addFeature(new Feature(feature));
|
addFeature(new Feature(feature));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a collection of features to the packet. Does noting if featuresToAdd is null.
|
||||||
|
*
|
||||||
|
* @param featuresToAdd
|
||||||
|
*/
|
||||||
|
public void addFeatures(Collection<String> featuresToAdd) {
|
||||||
|
if (featuresToAdd == null) return;
|
||||||
|
for (String feature : featuresToAdd) {
|
||||||
|
addFeature(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addFeature(Feature feature) {
|
private void addFeature(Feature feature) {
|
||||||
synchronized (features) {
|
synchronized (features) {
|
||||||
features.add(feature);
|
features.add(feature);
|
||||||
|
@ -82,6 +126,18 @@ public class DiscoverInfo extends IQ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds identities to the DiscoverInfo stanza
|
||||||
|
*
|
||||||
|
* @param identitiesToAdd
|
||||||
|
*/
|
||||||
|
public void addIdentities(Collection<Identity> identitiesToAdd) {
|
||||||
|
if (identitiesToAdd == null) return;
|
||||||
|
synchronized (identities) {
|
||||||
|
identities.addAll(identitiesToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the discovered identities of an XMPP entity.
|
* Returns the discovered identities of an XMPP entity.
|
||||||
*
|
*
|
||||||
|
@ -158,6 +214,40 @@ public class DiscoverInfo extends IQ {
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a DiscoverInfo response contains duplicate identities.
|
||||||
|
*
|
||||||
|
* @return true if duplicate identities where found, otherwise false
|
||||||
|
*/
|
||||||
|
public boolean containsDuplicateIdentities() {
|
||||||
|
List<Identity> checkedIdentities = new LinkedList<Identity>();
|
||||||
|
for (Identity i : identities) {
|
||||||
|
for (Identity i2 : checkedIdentities) {
|
||||||
|
if (i.equals(i2))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
checkedIdentities.add(i);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a DiscoverInfo response contains duplicate features.
|
||||||
|
*
|
||||||
|
* @return true if duplicate identities where found, otherwise false
|
||||||
|
*/
|
||||||
|
public boolean containsDuplicateFeatures() {
|
||||||
|
List<Feature> checkedFeatures = new LinkedList<Feature>();
|
||||||
|
for (Feature f : features) {
|
||||||
|
for (Feature f2 : checkedFeatures) {
|
||||||
|
if (f.equals(f2))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
checkedFeatures.add(f);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the identity of a given XMPP entity. An entity may have many identities but all
|
* Represents the identity of a given XMPP entity. An entity may have many identities but all
|
||||||
* the identities SHOULD have the same name.<p>
|
* the identities SHOULD have the same name.<p>
|
||||||
|
@ -167,21 +257,26 @@ public class DiscoverInfo extends IQ {
|
||||||
* attributes.
|
* attributes.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static class Identity {
|
public static class Identity implements Comparable<Object> {
|
||||||
|
|
||||||
private String category;
|
private String category;
|
||||||
private String name;
|
private String name;
|
||||||
private String type;
|
private String type;
|
||||||
|
private String lang; // 'xml:lang;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new identity for an XMPP entity.
|
* Creates a new identity for an XMPP entity.
|
||||||
|
* 'category' and 'type' are required by
|
||||||
|
* <a href="http://xmpp.org/extensions/xep-0030.html#schemas">XEP-30 XML Schemas</a>
|
||||||
*
|
*
|
||||||
* @param category the entity's category.
|
* @param category the entity's category (required as per XEP-30).
|
||||||
* @param name the entity's name.
|
* @param name the entity's name.
|
||||||
|
* @param type the entity's type (required as per XEP-30).
|
||||||
*/
|
*/
|
||||||
public Identity(String category, String name) {
|
public Identity(String category, String name, String type) {
|
||||||
this.category = category;
|
this.category = category;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -223,16 +318,106 @@ public class DiscoverInfo extends IQ {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the natural language (xml:lang) for this identity (optional)
|
||||||
|
*
|
||||||
|
* @param lang the xml:lang of this Identity
|
||||||
|
*/
|
||||||
|
public void setLanguage(String lang) {
|
||||||
|
this.lang = lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the identities natural language if one is set
|
||||||
|
*
|
||||||
|
* @return the value of xml:lang of this Identity
|
||||||
|
*/
|
||||||
|
public String getLanguage() {
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
|
||||||
public String toXML() {
|
public String toXML() {
|
||||||
StringBuilder buf = new StringBuilder();
|
StringBuilder buf = new StringBuilder();
|
||||||
buf.append("<identity category=\"").append(StringUtils.escapeForXML(category)).append("\"");
|
buf.append("<identity");
|
||||||
|
// Check if this packet has 'lang' set and maybe append it to the resulting string
|
||||||
|
if (lang != null)
|
||||||
|
buf.append(" xml:lang=\"").append(StringUtils.escapeForXML(lang)).append("\"");
|
||||||
|
// Category must always be set
|
||||||
|
buf.append(" category=\"").append(StringUtils.escapeForXML(category)).append("\"");
|
||||||
|
// Name must always be set
|
||||||
buf.append(" name=\"").append(StringUtils.escapeForXML(name)).append("\"");
|
buf.append(" name=\"").append(StringUtils.escapeForXML(name)).append("\"");
|
||||||
|
// Check if this packet has 'type' set and maybe append it to the resulting string
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
buf.append(" type=\"").append(StringUtils.escapeForXML(type)).append("\"");
|
buf.append(" type=\"").append(StringUtils.escapeForXML(type)).append("\"");
|
||||||
}
|
}
|
||||||
buf.append("/>");
|
buf.append("/>");
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check equality for Identity for category, type, lang and name
|
||||||
|
* in that order as defined by
|
||||||
|
* <a href="http://xmpp.org/extensions/xep-0115.html#ver-proc">XEP-0015 5.4 Processing Method (Step 3.3)</a>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (obj == this)
|
||||||
|
return true;
|
||||||
|
if (obj.getClass() != getClass())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj;
|
||||||
|
if (!this.category.equals(other.category))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String otherLang = other.lang == null ? "" : other.lang;
|
||||||
|
String thisLang = lang == null ? "" : lang;
|
||||||
|
|
||||||
|
if (!other.type.equals(type))
|
||||||
|
return false;
|
||||||
|
if (!otherLang.equals(thisLang))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
String otherName = other.name == null ? "" : other.name;
|
||||||
|
String thisName = name == null ? "" : other.name;
|
||||||
|
if (!thisName.equals(otherName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares and identity with another object. The comparison order is:
|
||||||
|
* Category, Type, Lang. If all three are identical the other Identity is considered equal.
|
||||||
|
* Name is not used for comparision, as defined by XEP-0115
|
||||||
|
*
|
||||||
|
* @param obj
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int compareTo(Object obj) {
|
||||||
|
|
||||||
|
DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj;
|
||||||
|
String otherLang = other.lang == null ? "" : other.lang;
|
||||||
|
String thisLang = lang == null ? "" : lang;
|
||||||
|
|
||||||
|
if (category.equals(other.category)) {
|
||||||
|
if (type.equals(other.type)) {
|
||||||
|
if (thisLang.equals(otherLang)) {
|
||||||
|
// Don't compare on name, XEP-30 says that name SHOULD
|
||||||
|
// be equals for all identities of an entity
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return thisLang.compareTo(otherLang);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return type.compareTo(other.type);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return category.compareTo(other.category);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -268,5 +453,17 @@ public class DiscoverInfo extends IQ {
|
||||||
buf.append("<feature var=\"").append(StringUtils.escapeForXML(variable)).append("\"/>");
|
buf.append("<feature var=\"").append(StringUtils.escapeForXML(variable)).append("\"/>");
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (obj == this)
|
||||||
|
return true;
|
||||||
|
if (obj.getClass() != getClass())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DiscoverInfo.Feature other = (DiscoverInfo.Feature) obj;
|
||||||
|
return variable.equals(other.variable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ package org.jivesoftware.smackx.packet;
|
||||||
import org.jivesoftware.smack.packet.IQ;
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
import org.jivesoftware.smack.util.StringUtils;
|
import org.jivesoftware.smack.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -55,6 +56,18 @@ public class DiscoverItems extends IQ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a collection of items to the discovered information. Does nothing if itemsToAdd is null
|
||||||
|
*
|
||||||
|
* @param itemsToAdd
|
||||||
|
*/
|
||||||
|
public void addItems(Collection<Item> itemsToAdd) {
|
||||||
|
if (itemsToAdd == null) return;
|
||||||
|
for (Item i : itemsToAdd) {
|
||||||
|
addItem(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the discovered items of the queried XMPP entity.
|
* Returns the discovered items of the queried XMPP entity.
|
||||||
*
|
*
|
||||||
|
|
|
@ -42,6 +42,7 @@ public class DiscoverInfoProvider implements IQProvider {
|
||||||
String name = "";
|
String name = "";
|
||||||
String type = "";
|
String type = "";
|
||||||
String variable = "";
|
String variable = "";
|
||||||
|
String lang = "";
|
||||||
discoverInfo.setNode(parser.getAttributeValue("", "node"));
|
discoverInfo.setNode(parser.getAttributeValue("", "node"));
|
||||||
while (!done) {
|
while (!done) {
|
||||||
int eventType = parser.next();
|
int eventType = parser.next();
|
||||||
|
@ -51,6 +52,7 @@ public class DiscoverInfoProvider implements IQProvider {
|
||||||
category = parser.getAttributeValue("", "category");
|
category = parser.getAttributeValue("", "category");
|
||||||
name = parser.getAttributeValue("", "name");
|
name = parser.getAttributeValue("", "name");
|
||||||
type = parser.getAttributeValue("", "type");
|
type = parser.getAttributeValue("", "type");
|
||||||
|
lang = parser.getAttributeValue(parser.getNamespace("xml"), "lang");
|
||||||
}
|
}
|
||||||
else if (parser.getName().equals("feature")) {
|
else if (parser.getName().equals("feature")) {
|
||||||
// Initialize the variables from the parsed XML
|
// Initialize the variables from the parsed XML
|
||||||
|
@ -64,8 +66,9 @@ public class DiscoverInfoProvider implements IQProvider {
|
||||||
} else if (eventType == XmlPullParser.END_TAG) {
|
} else if (eventType == XmlPullParser.END_TAG) {
|
||||||
if (parser.getName().equals("identity")) {
|
if (parser.getName().equals("identity")) {
|
||||||
// Create a new identity and add it to the discovered info.
|
// Create a new identity and add it to the discovered info.
|
||||||
identity = new DiscoverInfo.Identity(category, name);
|
identity = new DiscoverInfo.Identity(category, name, type);
|
||||||
identity.setType(type);
|
if (lang != null)
|
||||||
|
identity.setLanguage(lang);
|
||||||
discoverInfo.addIdentity(identity);
|
discoverInfo.addIdentity(identity);
|
||||||
}
|
}
|
||||||
if (parser.getName().equals("feature")) {
|
if (parser.getName().equals("feature")) {
|
||||||
|
|
|
@ -247,8 +247,7 @@ public class Socks5ByteStreamManagerTest {
|
||||||
// build discover info for proxy containing information about NOT being a Socks5
|
// build discover info for proxy containing information about NOT being a Socks5
|
||||||
// proxy
|
// proxy
|
||||||
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
||||||
Identity identity = new Identity("noproxy", proxyJID);
|
Identity identity = new Identity("noproxy", proxyJID, "bytestreams");
|
||||||
identity.setType("bytestreams");
|
|
||||||
proxyInfo.addIdentity(identity);
|
proxyInfo.addIdentity(identity);
|
||||||
|
|
||||||
// return the proxy identity if proxy is queried
|
// return the proxy identity if proxy is queried
|
||||||
|
@ -312,8 +311,7 @@ public class Socks5ByteStreamManagerTest {
|
||||||
// build discover info for proxy containing information about NOT being a Socks5
|
// build discover info for proxy containing information about NOT being a Socks5
|
||||||
// proxy
|
// proxy
|
||||||
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
||||||
Identity identity = new Identity("noproxy", proxyJID);
|
Identity identity = new Identity("noproxy", proxyJID, "bytestreams");
|
||||||
identity.setType("bytestreams");
|
|
||||||
proxyInfo.addIdentity(identity);
|
proxyInfo.addIdentity(identity);
|
||||||
|
|
||||||
// return the proxy identity if proxy is queried
|
// return the proxy identity if proxy is queried
|
||||||
|
@ -403,8 +401,7 @@ public class Socks5ByteStreamManagerTest {
|
||||||
|
|
||||||
// build discover info for proxy containing information about being a SOCKS5 proxy
|
// build discover info for proxy containing information about being a SOCKS5 proxy
|
||||||
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
||||||
Identity identity = new Identity("proxy", proxyJID);
|
Identity identity = new Identity("proxy", proxyJID, "bytestreams");
|
||||||
identity.setType("bytestreams");
|
|
||||||
proxyInfo.addIdentity(identity);
|
proxyInfo.addIdentity(identity);
|
||||||
|
|
||||||
// return the socks5 bytestream proxy identity if proxy is queried
|
// return the socks5 bytestream proxy identity if proxy is queried
|
||||||
|
@ -494,8 +491,7 @@ public class Socks5ByteStreamManagerTest {
|
||||||
|
|
||||||
// build discover info for proxy containing information about being a SOCKS5 proxy
|
// build discover info for proxy containing information about being a SOCKS5 proxy
|
||||||
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
||||||
Identity identity = new Identity("proxy", proxyJID);
|
Identity identity = new Identity("proxy", proxyJID, "bytestreams");
|
||||||
identity.setType("bytestreams");
|
|
||||||
proxyInfo.addIdentity(identity);
|
proxyInfo.addIdentity(identity);
|
||||||
|
|
||||||
// return the socks5 bytestream proxy identity if proxy is queried
|
// return the socks5 bytestream proxy identity if proxy is queried
|
||||||
|
@ -577,8 +573,7 @@ public class Socks5ByteStreamManagerTest {
|
||||||
|
|
||||||
// build discover info for proxy containing information about being a SOCKS5 proxy
|
// build discover info for proxy containing information about being a SOCKS5 proxy
|
||||||
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
||||||
Identity identity = new Identity("proxy", proxyJID);
|
Identity identity = new Identity("proxy", proxyJID, "bytestreams");
|
||||||
identity.setType("bytestreams");
|
|
||||||
proxyInfo.addIdentity(identity);
|
proxyInfo.addIdentity(identity);
|
||||||
|
|
||||||
// return the socks5 bytestream proxy identity if proxy is queried
|
// return the socks5 bytestream proxy identity if proxy is queried
|
||||||
|
@ -672,8 +667,7 @@ public class Socks5ByteStreamManagerTest {
|
||||||
|
|
||||||
// build discover info for proxy containing information about being a SOCKS5 proxy
|
// build discover info for proxy containing information about being a SOCKS5 proxy
|
||||||
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
DiscoverInfo proxyInfo = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
||||||
Identity identity = new Identity("proxy", proxyJID);
|
Identity identity = new Identity("proxy", proxyJID, "bytestreams");
|
||||||
identity.setType("bytestreams");
|
|
||||||
proxyInfo.addIdentity(identity);
|
proxyInfo.addIdentity(identity);
|
||||||
|
|
||||||
// return the socks5 bytestream proxy identity if proxy is queried
|
// return the socks5 bytestream proxy identity if proxy is queried
|
||||||
|
@ -1026,7 +1020,7 @@ public class Socks5ByteStreamManagerTest {
|
||||||
*/
|
*/
|
||||||
DiscoverInfo proxyInfo1 = Socks5PacketUtils.createDiscoverInfo("proxy2.xmpp-server",
|
DiscoverInfo proxyInfo1 = Socks5PacketUtils.createDiscoverInfo("proxy2.xmpp-server",
|
||||||
initiatorJID);
|
initiatorJID);
|
||||||
Identity identity1 = new Identity("proxy", "proxy2.xmpp-server");
|
Identity identity1 = new Identity("proxy", "proxy2.xmpp-server", "bytestreams");
|
||||||
identity1.setType("bytestreams");
|
identity1.setType("bytestreams");
|
||||||
proxyInfo1.addIdentity(identity1);
|
proxyInfo1.addIdentity(identity1);
|
||||||
|
|
||||||
|
@ -1036,8 +1030,7 @@ public class Socks5ByteStreamManagerTest {
|
||||||
|
|
||||||
// build discover info for proxy containing information about being a SOCKS5 proxy
|
// build discover info for proxy containing information about being a SOCKS5 proxy
|
||||||
DiscoverInfo proxyInfo2 = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
DiscoverInfo proxyInfo2 = Socks5PacketUtils.createDiscoverInfo(proxyJID, initiatorJID);
|
||||||
Identity identity2 = new Identity("proxy", proxyJID);
|
Identity identity2 = new Identity("proxy", proxyJID, "bytestreams");
|
||||||
identity2.setType("bytestreams");
|
|
||||||
proxyInfo2.addIdentity(identity2);
|
proxyInfo2.addIdentity(identity2);
|
||||||
|
|
||||||
// return the SOCKS5 bytestream proxy identity if proxy is queried
|
// return the SOCKS5 bytestream proxy identity if proxy is queried
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
package org.jivesoftware.smackx.entitycaps;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smack.util.Base32Encoder;
|
||||||
|
import org.jivesoftware.smack.util.Base64Encoder;
|
||||||
|
import org.jivesoftware.smack.util.StringEncoder;
|
||||||
|
import org.jivesoftware.smackx.FormField;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.cache.EntityCapsPersistentCache;
|
||||||
|
import org.jivesoftware.smackx.entitycaps.cache.SimpleDirectoryPersistentCache;
|
||||||
|
import org.jivesoftware.smackx.packet.DataForm;
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
|
public class EntityCapsManagerTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://xmpp.org/extensions/xep-0115.html#ver-gen-complex">XEP-
|
||||||
|
* 0115 Complex Generation Example</a>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testComplexGenerationExample() {
|
||||||
|
DiscoverInfo di = createComplexSamplePacket();
|
||||||
|
|
||||||
|
String ver = EntityCapsManager.generateVerificationString(di, "sha-1");
|
||||||
|
assertEquals("q07IKJEyjvHSyhy//CH0CxmKi8w=", ver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleDirectoryCacheBase64() throws IOException {
|
||||||
|
EntityCapsManager.persistentCache = null;
|
||||||
|
testSimpleDirectoryCache(Base64Encoder.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleDirectoryCacheBase32() throws IOException {
|
||||||
|
EntityCapsManager.persistentCache = null;
|
||||||
|
testSimpleDirectoryCache(Base32Encoder.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVerificationDuplicateFeatures() {
|
||||||
|
DiscoverInfo di = createMalformedDiscoverInfo();
|
||||||
|
assertTrue(di.containsDuplicateFeatures());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVerificationDuplicateIdentities() {
|
||||||
|
DiscoverInfo di = createMalformedDiscoverInfo();
|
||||||
|
assertTrue(di.containsDuplicateIdentities());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVerificationDuplicateDataForm() {
|
||||||
|
DiscoverInfo di = createMalformedDiscoverInfo();
|
||||||
|
assertTrue(EntityCapsManager.verifyPacketExtensions(di));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testSimpleDirectoryCache(StringEncoder stringEncoder) throws IOException {
|
||||||
|
|
||||||
|
EntityCapsPersistentCache cache = new SimpleDirectoryPersistentCache(createTempDirectory());
|
||||||
|
EntityCapsManager.setPersistentCache(cache);
|
||||||
|
|
||||||
|
DiscoverInfo di = createComplexSamplePacket();
|
||||||
|
String nodeVer = di.getNode() + "#" + EntityCapsManager.generateVerificationString(di, "sha-1");
|
||||||
|
|
||||||
|
// Save the data in EntityCapsManager
|
||||||
|
EntityCapsManager.addDiscoverInfoByNode(nodeVer, di);
|
||||||
|
|
||||||
|
// Lose all the data
|
||||||
|
EntityCapsManager.caps.clear();
|
||||||
|
|
||||||
|
// Restore the data from the persistent Cache
|
||||||
|
cache.replay();
|
||||||
|
|
||||||
|
DiscoverInfo restored_di = EntityCapsManager.getDiscoveryInfoByNodeVer(nodeVer);
|
||||||
|
assertNotNull(restored_di);
|
||||||
|
assertEquals(di.toXML(), restored_di.toXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DiscoverInfo createComplexSamplePacket() {
|
||||||
|
DiscoverInfo di = new DiscoverInfo();
|
||||||
|
di.setFrom("benvolio@capulet.lit/230193");
|
||||||
|
di.setPacketID("disco1");
|
||||||
|
di.setTo("juliet@capulet.lit/chamber");
|
||||||
|
di.setType(IQ.Type.RESULT);
|
||||||
|
|
||||||
|
Collection<DiscoverInfo.Identity> identities = new LinkedList<DiscoverInfo.Identity>();
|
||||||
|
DiscoverInfo.Identity i = new DiscoverInfo.Identity("client", "Psi 0.11", "pc");
|
||||||
|
i.setLanguage("en");
|
||||||
|
identities.add(i);
|
||||||
|
i = new DiscoverInfo.Identity("client", "Ψ 0.11", "pc");
|
||||||
|
i.setLanguage("el");
|
||||||
|
identities.add(i);
|
||||||
|
di.addIdentities(identities);
|
||||||
|
|
||||||
|
di.addFeature("http://jabber.org/protocol/disco#items");
|
||||||
|
di.addFeature(EntityCapsManager.NAMESPACE);
|
||||||
|
di.addFeature("http://jabber.org/protocol/muc");
|
||||||
|
di.addFeature("http://jabber.org/protocol/disco#info");
|
||||||
|
|
||||||
|
DataForm df = new DataForm("result");
|
||||||
|
|
||||||
|
FormField ff = new FormField("os");
|
||||||
|
ff.addValue("Mac");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("FORM_TYPE");
|
||||||
|
ff.setType("hidden");
|
||||||
|
ff.addValue("urn:xmpp:dataforms:softwareinfo");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("ip_version");
|
||||||
|
ff.addValue("ipv4");
|
||||||
|
ff.addValue("ipv6");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("os_version");
|
||||||
|
ff.addValue("10.5.1");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("software");
|
||||||
|
ff.addValue("Psi");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("software_version");
|
||||||
|
ff.addValue("0.11");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
di.addExtension(df);
|
||||||
|
return di;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DiscoverInfo createMalformedDiscoverInfo() {
|
||||||
|
DiscoverInfo di = new DiscoverInfo();
|
||||||
|
di.setFrom("benvolio@capulet.lit/230193");
|
||||||
|
di.setPacketID("disco1");
|
||||||
|
di.setTo(")juliet@capulet.lit/chamber");
|
||||||
|
di.setType(IQ.Type.RESULT);
|
||||||
|
|
||||||
|
Collection<DiscoverInfo.Identity> identities = new LinkedList<DiscoverInfo.Identity>();
|
||||||
|
DiscoverInfo.Identity i = new DiscoverInfo.Identity("client", "Psi 0.11", "pc");
|
||||||
|
i.setLanguage("en");
|
||||||
|
identities.add(i);
|
||||||
|
i = new DiscoverInfo.Identity("client", "Ψ 0.11", "pc");
|
||||||
|
i.setLanguage("el");
|
||||||
|
identities.add(i);
|
||||||
|
di.addIdentities(identities);
|
||||||
|
// Failure 1: Duplicate identities
|
||||||
|
i = new DiscoverInfo.Identity("client", "Ψ 0.11", "pc");
|
||||||
|
i.setLanguage("el");
|
||||||
|
identities.add(i);
|
||||||
|
di.addIdentities(identities);
|
||||||
|
|
||||||
|
di.addFeature("http://jabber.org/protocol/disco#items");
|
||||||
|
di.addFeature(EntityCapsManager.NAMESPACE);
|
||||||
|
di.addFeature("http://jabber.org/protocol/muc");
|
||||||
|
di.addFeature("http://jabber.org/protocol/disco#info");
|
||||||
|
// Failure 2: Duplicate features
|
||||||
|
di.addFeature("http://jabber.org/protocol/disco#info");
|
||||||
|
|
||||||
|
DataForm df = new DataForm("result");
|
||||||
|
|
||||||
|
FormField ff = new FormField("os");
|
||||||
|
ff.addValue("Mac");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("FORM_TYPE");
|
||||||
|
ff.setType("hidden");
|
||||||
|
ff.addValue("urn:xmpp:dataforms:softwareinfo");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("ip_version");
|
||||||
|
ff.addValue("ipv4");
|
||||||
|
ff.addValue("ipv6");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("os_version");
|
||||||
|
ff.addValue("10.5.1");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("software");
|
||||||
|
ff.addValue("Psi");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("software_version");
|
||||||
|
ff.addValue("0.11");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
di.addExtension(df);
|
||||||
|
|
||||||
|
// Failure 3: Another service discovery information form with the same
|
||||||
|
// FORM_TYPE
|
||||||
|
df = new DataForm("result");
|
||||||
|
|
||||||
|
ff = new FormField("FORM_TYPE");
|
||||||
|
ff.setType("hidden");
|
||||||
|
ff.addValue("urn:xmpp:dataforms:softwareinfo");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
ff = new FormField("software");
|
||||||
|
ff.addValue("smack");
|
||||||
|
df.addField(ff);
|
||||||
|
|
||||||
|
di.addExtension(df);
|
||||||
|
|
||||||
|
return di;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File createTempDirectory() throws IOException {
|
||||||
|
String tmpdir = System.getProperty("java.io.tmpdir");
|
||||||
|
File tmp;
|
||||||
|
tmp = File.createTempFile(tmpdir, "entityCaps");
|
||||||
|
tmp.delete();
|
||||||
|
tmp.mkdir();
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -50,8 +50,7 @@ public class ConfigureFormTest
|
||||||
ThreadedDummyConnection con = new ThreadedDummyConnection();
|
ThreadedDummyConnection con = new ThreadedDummyConnection();
|
||||||
PubSubManager mgr = new PubSubManager(con);
|
PubSubManager mgr = new PubSubManager(con);
|
||||||
DiscoverInfo info = new DiscoverInfo();
|
DiscoverInfo info = new DiscoverInfo();
|
||||||
Identity ident = new Identity("pubsub", null);
|
Identity ident = new Identity("pubsub", null, "leaf");
|
||||||
ident.setType("leaf");
|
|
||||||
info.addIdentity(ident);
|
info.addIdentity(ident);
|
||||||
con.addIQReply(info);
|
con.addIQReply(info);
|
||||||
|
|
||||||
|
@ -78,8 +77,7 @@ public class ConfigureFormTest
|
||||||
ThreadedDummyConnection con = new ThreadedDummyConnection();
|
ThreadedDummyConnection con = new ThreadedDummyConnection();
|
||||||
PubSubManager mgr = new PubSubManager(con);
|
PubSubManager mgr = new PubSubManager(con);
|
||||||
DiscoverInfo info = new DiscoverInfo();
|
DiscoverInfo info = new DiscoverInfo();
|
||||||
Identity ident = new Identity("pubsub", null);
|
Identity ident = new Identity("pubsub", null, "leaf");
|
||||||
ident.setType("leaf");
|
|
||||||
info.addIdentity(ident);
|
info.addIdentity(ident);
|
||||||
con.addIQReply(info);
|
con.addIQReply(info);
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class ReconnectionTest extends SmackTestCase {
|
||||||
|
|
||||||
public void testAutomaticReconnection() throws Exception {
|
public void testAutomaticReconnection() throws Exception {
|
||||||
XMPPConnection connection = getConnection(0);
|
XMPPConnection connection = getConnection(0);
|
||||||
XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
|
XMPPConnectionTestListener listener = new XMPPConnectionTestListener();
|
||||||
connection.addConnectionListener(listener);
|
connection.addConnectionListener(listener);
|
||||||
|
|
||||||
// Simulates an error in the connection
|
// Simulates an error in the connection
|
||||||
|
|
|
@ -34,6 +34,7 @@ import junit.framework.TestCase;
|
||||||
import org.jivesoftware.smack.ConnectionConfiguration;
|
import org.jivesoftware.smack.ConnectionConfiguration;
|
||||||
import org.jivesoftware.smack.XMPPConnection;
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
import org.jivesoftware.smack.XMPPException;
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.util.ConnectionUtils;
|
||||||
import org.xmlpull.mxp1.MXParser;
|
import org.xmlpull.mxp1.MXParser;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
|
|
||||||
|
@ -504,6 +505,15 @@ public abstract class SmackTestCase extends TestCase {
|
||||||
return "config/" + fullClassName.substring(firstChar) + ".xml";
|
return "config/" + fullClassName.substring(firstChar) + ".xml";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes all connections with each other: They all become friends
|
||||||
|
*
|
||||||
|
* @throws XMPPException
|
||||||
|
*/
|
||||||
|
protected void letsAllBeFriends() throws XMPPException {
|
||||||
|
ConnectionUtils.letsAllBeFriends(connections);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares two contents of two byte arrays to make sure that they are equal
|
* Compares two contents of two byte arrays to make sure that they are equal
|
||||||
*
|
*
|
||||||
|
|
27
test/org/jivesoftware/smack/util/ConnectionUtils.java
Normal file
27
test/org/jivesoftware/smack/util/ConnectionUtils.java
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package org.jivesoftware.smack.util;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.Connection;
|
||||||
|
import org.jivesoftware.smack.Roster;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
|
||||||
|
public class ConnectionUtils {
|
||||||
|
|
||||||
|
public static void becomeFriends(Connection con0, Connection con1) throws XMPPException {
|
||||||
|
Roster r0 = con0.getRoster();
|
||||||
|
Roster r1 = con1.getRoster();
|
||||||
|
r0.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
|
||||||
|
r1.setSubscriptionMode(Roster.SubscriptionMode.accept_all);
|
||||||
|
r0.createEntry(con1.getUser(), "u2", null);
|
||||||
|
r1.createEntry(con0.getUser(), "u1", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void letsAllBeFriends(Connection[] connections) throws XMPPException {
|
||||||
|
for (Connection c1 : connections) {
|
||||||
|
for (Connection c2 : connections) {
|
||||||
|
if (c1 == c2)
|
||||||
|
continue;
|
||||||
|
becomeFriends(c1, c2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ public class ServiceDiscoveryManagerTest extends SmackTestCase {
|
||||||
.getInstanceFor(getConnection(0));
|
.getInstanceFor(getConnection(0));
|
||||||
try {
|
try {
|
||||||
// Discover the information of another Smack client
|
// Discover the information of another Smack client
|
||||||
DiscoverInfo info = discoManager.discoverInfo(getFullJID(1));
|
DiscoverInfo info = discoManager.discoverInfo(getFullJID(1));
|
||||||
// Check the identity of the Smack client
|
// Check the identity of the Smack client
|
||||||
Iterator<Identity> identities = info.getIdentities();
|
Iterator<Identity> identities = info.getIdentities();
|
||||||
assertTrue("No identities were found", identities.hasNext());
|
assertTrue("No identities were found", identities.hasNext());
|
||||||
|
|
147
test/org/jivesoftware/smackx/entitycaps/EntityCapsTest.java
Normal file
147
test/org/jivesoftware/smackx/entitycaps/EntityCapsTest.java
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
package org.jivesoftware.smackx.entitycaps;
|
||||||
|
|
||||||
|
import org.jivesoftware.smack.PacketListener;
|
||||||
|
import org.jivesoftware.smack.SmackConfiguration;
|
||||||
|
import org.jivesoftware.smack.XMPPConnection;
|
||||||
|
import org.jivesoftware.smack.XMPPException;
|
||||||
|
import org.jivesoftware.smack.filter.AndFilter;
|
||||||
|
import org.jivesoftware.smack.filter.PacketTypeFilter;
|
||||||
|
import org.jivesoftware.smack.filter.IQTypeFilter;
|
||||||
|
import org.jivesoftware.smack.packet.IQ;
|
||||||
|
import org.jivesoftware.smack.packet.Packet;
|
||||||
|
import org.jivesoftware.smack.test.SmackTestCase;
|
||||||
|
import org.jivesoftware.smackx.ServiceDiscoveryManager;
|
||||||
|
import org.jivesoftware.smackx.packet.DiscoverInfo;
|
||||||
|
|
||||||
|
public class EntityCapsTest extends SmackTestCase {
|
||||||
|
|
||||||
|
private static final String DISCOVER_TEST_FEATURE = "entityCapsTest";
|
||||||
|
|
||||||
|
XMPPConnection con0;
|
||||||
|
XMPPConnection con1;
|
||||||
|
EntityCapsManager ecm0;
|
||||||
|
EntityCapsManager ecm1;
|
||||||
|
ServiceDiscoveryManager sdm0;
|
||||||
|
ServiceDiscoveryManager sdm1;
|
||||||
|
|
||||||
|
private boolean discoInfoSend = false;
|
||||||
|
|
||||||
|
public EntityCapsTest(String arg0) {
|
||||||
|
super(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getMaxConnections() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
SmackConfiguration.setAutoEnableEntityCaps(true);
|
||||||
|
SmackConfiguration.setPacketReplyTimeout(1000 * 60 * 5);
|
||||||
|
con0 = getConnection(0);
|
||||||
|
con1 = getConnection(1);
|
||||||
|
ecm0 = EntityCapsManager.getInstanceFor(getConnection(0));
|
||||||
|
ecm1 = EntityCapsManager.getInstanceFor(getConnection(1));
|
||||||
|
sdm0 = ServiceDiscoveryManager.getInstanceFor(con0);
|
||||||
|
sdm1 = ServiceDiscoveryManager.getInstanceFor(con1);
|
||||||
|
letsAllBeFriends();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testLocalEntityCaps() throws InterruptedException {
|
||||||
|
DiscoverInfo info = EntityCapsManager.getDiscoveryInfoByNodeVer(ecm1.getLocalNodeVer());
|
||||||
|
assertFalse(info.containsFeature(DISCOVER_TEST_FEATURE));
|
||||||
|
|
||||||
|
dropWholeEntityCapsCache();
|
||||||
|
|
||||||
|
// This should cause a new presence stanza from con1 with and updated
|
||||||
|
// 'ver' String
|
||||||
|
sdm1.addFeature(DISCOVER_TEST_FEATURE);
|
||||||
|
|
||||||
|
// Give the server some time to handle the stanza and send it to con0
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
// The presence stanza should get received by con0 and the data should
|
||||||
|
// be recorded in the map
|
||||||
|
// Note that while both connections use the same static Entity Caps
|
||||||
|
// cache,
|
||||||
|
// it's assured that *not* con1 added the data to the Entity Caps cache.
|
||||||
|
// Every time the entities features
|
||||||
|
// and identities change only a new caps 'ver' is calculated and send
|
||||||
|
// with the presence stanza
|
||||||
|
// The other connection has to receive this stanza and record the
|
||||||
|
// information in order for this test to succeed.
|
||||||
|
info = EntityCapsManager.getDiscoveryInfoByNodeVer(ecm1.getLocalNodeVer());
|
||||||
|
assertNotNull(info);
|
||||||
|
assertTrue(info.containsFeature(DISCOVER_TEST_FEATURE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if entity caps actually prevent a disco info request and reply
|
||||||
|
*
|
||||||
|
* @throws XMPPException
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void testPreventDiscoInfo() throws XMPPException {
|
||||||
|
con0.addPacketSendingListener(new PacketListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void processPacket(Packet packet) {
|
||||||
|
discoInfoSend = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}, new AndFilter(new PacketTypeFilter(DiscoverInfo.class), new IQTypeFilter(IQ.Type.GET)));
|
||||||
|
|
||||||
|
// add a bogus feature so that con1 ver won't match con0's
|
||||||
|
sdm1.addFeature(DISCOVER_TEST_FEATURE);
|
||||||
|
|
||||||
|
dropCapsCache();
|
||||||
|
// discover that
|
||||||
|
DiscoverInfo info = sdm0.discoverInfo(con1.getUser());
|
||||||
|
// that discovery should cause a disco#info
|
||||||
|
assertTrue(discoInfoSend);
|
||||||
|
assertTrue(info.containsFeature(DISCOVER_TEST_FEATURE));
|
||||||
|
discoInfoSend = false;
|
||||||
|
|
||||||
|
// discover that
|
||||||
|
info = sdm0.discoverInfo(con1.getUser());
|
||||||
|
// that discovery shouldn't cause a disco#info
|
||||||
|
assertFalse(discoInfoSend);
|
||||||
|
assertTrue(info.containsFeature(DISCOVER_TEST_FEATURE));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCapsChanged() {
|
||||||
|
String nodeVerBefore = EntityCapsManager.getNodeVersionByJid(con1.getUser());
|
||||||
|
sdm1.addFeature(DISCOVER_TEST_FEATURE);
|
||||||
|
String nodeVerAfter = EntityCapsManager.getNodeVersionByJid(con1.getUser());
|
||||||
|
|
||||||
|
assertFalse(nodeVerBefore.equals(nodeVerAfter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testEntityCaps() throws XMPPException, InterruptedException {
|
||||||
|
dropWholeEntityCapsCache();
|
||||||
|
sdm1.addFeature(DISCOVER_TEST_FEATURE);
|
||||||
|
|
||||||
|
Thread.sleep(3000);
|
||||||
|
|
||||||
|
DiscoverInfo info = sdm0.discoverInfo(con1.getUser());
|
||||||
|
assertTrue(info.containsFeature(DISCOVER_TEST_FEATURE));
|
||||||
|
|
||||||
|
String u1ver = EntityCapsManager.getNodeVersionByJid(con1.getUser());
|
||||||
|
assertNotNull(u1ver);
|
||||||
|
|
||||||
|
DiscoverInfo entityInfo = EntityCapsManager.caps.get(u1ver);
|
||||||
|
assertNotNull(entityInfo);
|
||||||
|
|
||||||
|
assertEquals(info.toXML(), entityInfo.toXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dropWholeEntityCapsCache() {
|
||||||
|
EntityCapsManager.caps.clear();
|
||||||
|
EntityCapsManager.jidCaps.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void dropCapsCache() {
|
||||||
|
EntityCapsManager.caps.clear();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue