diff --git a/core/src/main/scala/sttp/model/sse/ServerSentEvent.scala b/core/src/main/scala/sttp/model/sse/ServerSentEvent.scala index 3919a58..e40b088 100644 --- a/core/src/main/scala/sttp/model/sse/ServerSentEvent.scala +++ b/core/src/main/scala/sttp/model/sse/ServerSentEvent.scala @@ -35,8 +35,7 @@ object ServerSentEvent { event.foldLeft(ServerSentEvent()) { (event, line) => if (line.startsWith("data:")) combineData(event, removeLeadingSpace(line.substring(5))) else if (line.startsWith("id:")) event.copy(id = Some(removeLeadingSpace(line.substring(3)))) - else if (line.startsWith("retry:")) - event.copy(retry = ParseUtils.toIntOption(removeLeadingSpace(line.substring(6)))) + else if (line.startsWith("retry:")) combineRetry(event, removeLeadingSpace(line.substring(6))) else if (line.startsWith("event:")) event.copy(eventType = Some(removeLeadingSpace(line.substring(6)))) else if (line == "data") combineData(event, "") else if (line == "id") event.copy(id = Some("")) @@ -45,6 +44,15 @@ object ServerSentEvent { } } + /** The spec accepts only ASCII digits here, and says to ignore the field otherwise - so a value that isn't accepted + * leaves any previously parsed one in place. `toIntOption` is still needed to reject a value too large for an `Int`, + * and `isDigit` would not do instead of the range check: it, like `toIntOption`, accepts non-ASCII digits. + */ + private def combineRetry(event: ServerSentEvent, newRetry: String): ServerSentEvent = + if (newRetry.nonEmpty && newRetry.forall(c => c >= '0' && c <= '9')) + ParseUtils.toIntOption(newRetry).fold(event)(retry => event.copy(retry = Some(retry))) + else event + private def combineData(event: ServerSentEvent, newData: String): ServerSentEvent = { event match { case e @ ServerSentEvent(Some(oldData), _, _, _) => e.copy(data = Some(s"$oldData\n$newData")) diff --git a/core/src/test/scala/sttp/model/sse/ServerSentEventTest.scala b/core/src/test/scala/sttp/model/sse/ServerSentEventTest.scala index dc36118..1451f6c 100644 --- a/core/src/test/scala/sttp/model/sse/ServerSentEventTest.scala +++ b/core/src/test/scala/sttp/model/sse/ServerSentEventTest.scala @@ -108,4 +108,14 @@ class ServerSentEventTest extends AnyFlatSpec with Matchers { val sse = ServerSentEvent(Some("a\n")) ServerSentEvent.parse(sse.toString.split("\n").toList) shouldBe sse } + + "parse" should "keep an earlier retry value when a later one is not a number" in { + ServerSentEvent.parse(List("retry: 5", "retry: x")).retry shouldBe Some(5) + } + + "parse" should "ignore a retry value that is not made up of ASCII digits" in { + ServerSentEvent.parse(List("retry: -1")).retry shouldBe None + ServerSentEvent.parse(List("retry: +5")).retry shouldBe None + ServerSentEvent.parse(List("retry: ٥")).retry shouldBe None + } }