From c265dcf01fc81a8d51e45ff2837c5d3d1f8f4d55 Mon Sep 17 00:00:00 2001 From: Dylan McCormick Date: Wed, 29 Jul 2026 19:43:46 -0400 Subject: [PATCH 1/2] Add HTTP method constraint for categorizing SSE requests --- src/WebRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 3c9bb4d1..3912802a 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -668,7 +668,7 @@ bool AsyncWebServerRequest::_parseReqHeader() { #else const char *substr = std::strstr(lowcase.c_str(), String(T_text_event_stream).c_str()); #endif - if (substr != NULL) { + if (substr != NULL && _method == AsyncWebRequestMethod::HTTP_GET) { // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] _reqconntype = RCT_EVENT; } From 7e062506cd6b5448f55f5faaa2f6785b21756d3d Mon Sep 17 00:00:00 2001 From: Mathieu Carbou Date: Thu, 30 Jul 2026 22:17:17 +0200 Subject: [PATCH 2/2] refactor(sse): guard WS/SSE connection-type classification against header-order overwrites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both the WebSocket (Upgrade: websocket) and SSE (Accept: text/event-stream) branches now classify the connection only when the method is HTTP_GET (per RFC 6455 §4.1 and HTML §9.2 respectively) and only when _reqconntype is still a plain HTTP connection (RCT_DEFAULT/RCT_HTTP). This makes classification header-order independent so neither branch can clobber the other, addressing the Copilot review concern on PR #470 and the symmetric pre-existing issue in the Upgrade branch. Also removes a stray 'l' typo before the SSE classification guard. --- src/WebRequest.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/WebRequest.cpp b/src/WebRequest.cpp index 3912802a..eb244c90 100644 --- a/src/WebRequest.cpp +++ b/src/WebRequest.cpp @@ -659,7 +659,13 @@ bool AsyncWebServerRequest::_parseReqHeader() { } } else if (name.equalsIgnoreCase(T_UPGRADE) && value.equalsIgnoreCase(T_WS)) { // WebSocket request can be uniquely identified by header: [Upgrade: websocket] - _reqconntype = RCT_WS; + // Per RFC 6455 §4.1 the handshake is a GET. Only classify when the + // connection is still a plain HTTP connection so a previously detected + // SSE request (or any other classified type) cannot be clobbered by + // header ordering. + if (_method == AsyncWebRequestMethod::HTTP_GET && (_reqconntype == RCT_DEFAULT || _reqconntype == RCT_HTTP)) { + _reqconntype = RCT_WS; + } } else if (name.equalsIgnoreCase(T_ACCEPT)) { String lowcase(value); lowcase.toLowerCase(); @@ -668,7 +674,11 @@ bool AsyncWebServerRequest::_parseReqHeader() { #else const char *substr = std::strstr(lowcase.c_str(), String(T_text_event_stream).c_str()); #endif - if (substr != NULL && _method == AsyncWebRequestMethod::HTTP_GET) { + // Server-Sent Events (HTML §9.2) are GET-only connections negotiated via + // Accept: text/event-stream. Only classify when the connection is still + // a plain HTTP connection so a previously detected WebSocket upgrade + // cannot be clobbered by header ordering. + if (substr != NULL && _method == AsyncWebRequestMethod::HTTP_GET && (_reqconntype == RCT_DEFAULT || _reqconntype == RCT_HTTP)) { // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] _reqconntype = RCT_EVENT; }