mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-22 05:49:04 +00:00
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:
@@ -20,6 +20,7 @@ import java.time.Duration;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -239,16 +240,23 @@ public final class ServerSentEvent<T> {
|
||||
|
||||
@Override
|
||||
public Builder<T> id(String id) {
|
||||
checkEvent(id);
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> event(String event) {
|
||||
checkEvent(event);
|
||||
this.event = event;
|
||||
return this;
|
||||
}
|
||||
|
||||
private static void checkEvent(String content) {
|
||||
Assert.isTrue(content.indexOf('\n') == -1 && content.indexOf('\r') == -1,
|
||||
"illegal character '\\n' or '\\r' in event content");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder<T> retry(Duration retry) {
|
||||
this.retry = retry;
|
||||
|
||||
+29
-3
@@ -40,7 +40,6 @@ import org.springframework.http.ReactiveHttpOutputMessage;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@code HttpMessageWriter} for {@code "text/event-stream"} responses.
|
||||
@@ -48,6 +47,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Sebastien Deleuze
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Object> {
|
||||
@@ -129,8 +129,9 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
|
||||
result = Flux.just(encodeText(sseText + "\n", mediaType, factory));
|
||||
}
|
||||
else if (data instanceof String text) {
|
||||
text = StringUtils.replace(text, "\n", "\ndata:");
|
||||
result = Flux.just(encodeText(sseText + text + "\n\n", mediaType, factory));
|
||||
StringBuilder sb = new StringBuilder(sseText);
|
||||
writeStringData(text, sb);
|
||||
result = Flux.just(encodeText(sb.toString(), mediaType, factory));
|
||||
}
|
||||
else {
|
||||
result = encodeEvent(sseText, data, dataType, mediaType, factory, hints);
|
||||
@@ -140,6 +141,31 @@ public class ServerSentEventHttpMessageWriter implements HttpMessageWriter<Objec
|
||||
});
|
||||
}
|
||||
|
||||
private void writeStringData(String input, StringBuilder sb) {
|
||||
if (input.indexOf('\n') == -1 && input.indexOf('\r') == -1) {
|
||||
sb.append(input);
|
||||
}
|
||||
else {
|
||||
int length = input.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = input.charAt(i);
|
||||
if (c == '\r') {
|
||||
if (i + 1 < length && input.charAt(i + 1) == '\n') {
|
||||
i++;
|
||||
}
|
||||
sb.append("\ndata:");
|
||||
}
|
||||
else if (c == '\n') {
|
||||
sb.append("\ndata:");
|
||||
}
|
||||
else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.append("\n\n");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> Flux<DataBuffer> encodeEvent(CharSequence sseText, T data, ResolvableType dataType,
|
||||
MediaType mediaType, DataBufferFactory factory, Map<String, Object> hints) {
|
||||
|
||||
Reference in New Issue
Block a user