Guard against invalid id/event values in Server Sent Events

Prior to this commit, our implementation of Server Sent Events (SSE),
`SseEmitter` (MVC) and `ServerSentEvent` (WebFlux), would not guard
against invalid characters if the application mistakenly inserts such
characters in the `id` or `event` types.
Both implementations would also behave differently when it comes
to escaping comment multi-line events.

This commit ensures that both implementations handle multi-line comment
events and reject invalid characters in id/event types.
This commit also optimizes `String` concatenation and memory usage
when writing data.

Fixes gh-36440
This commit is contained in:
Brian Clozel
2026-03-10 17:30:24 +01:00
parent 37e8aa76e9
commit 6e9758700a
6 changed files with 183 additions and 25 deletions
@@ -110,12 +110,13 @@ class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAllocating
super.bufferFactory = bufferFactory;
MockServerHttpResponse outputMessage = new MockServerHttpResponse(super.bufferFactory);
Flux<String> source = Flux.just("foo\nbar", "foo\nbaz");
Flux<String> source = Flux.just("first\nsecond", "first\rsecond", "first\r\nsecond");
testWrite(source, outputMessage, String.class);
StepVerifier.create(outputMessage.getBody())
.consumeNextWith(stringConsumer("data:foo\ndata:bar\n\n"))
.consumeNextWith(stringConsumer("data:foo\ndata:baz\n\n"))
.consumeNextWith(stringConsumer("data:first\ndata:second\n\n"))
.consumeNextWith(stringConsumer("data:first\ndata:second\n\n"))
.consumeNextWith(stringConsumer("data:first\ndata:second\n\n"))
.expectComplete()
.verify();
}
@@ -0,0 +1,55 @@
/*
* Copyright 2002-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.http.codec;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests for {@link ServerSentEvent}.
* @author Brian Clozel
*/
class ServerSentEventTests {
@ParameterizedTest(name = "{1}")
@MethodSource("newLineCharacters")
void rejectsInvalidId(String newLine, String description) {
assertThatIllegalArgumentException().isThrownBy(() ->
ServerSentEvent.<String>builder().id("first" + newLine + "second").build());
}
@ParameterizedTest(name = "{1}")
@MethodSource("newLineCharacters")
void rejectsInvalidEvent(String newLine, String description) {
assertThatIllegalArgumentException().isThrownBy(() ->
ServerSentEvent.<String>builder().event("first" + newLine + "second").build());
}
private static Stream<Arguments> newLineCharacters() {
return Stream.of(
Arguments.of("\n", "LF"),
Arguments.of("\r", "CR"),
Arguments.of("\r\n", "CRLF")
);
}
}