mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-23 06:29:10 +00:00
Reset TwoByteMatcher partial match on mismatching byte
DataBufferUtils.TwoByteMatcher inherited AbstractNestedMatcher.match(byte) without providing the mismatch fallback that its siblings implement (KnuthMorrisPrattMatcher backtracks via its suffix-prefix table, and SingleByteMatcher is stateless). As a result, once the first delimiter byte had matched, the match counter stayed at 1 across any number of intervening non-matching bytes, so a later occurrence of the second delimiter byte falsely completed the match. For a two-byte delimiter such as \r\n this made the matcher report a match across non-contiguous bytes. CompositeMatcher prefers the longest delimiter that matches at a position, so the false \r\n match was chosen over a real single \n, causing StringDecoder to strip two bytes and drop the character preceding a lone \n whenever a line contained a stray \r. TwoByteMatcher now overrides match(byte) to reset the counter to 0 when the incoming byte is not the expected next delimiter byte before delegating to super.match(), mirroring KnuthMorrisPrattMatcher. A genuine contiguous delimiter is unaffected. Signed-off-by: junhyeong9812 <pickjog@gmail.com>
This commit is contained in:
committed by
Brian Clozel
parent
b556766e1a
commit
7f1966f5f5
@@ -917,6 +917,14 @@ public abstract class DataBufferUtils {
|
||||
super(delimiter);
|
||||
Assert.isTrue(delimiter.length == 2, "Expected a 2-byte delimiter");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(byte b) {
|
||||
if (getMatches() > 0 && b != delimiter()[getMatches()]) {
|
||||
setMatches(0);
|
||||
}
|
||||
return super.match(b);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -153,6 +153,30 @@ class StringDecoderTests extends AbstractDecoderTests<StringDecoder> {
|
||||
.verify());
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodePreservesCharacterBeforeLoneNewlineAfterCarriageReturn() {
|
||||
Flux<DataBuffer> input = Flux.just(stringBuffer("a\rXY\nb"));
|
||||
|
||||
testDecode(input, String.class, step -> step
|
||||
.expectNext("a\rXY")
|
||||
.expectNext("b")
|
||||
.expectComplete()
|
||||
.verify());
|
||||
}
|
||||
|
||||
@Test
|
||||
void decodePreservesCharacterAcrossBuffersAfterCarriageReturn() {
|
||||
Flux<DataBuffer> input = Flux.just(
|
||||
stringBuffer("a\r"),
|
||||
stringBuffer("Xb\n")
|
||||
);
|
||||
|
||||
testDecode(input, String.class, step -> step
|
||||
.expectNext("a\rXb")
|
||||
.expectComplete()
|
||||
.verify());
|
||||
}
|
||||
|
||||
@Test
|
||||
void maxInMemoryLimit() {
|
||||
Flux<DataBuffer> input = Flux.just(
|
||||
|
||||
+42
@@ -1323,6 +1323,48 @@ class DataBufferUtilsTests extends AbstractDataBufferAllocatingTests {
|
||||
release(foo);
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void matcherDoesNotMatchAcrossNonContiguousDelimiterBytes(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer buffer = stringBuffer("a\rXY\nb");
|
||||
|
||||
byte[] delims = "\r\n".getBytes(StandardCharsets.UTF_8);
|
||||
DataBufferUtils.Matcher matcher = DataBufferUtils.matcher(delims);
|
||||
int result = matcher.match(buffer);
|
||||
assertThat(result).isEqualTo(-1);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void matcherMatchesContiguousTwoByteDelimiter(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer buffer = stringBuffer("a\r\nb");
|
||||
|
||||
byte[] delims = "\r\n".getBytes(StandardCharsets.UTF_8);
|
||||
DataBufferUtils.Matcher matcher = DataBufferUtils.matcher(delims);
|
||||
int result = matcher.match(buffer);
|
||||
assertThat(result).isEqualTo(2);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void matcherDoesNotMatchAfterRepeatedFirstDelimiterByte(DataBufferFactory bufferFactory) {
|
||||
super.bufferFactory = bufferFactory;
|
||||
|
||||
DataBuffer buffer = stringBuffer("a\r\rX\nb");
|
||||
|
||||
byte[] delims = "\r\n".getBytes(StandardCharsets.UTF_8);
|
||||
DataBufferUtils.Matcher matcher = DataBufferUtils.matcher(delims);
|
||||
int result = matcher.match(buffer);
|
||||
assertThat(result).isEqualTo(-1);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@ParameterizedDataBufferAllocatingTest
|
||||
void propagateContextByteChannel(DataBufferFactory bufferFactory) throws IOException {
|
||||
Path path = Paths.get(this.resource.getURI());
|
||||
|
||||
Reference in New Issue
Block a user