ServerSentEventParser silently drops data: lines whose value contains U+2028/U+2029/U+0085
Versions
Reproduced in released 1.3.0, the main branch, and the v2.0.0-M1 tag — the field regex is identical in all three.
Description
ServerSentEventParser matches SSE fields with:
private val Field = """([^:]+): ?(.*)""".r
used via Scala Regex unapply, which requires a full matches(). Java's . (without DOTALL) does not match the Unicode line terminators U+0085 (NEL), U+2028 (LINE SEPARATOR), U+2029 (PARAGRAPH SEPARATOR). LineParser splits the byte stream only on CR/LF/CRLF, so those characters legitimately remain inside a line. When a data: value contains one, the full-line Field match fails, the line falls through to case _ => // ignore, and no ServerSentEvent is emitted — silently, with no error and no log.
Per the WHATWG SSE spec, an event stream's lines end only on CR/LF/CRLF; U+2028/U+2029/U+0085 are ordinary field content. So a spec-conformant server can legally send them and the client drops the event.
Reproduction
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.http.scaladsl.unmarshalling.sse.EventStreamParser
import org.apache.pekko.stream.scaladsl.{Sink, Source}
import org.apache.pekko.util.ByteString
import scala.concurrent.Await
import scala.concurrent.duration._
implicit val sys = ActorSystem()
val ls = 0x2028.toChar.toString // U+2028; also fails for 0x2029 and 0x0085
val bytes = ByteString(s"data: before${ls}after\n\n")
val events = Await.result(
Source.single(bytes).via(EventStreamParser(Int.MaxValue, Int.MaxValue)).runWith(Sink.seq),
5.seconds)
// events == Vector() ; expected Vector(ServerSentEvent("before<U+2028>after"))
Suggested fix
Make the value group span line terminators, e.g. add a DOTALL flag:
private val Field = """(?s)([^:]+): ?(.*)""".r
(?s) affects only the . in the value group; [^:]+ (a negated class) is unaffected, so field-name / event: / id: / retry: handling is unchanged.
Impact
Any consumer using EventStreamParser against a server that emits these code points loses events silently. In our case a single U+2028 in a JSON payload stalled cross-region config replication with no error surfaced.
ServerSentEventParsersilently dropsdata:lines whose value contains U+2028/U+2029/U+0085Versions
Reproduced in released 1.3.0, the
mainbranch, and thev2.0.0-M1tag — the field regex is identical in all three.Description
ServerSentEventParsermatches SSE fields with:used via Scala
Regexunapply, which requires a fullmatches(). Java's.(withoutDOTALL) does not match the Unicode line terminators U+0085 (NEL), U+2028 (LINE SEPARATOR), U+2029 (PARAGRAPH SEPARATOR).LineParsersplits the byte stream only on CR/LF/CRLF, so those characters legitimately remain inside a line. When adata:value contains one, the full-lineFieldmatch fails, the line falls through tocase _ => // ignore, and noServerSentEventis emitted — silently, with no error and no log.Per the WHATWG SSE spec, an event stream's lines end only on CR/LF/CRLF; U+2028/U+2029/U+0085 are ordinary field content. So a spec-conformant server can legally send them and the client drops the event.
Reproduction
Suggested fix
Make the value group span line terminators, e.g. add a DOTALL flag:
(?s)affects only the.in the value group;[^:]+(a negated class) is unaffected, so field-name /event:/id:/retry:handling is unchanged.Impact
Any consumer using
EventStreamParseragainst a server that emits these code points loses events silently. In our case a single U+2028 in a JSON payload stalled cross-region config replication with no error surfaced.