Skip to content

Commit b331d65

Browse files
sdeleuzejhoeller
authored andcommitted
Check STOMP headers against ending backslash
Issue: SPR-12418 (cherry picked from commit 1803348)
1 parent 1823ce1 commit b331d65

File tree

3 files changed

+55
-52
lines changed

3 files changed

+55
-52
lines changed

spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompDecoder.java

+22-31
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -56,13 +56,12 @@ public class StompDecoder {
5656

5757
/**
5858
* Decodes one or more STOMP frames from the given {@code ByteBuffer} into a
59-
* list of {@link Message}s. If the input buffer contains any incplcontains partial STOMP frame content, or additional
60-
* content with a partial STOMP frame, the buffer is reset and {@code null} is
61-
* returned.
62-
*
63-
* @param buffer The buffer to decode the STOMP frame from
64-
*
65-
* @return the decoded messages or an empty list
59+
* list of {@link Message}s. If the input buffer contains partial STOMP frame
60+
* content, or additional content with a partial STOMP frame, the buffer is
61+
* reset and {@code null} is returned.
62+
* @param buffer the buffer to decode the STOMP frame from
63+
* @return the decoded messages, or an empty list if none
64+
* @throws StompConversionException raised in case of decoding issues
6665
*/
6766
public List<Message<byte[]>> decode(ByteBuffer buffer) {
6867
return decode(buffer, new LinkedMultiValueMap<String, String>());
@@ -71,35 +70,29 @@ public List<Message<byte[]>> decode(ByteBuffer buffer) {
7170
/**
7271
* Decodes one or more STOMP frames from the given {@code buffer} and returns
7372
* a list of {@link Message}s.
74-
*
7573
* <p>If the given ByteBuffer contains only partial STOMP frame content and no
7674
* complete STOMP frames, an empty list is returned, and the buffer is reset to
7775
* to where it was.
78-
*
7976
* <p>If the buffer contains one ore more STOMP frames, those are returned and
8077
* the buffer reset to point to the beginning of the unused partial content.
81-
*
8278
* <p>The input headers map is used to store successfully parsed headers and
8379
* is cleared after ever successfully read message. So when partial content is
8480
* read the caller can check if a "content-length" header was read, which helps
8581
* to determine how much more content is needed before the next STOMP frame
8682
* can be decoded.
87-
*
88-
* @param buffer The buffer to decode the STOMP frame from
83+
* @param buffer the buffer to decode the STOMP frame from
8984
* @param headers an empty map that will contain successfully parsed headers
9085
* in cases where the partial buffer ended with a partial STOMP frame
91-
*
92-
* @return decoded messages or an empty list
86+
* @return the decoded messages, or an empty list if none
9387
* @throws StompConversionException raised in case of decoding issues
9488
*/
9589
public List<Message<byte[]>> decode(ByteBuffer buffer, MultiValueMap<String, String> headers) {
9690
Assert.notNull(headers, "headers is required");
9791
List<Message<byte[]>> messages = new ArrayList<Message<byte[]>>();
9892
while (buffer.hasRemaining()) {
99-
Message<byte[]> m = decodeMessage(buffer, headers);
100-
if (m != null) {
101-
messages.add(m);
102-
headers.clear();
93+
Message<byte[]> message = decodeMessage(buffer, headers);
94+
if (message != null) {
95+
messages.add(message);
10396
}
10497
else {
10598
break;
@@ -112,20 +105,17 @@ public List<Message<byte[]>> decode(ByteBuffer buffer, MultiValueMap<String, Str
112105
* Decode a single STOMP frame from the given {@code buffer} into a {@link Message}.
113106
*/
114107
private Message<byte[]> decodeMessage(ByteBuffer buffer, MultiValueMap<String, String> headers) {
115-
116108
Message<byte[]> decodedMessage = null;
117109
skipLeadingEol(buffer);
118110
buffer.mark();
119111

120112
String command = readCommand(buffer);
121113
if (command.length() > 0) {
122-
123114
readHeaders(buffer, headers);
124115
byte[] payload = readPayload(buffer, headers);
125-
126116
if (payload != null) {
127117
StompCommand stompCommand = StompCommand.valueOf(command);
128-
if ((payload.length > 0) && (!stompCommand.isBodyAllowed())) {
118+
if (payload.length > 0 && !stompCommand.isBodyAllowed()) {
129119
throw new StompConversionException(stompCommand + " shouldn't have but " +
130120
"has a payload with length=" + payload.length + ", headers=" + headers);
131121
}
@@ -137,7 +127,7 @@ private Message<byte[]> decodeMessage(ByteBuffer buffer, MultiValueMap<String, S
137127
}
138128
else {
139129
if (logger.isTraceEnabled()) {
140-
logger.trace("Received incomplete frame. Resetting buffer.");
130+
logger.trace("Incomplete frame, resetting input buffer...");
141131
}
142132
buffer.reset();
143133
}
@@ -182,10 +172,10 @@ private void readHeaders(ByteBuffer buffer, MultiValueMap<String, String> header
182172
if (headerStream.size() > 0) {
183173
String header = new String(headerStream.toByteArray(), UTF8_CHARSET);
184174
int colonIndex = header.indexOf(':');
185-
if ((colonIndex <= 0) || (colonIndex == header.length() - 1)) {
175+
if (colonIndex <= 0 || colonIndex == header.length() - 1) {
186176
if (buffer.remaining() > 0) {
187-
throw new StompConversionException(
188-
"Illegal header: '" + header + "'. A header must be of the form <name>:<value>");
177+
throw new StompConversionException("Illegal header: '" + header +
178+
"'. A header must be of the form <name>:<value>.");
189179
}
190180
}
191181
else {
@@ -205,13 +195,15 @@ private void readHeaders(ByteBuffer buffer, MultiValueMap<String, String> header
205195
* <a href="http://stomp.github.io/stomp-specification-1.2.html#Value_Encoding">"Value Encoding"</a>.
206196
*/
207197
private String unescape(String inString) {
208-
209-
StringBuilder sb = new StringBuilder();
210-
int pos = 0; // position in the old string
198+
StringBuilder sb = new StringBuilder(inString.length());
199+
int pos = 0; // position in the old string
211200
int index = inString.indexOf("\\");
212201

213202
while (index >= 0) {
214203
sb.append(inString.substring(pos, index));
204+
if (index + 1 >= inString.length()) {
205+
throw new StompConversionException("Illegal escape sequence at index " + index + ": " + inString);
206+
}
215207
Character c = inString.charAt(index + 1);
216208
if (c == 'r') {
217209
sb.append('\r');
@@ -282,7 +274,6 @@ protected Integer getContentLength(MultiValueMap<String, String> headers) {
282274

283275
/**
284276
* Try to read an EOL incrementing the buffer position if successful.
285-
*
286277
* @return whether an EOL was consumed
287278
*/
288279
private boolean tryConsumeEndOfLine(ByteBuffer buffer) {

spring-messaging/src/main/java/org/springframework/messaging/simp/stomp/StompEncoder.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -74,8 +74,8 @@ public byte[] encode(Message<byte[]> message) {
7474

7575
return baos.toByteArray();
7676
}
77-
catch (IOException e) {
78-
throw new StompConversionException("Failed to encode STOMP frame", e);
77+
catch (IOException ex) {
78+
throw new StompConversionException("Failed to encode STOMP frame", ex);
7979
}
8080
}
8181

@@ -108,8 +108,8 @@ private void writeHeaders(StompHeaderAccessor headers, Message<byte[]> message,
108108
}
109109

110110
private byte[] encodeHeaderString(String input, boolean escape) {
111-
input = escape ? escape(input) : input;
112-
return input.getBytes(UTF8_CHARSET);
111+
String inputToUse = (escape ? escape(input) : input);
112+
return inputToUse.getBytes(UTF8_CHARSET);
113113
}
114114

115115
/**

spring-messaging/src/test/java/org/springframework/messaging/simp/stomp/BufferingStompDecoderTests.java

+28-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* you may not use this file except in compliance with the License.
66
* You may obtain a copy of the License at
77
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0
99
*
1010
* Unless required by applicable law or agreed to in writing, software
1111
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -16,18 +16,16 @@
1616

1717
package org.springframework.messaging.simp.stomp;
1818

19-
import org.junit.Test;
20-
import org.springframework.messaging.Message;
21-
import org.springframework.messaging.converter.MessageConversionException;
22-
2319
import java.nio.ByteBuffer;
2420
import java.nio.charset.Charset;
25-
import java.util.Arrays;
21+
import java.util.Collections;
2622
import java.util.List;
2723

28-
import static org.junit.Assert.assertEquals;
29-
import static org.junit.Assert.assertNull;
30-
import static org.junit.Assert.fail;
24+
import org.junit.Test;
25+
26+
import org.springframework.messaging.Message;
27+
28+
import static org.junit.Assert.*;
3129

3230

3331
/**
@@ -38,10 +36,11 @@
3836
*/
3937
public class BufferingStompDecoderTests {
4038

39+
private final StompDecoder STOMP_DECODER = new StompDecoder();
40+
4141

4242
@Test
4343
public void basic() throws InterruptedException {
44-
4544
BufferingStompDecoder stompDecoder = new BufferingStompDecoder(128);
4645
String chunk = "SEND\na:alpha\n\nMessage body\0";
4746

@@ -52,7 +51,7 @@ public void basic() throws InterruptedException {
5251
assertEquals(0, stompDecoder.getBufferSize());
5352
assertNull(stompDecoder.getExpectedContentLength());
5453
}
55-
54+
5655
@Test
5756
public void oneMessageInTwoChunks() throws InterruptedException {
5857

@@ -61,7 +60,7 @@ public void oneMessageInTwoChunks() throws InterruptedException {
6160
String chunk2 = " body\0";
6261

6362
List<Message<byte[]>> messages = stompDecoder.decode(toByteBuffer(chunk1));
64-
assertEquals(Arrays.asList(), messages);
63+
assertEquals(Collections.<Message<byte[]>>emptyList(), messages);
6564

6665
messages = stompDecoder.decode(toByteBuffer(chunk2));
6766
assertEquals(1, messages.size());
@@ -73,7 +72,6 @@ public void oneMessageInTwoChunks() throws InterruptedException {
7372

7473
@Test
7574
public void twoMessagesInOneChunk() throws InterruptedException {
76-
7775
BufferingStompDecoder stompDecoder = new BufferingStompDecoder(128);
7876
String chunk = "SEND\na:alpha\n\nPayload1\0" + "SEND\na:alpha\n\nPayload2\0";
7977
List<Message<byte[]>> messages = stompDecoder.decode(toByteBuffer(chunk));
@@ -88,7 +86,6 @@ public void twoMessagesInOneChunk() throws InterruptedException {
8886

8987
@Test
9088
public void oneFullAndOneSplitMessageContentLength() throws InterruptedException {
91-
9289
int contentLength = "Payload2a-Payload2b".getBytes().length;
9390

9491
BufferingStompDecoder stompDecoder = new BufferingStompDecoder(128);
@@ -119,7 +116,6 @@ public void oneFullAndOneSplitMessageContentLength() throws InterruptedException
119116

120117
@Test
121118
public void oneFullAndOneSplitMessageNoContentLength() throws InterruptedException {
122-
123119
BufferingStompDecoder stompDecoder = new BufferingStompDecoder(128);
124120
String chunk1 = "SEND\na:alpha\n\nPayload1\0SEND\na:alpha\n";
125121
List<Message<byte[]>> messages = stompDecoder.decode(toByteBuffer(chunk1));
@@ -148,7 +144,6 @@ public void oneFullAndOneSplitMessageNoContentLength() throws InterruptedExcepti
148144

149145
@Test
150146
public void oneFullAndOneSplitWithContentLengthExceedingBufferSize() throws InterruptedException {
151-
152147
BufferingStompDecoder stompDecoder = new BufferingStompDecoder(128);
153148
String chunk1 = "SEND\na:alpha\n\nPayload1\0SEND\ncontent-length:129\n";
154149
List<Message<byte[]>> messages = stompDecoder.decode(toByteBuffer(chunk1));
@@ -165,6 +160,7 @@ public void oneFullAndOneSplitWithContentLengthExceedingBufferSize() throws Inte
165160
fail("Expected exception");
166161
}
167162
catch (StompConversionException ex) {
163+
// expected
168164
}
169165
}
170166

@@ -175,6 +171,22 @@ public void bufferSizeLimit() throws InterruptedException {
175171
stompDecoder.decode(toByteBuffer(payload));
176172
}
177173

174+
@Test
175+
public void incompleteCommand() {
176+
BufferingStompDecoder stompDecoder = new BufferingStompDecoder(128);
177+
String chunk = "MESSAG";
178+
179+
List<Message<byte[]>> messages = stompDecoder.decode(toByteBuffer(chunk));
180+
assertEquals(0, messages.size());
181+
}
182+
183+
@Test(expected = StompConversionException.class) // SPR-12418
184+
public void endingBackslashHeaderValueCheck() {
185+
BufferingStompDecoder stompDecoder = new BufferingStompDecoder(128);
186+
String payload = "SEND\na:alpha\\\n\nMessage body\0";
187+
stompDecoder.decode(toByteBuffer(payload));
188+
}
189+
178190

179191
private ByteBuffer toByteBuffer(String chunk) {
180192
return ByteBuffer.wrap(chunk.getBytes(Charset.forName("UTF-8")));

0 commit comments

Comments
 (0)