[http_request] Fix infinite loop when server doesn't send Content-Length header

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 <noreply@anthropic.com>
This commit is contained in:
Pascal Vizeli
2025-12-14 16:18:37 +00:00
co-authored by Claude Sonnet 4.5
parent e0ce66e011
commit f20f3e0525
2 changed files with 18 additions and 5 deletions
@@ -255,6 +255,9 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
size_t read_index = 0;
while (container->get_bytes_read() < max_length) {
int read = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
if (read <= 0) {
break;
}
App.feed_wdt();
yield();
read_index += read;
@@ -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();
}