This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 8af35fd319 Quiet ESI streaming gunzip zero-output logs (#13171)
8af35fd319 is described below

commit 8af35fd31944e9cd64763fa52d4e443c2f178f3a
Author: Brian Neradt <[email protected]>
AuthorDate: Mon Jun 1 14:59:44 2026 -0500

    Quiet ESI streaming gunzip zero-output logs (#13171)
    
    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 ec1341d818..19bfc6c205 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<Bytef *>(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<Bytef *>(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 a6bd50fa59..5d19366a7c 100644
--- a/plugins/esi/test/gzip_test.cc
+++ b/plugins/esi/test/gzip_test.cc
@@ -26,12 +26,16 @@
 
 #include <catch2/catch_test_macros.hpp>
 
+#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 2596a46205..d73b973d65 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);
+  }
 }

Reply via email to