mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-12-22 20:47:57 +01:00
Move Packet 'properties' from core to extensions
Code for Jive's packet properties should not be in 'core'. Also de-serializing input from network causes some security implications, it is therefore disabled as default.
This commit is contained in:
parent
c2b214f8d8
commit
6e08a10186
11 changed files with 516 additions and 281 deletions
|
@ -20,29 +20,21 @@ package org.jivesoftware.smack.packet;
|
|||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Base class for XMPP packets. Every packet has a unique ID (which is automatically
|
||||
* generated, but can be overriden). Optionally, the "to" and "from" fields can be set,
|
||||
* as well as an arbitrary number of properties.
|
||||
*
|
||||
* Properties provide an easy mechanism for clients to share data. Each property has a
|
||||
* String name, and a value that is a Java primitive (int, long, float, double, boolean)
|
||||
* or any Serializable object (a Java object is Serializable when it implements the
|
||||
* Serializable interface).
|
||||
* generated, but can be overridden). Optionally, the "to" and "from" fields can be set.
|
||||
*
|
||||
* @author Matt Tucker
|
||||
*/
|
||||
public abstract class Packet {
|
||||
private static final Logger LOGGER = Logger.getLogger(Packet.class.getName());
|
||||
|
||||
|
||||
protected static final String DEFAULT_LANGUAGE =
|
||||
java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US);
|
||||
|
||||
|
@ -88,7 +80,6 @@ public abstract class Packet {
|
|||
private final List<PacketExtension> packetExtensions
|
||||
= new CopyOnWriteArrayList<PacketExtension>();
|
||||
|
||||
private final Map<String,Object> properties = new HashMap<String, Object>();
|
||||
private XMPPError error = null;
|
||||
|
||||
public Packet() {
|
||||
|
@ -292,60 +283,6 @@ public abstract class Packet {
|
|||
packetExtensions.remove(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the packet property with the specified name or <tt>null</tt> if the
|
||||
* property doesn't exist. Property values that were originally primitives will
|
||||
* be returned as their object equivalent. For example, an int property will be
|
||||
* returned as an Integer, a double as a Double, etc.
|
||||
*
|
||||
* @param name the name of the property.
|
||||
* @return the property, or <tt>null</tt> if the property doesn't exist.
|
||||
*/
|
||||
public synchronized Object getProperty(String name) {
|
||||
if (properties == null) {
|
||||
return null;
|
||||
}
|
||||
return properties.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property with an Object as the value. The value must be Serializable
|
||||
* or an IllegalArgumentException will be thrown.
|
||||
*
|
||||
* @param name the name of the property.
|
||||
* @param value the value of the property.
|
||||
*/
|
||||
public synchronized void setProperty(String name, Object value) {
|
||||
if (!(value instanceof Serializable)) {
|
||||
throw new IllegalArgumentException("Value must be serialiazble");
|
||||
}
|
||||
properties.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a property.
|
||||
*
|
||||
* @param name the name of the property to delete.
|
||||
*/
|
||||
public synchronized void deleteProperty(String name) {
|
||||
if (properties == null) {
|
||||
return;
|
||||
}
|
||||
properties.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable collection of all the property names that are set.
|
||||
*
|
||||
* @return all property names.
|
||||
*/
|
||||
public synchronized Collection<String> getPropertyNames() {
|
||||
if (properties == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return Collections.unmodifiableSet(new HashSet<String>(properties.keySet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the packet as XML. Every concrete extension of Packet must implement
|
||||
* this method. In addition to writing out packet-specific data, every sub-class
|
||||
|
@ -368,88 +305,6 @@ public abstract class Packet {
|
|||
for (PacketExtension extension : getExtensions()) {
|
||||
xml.append(extension.toXML());
|
||||
}
|
||||
// Add in packet properties.
|
||||
if (properties != null && !properties.isEmpty()) {
|
||||
xml.halfOpenElement("properties").xmlnsAttribute("http://www.jivesoftware.com/xmlns/xmpp/properties");
|
||||
xml.rightAngelBracket();
|
||||
// Loop through all properties and write them out.
|
||||
for (String name : getPropertyNames()) {
|
||||
Object value = getProperty(name);
|
||||
xml.openElement("property");
|
||||
xml.element("name", name);
|
||||
xml.halfOpenElement("value");
|
||||
|
||||
String type;
|
||||
String valueStr;
|
||||
if (value instanceof Integer) {
|
||||
type = "integer";
|
||||
valueStr = Integer.toString((Integer)value);
|
||||
}
|
||||
else if (value instanceof Long) {
|
||||
type = "long";
|
||||
valueStr = Long.toString((Long) value);
|
||||
}
|
||||
else if (value instanceof Float) {
|
||||
type = "float";
|
||||
valueStr = Float.toString((Float) value);
|
||||
}
|
||||
else if (value instanceof Double) {
|
||||
type = "double";
|
||||
valueStr = Double.toString((Double) value);
|
||||
}
|
||||
else if (value instanceof Boolean) {
|
||||
type = "boolean";
|
||||
valueStr = Boolean.toString((Boolean) value);
|
||||
}
|
||||
else if (value instanceof String) {
|
||||
type = "string";
|
||||
valueStr = (String) value;
|
||||
}
|
||||
// Otherwise, it's a generic Serializable object. Serialized objects are in
|
||||
// a binary format, which won't work well inside of XML. Therefore, we base-64
|
||||
// encode the binary data before adding it.
|
||||
else {
|
||||
ByteArrayOutputStream byteStream = null;
|
||||
ObjectOutputStream out = null;
|
||||
try {
|
||||
byteStream = new ByteArrayOutputStream();
|
||||
out = new ObjectOutputStream(byteStream);
|
||||
out.writeObject(value);
|
||||
type ="java-object";
|
||||
valueStr = StringUtils.encodeBase64(byteStream.toByteArray());
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Error encoding java object", e);
|
||||
type ="java-object";
|
||||
valueStr = "Serializing error: " + e.getMessage();
|
||||
}
|
||||
finally {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
if (byteStream != null) {
|
||||
try {
|
||||
byteStream.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
xml.attribute("type", type);
|
||||
xml.rightAngelBracket();
|
||||
xml.escape(valueStr);
|
||||
xml.closeElement("value");
|
||||
xml.closeElement("property");
|
||||
}
|
||||
xml.closeElement("properties");
|
||||
}
|
||||
return xml;
|
||||
}
|
||||
|
||||
|
@ -479,10 +334,6 @@ public abstract class Packet {
|
|||
if (packetID != null ? !packetID.equals(packet.packetID) : packet.packetID != null) {
|
||||
return false;
|
||||
}
|
||||
if (properties != null ? !properties.equals(packet.properties)
|
||||
: packet.properties != null) {
|
||||
return false;
|
||||
}
|
||||
if (to != null ? !to.equals(packet.to) : packet.to != null) { return false; }
|
||||
return !(xmlns != null ? !xmlns.equals(packet.xmlns) : packet.xmlns != null);
|
||||
}
|
||||
|
@ -495,7 +346,6 @@ public abstract class Packet {
|
|||
result = 31 * result + (to != null ? to.hashCode() : 0);
|
||||
result = 31 * result + (from != null ? from.hashCode() : 0);
|
||||
result = 31 * result + packetExtensions.hashCode();
|
||||
result = 31 * result + properties.hashCode();
|
||||
result = 31 * result + (error != null ? error.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
*/
|
||||
package org.jivesoftware.smack.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
@ -55,12 +53,6 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||
*/
|
||||
public class PacketParserUtils {
|
||||
private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName());
|
||||
|
||||
/**
|
||||
* Namespace used to store packet properties.
|
||||
*/
|
||||
private static final String PROPERTIES_NAMESPACE =
|
||||
"http://www.jivesoftware.com/xmlns/xmpp/properties";
|
||||
|
||||
/**
|
||||
* Parses a message packet.
|
||||
|
@ -69,7 +61,7 @@ public class PacketParserUtils {
|
|||
* @return a Message packet.
|
||||
* @throws Exception if an exception occurs while parsing the packet.
|
||||
*/
|
||||
public static Packet parseMessage(XmlPullParser parser) throws Exception {
|
||||
public static Message parseMessage(XmlPullParser parser) throws Exception {
|
||||
Message message = new Message();
|
||||
String id = parser.getAttributeValue("", "id");
|
||||
message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
|
||||
|
@ -93,7 +85,6 @@ public class PacketParserUtils {
|
|||
// in arbitrary sub-elements.
|
||||
boolean done = false;
|
||||
String thread = null;
|
||||
Map<String, Object> properties = null;
|
||||
while (!done) {
|
||||
int eventType = parser.next();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
|
@ -131,11 +122,6 @@ public class PacketParserUtils {
|
|||
else if (elementName.equals("error")) {
|
||||
message.setError(parseError(parser));
|
||||
}
|
||||
else if (elementName.equals("properties") &&
|
||||
namespace.equals(PROPERTIES_NAMESPACE))
|
||||
{
|
||||
properties = parseProperties(parser);
|
||||
}
|
||||
// Otherwise, it must be a packet extension.
|
||||
else {
|
||||
message.addExtension(
|
||||
|
@ -150,12 +136,6 @@ public class PacketParserUtils {
|
|||
}
|
||||
|
||||
message.setThread(thread);
|
||||
// Set packet properties.
|
||||
if (properties != null) {
|
||||
for (String name : properties.keySet()) {
|
||||
message.setProperty(name, properties.get(name));
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
|
@ -246,15 +226,6 @@ public class PacketParserUtils {
|
|||
else if (elementName.equals("error")) {
|
||||
presence.setError(parseError(parser));
|
||||
}
|
||||
else if (elementName.equals("properties") &&
|
||||
namespace.equals(PROPERTIES_NAMESPACE))
|
||||
{
|
||||
Map<String,Object> properties = parseProperties(parser);
|
||||
// Set packet properties.
|
||||
for (String name : properties.keySet()) {
|
||||
presence.setProperty(name, properties.get(name));
|
||||
}
|
||||
}
|
||||
// Otherwise, it must be a packet extension.
|
||||
else {
|
||||
try {
|
||||
|
@ -549,87 +520,6 @@ public class PacketParserUtils {
|
|||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a properties sub-packet. If any errors occur while de-serializing Java object
|
||||
* properties, an exception will be printed and not thrown since a thrown
|
||||
* exception will shut down the entire connection. ClassCastExceptions will occur
|
||||
* when both the sender and receiver of the packet don't have identical versions
|
||||
* of the same class.
|
||||
*
|
||||
* @param parser the XML parser, positioned at the start of a properties sub-packet.
|
||||
* @return a map of the properties.
|
||||
* @throws Exception if an error occurs while parsing the properties.
|
||||
*/
|
||||
public static Map<String, Object> parseProperties(XmlPullParser parser) throws Exception {
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
while (true) {
|
||||
int eventType = parser.next();
|
||||
if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) {
|
||||
// Parse a property
|
||||
boolean done = false;
|
||||
String name = null;
|
||||
String type = null;
|
||||
String valueText = null;
|
||||
Object value = null;
|
||||
while (!done) {
|
||||
eventType = parser.next();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String elementName = parser.getName();
|
||||
if (elementName.equals("name")) {
|
||||
name = parser.nextText();
|
||||
}
|
||||
else if (elementName.equals("value")) {
|
||||
type = parser.getAttributeValue("", "type");
|
||||
valueText = parser.nextText();
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals("property")) {
|
||||
if ("integer".equals(type)) {
|
||||
value = Integer.valueOf(valueText);
|
||||
}
|
||||
else if ("long".equals(type)) {
|
||||
value = Long.valueOf(valueText);
|
||||
}
|
||||
else if ("float".equals(type)) {
|
||||
value = Float.valueOf(valueText);
|
||||
}
|
||||
else if ("double".equals(type)) {
|
||||
value = Double.valueOf(valueText);
|
||||
}
|
||||
else if ("boolean".equals(type)) {
|
||||
value = Boolean.valueOf(valueText);
|
||||
}
|
||||
else if ("string".equals(type)) {
|
||||
value = valueText;
|
||||
}
|
||||
else if ("java-object".equals(type)) {
|
||||
try {
|
||||
byte [] bytes = StringUtils.decodeBase64(valueText);
|
||||
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
|
||||
value = in.readObject();
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Error parsing java object", e);
|
||||
}
|
||||
}
|
||||
if (name != null && value != null) {
|
||||
properties.put(name, value);
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals("properties")) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses SASL authentication error packets.
|
||||
*
|
||||
|
|
|
@ -90,6 +90,11 @@
|
|||
<td><a href="http://www.xmpp.org/extensions/xep-0332.html">XEP-0332</a></td>
|
||||
<td>Allows to transport HTTP communication over XMPP peer-to-peer networks.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><a href="properties.html">Jive Properties</a></td>
|
||||
<td>N/A</td>
|
||||
<td>TODO</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -10,10 +10,6 @@
|
|||
Packet Properties
|
||||
</div>
|
||||
|
||||
<div class="nav">
|
||||
« <a href="index.html">Table of Contents</a>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Smack provides an easy mechanism for attaching arbitrary properties to packets. Each property
|
||||
has a String name, and a value that is a Java primitive (int, long, float, double, boolean) or
|
||||
|
@ -32,10 +28,13 @@ demonstrates how to set properties:
|
|||
|
||||
<div class="code"><pre>
|
||||
Message message = chat.createMessage();
|
||||
JivePropertiesExtension jpe = new JivePropertiesExtension();
|
||||
<font color="gray"><i>// Add a Color object as a property.</i></font>
|
||||
message.setProperty(<font color="blue">"favoriteColor"</font>, new Color(0, 0, 255));
|
||||
jpe.setProperty(<font color="blue">"favoriteColor"</font>, new Color(0, 0, 255));
|
||||
<font color="gray"><i>// Add an int as a property.</i></font>
|
||||
message.setProperty(<font color="blue">"favoriteNumber"</font>, 4);
|
||||
jpe.setProperty(<font color="blue">"favoriteNumber"</font>, 4);
|
||||
<font color="gray"><i>// Add the JivePropertiesExtension to the message packet</i></font>
|
||||
message.addPacketExtension(jpe);
|
||||
chat.sendMessage(message);
|
||||
</pre></div>
|
||||
|
||||
|
@ -45,14 +44,23 @@ Getting those same properties would use the following code:
|
|||
|
||||
<div class="code"><pre>
|
||||
Message message = chat.nextMessage();
|
||||
<font color="gray"><i>// Get the JivePropertiesExtension</i></font>
|
||||
JivePropertiesExtension jpe = message.getExtension(JivePropertiesExtension.NAMESPACE);
|
||||
<font color="gray"><i>// Get a Color object property.</i></font>
|
||||
Color favoriteColor = (Color)message.getProperty(<font color="blue">"favoriteColor"</font>);
|
||||
Color favoriteColor = (Color)jpe.getProperty(<font color="blue">"favoriteColor"</font>);
|
||||
<font color="gray"><i>// Get an int property. Note that properties are always returned as
|
||||
// Objects, so we must cast the value to an Integer, then convert
|
||||
// it to an int.</i></font>
|
||||
int favoriteNumber = ((Integer)message.getProperty(<font color="blue">"favoriteNumber"</font>)).intValue();
|
||||
int favoriteNumber = ((Integer)jpe.getProperty(<font color="blue">"favoriteNumber"</font>)).intValue();
|
||||
</pre></div>
|
||||
|
||||
<p>
|
||||
For convenience <code>JivePropertiesManager</code> contains two helper
|
||||
methods namely <code>addProperty(Packet packet, String name, Object
|
||||
value)</code> and
|
||||
<code>getProperty(Packet packet, String name)</code>.
|
||||
</p>
|
||||
|
||||
<p class="subheader">
|
||||
Objects as Properties
|
||||
</p>
|
||||
|
@ -63,10 +71,6 @@ you should keep the following in mind:
|
|||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Packet extensions are the more standard way to add extra data to XMPP stanzas. Using
|
||||
properties may be more convenient in some cases, however, since Smack will do the
|
||||
work of handling the XML.
|
||||
|
||||
<li>When you send a Java object as a property, only clients running Java will be able to
|
||||
interpret the data. So, consider using a series of primitive values to transfer data
|
||||
instead.
|
|
@ -24,6 +24,7 @@
|
|||
<a href="pubsub.html">PubSub</a><br>
|
||||
<a href="caps.html">Entity Capabilities</a><br>
|
||||
<a href="privacy.html">Privacy</a><br>
|
||||
<a href="properties.html">JiveProperties</a>
|
||||
</p>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
<li><a href="roster.html">Roster and Presence</a>
|
||||
<li><a href="processing.html">Processing Incoming Packets</a>
|
||||
<li><a href="providers.html">Provider Architecture</a>
|
||||
<li><a href="properties.html">Packet Properties</a>
|
||||
<li><a href="debugging.html">Debugging with Smack</a>
|
||||
<p>
|
||||
<li><a href="extensions/index.html">Smack Extensions Manual</a>
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2014 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.jiveproperties;
|
||||
|
||||
import org.jivesoftware.smack.packet.Packet;
|
||||
import org.jivesoftware.smackx.jiveproperties.packet.JivePropertiesExtension;
|
||||
|
||||
public class JivePropertiesManager {
|
||||
|
||||
private static boolean javaObjectEnabled = false;
|
||||
|
||||
/**
|
||||
* Enables deserialization of Java objects embedded in the 'properties' packet extension. Since
|
||||
* this is a security sensitive feature, it is disabled per default in Smack. Only enable it if
|
||||
* you are sure that you understand the potential security implications it can cause.
|
||||
* <p>
|
||||
* See also:
|
||||
* <ul>
|
||||
* <li> <a href="http://stackoverflow.com/questions/19054460/">"What is the security impact of deserializing untrusted data in Java?" on Stackoverflow<a>
|
||||
* <ul>
|
||||
* @param enabled true to enable Java object deserialization
|
||||
*/
|
||||
public static void setJavaObjectEnabled(boolean enabled) {
|
||||
JivePropertiesManager.javaObjectEnabled = enabled;
|
||||
}
|
||||
|
||||
public static boolean isJavaObjectEnabled() {
|
||||
return javaObjectEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to add a property to a packet.
|
||||
*
|
||||
* @param packet the packet to add the property to.
|
||||
* @param name the name of the property to add.
|
||||
* @param value the value of the property to add.
|
||||
*/
|
||||
public static void addProperty(Packet packet, String name, Object value) {
|
||||
JivePropertiesExtension jpe = (JivePropertiesExtension) packet.getExtension(JivePropertiesExtension.NAMESPACE);
|
||||
if (jpe == null) {
|
||||
jpe = new JivePropertiesExtension();
|
||||
packet.addExtension(jpe);
|
||||
}
|
||||
jpe.setProperty(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to get a property from a packet. Will return null if the packet contains
|
||||
* not property with the given name.
|
||||
*
|
||||
* @param packet
|
||||
* @param name
|
||||
* @return the property or <tt>null</tt> if none found.
|
||||
*/
|
||||
public static Object getProperty(Packet packet, String name) {
|
||||
Object res = null;
|
||||
JivePropertiesExtension jpe = (JivePropertiesExtension) packet.getExtension(JivePropertiesExtension.NAMESPACE);
|
||||
if (jpe != null) {
|
||||
res = jpe.getProperty(name);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,210 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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.jiveproperties.packet;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smack.util.XmlStringBuilder;
|
||||
|
||||
/**
|
||||
* Properties provide an easy mechanism for clients to share data. Each property has a
|
||||
* String name, and a value that is a Java primitive (int, long, float, double, boolean)
|
||||
* or any Serializable object (a Java object is Serializable when it implements the
|
||||
* Serializable interface).
|
||||
*
|
||||
*/
|
||||
public class JivePropertiesExtension implements PacketExtension {
|
||||
/**
|
||||
* Namespace used to store packet properties.
|
||||
*/
|
||||
public static final String NAMESPACE = "http://www.jivesoftware.com/xmlns/xmpp/properties";
|
||||
|
||||
public static final String ELEMENT = "properties";
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(JivePropertiesExtension.class.getName());
|
||||
|
||||
private final Map<String, Object> properties;
|
||||
|
||||
public JivePropertiesExtension() {
|
||||
properties = new HashMap<String, Object>();
|
||||
}
|
||||
|
||||
public JivePropertiesExtension(Map<String, Object> properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the packet property with the specified name or <tt>null</tt> if the
|
||||
* property doesn't exist. Property values that were originally primitives will
|
||||
* be returned as their object equivalent. For example, an int property will be
|
||||
* returned as an Integer, a double as a Double, etc.
|
||||
*
|
||||
* @param name the name of the property.
|
||||
* @return the property, or <tt>null</tt> if the property doesn't exist.
|
||||
*/
|
||||
public synchronized Object getProperty(String name) {
|
||||
if (properties == null) {
|
||||
return null;
|
||||
}
|
||||
return properties.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a property with an Object as the value. The value must be Serializable
|
||||
* or an IllegalArgumentException will be thrown.
|
||||
*
|
||||
* @param name the name of the property.
|
||||
* @param value the value of the property.
|
||||
*/
|
||||
public synchronized void setProperty(String name, Object value) {
|
||||
if (!(value instanceof Serializable)) {
|
||||
throw new IllegalArgumentException("Value must be serialiazble");
|
||||
}
|
||||
properties.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a property.
|
||||
*
|
||||
* @param name the name of the property to delete.
|
||||
*/
|
||||
public synchronized void deleteProperty(String name) {
|
||||
if (properties == null) {
|
||||
return;
|
||||
}
|
||||
properties.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable collection of all the property names that are set.
|
||||
*
|
||||
* @return all property names.
|
||||
*/
|
||||
public synchronized Collection<String> getPropertyNames() {
|
||||
if (properties == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return Collections.unmodifiableSet(new HashSet<String>(properties.keySet()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementName() {
|
||||
return ELEMENT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNamespace() {
|
||||
return NAMESPACE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence toXML() {
|
||||
XmlStringBuilder xml = new XmlStringBuilder(this);
|
||||
xml.rightAngelBracket();
|
||||
// Loop through all properties and write them out.
|
||||
for (String name : getPropertyNames()) {
|
||||
Object value = getProperty(name);
|
||||
xml.openElement("property");
|
||||
xml.element("name", name);
|
||||
xml.halfOpenElement("value");
|
||||
|
||||
String type;
|
||||
String valueStr;
|
||||
if (value instanceof Integer) {
|
||||
type = "integer";
|
||||
valueStr = Integer.toString((Integer) value);
|
||||
}
|
||||
else if (value instanceof Long) {
|
||||
type = "long";
|
||||
valueStr = Long.toString((Long) value);
|
||||
}
|
||||
else if (value instanceof Float) {
|
||||
type = "float";
|
||||
valueStr = Float.toString((Float) value);
|
||||
}
|
||||
else if (value instanceof Double) {
|
||||
type = "double";
|
||||
valueStr = Double.toString((Double) value);
|
||||
}
|
||||
else if (value instanceof Boolean) {
|
||||
type = "boolean";
|
||||
valueStr = Boolean.toString((Boolean) value);
|
||||
}
|
||||
else if (value instanceof String) {
|
||||
type = "string";
|
||||
valueStr = (String) value;
|
||||
}
|
||||
// Otherwise, it's a generic Serializable object. Serialized objects are in
|
||||
// a binary format, which won't work well inside of XML. Therefore, we base-64
|
||||
// encode the binary data before adding it.
|
||||
else {
|
||||
ByteArrayOutputStream byteStream = null;
|
||||
ObjectOutputStream out = null;
|
||||
try {
|
||||
byteStream = new ByteArrayOutputStream();
|
||||
out = new ObjectOutputStream(byteStream);
|
||||
out.writeObject(value);
|
||||
type = "java-object";
|
||||
valueStr = StringUtils.encodeBase64(byteStream.toByteArray());
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Error encoding java object", e);
|
||||
type = "java-object";
|
||||
valueStr = "Serializing error: " + e.getMessage();
|
||||
}
|
||||
finally {
|
||||
if (out != null) {
|
||||
try {
|
||||
out.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
if (byteStream != null) {
|
||||
try {
|
||||
byteStream.close();
|
||||
}
|
||||
catch (Exception e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
xml.attribute("type", type);
|
||||
xml.rightAngelBracket();
|
||||
xml.escape(valueStr);
|
||||
xml.closeElement("value");
|
||||
xml.closeElement("property");
|
||||
}
|
||||
xml.closeElement(this);
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2003-2007 Jive Software.
|
||||
*
|
||||
* 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.jiveproperties.provider;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.jivesoftware.smack.packet.PacketExtension;
|
||||
import org.jivesoftware.smack.provider.PacketExtensionProvider;
|
||||
import org.jivesoftware.smack.util.StringUtils;
|
||||
import org.jivesoftware.smackx.jiveproperties.JivePropertiesManager;
|
||||
import org.jivesoftware.smackx.jiveproperties.packet.JivePropertiesExtension;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
|
||||
public class JivePropertiesExtensionProvider implements PacketExtensionProvider {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(JivePropertiesExtensionProvider.class.getName());
|
||||
|
||||
/**
|
||||
* Parse a properties sub-packet. If any errors occur while de-serializing Java object
|
||||
* properties, an exception will be printed and not thrown since a thrown exception will shut
|
||||
* down the entire connection. ClassCastExceptions will occur when both the sender and receiver
|
||||
* of the packet don't have identical versions of the same class.
|
||||
* <p>
|
||||
* Note that you have to explicitly enabled Java object deserialization with @{link
|
||||
* {@link JivePropertiesManager#setJavaObjectEnabled(boolean)}
|
||||
*
|
||||
* @param parser the XML parser, positioned at the start of a properties sub-packet.
|
||||
* @return a map of the properties.
|
||||
* @throws Exception if an error occurs while parsing the properties.
|
||||
*/
|
||||
@Override
|
||||
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
|
||||
Map<String, Object> properties = new HashMap<String, Object>();
|
||||
while (true) {
|
||||
int eventType = parser.next();
|
||||
if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) {
|
||||
// Parse a property
|
||||
boolean done = false;
|
||||
String name = null;
|
||||
String type = null;
|
||||
String valueText = null;
|
||||
Object value = null;
|
||||
while (!done) {
|
||||
eventType = parser.next();
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String elementName = parser.getName();
|
||||
if (elementName.equals("name")) {
|
||||
name = parser.nextText();
|
||||
}
|
||||
else if (elementName.equals("value")) {
|
||||
type = parser.getAttributeValue("", "type");
|
||||
valueText = parser.nextText();
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals("property")) {
|
||||
if ("integer".equals(type)) {
|
||||
value = Integer.valueOf(valueText);
|
||||
}
|
||||
else if ("long".equals(type)) {
|
||||
value = Long.valueOf(valueText);
|
||||
}
|
||||
else if ("float".equals(type)) {
|
||||
value = Float.valueOf(valueText);
|
||||
}
|
||||
else if ("double".equals(type)) {
|
||||
value = Double.valueOf(valueText);
|
||||
}
|
||||
else if ("boolean".equals(type)) {
|
||||
value = Boolean.valueOf(valueText);
|
||||
}
|
||||
else if ("string".equals(type)) {
|
||||
value = valueText;
|
||||
}
|
||||
else if ("java-object".equals(type)) {
|
||||
if (JivePropertiesManager.isJavaObjectEnabled()) {
|
||||
try {
|
||||
byte[] bytes = StringUtils.decodeBase64(valueText);
|
||||
ObjectInputStream in = new ObjectInputStream(
|
||||
new ByteArrayInputStream(bytes));
|
||||
value = in.readObject();
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "Error parsing java object", e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.severe("JavaObject is not enabled. Enable with JivePropertiesManager.setJavaObjectEnabled(true)");
|
||||
}
|
||||
}
|
||||
if (name != null && value != null) {
|
||||
properties.put(name, value);
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (eventType == XmlPullParser.END_TAG) {
|
||||
if (parser.getName().equals(JivePropertiesExtension.ELEMENT)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new JivePropertiesExtension(properties);
|
||||
}
|
||||
|
||||
}
|
|
@ -456,4 +456,10 @@
|
|||
<className>org.jivesoftware.smackx.amp.provider.AMPExtensionProvider</className>
|
||||
</extensionProvider>
|
||||
|
||||
<!-- JiveProperties -->
|
||||
<extensionProvider>
|
||||
<elementName>properties</elementName>
|
||||
<namespace>http://www.jivesoftware.com/xmlns/xmpp/properties</namespace>
|
||||
<className>org.jivesoftware.smackx.jiveproperties.provider.JivePropertiesExtensionProvider</className>
|
||||
</extensionProvider>
|
||||
</smackProviders>
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
*
|
||||
* Copyright 2014 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.jiveproperties;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import org.jivesoftware.smack.packet.Message;
|
||||
import org.jivesoftware.smack.test.util.TestUtils;
|
||||
import org.jivesoftware.smack.util.PacketParserUtils;
|
||||
import org.jivesoftware.smackx.InitExtensions;
|
||||
import org.jivesoftware.smackx.jiveproperties.packet.JivePropertiesExtension;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class JivePropertiesExtensionTest extends InitExtensions {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
JivePropertiesManager.setJavaObjectEnabled(true);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
JivePropertiesManager.setJavaObjectEnabled(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkProvider() throws Exception {
|
||||
// @formatter:off
|
||||
String properties = "<message from='romeo@example.net/orchard' to='juliet@example.com/balcony'>"
|
||||
+ "<body>Neither, fair saint, if either thee dislike.</body>"
|
||||
+ "<properties xmlns='http://www.jivesoftware.com/xmlns/xmpp/properties'>"
|
||||
+ "<property>"
|
||||
+ "<name>FooBar</name>"
|
||||
+ "<value type='integer'>42</value>"
|
||||
+ "</property>"
|
||||
+ "</properties>"
|
||||
+ "</message>";
|
||||
// @formatter:on
|
||||
|
||||
Message message = PacketParserUtils.parseMessage(TestUtils.getMessageParser(properties));
|
||||
JivePropertiesExtension jpe = (JivePropertiesExtension) message.getExtension(JivePropertiesExtension.NAMESPACE);
|
||||
assertNotNull(jpe);
|
||||
|
||||
Integer integer = (Integer) jpe.getProperty("FooBar");
|
||||
assertNotNull(integer);
|
||||
int fourtytwo = integer;
|
||||
assertEquals(42, fourtytwo);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue