diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java
index 3966bcba7d70..1ca198f35098 100644
--- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java
+++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java
@@ -163,7 +163,10 @@ else if (line.startsWith("event:")) {
sseBuilder.event(line.substring(6).trim());
}
else if (line.startsWith("retry:")) {
- sseBuilder.retry(Duration.ofMillis(Long.parseLong(line.substring(6).trim())));
+ Long retry = parseRetry(line.substring(6).trim());
+ if (retry != null) {
+ sseBuilder.retry(Duration.ofMillis(retry));
+ }
}
else if (line.startsWith(":")) {
comment = (comment != null ? comment : new StringBuilder());
@@ -188,6 +191,32 @@ else if (line.startsWith(":")) {
}
}
+ /**
+ * Parse the value of a {@code retry} field, which is to be ignored unless it
+ * consists solely of ASCII digits and fits into a {@code long}.
+ * @return the reconnection time in milliseconds, or {@code null} to ignore the field
+ * @see
+ * HTML Living Standard: interpreting an event stream
+ */
+ private static @Nullable Long parseRetry(String value) {
+ if (value.isEmpty()) {
+ return null;
+ }
+ for (int i = 0; i < value.length(); i++) {
+ char ch = value.charAt(i);
+ if (ch < '0' || ch > '9') {
+ return null;
+ }
+ }
+ try {
+ return Long.parseLong(value);
+ }
+ catch (NumberFormatException ex) {
+ // Too large for a long: ignore the field rather than failing the stream.
+ return null;
+ }
+ }
+
private @Nullable Object decodeData(StringBuilder data, ResolvableType dataType, Map hints) {
if (String.class == dataType.resolve()) {
return data.substring(0, data.length() - 1);
diff --git a/spring-web/src/test/java/org/springframework/http/codec/ServerSentEventHttpMessageReaderTests.java b/spring-web/src/test/java/org/springframework/http/codec/ServerSentEventHttpMessageReaderTests.java
index 3693bed3755d..7c704e5cf842 100644
--- a/spring-web/src/test/java/org/springframework/http/codec/ServerSentEventHttpMessageReaderTests.java
+++ b/spring-web/src/test/java/org/springframework/http/codec/ServerSentEventHttpMessageReaderTests.java
@@ -137,6 +137,29 @@ void trimWhitespace() {
.verify();
}
+ @Test
+ @SuppressWarnings("rawtypes")
+ void ignoreInvalidRetry() {
+ MockServerHttpRequest request = MockServerHttpRequest.post("/")
+ .body(Mono.just(stringBuffer(
+ "retry:none\ndata:foo\n\n" +
+ "retry:\ndata:bar\n\n" +
+ "retry:-1\ndata:baz\n\n" +
+ "retry:99999999999999999999\ndata:qux\n\n")));
+
+ Flux events = this.reader
+ .read(ResolvableType.forClassWithGenerics(ServerSentEvent.class, String.class),
+ request, Collections.emptyMap()).cast(ServerSentEvent.class);
+
+ StepVerifier.create(events)
+ .expectNext(ServerSentEvent.builder().data("foo").build())
+ .expectNext(ServerSentEvent.builder().data("bar").build())
+ .expectNext(ServerSentEvent.builder().data("baz").build())
+ .expectNext(ServerSentEvent.builder().data("qux").build())
+ .expectComplete()
+ .verify();
+ }
+
@Test // gh-35412
void emptyLines() {
MockServerHttpRequest request = MockServerHttpRequest.post("/")