diff --git a/src/AudioTools/Communication/HTTP/HttpLineReader.h b/src/AudioTools/Communication/HTTP/HttpLineReader.h index 8f5e5ae46..0c8cf9d53 100644 --- a/src/AudioTools/Communication/HTTP/HttpLineReader.h +++ b/src/AudioTools/Communication/HTTP/HttpLineReader.h @@ -72,67 +72,7 @@ class HttpLineReader { } str[result - 1] = 0; if (is_buffer_overflow) { - // SAFETY FIX: Don't print potentially corrupted binary data with %s - // Binary garbage can contain terminal escape codes or invalid UTF-8 that crashes Serial.printf() - // - // This fix prevents ESP32 hard resets when HTTP servers send malformed headers - // Real-world trigger: http://fast.citrus3.com:2020/stream/wtmj-radio - // - // Strategy: - // 1. Sanitize the actual buffer (prevents parser poisoning downstream) - // 2. Count printable vs binary content - // 3. Use hex dump for binary garbage (safer than string masking) - // 4. Limit output to 256 bytes (prevents log spam) - - int printable = 0; - int non_printable = 0; - int actual_len = 0; - - // First pass: count and find actual length - for (int i = 0; i < len && str[i] != 0; i++) { - actual_len = i + 1; - if (str[i] >= 32 && str[i] <= 126) { - printable++; - } else if (str[i] != '\r' && str[i] != '\n' && str[i] != '\t') { - non_printable++; - // CRITICAL: Sanitize the actual buffer to prevent parser poisoning - // Replace binary garbage with space to avoid confusing HTTP header parser - str[i] = ' '; - } - } - - // Limit logging output to 256 bytes to prevent excessive serial spam - int log_len = (actual_len > 256) ? 256 : actual_len; - - // If mostly binary garbage (>50% non-printable), use hex dump for safety - if (non_printable > printable) { - LOGE("Line cut off: [%d bytes, %d binary chars - showing hex dump of first %d bytes]", - actual_len, non_printable, (log_len > 32 ? 32 : log_len)); - - // Hex dump (safer than string output - never misinterpreted) - // Show first 32 bytes maximum - int hex_len = (log_len > 32) ? 32 : log_len; - for (int i = 0; i < hex_len; i += 16) { - char hex_line[64]; - int line_len = (hex_len - i > 16) ? 16 : (hex_len - i); - int pos = 0; - for (int j = 0; j < line_len; j++) { - pos += snprintf(hex_line + pos, sizeof(hex_line) - pos, "%02X ", (uint8_t)str[i + j]); - } - LOGE(" %04X: %s", i, hex_line); - } - } else { - // Mostly printable - safe to log as string (already sanitized in-place above) - // Truncate to 256 bytes for logging - if (log_len < actual_len) { - char saved = str[log_len]; - str[log_len] = 0; - LOGE("Line cut off: %s... [%d more bytes]", str, actual_len - log_len); - str[log_len] = saved; - } else { - LOGE("Line cut off: %s", str); - } - } + LOGE("HttpLineReader %s", "readlnInternal->cut off too long line"); } return result; diff --git a/src/AudioTools/CoreAudio/AudioMetaData/MetaDataICY.h b/src/AudioTools/CoreAudio/AudioMetaData/MetaDataICY.h index 09f77128a..831927335 100644 --- a/src/AudioTools/CoreAudio/AudioMetaData/MetaDataICY.h +++ b/src/AudioTools/CoreAudio/AudioMetaData/MetaDataICY.h @@ -1,10 +1,13 @@ #pragma once -#include //isascii #include "AudioToolsConfig.h" #include "AbstractMetaData.h" #include "AudioTools/CoreAudio/AudioBasic/StrView.h" #include "AudioTools/Communication/HTTP/AbstractURLStream.h" +#ifndef AUDIOTOOLS_METADATA_ICY_ASCII_ONLY +#define AUDIOTOOLS_METADATA_ICY_ASCII_ONLY true +#endif + namespace audio_tools { /** @@ -109,12 +112,7 @@ class MetaDataICY : public AbstractMetaData { metaDataLen = metaSize(ch); LOGI("metaDataLen: %d", metaDataLen); if (metaDataLen > 0) { - // Enhanced validation: reject suspiciously large metadata (>4080 bytes = 255*16) - // Also reject extremely small metadata that's unlikely to be valid - if (metaDataLen > 4080 || metaDataLen < 16) { - LOGW("Suspicious metaDataLen %d -> skipping metadata block", metaDataLen); - nextStatus = ProcessData; - } else if (metaDataLen > 200) { + if (metaDataLen > 200) { LOGI("Unexpected metaDataLen -> processed as data"); nextStatus = ProcessData; } else { @@ -170,46 +168,28 @@ class MetaDataICY : public AbstractMetaData { /// determines the meta data size from the size byte virtual int metaSize(uint8_t metaSize) { return metaSize * 16; } - inline bool isAscii(uint8_t ch){ return ch < 128;} - - /// Make sure that the result is a valid ASCII string with printable characters - /// Enhanced validation to reject corrupted metadata before it affects audio stream - virtual bool isAscii(char* result, int l) { - if (l < 1) return false; - - // Check entire metadata string, not just first 10 characters - int printable_count = 0; - int control_count = 0; - + /// Make sure that the result is a printable string + virtual bool isPrintable(const char* str, int l) { + int remain = 0; for (int j = 0; j < l; j++) { - uint8_t ch = (uint8_t)result[j]; - - // Reject non-ASCII bytes (>= 128) - if (ch >= 128) return false; - - // Count printable vs control characters - if (ch >= 32 && ch <= 126) { - printable_count++; - } else if (ch == '\n' || ch == '\r' || ch == '\t' || ch == 0) { - // Allow common control characters - continue; + uint8_t ch = str[j]; + if (remain) { + if (ch < 0x80 || ch > 0xbf) return false; + remain--; } else { - // Unusual control character - control_count++; + if (ch < 0x80) { // ASCII + if (ch != '\n' && ch != '\r' && ch != '\t' && ch < 32 || ch == 127) + return false; // control chars + } +#if !AUDIOTOOLS_METADATA_ICY_ASCII_ONLY + else if (ch >= 0xc2 && ch <= 0xdf) remain = 1; + else if (ch >= 0xe0 && ch <= 0xef) remain = 2; + else if (ch >= 0xf0 && ch <= 0xf4) remain = 3; +#endif + else return false; } } - - // Require at least 50% printable characters to reject binary garbage - // 50% threshold is the absolute minimum - accepts any ICY padding strategy - // Binary garbage typically has < 30% printable, so 50% provides good separation - // Super CFL: 68.8% (33/48) easily passes - if (printable_count < (l * 0.50)) { - LOGW("Metadata validation failed: only %d/%d printable (%.1f%%)", - printable_count, l, (printable_count * 100.0) / l); - return false; - } - - return true; + return remain == 0; } /// allocates the memory to store the metadata / we support changing sizes @@ -226,8 +206,7 @@ class MetaDataICY : public AbstractMetaData { // CHECK_MEMORY(); TRACED(); metaData[len] = 0; - // Use full validation on entire metadata string, not just first 12 bytes - if (isAscii(metaData, len)) { + if (isPrintable(metaData, len)) { LOGI("%s", metaData); StrView meta(metaData, len + 1, len); int start = meta.indexOf("StreamTitle="); @@ -247,7 +226,6 @@ class MetaDataICY : public AbstractMetaData { // Don't print corrupted binary data - could contain terminal control codes LOGW("Unexpected Data: corrupted metadata block rejected (len=%d)", len); // Signal corruption to application so it can disconnect/reconnect - // This is critical: metaint boundary is now desynchronized and audio will glitch if (callback != nullptr) { callback(Corrupted, nullptr, len); }