/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.representation;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import org.restlet.data.MediaType;
/**
* Representation based on a serializable Java object.<br>
* It supports binary representations of JavaBeans using the
* {@link ObjectInputStream} and {@link ObjectOutputStream} classes. In this
* case, it handles representations having the following media type:
* {@link MediaType#APPLICATION_JAVA_OBJECT}
* ("application/x-java-serialized-object"). It also supports textual
* representations of JavaBeans using the {@link java.beans.XMLEncoder} and
* {@link java.beans.XMLDecoder} classes. In this case, it handles
* representations having the following media type:
* {@link MediaType#APPLICATION_JAVA_OBJECT_XML}
* ("application/x-java-serialized-object+xml").<br>
* <br>
* SECURITY WARNING: The usage of {@link java.beans.XMLDecoder} when
* deserializing XML presentations from untrusted sources can lead to malicious
* attacks. As pointed <a href=
* "http://blog.diniscruz.com/2013/08/using-xmldecoder-to-execute-server-side.html"
* >here</a>, the {@link java.beans.XMLDecoder} is able to force the JVM to
* execute unwanted Java code described inside the XML file. Thus, the support
* of such format has been disabled by default. You can activate this support by
* turning on the following system property:
* org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED.<br>
* <br>
* SECURITY WARNING: The usage of {@link ObjectInputStream} when deserializing
* binary presentations from untrusted sources can lead to malicious attacks. As
* pointed <a
* href="https://github.com/restlet/restlet-framework-java/issues/778"
* >here</a>, the {@link ObjectInputStream} is able to force the JVM to execute
* unwanted Java code. Thus, the support of such format has been disabled by
* default. You can activate this support by turning on the following system
* property: "org.restlet.representation.ObjectRepresentation
* .VARIANT_OBJECT_BINARY_SUPPORTED".
*
* @author Jerome Louvel
* @param <T>
* The class to serialize, see {@link Serializable}
*/
public class ObjectRepresentation<T extends Serializable> extends
OutputRepresentation {
/** Indicates whether the JavaBeans XML deserialization is supported or not. */
public static boolean VARIANT_OBJECT_XML_SUPPORTED = Boolean
.getBoolean("org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED");
/**
* Indicates whether the JavaBeans binary deserialization is supported or
* not.
*/
public static boolean VARIANT_OBJECT_BINARY_SUPPORTED = Boolean
.getBoolean("org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED");
/** The serializable object. */
private volatile T object;
/**
* Constructor reading the object from a serialized representation. This
* representation must have the proper media type:
* "application/x-java-serialized-object".
*
* @param serializedRepresentation
* The serialized representation.
* @throws IOException
* @throws ClassNotFoundException
* @throws IllegalArgumentException
*/
public ObjectRepresentation(Representation serializedRepresentation)
throws IOException, ClassNotFoundException,
IllegalArgumentException {
this(serializedRepresentation, null);
}
/**
* Constructor reading the object from a serialized representation. This
* representation must have the proper media type:
* "application/x-java-serialized-object".
*
* @param serializedRepresentation
* The serialized representation.
* @param classLoader
* The class loader used to read the object.
* @throws IOException
* @throws ClassNotFoundException
* @throws IllegalArgumentException
*/
public ObjectRepresentation(Representation serializedRepresentation,
final ClassLoader classLoader) throws IOException,
ClassNotFoundException, IllegalArgumentException {
this(serializedRepresentation, classLoader,
VARIANT_OBJECT_BINARY_SUPPORTED, VARIANT_OBJECT_XML_SUPPORTED);
}
/**
* Constructor reading the object from a serialized representation. This
* representation must have the proper media type:
* "application/x-java-serialized-object".
*
* @param serializedRepresentation
* The serialized representation.
* @param classLoader
* The class loader used to read the object.
* @param variantObjectBinarySupported
* Indicates whether the JavaBeans binary deserialization is
* supported or not.
* @param variantObjectXmlSupported
* Indicates whether the JavaBeans XML deserialization is
* supported or not.
* @throws IOException
* @throws ClassNotFoundException
* @throws IllegalArgumentException
*/
@SuppressWarnings("unchecked")
public ObjectRepresentation(Representation serializedRepresentation,
final ClassLoader classLoader,
boolean variantObjectBinarySupported,
boolean variantObjectXmlSupported) throws IOException,
ClassNotFoundException, IllegalArgumentException {
super(MediaType.APPLICATION_JAVA_OBJECT);
if (MediaType.APPLICATION_JAVA_OBJECT.equals(serializedRepresentation
.getMediaType())) {
if (!variantObjectBinarySupported) {
throw new IllegalArgumentException(
"SECURITY WARNING: The usage of ObjectInputStream when "
+ "deserializing binary representations from unstrusted "
+ "sources can lead to malicious attacks. As pointed "
+ "here (https://github.com/restlet/restlet-framework-java/issues/778), "
+ "the ObjectInputStream class is able to force the JVM to execute unwanted "
+ "Java code. Thus, the support of such format has been disactivated "
+ "by default. You can activate this support by turning on the following system property: "
+ "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_BINARY_SUPPORTED.");
}
setMediaType(MediaType.APPLICATION_JAVA_OBJECT);
InputStream is = serializedRepresentation.getStream();
ObjectInputStream ois = null;
if (classLoader != null) {
ois = new ObjectInputStream(is) {
@Override
protected Class<?> resolveClass(
java.io.ObjectStreamClass desc)
throws java.io.IOException,
java.lang.ClassNotFoundException {
return Class
.forName(desc.getName(), false, classLoader);
}
};
} else {
ois = new ObjectInputStream(is);
}
this.object = (T) ois.readObject();
if (is.read() != -1) {
throw new IOException(
"The input stream has not been fully read.");
}
ois.close();
// [ifndef android]
} else if (MediaType.APPLICATION_JAVA_OBJECT_XML
.equals(serializedRepresentation.getMediaType())) {
if (!variantObjectXmlSupported) {
throw new IllegalArgumentException(
"SECURITY WARNING: The usage of XMLDecoder when "
+ "deserializing XML representations from unstrusted "
+ "sources can lead to malicious attacks. As pointed "
+ "here (http://blog.diniscruz.com/2013/08/using-xmldecoder-to-execute-server-side.html), "
+ "the XMLDecoder class is able to force the JVM to "
+ "execute unwanted Java code described inside the XML "
+ "file. Thus, the support of such format has been "
+ "disactivated by default. You can activate this "
+ "support by turning on the following system property: "
+ "org.restlet.representation.ObjectRepresentation.VARIANT_OBJECT_XML_SUPPORTED.");
}
setMediaType(MediaType.APPLICATION_JAVA_OBJECT_XML);
InputStream is = serializedRepresentation.getStream();
java.beans.XMLDecoder decoder = new java.beans.XMLDecoder(is);
this.object = (T) decoder.readObject();
if (is.read() != -1) {
decoder.close();
throw new IOException(
"The input stream has not been fully read.");
}
decoder.close();
// [enddef]
} else {
throw new IllegalArgumentException(
"The serialized representation must have this media type: "
+ MediaType.APPLICATION_JAVA_OBJECT.toString()
+ " or this one: "
+ MediaType.APPLICATION_JAVA_OBJECT_XML.toString());
}
}
/**
* Constructor for the {@link MediaType#APPLICATION_JAVA_OBJECT} type.
*
* @param object
* The serializable object.
*/
public ObjectRepresentation(T object) {
super(MediaType.APPLICATION_JAVA_OBJECT);
this.object = object;
}
/**
* Constructor for either the {@link MediaType#APPLICATION_JAVA_OBJECT} type
* or the {@link MediaType#APPLICATION_XML} type. In the first case, the
* Java Object Serialization mechanism is used, based on
* {@link ObjectOutputStream}. In the latter case, the JavaBeans XML
* serialization is used, based on {@link java.beans.XMLEncoder}.
*
* @param object
* The serializable object.
* @param mediaType
* The media type.
*/
public ObjectRepresentation(T object, MediaType mediaType) {
super((mediaType == null) ? MediaType.APPLICATION_JAVA_OBJECT
: mediaType);
this.object = object;
}
/**
* Returns the represented object.
*
* @return The represented object.
* @throws IOException
*/
public T getObject() throws IOException {
return this.object;
}
/**
* Releases the represented object.
*/
@Override
public void release() {
setObject(null);
super.release();
}
/**
* Sets the represented object.
*
* @param object
* The represented object.
*/
public void setObject(T object) {
this.object = object;
}
@Override
public void write(OutputStream outputStream) throws IOException {
if (MediaType.APPLICATION_JAVA_OBJECT.isCompatible(getMediaType())) {
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeObject(getObject());
oos.flush();
// [ifndef android]
} else if (MediaType.APPLICATION_JAVA_OBJECT_XML
.isCompatible(getMediaType())) {
java.beans.XMLEncoder encoder = new java.beans.XMLEncoder(
outputStream);
encoder.writeObject(getObject());
encoder.close();
// [enddef]
}
}
}