From fee592c2c2f2727c865169e32f45684abfd2d47b Mon Sep 17 00:00:00 2001 From: yhirose Date: Thu, 8 Oct 2026 21:07:40 -0400 Subject: [PATCH] Ignore a leading UTF-8 BOM in an SSE stream The SSE spec strips one BOM at the start of the stream, but SSEClient kept it as part of the first field name, so the first event was silently dropped. --- httplib.h | 7 +++++++ test/test.cc | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/httplib.h b/httplib.h index c52ba15..7b717de 100644 --- a/httplib.h +++ b/httplib.h @@ -4993,6 +4993,7 @@ inline void SSEClient::run_event_loop() { SSEMessage current_msg; auto has_data = false; auto has_id = false; + auto first_line = true; while (running_.load() && result.next()) { buffer.append(result.data(), result.size()); @@ -5010,6 +5011,12 @@ inline void SSEClient::run_event_loop() { // one without a colon, sees the same line if (!line.empty() && line.back() == '\r') { line.pop_back(); } + // A UTF-8 BOM at the start of the stream is ignored per the SSE spec + if (first_line) { + first_line = false; + if (line.compare(0, 3, "\xEF\xBB\xBF") == 0) { line.erase(0, 3); } + } + // Parse the line and check if event is complete auto event_complete = parse_sse_line( line, current_msg, reconnect_interval_ms_, has_data, has_id); diff --git a/test/test.cc b/test/test.cc index 0c3c6cd..e5deedf 100644 --- a/test/test.cc +++ b/test/test.cc @@ -22793,6 +22793,16 @@ TEST_F(SSEParsingTest, FieldNameOnlyWithCarriageReturn) { EXPECT_EQ(msgs[0].data, ""); } +TEST_F(SSEParsingTest, LeadingByteOrderMarkIgnored) { + // Only a BOM at the start of the stream is ignored + auto msgs = parse("\xEF\xBB\xBF" + "data: first\n\n" + "\xEF\xBB\xBF" + "data: second\n\n"); + ASSERT_EQ(msgs.size(), 1u); + EXPECT_EQ(msgs[0].data, "first"); +} + TEST_F(SSEParsingTest, UnknownFieldsIgnored) { auto msgs = parse("unknown: value\ndata: hello\n\n"); ASSERT_EQ(msgs.size(), 1u);