1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2024-11-26 08:12:05 +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:
Florian Schmaus 2014-04-26 15:43:58 +02:00
parent c2b214f8d8
commit 6e08a10186
11 changed files with 516 additions and 281 deletions

View file

@ -20,28 +20,20 @@ package org.jivesoftware.smack.packet;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smack.util.XmlStringBuilder;
import java.io.ByteArrayOutputStream; import java.util.ArrayList;
import java.io.ObjectOutputStream; import java.util.Collection;
import java.io.Serializable; import java.util.Collections;
import java.util.*; import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList; 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 * 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, * generated, but can be overridden). 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).
* *
* @author Matt Tucker * @author Matt Tucker
*/ */
public abstract class Packet { public abstract class Packet {
private static final Logger LOGGER = Logger.getLogger(Packet.class.getName());
protected static final String DEFAULT_LANGUAGE = protected static final String DEFAULT_LANGUAGE =
java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US); java.util.Locale.getDefault().getLanguage().toLowerCase(Locale.US);
@ -88,7 +80,6 @@ public abstract class Packet {
private final List<PacketExtension> packetExtensions private final List<PacketExtension> packetExtensions
= new CopyOnWriteArrayList<PacketExtension>(); = new CopyOnWriteArrayList<PacketExtension>();
private final Map<String,Object> properties = new HashMap<String, Object>();
private XMPPError error = null; private XMPPError error = null;
public Packet() { public Packet() {
@ -292,60 +283,6 @@ public abstract class Packet {
packetExtensions.remove(extension); 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 * 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 * 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()) { for (PacketExtension extension : getExtensions()) {
xml.append(extension.toXML()); 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; return xml;
} }
@ -479,10 +334,6 @@ public abstract class Packet {
if (packetID != null ? !packetID.equals(packet.packetID) : packet.packetID != null) { if (packetID != null ? !packetID.equals(packet.packetID) : packet.packetID != null) {
return false; 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; } if (to != null ? !to.equals(packet.to) : packet.to != null) { return false; }
return !(xmlns != null ? !xmlns.equals(packet.xmlns) : packet.xmlns != null); 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 + (to != null ? to.hashCode() : 0);
result = 31 * result + (from != null ? from.hashCode() : 0); result = 31 * result + (from != null ? from.hashCode() : 0);
result = 31 * result + packetExtensions.hashCode(); result = 31 * result + packetExtensions.hashCode();
result = 31 * result + properties.hashCode();
result = 31 * result + (error != null ? error.hashCode() : 0); result = 31 * result + (error != null ? error.hashCode() : 0);
return result; return result;
} }

View file

@ -16,9 +16,7 @@
*/ */
package org.jivesoftware.smack.util; package org.jivesoftware.smack.util;
import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -56,12 +54,6 @@ import org.xmlpull.v1.XmlPullParserException;
public class PacketParserUtils { public class PacketParserUtils {
private static final Logger LOGGER = Logger.getLogger(PacketParserUtils.class.getName()); 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. * Parses a message packet.
* *
@ -69,7 +61,7 @@ public class PacketParserUtils {
* @return a Message packet. * @return a Message packet.
* @throws Exception if an exception occurs while parsing the 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(); Message message = new Message();
String id = parser.getAttributeValue("", "id"); String id = parser.getAttributeValue("", "id");
message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id);
@ -93,7 +85,6 @@ public class PacketParserUtils {
// in arbitrary sub-elements. // in arbitrary sub-elements.
boolean done = false; boolean done = false;
String thread = null; String thread = null;
Map<String, Object> properties = null;
while (!done) { while (!done) {
int eventType = parser.next(); int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) { if (eventType == XmlPullParser.START_TAG) {
@ -131,11 +122,6 @@ public class PacketParserUtils {
else if (elementName.equals("error")) { else if (elementName.equals("error")) {
message.setError(parseError(parser)); message.setError(parseError(parser));
} }
else if (elementName.equals("properties") &&
namespace.equals(PROPERTIES_NAMESPACE))
{
properties = parseProperties(parser);
}
// Otherwise, it must be a packet extension. // Otherwise, it must be a packet extension.
else { else {
message.addExtension( message.addExtension(
@ -150,12 +136,6 @@ public class PacketParserUtils {
} }
message.setThread(thread); message.setThread(thread);
// Set packet properties.
if (properties != null) {
for (String name : properties.keySet()) {
message.setProperty(name, properties.get(name));
}
}
return message; return message;
} }
@ -246,15 +226,6 @@ public class PacketParserUtils {
else if (elementName.equals("error")) { else if (elementName.equals("error")) {
presence.setError(parseError(parser)); 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. // Otherwise, it must be a packet extension.
else { else {
try { try {
@ -549,87 +520,6 @@ public class PacketParserUtils {
return methods; 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. * Parses SASL authentication error packets.
* *

View file

@ -90,6 +90,11 @@
<td><a href="http://www.xmpp.org/extensions/xep-0332.html">XEP-0332</a></td> <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> <td>Allows to transport HTTP communication over XMPP peer-to-peer networks.</td>
</tr> </tr>
<tr>
<td><a href="properties.html">Jive Properties</a></td>
<td>N/A</td>
<td>TODO</td>
</tr>
</table> </table>
</body> </body>
</html> </html>

View file

@ -10,10 +10,6 @@
Packet Properties Packet Properties
</div> </div>
<div class="nav">
&laquo; <a href="index.html">Table of Contents</a>
</div>
<p> <p>
Smack provides an easy mechanism for attaching arbitrary properties to packets. Each property 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 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> <div class="code"><pre>
Message message = chat.createMessage(); Message message = chat.createMessage();
JivePropertiesExtension jpe = new JivePropertiesExtension();
<font color="gray"><i>// Add a Color object as a property.</i></font> <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> <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); chat.sendMessage(message);
</pre></div> </pre></div>
@ -45,14 +44,23 @@ Getting those same properties would use the following code:
<div class="code"><pre> <div class="code"><pre>
Message message = chat.nextMessage(); 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> <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 <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 // Objects, so we must cast the value to an Integer, then convert
// it to an int.</i></font> // 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> </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"> <p class="subheader">
Objects as Properties Objects as Properties
</p> </p>
@ -63,10 +71,6 @@ you should keep the following in mind:
</p> </p>
<ul> <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 <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 interpret the data. So, consider using a series of primitive values to transfer data
instead. instead.

View file

@ -24,6 +24,7 @@
<a href="pubsub.html">PubSub</a><br> <a href="pubsub.html">PubSub</a><br>
<a href="caps.html">Entity Capabilities</a><br> <a href="caps.html">Entity Capabilities</a><br>
<a href="privacy.html">Privacy</a><br> <a href="privacy.html">Privacy</a><br>
<a href="properties.html">JiveProperties</a>
</p> </p>
</body> </body>

View file

@ -20,7 +20,6 @@
<li><a href="roster.html">Roster and Presence</a> <li><a href="roster.html">Roster and Presence</a>
<li><a href="processing.html">Processing Incoming Packets</a> <li><a href="processing.html">Processing Incoming Packets</a>
<li><a href="providers.html">Provider Architecture</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> <li><a href="debugging.html">Debugging with Smack</a>
<p> <p>
<li><a href="extensions/index.html">Smack Extensions Manual</a> <li><a href="extensions/index.html">Smack Extensions Manual</a>

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -456,4 +456,10 @@
<className>org.jivesoftware.smackx.amp.provider.AMPExtensionProvider</className> <className>org.jivesoftware.smackx.amp.provider.AMPExtensionProvider</className>
</extensionProvider> </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> </smackProviders>

View file

@ -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);
}
}