From f20f3e052599b152463dfb72bdeb497654f310b3 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 14 Dec 2025 15:15:37 +0000 Subject: [PATCH 1/3] [http_request] Fix infinite loop when server doesn't send Content-Length header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes an issue where the http_request component would enter an infinite loop when an HTTP server doesn't send a Content-Length header or closes the connection prematurely. The read loop was assuming read operations would always return data, but: 1. When the stream pointer becomes invalid (connection closed), read() returns -1 2. When no more data is available, read() returns 0 Without these checks, the loop would continue indefinitely, causing: - "Stream pointer vanished!" errors (Arduino platform) - CPU spinning on zero-byte reads - Watchdog timeouts The fix adds validation checks to break out of read loops when read() returns <= 0 (covering both error and end-of-stream conditions). This is applied to: - Response capture loops (http_request.h) - OTA firmware download loop (ota_http_request.cpp) - MD5 verification download loop (ota_http_request.cpp) This allows graceful handling of non-compliant HTTP servers while maintaining compatibility with properly formatted responses. Fixes esphome/issues#6682 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../components/http_request/http_request.h | 3 +++ .../http_request/ota/ota_http_request.cpp | 20 ++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index 8a82a44d7d..8adf13b954 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -255,6 +255,9 @@ template class HttpRequestSendAction : public Action { size_t read_index = 0; while (container->get_bytes_read() < max_length) { int read = container->read(buf + read_index, std::min(max_length - read_index, 512)); + if (read <= 0) { + break; + } App.feed_wdt(); yield(); read_index += read; diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 4552fcc9df..6cd3ad8e30 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -132,11 +132,18 @@ uint8_t OtaHttpRequestComponent::do_ota_() { App.feed_wdt(); yield(); - if (bufsize < 0) { - ESP_LOGE(TAG, "Stream closed"); - this->cleanup_(std::move(backend), container); - return OTA_CONNECTION_ERROR; - } else if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) { + // Exit loop if no data available (stream closed or end of data) + if (bufsize <= 0) { + if (bufsize < 0) { + ESP_LOGE(TAG, "Stream closed with error"); + this->cleanup_(std::move(backend), container); + return OTA_CONNECTION_ERROR; + } + // bufsize == 0: no more data available, exit loop + break; + } + + if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) { // add read bytes to MD5 md5_receive.add(buf, bufsize); @@ -247,6 +254,9 @@ bool OtaHttpRequestComponent::http_get_md5_() { int read_len = 0; while (container->get_bytes_read() < MD5_SIZE) { read_len = container->read((uint8_t *) this->md5_expected_.data(), MD5_SIZE); + if (read_len <= 0) { + break; + } App.feed_wdt(); yield(); } From c4d9ed7b701ee2882ba49705bb5f80d4fd1b1fed Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 14 Dec 2025 17:06:53 +0000 Subject: [PATCH 2/3] [http_request] Fix infinite loop on read error in update component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The update component had the same infinite loop issue as the OTA component when network read errors occurred. If container->read() returned an error (negative value), it would be added to read_index and the loop would continue indefinitely since get_bytes_read() would never reach content_length. This fix breaks out of the read loop on any read error (read_bytes <= 0), preventing watchdog resets and infinite loops during manifest downloads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .../components/http_request/update/http_request_update.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 26af754e69..22cad625d1 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -76,6 +76,11 @@ void HttpRequestUpdate::update_task(void *params) { yield(); + if (read_bytes <= 0) { + // Network error or connection closed - break to avoid infinite loop + break; + } + read_index += read_bytes; } From 2dbaedbda2b32bdfe8418e64ee6ecd0e8adde72f Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 15 Dec 2025 12:17:47 +0000 Subject: [PATCH 3/3] Simplify condition check - remove redundant bufsize > 0 check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bufsize > 0 check is redundant because the previous if statement already handles all cases where bufsize <= 0, ensuring that by the time we reach this condition, bufsize is always positive. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- esphome/components/http_request/ota/ota_http_request.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 6cd3ad8e30..b257518e06 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -143,7 +143,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() { break; } - if (bufsize > 0 && bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) { + if (bufsize <= OtaHttpRequestComponent::HTTP_RECV_BUFFER) { // add read bytes to MD5 md5_receive.add(buf, bufsize);