1
0
Fork 0
mirror of https://github.com/vanitasvitae/Smack.git synced 2025-09-10 17:49:38 +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

@ -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>
</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>

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