Better parsing for STOMP body with no length.
Motivation: The STOMP decoder used to fail when parsing a frame with no content-length and a body split across multiple packets. Modifications: Support contentLength of -1 (indicating indeterminate length) and added a check to getContentLength. Moved the NUL byte searching from the readHeaders() method out to the main decoder loop. Result: A STOMP frame can be properly parsed even if it's missing the content-length header and the NUL byte is in a later packet.
This commit is contained in:
parent
c8a941d01e
commit
2adf6e5358
@ -29,7 +29,8 @@ import io.netty.util.internal.StringUtil;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static io.netty.buffer.ByteBufUtil.*;
|
||||
import static io.netty.buffer.ByteBufUtil.indexOf;
|
||||
import static io.netty.buffer.ByteBufUtil.readBytes;
|
||||
|
||||
/**
|
||||
* Decodes {@link ByteBuf}s into {@link StompHeadersSubframe}s and
|
||||
@ -73,7 +74,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
|
||||
private final int maxChunkSize;
|
||||
private int alreadyReadChunkSize;
|
||||
private LastStompContentSubframe lastContent;
|
||||
private long contentLength;
|
||||
private long contentLength = -1;
|
||||
|
||||
public StompSubframeDecoder() {
|
||||
this(DEFAULT_MAX_LINE_LENGTH, DEFAULT_CHUNK_SIZE);
|
||||
@ -134,21 +135,39 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
|
||||
if (toRead > maxChunkSize) {
|
||||
toRead = maxChunkSize;
|
||||
}
|
||||
int remainingLength = (int) (contentLength - alreadyReadChunkSize);
|
||||
if (toRead > remainingLength) {
|
||||
toRead = remainingLength;
|
||||
}
|
||||
ByteBuf chunkBuffer = readBytes(ctx.alloc(), in, toRead);
|
||||
if ((alreadyReadChunkSize += toRead) >= contentLength) {
|
||||
lastContent = new DefaultLastStompContentSubframe(chunkBuffer);
|
||||
checkpoint(State.FINALIZE_FRAME_READ);
|
||||
if (this.contentLength >= 0) {
|
||||
int remainingLength = (int) (contentLength - alreadyReadChunkSize);
|
||||
if (toRead > remainingLength) {
|
||||
toRead = remainingLength;
|
||||
}
|
||||
ByteBuf chunkBuffer = readBytes(ctx.alloc(), in, toRead);
|
||||
if ((alreadyReadChunkSize += toRead) >= contentLength) {
|
||||
lastContent = new DefaultLastStompContentSubframe(chunkBuffer);
|
||||
checkpoint(State.FINALIZE_FRAME_READ);
|
||||
} else {
|
||||
out.add(new DefaultStompContentSubframe(chunkBuffer));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
DefaultStompContentSubframe chunk;
|
||||
chunk = new DefaultStompContentSubframe(chunkBuffer);
|
||||
out.add(chunk);
|
||||
}
|
||||
if (alreadyReadChunkSize < contentLength) {
|
||||
return;
|
||||
int nulIndex = indexOf(in, in.readerIndex(), in.writerIndex(), StompConstants.NUL);
|
||||
if (nulIndex == in.readerIndex()) {
|
||||
checkpoint(State.FINALIZE_FRAME_READ);
|
||||
} else {
|
||||
if (nulIndex > 0) {
|
||||
toRead = nulIndex - in.readerIndex();
|
||||
} else {
|
||||
toRead = in.writerIndex() - in.readerIndex();
|
||||
}
|
||||
ByteBuf chunkBuffer = readBytes(ctx.alloc(), in, toRead);
|
||||
alreadyReadChunkSize += toRead;
|
||||
if (nulIndex > 0) {
|
||||
lastContent = new DefaultLastStompContentSubframe(chunkBuffer);
|
||||
checkpoint(State.FINALIZE_FRAME_READ);
|
||||
} else {
|
||||
out.add(new DefaultStompContentSubframe(chunkBuffer));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall through.
|
||||
case FINALIZE_FRAME_READ:
|
||||
@ -198,28 +217,23 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
|
||||
headers.add(split[0], split[1]);
|
||||
}
|
||||
} else {
|
||||
long contentLength = -1;
|
||||
if (headers.contains(StompHeaders.CONTENT_LENGTH)) {
|
||||
contentLength = getContentLength(headers, 0);
|
||||
} else {
|
||||
int globalIndex = indexOf(buffer, buffer.readerIndex(),
|
||||
buffer.writerIndex(), StompConstants.NUL);
|
||||
if (globalIndex != -1) {
|
||||
contentLength = globalIndex - buffer.readerIndex();
|
||||
this.contentLength = getContentLength(headers, 0);
|
||||
if (this.contentLength == 0) {
|
||||
return State.FINALIZE_FRAME_READ;
|
||||
}
|
||||
}
|
||||
if (contentLength > 0) {
|
||||
this.contentLength = contentLength;
|
||||
return State.READ_CONTENT;
|
||||
} else {
|
||||
return State.FINALIZE_FRAME_READ;
|
||||
}
|
||||
return State.READ_CONTENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static long getContentLength(StompHeaders headers, long defaultValue) {
|
||||
return headers.getLong(StompHeaders.CONTENT_LENGTH, defaultValue);
|
||||
long contentLength = headers.getLong(StompHeaders.CONTENT_LENGTH, defaultValue);
|
||||
if (contentLength < 0) {
|
||||
throw new DecoderException(StompHeaders.CONTENT_LENGTH + " must be non-negative");
|
||||
}
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
private static void skipNullCharacter(ByteBuf buffer) {
|
||||
@ -264,7 +278,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
|
||||
|
||||
private void resetDecoder() {
|
||||
checkpoint(State.SKIP_CONTROL_CHARACTERS);
|
||||
contentLength = 0;
|
||||
contentLength = -1;
|
||||
alreadyReadChunkSize = 0;
|
||||
lastContent = null;
|
||||
}
|
||||
|
@ -66,6 +66,39 @@ public class StompSubframeAggregatorTest {
|
||||
Assert.assertNull(channel.readInbound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleFrameWithBodyAndNoContentLength() {
|
||||
ByteBuf incoming = Unpooled.buffer();
|
||||
incoming.writeBytes(StompTestConstants.SEND_FRAME_4.getBytes());
|
||||
channel.writeInbound(incoming);
|
||||
|
||||
StompFrame frame = channel.readInbound();
|
||||
Assert.assertNotNull(frame);
|
||||
Assert.assertEquals(StompCommand.SEND, frame.command());
|
||||
Assert.assertEquals("body", frame.content().toString(CharsetUtil.UTF_8));
|
||||
frame.release();
|
||||
|
||||
Assert.assertNull(channel.readInbound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleFrameWithSplitBodyAndNoContentLength() {
|
||||
for (int n = 0; n < StompTestConstants.SEND_FRAMES_3.length; ++n) {
|
||||
ByteBuf incoming = Unpooled.buffer();
|
||||
incoming.writeBytes(StompTestConstants.SEND_FRAMES_3[n].getBytes());
|
||||
channel.writeInbound(incoming);
|
||||
channel.flush();
|
||||
}
|
||||
|
||||
StompFrame frame = channel.readInbound();
|
||||
Assert.assertNotNull(frame);
|
||||
Assert.assertEquals(StompCommand.SEND, frame.command());
|
||||
Assert.assertEquals("first part of body\nsecond part of body", frame.content().toString(CharsetUtil.UTF_8));
|
||||
frame.release();
|
||||
|
||||
Assert.assertNull(channel.readInbound());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleFrameChunked() {
|
||||
EmbeddedChannel channel = new EmbeddedChannel(
|
||||
|
@ -42,6 +42,21 @@ public final class StompTestConstants {
|
||||
'\n' +
|
||||
"hello, queue a!!!" +
|
||||
"\0\n";
|
||||
public static final String[] SEND_FRAMES_3 = {
|
||||
"SEND\n" +
|
||||
"destination:/queue/a\n" +
|
||||
"content-type:text/plain\n" +
|
||||
'\n' +
|
||||
"first part of body\n",
|
||||
"second part of body\0"
|
||||
};
|
||||
|
||||
public static final String SEND_FRAME_4 = "SEND\n" +
|
||||
"destination:/queue/a\n" +
|
||||
"content-type:text/plain\n" +
|
||||
'\n' +
|
||||
"body\0";
|
||||
|
||||
private StompTestConstants() { }
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user