1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-09-10 18:59:41 +02: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,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;
}

View file

@ -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.
*