From 498b0f6be30d108dd78dc427beaad11767589a84 Mon Sep 17 00:00:00 2001 From: bneradt Date: Mon, 18 May 2026 13:22:35 -0500 Subject: [PATCH] Quiet ESI streaming gunzip zero-output logs ESI streaming gunzip can receive valid gzip chunks that consume input without producing output, such as an initial header-only chunk. Those chunks currently emit a `buf below zero` error even though the stream continues successfully, which creates noisy Traffic Server logs. This treats zero-output progress as normal streaming inflate behavior and reserves error logging for actual zlib failures. It also extends the ESI gzip unit test support to capture error logs and covers the header-only chunk case. --- plugins/esi/lib/EsiGunzip.cc | 27 +++++++++++++++------------ plugins/esi/test/gzip_test.cc | 26 ++++++++++++++++++++++++++ plugins/esi/test/print_funcs.cc | 14 +++++++++++++- 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/plugins/esi/lib/EsiGunzip.cc b/plugins/esi/lib/EsiGunzip.cc index ec1341d8182..19bfc6c2057 100644 --- a/plugins/esi/lib/EsiGunzip.cc +++ b/plugins/esi/lib/EsiGunzip.cc @@ -72,29 +72,32 @@ EsiGunzip::stream_decode(const char *data, int data_len, std::string &udata) int32_t curr_buf_size; do { - _zstrm.next_out = reinterpret_cast(raw_buf); - _zstrm.avail_out = BUF_SIZE; - inflate_result = inflate(&_zstrm, Z_SYNC_FLUSH); - curr_buf_size = -1; - if ((inflate_result == Z_OK) || (inflate_result == Z_BUF_ERROR)) { - curr_buf_size = BUF_SIZE - _zstrm.avail_out; - } else if (inflate_result == Z_STREAM_END) { + _zstrm.next_out = reinterpret_cast(raw_buf); + _zstrm.avail_out = BUF_SIZE; + auto const avail_in_before = _zstrm.avail_in; + inflate_result = inflate(&_zstrm, Z_SYNC_FLUSH); + if ((inflate_result == Z_OK) || (inflate_result == Z_BUF_ERROR) || (inflate_result == Z_STREAM_END)) { curr_buf_size = BUF_SIZE - _zstrm.avail_out; + } else { + TSError("[%s] Failure while inflating; error code %d", __FUNCTION__, inflate_result); + _success = false; + return false; } if (curr_buf_size > BUF_SIZE) { TSError("[%s] buf too large", __FUNCTION__); break; } - if (curr_buf_size < 1) { - TSError("[%s] buf below zero", __FUNCTION__); + if (curr_buf_size < 1 && avail_in_before == _zstrm.avail_in) { break; } // push empty object onto list and add data to in-list object to // avoid data copy for temporary - buf_list.push_back(string()); - string &curr_buf = buf_list.back(); - curr_buf.assign(raw_buf, curr_buf_size); + if (curr_buf_size > 0) { + buf_list.push_back(string()); + string &curr_buf = buf_list.back(); + curr_buf.assign(raw_buf, curr_buf_size); + } if (inflate_result == Z_STREAM_END) { break; diff --git a/plugins/esi/test/gzip_test.cc b/plugins/esi/test/gzip_test.cc index a6bd50fa593..5d19366a7ca 100644 --- a/plugins/esi/test/gzip_test.cc +++ b/plugins/esi/test/gzip_test.cc @@ -26,12 +26,16 @@ #include +#include "EsiGunzip.h" #include "Utils.h" #include "gzip.h" using std::string; using namespace EsiLib; +extern void enableFakeErrorLog(); +extern string gFakeErrorLog; + TEST_CASE("test esi plugin - gzip") { SECTION("===================== Test 1") @@ -102,4 +106,26 @@ TEST_CASE("test esi plugin - gzip") // check output of gunzip CHECK(gunzip(expected_cdata, 32, buf_list) == false); } + + SECTION("streaming gunzip does not log an error for chunks with no output") + { + const char expected_data[] = "Hello World!"; + + string cdata; + REQUIRE(gzip(expected_data, 12, cdata)); + + EsiGunzip gunzip; + string data; + + enableFakeErrorLog(); + REQUIRE(gunzip.stream_decode(cdata.data(), GZIP_HEADER_SIZE, data)); + CHECK(data.empty()); + CHECK(gFakeErrorLog.empty()); + + REQUIRE(gunzip.stream_decode(cdata.data() + GZIP_HEADER_SIZE, cdata.size() - GZIP_HEADER_SIZE, data)); + REQUIRE(gunzip.stream_finish()); + + CHECK(data == expected_data); + CHECK(gFakeErrorLog.empty()); + } } diff --git a/plugins/esi/test/print_funcs.cc b/plugins/esi/test/print_funcs.cc index 2596a462054..d73b973d654 100644 --- a/plugins/esi/test/print_funcs.cc +++ b/plugins/esi/test/print_funcs.cc @@ -34,9 +34,11 @@ static const int LINE_SIZE = 1024 * 1024; namespace { bool fakeDebugLogEnabled; -} +bool fakeErrorLogEnabled; +} // namespace std::string gFakeDebugLog; +std::string gFakeErrorLog; void enableFakeDebugLog() @@ -45,6 +47,13 @@ enableFakeDebugLog() gFakeDebugLog.assign(""); } +void +enableFakeErrorLog() +{ + fakeErrorLogEnabled = true; + gFakeErrorLog.assign(""); +} + void DbgCtl::print(const char *tag, const char * /* file */, const char * /* function */, int /* line */, const char *fmt, ...) { @@ -101,4 +110,7 @@ TSError(const char *fmt, ...) vsnprintf(buf, LINE_SIZE, fmt, ap); printf("Error: %s\n", buf); va_end(ap); + if (fakeErrorLogEnabled) { + gFakeErrorLog.append(buf); + } }