Fix StompSubframeDecoder.readHeaders produce not any notification when parsed line that contains multiple colon

Motivation:

By STOMP 1.2 specification - header name or value include any octet except CR or LF or ":".

Modification:

Add constructor argument that allows to enable / disable validation.

Result:

Fixes [#7083]
This commit is contained in:
amizurov 2017-08-28 23:58:26 +03:00 committed by Norman Maurer
parent c891c9c13f
commit cc336ef2f8
3 changed files with 73 additions and 15 deletions

View File

@ -15,6 +15,9 @@
*/ */
package io.netty.handler.codec.stomp; package io.netty.handler.codec.stomp;
import java.util.List;
import java.util.Locale;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -25,9 +28,6 @@ import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.stomp.StompSubframeDecoder.State; import io.netty.handler.codec.stomp.StompSubframeDecoder.State;
import io.netty.util.internal.AppendableCharSequence; import io.netty.util.internal.AppendableCharSequence;
import java.util.List;
import java.util.Locale;
import static io.netty.buffer.ByteBufUtil.indexOf; import static io.netty.buffer.ByteBufUtil.indexOf;
import static io.netty.buffer.ByteBufUtil.readBytes; import static io.netty.buffer.ByteBufUtil.readBytes;
@ -71,6 +71,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
private final int maxLineLength; private final int maxLineLength;
private final int maxChunkSize; private final int maxChunkSize;
private final boolean validateHeaders;
private int alreadyReadChunkSize; private int alreadyReadChunkSize;
private LastStompContentSubframe lastContent; private LastStompContentSubframe lastContent;
private long contentLength = -1; private long contentLength = -1;
@ -79,7 +80,15 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
this(DEFAULT_MAX_LINE_LENGTH, DEFAULT_CHUNK_SIZE); this(DEFAULT_MAX_LINE_LENGTH, DEFAULT_CHUNK_SIZE);
} }
public StompSubframeDecoder(boolean validateHeaders) {
this(DEFAULT_MAX_LINE_LENGTH, DEFAULT_CHUNK_SIZE, validateHeaders);
}
public StompSubframeDecoder(int maxLineLength, int maxChunkSize) { public StompSubframeDecoder(int maxLineLength, int maxChunkSize) {
this(maxLineLength, maxChunkSize, false);
}
public StompSubframeDecoder(int maxLineLength, int maxChunkSize, boolean validateHeaders) {
super(State.SKIP_CONTROL_CHARACTERS); super(State.SKIP_CONTROL_CHARACTERS);
if (maxLineLength <= 0) { if (maxLineLength <= 0) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -93,6 +102,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
} }
this.maxChunkSize = maxChunkSize; this.maxChunkSize = maxChunkSize;
this.maxLineLength = maxLineLength; this.maxLineLength = maxLineLength;
this.validateHeaders = validateHeaders;
} }
@Override @Override
@ -134,7 +144,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
if (toRead > maxChunkSize) { if (toRead > maxChunkSize) {
toRead = maxChunkSize; toRead = maxChunkSize;
} }
if (this.contentLength >= 0) { if (contentLength >= 0) {
int remainingLength = (int) (contentLength - alreadyReadChunkSize); int remainingLength = (int) (contentLength - alreadyReadChunkSize);
if (toRead > remainingLength) { if (toRead > remainingLength) {
toRead = remainingLength; toRead = remainingLength;
@ -214,11 +224,14 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
String[] split = line.split(":"); String[] split = line.split(":");
if (split.length == 2) { if (split.length == 2) {
headers.add(split[0], split[1]); headers.add(split[0], split[1]);
} else if (validateHeaders) {
throw new IllegalArgumentException("a header value or name contains a prohibited character ':'" +
", " + line);
} }
} else { } else {
if (headers.contains(StompHeaders.CONTENT_LENGTH)) { if (headers.contains(StompHeaders.CONTENT_LENGTH)) {
this.contentLength = getContentLength(headers, 0); contentLength = getContentLength(headers, 0);
if (this.contentLength == 0) { if (contentLength == 0) {
return State.FINALIZE_FRAME_READ; return State.FINALIZE_FRAME_READ;
} }
} }

View File

@ -18,12 +18,19 @@ package io.netty.handler.codec.stomp;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.CharsetUtil;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static io.netty.handler.codec.stomp.StompTestConstants.FRAME_WITH_INVALID_HEADER;
import static io.netty.util.CharsetUtil.US_ASCII;
import static io.netty.util.CharsetUtil.UTF_8;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
public class StompSubframeDecoderTest { public class StompSubframeDecoderTest {
@ -69,7 +76,7 @@ public class StompSubframeDecoderTest {
StompContentSubframe content = channel.readInbound(); StompContentSubframe content = channel.readInbound();
assertTrue(content instanceof LastStompContentSubframe); assertTrue(content instanceof LastStompContentSubframe);
String s = content.content().toString(CharsetUtil.UTF_8); String s = content.content().toString(UTF_8);
assertEquals("hello, queue a!!!", s); assertEquals("hello, queue a!!!", s);
content.release(); content.release();
@ -88,7 +95,7 @@ public class StompSubframeDecoderTest {
StompContentSubframe content = channel.readInbound(); StompContentSubframe content = channel.readInbound();
assertTrue(content instanceof LastStompContentSubframe); assertTrue(content instanceof LastStompContentSubframe);
String s = content.content().toString(CharsetUtil.UTF_8); String s = content.content().toString(UTF_8);
assertEquals("hello, queue a!", s); assertEquals("hello, queue a!", s);
content.release(); content.release();
@ -108,22 +115,22 @@ public class StompSubframeDecoderTest {
assertEquals(StompCommand.SEND, frame.command()); assertEquals(StompCommand.SEND, frame.command());
StompContentSubframe content = channel.readInbound(); StompContentSubframe content = channel.readInbound();
String s = content.content().toString(CharsetUtil.UTF_8); String s = content.content().toString(UTF_8);
assertEquals("hello", s); assertEquals("hello", s);
content.release(); content.release();
content = channel.readInbound(); content = channel.readInbound();
s = content.content().toString(CharsetUtil.UTF_8); s = content.content().toString(UTF_8);
assertEquals(", que", s); assertEquals(", que", s);
content.release(); content.release();
content = channel.readInbound(); content = channel.readInbound();
s = content.content().toString(CharsetUtil.UTF_8); s = content.content().toString(UTF_8);
assertEquals("ue a!", s); assertEquals("ue a!", s);
content.release(); content.release();
content = channel.readInbound(); content = channel.readInbound();
s = content.content().toString(CharsetUtil.UTF_8); s = content.content().toString(UTF_8);
assertEquals("!!", s); assertEquals("!!", s);
content.release(); content.release();
@ -155,4 +162,36 @@ public class StompSubframeDecoderTest {
assertNull(channel.readInbound()); assertNull(channel.readInbound());
} }
@Test
public void testValidateHeadersDecodingDisabled() {
ByteBuf invalidIncoming = Unpooled.copiedBuffer(FRAME_WITH_INVALID_HEADER.getBytes(US_ASCII));
assertTrue(channel.writeInbound(invalidIncoming));
StompHeadersSubframe frame = channel.readInbound();
assertNotNull(frame);
assertEquals(StompCommand.SEND, frame.command());
assertTrue(frame.headers().contains("destination"));
assertTrue(frame.headers().contains("content-type"));
assertFalse(frame.headers().contains("current-time"));
StompContentSubframe content = channel.readInbound();
String s = content.content().toString(UTF_8);
assertEquals("some body", s);
content.release();
}
@Test
public void testValidateHeadersDecodingEnabled() {
channel = new EmbeddedChannel(new StompSubframeDecoder(true));
ByteBuf invalidIncoming = Unpooled.copiedBuffer(FRAME_WITH_INVALID_HEADER.getBytes(US_ASCII));
assertTrue(channel.writeInbound(invalidIncoming));
StompHeadersSubframe frame = channel.readInbound();
assertNotNull(frame);
assertTrue(frame.decoderResult().isFailure());
assertEquals("a header value or name contains a prohibited character ':', current-time:2000-01-01T00:00:00",
frame.decoderResult().cause().getMessage());
}
} }

View File

@ -57,6 +57,12 @@ public final class StompTestConstants {
'\n' + '\n' +
"body\0"; "body\0";
private StompTestConstants() { } public static final String FRAME_WITH_INVALID_HEADER = "SEND\n" +
"destination:/some-destination\n" +
"content-type:text/plain\n" +
"current-time:2000-01-01T00:00:00\n" +
'\n' +
"some body\0";
private StompTestConstants() { }
} }