Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AMQP-766: Deserialization in Message.toString() #658

Merged
merged 2 commits into from
Sep 6, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import java.nio.charset.Charset;
import java.util.Arrays;

import org.springframework.amqp.utils.SerializationUtils;
import org.springframework.amqp.support.converter.SerializerMessageConverter;

/**
* The 0-8 and 0-9-1 AMQP specifications do not define an Message class or interface. Instead, when performing an
Expand All @@ -42,6 +42,12 @@ public class Message implements Serializable {

private static final String ENCODING = Charset.defaultCharset().name();

private static final SerializerMessageConverter SERIALIZER_MESSAGE_CONVERTER = new SerializerMessageConverter();

static {
SERIALIZER_MESSAGE_CONVERTER.setWhiteListPatterns(Arrays.asList("java.util.*", "java.lang.*"));
}

private final MessageProperties messageProperties;

private final byte[] body;
Expand All @@ -51,6 +57,22 @@ public Message(byte[] body, MessageProperties messageProperties) { //NOSONAR
this.messageProperties = messageProperties;
}

/**
* Add patterns to the white list of permissable package/class name patterns for
* deserialization in {@link #toString()}.
* The patterns will be applied in order until a match is found.
* A class can be fully qualified or a wildcard '*' is allowed at the
* beginning or end of the class name.
* Examples: {@code com.foo.*}, {@code *.MyClass}.
* By default, only {@code java.util} and {@code java.lang} classes will be
* deserialized.
* @param patterns the patterns.
* @since 1.5.7
*/
public static void addWhiteListPatterns(String... patterns) {
SERIALIZER_MESSAGE_CONVERTER.addWhiteListPatterns(patterns);
}

public byte[] getBody() {
return this.body; //NOSONAR
}
Expand Down Expand Up @@ -78,7 +100,7 @@ private String getBodyContentAsString() {
try {
String contentType = (this.messageProperties != null) ? this.messageProperties.getContentType() : null;
if (MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT.equals(contentType)) {
return SerializationUtils.deserialize(this.body).toString();
return SERIALIZER_MESSAGE_CONVERTER.fromMessage(this).toString();
}
if (MessageProperties.CONTENT_TYPE_TEXT_PLAIN.equals(contentType)
|| MessageProperties.CONTENT_TYPE_JSON.equals(contentType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.amqp.support.converter;

import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
Expand Down Expand Up @@ -47,6 +48,16 @@ public void setWhiteListPatterns(List<String> whiteListPatterns) {
this.whiteListPatterns.addAll(whiteListPatterns);
}

/**
* Add package/class patterns to the white list.
* @param patterns the patterns to add.
* @see #setWhiteListPatterns(List)
* @since 1.5.7
*/
public void addWhiteListPatterns(String... patterns) {
this.whiteListPatterns.addAll(Arrays.asList(patterns));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Collections.addAll(whiteListPatterns, patterns) can be used here.

}

protected void checkWhiteList(Class<?> clazz) throws IOException {
if (this.whiteListPatterns.isEmpty()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@

package org.springframework.amqp.core;

import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;

import org.junit.Test;

import org.springframework.amqp.support.converter.SimpleMessageConverter;
import org.springframework.amqp.utils.SerializationUtils;

/**
Expand Down Expand Up @@ -86,4 +92,27 @@ public void serialization() throws Exception {
assertEquals(new String(message.getBody()), new String(out.getBody()));
assertEquals(message.toString(), out.toString());
}

@Test
public void fooNotDeserialized() {
Message message = new SimpleMessageConverter().toMessage(new Foo(), new MessageProperties());
assertThat(message.toString(), not(containsString("aFoo")));
Message listMessage = new SimpleMessageConverter().toMessage(Collections.singletonList(new Foo()),
new MessageProperties());
assertThat(listMessage.toString(), not(containsString("aFoo")));
Message.addWhiteListPatterns(Foo.class.getName());
assertThat(message.toString(), containsString("aFoo"));
assertThat(listMessage.toString(), containsString("aFoo"));
}

@SuppressWarnings("serial")
public static class Foo implements Serializable {

@Override
public String toString() {
return "aFoo";
}

}

}
12 changes: 11 additions & 1 deletion src/reference/asciidoc/amqp.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ If you are not, then have a look at the resources listed in <<resources>>

===== Message

The 0-8 and 0-9-1 AMQP specifications do not define a Message class or interface.
The 0-9-1 AMQP specification does not define a Message class or interface.
Instead, when performing an operation such as `basicPublish()`, the content is passed as a byte-array argument and additional properties are passed in as separate arguments.
Spring AMQP defines a Message class as part of a more general AMQP domain model representation.
The purpose of the Message class is to simply encapsulate the body and properties within a single instance so that the API can in turn be simpler.
Expand Down Expand Up @@ -55,6 +55,16 @@ The `MessageProperties` interface defines several common properties such as 'mes
Those properties can also be extended with user-defined 'headers' by calling the `setHeader(String
key, Object value)` method.

[IMPORTANT]
----
Starting with versions `1.5.7, 1.6.11, 1.7.4, 2.0.0`, if a message body is a serialized `Serializable` java object, it is no longer deserialized (by default) when performing `toString()` operations (such as in log messages).
This is to prevent unsafe deserialization.
By default, only `java.util` and `java.lang` classes are deserialized.
To revert to the previous behavior, you can add allowable class/package patterns by invoking `Message.addWhiteListPatterns(...)`.
A simple `*` wildcard is supported, for example `com.foo.*, *.MyClass`.
Bodies that cannot be deserialized will be represented by `byte[<size>]` in log messages.
----

===== Exchange

The `Exchange` interface represents an AMQP Exchange, which is what a Message Producer sends to.
Expand Down