Skip to content

fix(static): don'\''t let zstd_static suppress the gzip fallback#76

Merged
eilandert merged 1 commit into
masterfrom
fix/gzip-fallback-latch
Jul 7, 2026
Merged

fix(static): don'\''t let zstd_static suppress the gzip fallback#76
eilandert merged 1 commit into
masterfrom
fix/gzip-fallback-latch

Conversation

@eilandert

Copy link
Copy Markdown
Member

MINOR from module audit.

Problem

ngx_http_zstd_ok() latches r->gzip_tested=1 / r->gzip_ok=0 as a side effect. The static handler called it under zstd_static on before checking whether the .zst exists. When the .zst was absent the handler declined — but gzip was already latched "not ok" for the request, so a later gzip filter / gzip_static handler short-circuited and served identity instead of gzip. The client lost its gzip fallback purely because the zstd module was enabled.

Fix

Split the shared helper:

  • ngx_http_zstd_accepts() — side-effect-free acceptance predicate, used by the static module (decision only).
  • ngx_http_zstd_ok() — keeps the latching behaviour for the on-the-fly filter, which commits to Content-Encoding: zstd immediately after and must keep gzip off. Now ngx_inline so the static TU (which includes the header but no longer calls it) does not trip -Werror=unused-function.

Tests

  • t/01-static.t TEST 26 — zstd_static on, plain file with no sibling .zst, through the gzip filter; asserts Content-Encoding: gzip. Verified to fail on pre-fix code.
  • TEST 27 — coexistence with the gzip_static module. Documents that gzip_static (a built-in CONTENT_PHASE handler) runs before the dynamic zstd_static handler and was never bitten by the latch; kept as a coexistence contract (passes pre-fix and post-fix).

Local: prove t/00 t/01 = 1224/1224 PASS.

ngx_http_zstd_ok() latches r->gzip_tested=1 / r->gzip_ok=0 as a side
effect (inherited from tokers/zstd-nginx-module). The static handler
called it under `zstd_static on` BEFORE checking whether the .zst file
exists. When the .zst was absent the handler declined — but gzip had
already been latched "not ok" for the request, so a later gzip filter or
gzip_static handler short-circuited on the cached decision and served
identity instead of gzip. The client lost its gzip fallback purely
because the zstd module was enabled.

Split the shared helper: ngx_http_zstd_accepts() is a side-effect-free
acceptance predicate for callers that only need the decision (the static
module); ngx_http_zstd_ok() keeps the latching behaviour for the on-the-fly
filter, which commits to Content-Encoding: zstd immediately after and must
keep gzip off. ngx_http_zstd_ok() is now ngx_inline: the static TU includes
the header but no longer calls it, and a plain `static` definition would
trip -Werror=unused-function there; an inline definition is exempt.

Tests (t/01-static.t):
- TEST 26 — zstd_static on, request a plain file with NO sibling .zst
  through the always-present gzip *filter*; asserts Content-Encoding: gzip.
  Verified to fail on pre-fix code (gzip suppressed -> identity).
- TEST 27 — coexistence with the gzip_static *module*. Documents that
  gzip_static (a built-in CONTENT_PHASE handler) runs before the dynamic
  zstd_static handler and so was never bitten by the latch; kept as a
  coexistence contract (passes on both pre-fix and fixed trees).
@eilandert
eilandert force-pushed the fix/gzip-fallback-latch branch from 4e0b805 to ec21a07 Compare July 7, 2026 19:48
@eilandert
eilandert merged commit abbf6a1 into master Jul 7, 2026
12 checks passed
@eilandert
eilandert deleted the fix/gzip-fallback-latch branch July 7, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant