git: e7df2a613c40 - main - www/{nginx,nginx-devel,freenginx}: 3rd-party modules management

From: Jochen Neumeister <joneum_at_FreeBSD.org>
Date: Sun, 04 Oct 2026 14:41:04 UTC
The branch main has been updated by joneum:

URL: https://cgit.FreeBSD.org/ports/commit/?id=e7df2a613c408a27dbceddb620a4f10dcc62113b

commit e7df2a613c408a27dbceddb620a4f10dcc62113b
Author:     Igor Valkov <viaprog@gmail.com>
AuthorDate: 2026-10-04 14:36:48 +0000
Commit:     Jochen Neumeister <joneum@FreeBSD.org>
CommitDate: 2026-10-04 14:40:40 +0000

    www/{nginx,nginx-devel,freenginx}: 3rd-party modules management
    
    Fix three defects in the body filter of the zstd module.  A worker spins
    at 100% CPU forever when an empty flush buffer arrives and the compressor
    has nothing left to flush.  A response is truncated at 131072 bytes when
    a single input buffer larger than that carries last_buf.  An output
    buffer that is already full when ZSTD_endStream() is due gets dropped.
    
    Upstream has not taken a code change since April 2024.
    
    PR:             299100
    Sponsored by:   Netzkommune GmbH
---
 www/freenginx/Makefile                             |  2 +-
 www/freenginx/Makefile.extmod                      |  3 ++
 www/freenginx/files/extra-patch-zstd-empty-flush   | 41 +++++++++++++++
 www/freenginx/files/extra-patch-zstd-full-outbuf   | 18 +++++++
 .../files/extra-patch-zstd-last-truncation         | 60 ++++++++++++++++++++++
 www/nginx-devel/Makefile                           |  2 +-
 www/nginx-devel/Makefile.extmod                    |  3 ++
 www/nginx-devel/files/extra-patch-zstd-empty-flush | 41 +++++++++++++++
 www/nginx-devel/files/extra-patch-zstd-full-outbuf | 18 +++++++
 .../files/extra-patch-zstd-last-truncation         | 60 ++++++++++++++++++++++
 www/nginx/Makefile                                 |  2 +-
 www/nginx/Makefile.extmod                          |  3 ++
 www/nginx/files/extra-patch-zstd-empty-flush       | 41 +++++++++++++++
 www/nginx/files/extra-patch-zstd-full-outbuf       | 18 +++++++
 www/nginx/files/extra-patch-zstd-last-truncation   | 60 ++++++++++++++++++++++
 15 files changed, 369 insertions(+), 3 deletions(-)

diff --git a/www/freenginx/Makefile b/www/freenginx/Makefile
index 9d2753616a7d..b106b905fea6 100644
--- a/www/freenginx/Makefile
+++ b/www/freenginx/Makefile
@@ -1,7 +1,7 @@
 PORTNAME=	freenginx
 PORTVERSION=	${NGINX_VERSION}
 .include "version.mk"
-PORTREVISION?=	17
+PORTREVISION?=	18
 CATEGORIES=	www
 MASTER_SITES=	https://freenginx.org/download/ \
 		LOCAL/joneum
diff --git a/www/freenginx/Makefile.extmod b/www/freenginx/Makefile.extmod
index ffc95be60609..523624f3b3b7 100644
--- a/www/freenginx/Makefile.extmod
+++ b/www/freenginx/Makefile.extmod
@@ -356,3 +356,6 @@ ZSTD_CONFIGURE_ENV=	ZSTD_INC=${LOCALBASE}/include \
 			ZSTD_LIB=${LOCALBASE}/lib
 ZSTD_GH_TUPLE=		tokers:zstd-nginx-module:f4ba115:zstd
 ZSTD_VARS=		DSO_EXTMODS+=zstd
+ZSTD_EXTRA_PATCHES=	${PATCHDIR}/extra-patch-zstd-empty-flush \
+			${PATCHDIR}/extra-patch-zstd-last-truncation \
+			${PATCHDIR}/extra-patch-zstd-full-outbuf
diff --git a/www/freenginx/files/extra-patch-zstd-empty-flush b/www/freenginx/files/extra-patch-zstd-empty-flush
new file mode 100644
index 000000000000..58d96477d8a4
--- /dev/null
+++ b/www/freenginx/files/extra-patch-zstd-empty-flush
@@ -0,0 +1,41 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -460,6 +460,38 @@
+     }
+ 
+     if (ngx_buf_size(ctx->out_buf) == 0) {
++
++        if (rc == 0 && ctx->flush) {
++
++            /*
++             * nothing to flush: pass an empty flush buf downstream,
++             * as the gzip filter does; otherwise ctx->flush is never
++             * reset and the body filter loops forever
++             */
++
++            b = ngx_calloc_buf(r->pool);
++            if (b == NULL) {
++                return NGX_ERROR;
++            }
++
++            b->flush = 1;
++
++            cl = ngx_alloc_chain_link(r->pool);
++            if (cl == NULL) {
++                return NGX_ERROR;
++            }
++
++            cl->buf = b;
++            cl->next = NULL;
++
++            *ctx->last_out = cl;
++            ctx->last_out = &cl->next;
++
++            ctx->flush = 0;
++
++            r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
++        }
++
+         return NGX_AGAIN;
+     }
+ 
diff --git a/www/freenginx/files/extra-patch-zstd-full-outbuf b/www/freenginx/files/extra-patch-zstd-full-outbuf
new file mode 100644
index 000000000000..9e6a83e87cfb
--- /dev/null
+++ b/www/freenginx/files/extra-patch-zstd-full-outbuf
@@ -0,0 +1,18 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -462,7 +462,14 @@
+ 
+         /* pending to call the ZSTD_endStream() */
+ 
+-        return NGX_AGAIN;
++        if (ctx->buffer_out.pos < ctx->buffer_out.size) {
++            return NGX_AGAIN;
++        }
++
++        /*
++         * the output buffer is full: pass it downstream now, otherwise
++         * ngx_http_zstd_filter_get_buf() replaces it and the data is lost
++         */
+ 
+     } else {
+         ctx->action = NGX_HTTP_ZSTD_FILTER_COMPRESS; /* restore */
diff --git a/www/freenginx/files/extra-patch-zstd-last-truncation b/www/freenginx/files/extra-patch-zstd-last-truncation
new file mode 100644
index 000000000000..bb3f8fc34004
--- /dev/null
+++ b/www/freenginx/files/extra-patch-zstd-last-truncation
@@ -0,0 +1,60 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -390,6 +390,7 @@
+ {
+     size_t        rc, pos_in, pos_out;
+     char         *hint;
++    ngx_uint_t    end;
+     ngx_chain_t  *cl;
+     ngx_buf_t    *b;
+ 
+@@ -440,6 +441,10 @@
+     ctx->out_buf->last += ctx->buffer_out.pos - pos_out;
+     ctx->redo = 0;
+ 
++    /* the frame is complete only when ZSTD_endStream() has flushed it all */
++
++    end = (rc == 0 && ctx->action == NGX_HTTP_ZSTD_FILTER_END);
++
+     if (rc > 0) {
+         if (ctx->action == NGX_HTTP_ZSTD_FILTER_COMPRESS) {
+             ctx->action = NGX_HTTP_ZSTD_FILTER_FLUSH;
+@@ -447,7 +452,11 @@
+ 
+         ctx->redo = 1;
+ 
+-    } else if (ctx->last && ctx->action != NGX_HTTP_ZSTD_FILTER_END) {
++    } else if (ctx->last && ctx->action != NGX_HTTP_ZSTD_FILTER_END
++               && ctx->buffer_in.pos == ctx->buffer_in.size)
++    {
++        /* end the frame only after the whole input has been consumed */
++
+         ctx->redo = 1;
+         ctx->action = NGX_HTTP_ZSTD_FILTER_END;
+ 
+@@ -502,13 +511,13 @@
+ 
+     b = ctx->out_buf;
+ 
+-    if (rc == 0 && (ctx->flush || ctx->last)) {
++    if (rc == 0 && (ctx->flush || end)) {
+         r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+ 
+         b->flush = ctx->flush;
+-        b->last_buf = ctx->last;
++        b->last_buf = end;
+ 
+-        ctx->done = ctx->last;
++        ctx->done = end;
+         ctx->flush = 0;
+     }
+ 
+@@ -522,7 +531,7 @@
+ 
+     ngx_memzero(&ctx->buffer_out, sizeof(ZSTD_outBuffer));
+ 
+-    return ctx->last && rc == 0 ? NGX_OK : NGX_AGAIN;
++    return end ? NGX_OK : NGX_AGAIN;
+ }
+ 
+ 
diff --git a/www/nginx-devel/Makefile b/www/nginx-devel/Makefile
index 754aab62c1e4..1004fdadea24 100644
--- a/www/nginx-devel/Makefile
+++ b/www/nginx-devel/Makefile
@@ -1,7 +1,7 @@
 PORTNAME?=	nginx
 PORTVERSION=	${NGINX_VERSION}
 .include "version.mk"
-PORTREVISION=	1
+PORTREVISION=	2
 CATEGORIES=	www
 MASTER_SITES=	https://nginx.org/download/
 PKGNAMESUFFIX?=	-devel
diff --git a/www/nginx-devel/Makefile.extmod b/www/nginx-devel/Makefile.extmod
index 5c96c2682f76..8a402b1548b6 100644
--- a/www/nginx-devel/Makefile.extmod
+++ b/www/nginx-devel/Makefile.extmod
@@ -353,3 +353,6 @@ ZSTD_CONFIGURE_ENV=	ZSTD_INC=${LOCALBASE}/include \
 			ZSTD_LIB=${LOCALBASE}/lib
 ZSTD_GH_TUPLE=		tokers:zstd-nginx-module:f4ba115:zstd
 ZSTD_VARS=		DSO_EXTMODS+=zstd
+ZSTD_EXTRA_PATCHES=	${PATCHDIR}/extra-patch-zstd-empty-flush \
+			${PATCHDIR}/extra-patch-zstd-last-truncation \
+			${PATCHDIR}/extra-patch-zstd-full-outbuf
diff --git a/www/nginx-devel/files/extra-patch-zstd-empty-flush b/www/nginx-devel/files/extra-patch-zstd-empty-flush
new file mode 100644
index 000000000000..58d96477d8a4
--- /dev/null
+++ b/www/nginx-devel/files/extra-patch-zstd-empty-flush
@@ -0,0 +1,41 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -460,6 +460,38 @@
+     }
+ 
+     if (ngx_buf_size(ctx->out_buf) == 0) {
++
++        if (rc == 0 && ctx->flush) {
++
++            /*
++             * nothing to flush: pass an empty flush buf downstream,
++             * as the gzip filter does; otherwise ctx->flush is never
++             * reset and the body filter loops forever
++             */
++
++            b = ngx_calloc_buf(r->pool);
++            if (b == NULL) {
++                return NGX_ERROR;
++            }
++
++            b->flush = 1;
++
++            cl = ngx_alloc_chain_link(r->pool);
++            if (cl == NULL) {
++                return NGX_ERROR;
++            }
++
++            cl->buf = b;
++            cl->next = NULL;
++
++            *ctx->last_out = cl;
++            ctx->last_out = &cl->next;
++
++            ctx->flush = 0;
++
++            r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
++        }
++
+         return NGX_AGAIN;
+     }
+ 
diff --git a/www/nginx-devel/files/extra-patch-zstd-full-outbuf b/www/nginx-devel/files/extra-patch-zstd-full-outbuf
new file mode 100644
index 000000000000..9e6a83e87cfb
--- /dev/null
+++ b/www/nginx-devel/files/extra-patch-zstd-full-outbuf
@@ -0,0 +1,18 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -462,7 +462,14 @@
+ 
+         /* pending to call the ZSTD_endStream() */
+ 
+-        return NGX_AGAIN;
++        if (ctx->buffer_out.pos < ctx->buffer_out.size) {
++            return NGX_AGAIN;
++        }
++
++        /*
++         * the output buffer is full: pass it downstream now, otherwise
++         * ngx_http_zstd_filter_get_buf() replaces it and the data is lost
++         */
+ 
+     } else {
+         ctx->action = NGX_HTTP_ZSTD_FILTER_COMPRESS; /* restore */
diff --git a/www/nginx-devel/files/extra-patch-zstd-last-truncation b/www/nginx-devel/files/extra-patch-zstd-last-truncation
new file mode 100644
index 000000000000..bb3f8fc34004
--- /dev/null
+++ b/www/nginx-devel/files/extra-patch-zstd-last-truncation
@@ -0,0 +1,60 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -390,6 +390,7 @@
+ {
+     size_t        rc, pos_in, pos_out;
+     char         *hint;
++    ngx_uint_t    end;
+     ngx_chain_t  *cl;
+     ngx_buf_t    *b;
+ 
+@@ -440,6 +441,10 @@
+     ctx->out_buf->last += ctx->buffer_out.pos - pos_out;
+     ctx->redo = 0;
+ 
++    /* the frame is complete only when ZSTD_endStream() has flushed it all */
++
++    end = (rc == 0 && ctx->action == NGX_HTTP_ZSTD_FILTER_END);
++
+     if (rc > 0) {
+         if (ctx->action == NGX_HTTP_ZSTD_FILTER_COMPRESS) {
+             ctx->action = NGX_HTTP_ZSTD_FILTER_FLUSH;
+@@ -447,7 +452,11 @@
+ 
+         ctx->redo = 1;
+ 
+-    } else if (ctx->last && ctx->action != NGX_HTTP_ZSTD_FILTER_END) {
++    } else if (ctx->last && ctx->action != NGX_HTTP_ZSTD_FILTER_END
++               && ctx->buffer_in.pos == ctx->buffer_in.size)
++    {
++        /* end the frame only after the whole input has been consumed */
++
+         ctx->redo = 1;
+         ctx->action = NGX_HTTP_ZSTD_FILTER_END;
+ 
+@@ -502,13 +511,13 @@
+ 
+     b = ctx->out_buf;
+ 
+-    if (rc == 0 && (ctx->flush || ctx->last)) {
++    if (rc == 0 && (ctx->flush || end)) {
+         r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+ 
+         b->flush = ctx->flush;
+-        b->last_buf = ctx->last;
++        b->last_buf = end;
+ 
+-        ctx->done = ctx->last;
++        ctx->done = end;
+         ctx->flush = 0;
+     }
+ 
+@@ -522,7 +531,7 @@
+ 
+     ngx_memzero(&ctx->buffer_out, sizeof(ZSTD_outBuffer));
+ 
+-    return ctx->last && rc == 0 ? NGX_OK : NGX_AGAIN;
++    return end ? NGX_OK : NGX_AGAIN;
+ }
+ 
+ 
diff --git a/www/nginx/Makefile b/www/nginx/Makefile
index 54b8dcb1993f..debf4ac3fed0 100644
--- a/www/nginx/Makefile
+++ b/www/nginx/Makefile
@@ -1,7 +1,7 @@
 PORTNAME=	nginx
 PORTVERSION=	${NGINX_VERSION}
 .include "version.mk"
-PORTREVISION=	1
+PORTREVISION=	2
 PORTEPOCH=	3
 CATEGORIES=	www
 MASTER_SITES=	https://nginx.org/download/ \
diff --git a/www/nginx/Makefile.extmod b/www/nginx/Makefile.extmod
index e201f8dc6eeb..07291ae59dbf 100644
--- a/www/nginx/Makefile.extmod
+++ b/www/nginx/Makefile.extmod
@@ -348,3 +348,6 @@ ZSTD_CONFIGURE_ENV=	ZSTD_INC=${LOCALBASE}/include \
 			ZSTD_LIB=${LOCALBASE}/lib
 ZSTD_GH_TUPLE=		tokers:zstd-nginx-module:f4ba115:zstd
 ZSTD_VARS=		DSO_EXTMODS+=zstd
+ZSTD_EXTRA_PATCHES=	${PATCHDIR}/extra-patch-zstd-empty-flush \
+			${PATCHDIR}/extra-patch-zstd-last-truncation \
+			${PATCHDIR}/extra-patch-zstd-full-outbuf
diff --git a/www/nginx/files/extra-patch-zstd-empty-flush b/www/nginx/files/extra-patch-zstd-empty-flush
new file mode 100644
index 000000000000..58d96477d8a4
--- /dev/null
+++ b/www/nginx/files/extra-patch-zstd-empty-flush
@@ -0,0 +1,41 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -460,6 +460,38 @@
+     }
+ 
+     if (ngx_buf_size(ctx->out_buf) == 0) {
++
++        if (rc == 0 && ctx->flush) {
++
++            /*
++             * nothing to flush: pass an empty flush buf downstream,
++             * as the gzip filter does; otherwise ctx->flush is never
++             * reset and the body filter loops forever
++             */
++
++            b = ngx_calloc_buf(r->pool);
++            if (b == NULL) {
++                return NGX_ERROR;
++            }
++
++            b->flush = 1;
++
++            cl = ngx_alloc_chain_link(r->pool);
++            if (cl == NULL) {
++                return NGX_ERROR;
++            }
++
++            cl->buf = b;
++            cl->next = NULL;
++
++            *ctx->last_out = cl;
++            ctx->last_out = &cl->next;
++
++            ctx->flush = 0;
++
++            r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
++        }
++
+         return NGX_AGAIN;
+     }
+ 
diff --git a/www/nginx/files/extra-patch-zstd-full-outbuf b/www/nginx/files/extra-patch-zstd-full-outbuf
new file mode 100644
index 000000000000..9e6a83e87cfb
--- /dev/null
+++ b/www/nginx/files/extra-patch-zstd-full-outbuf
@@ -0,0 +1,18 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -462,7 +462,14 @@
+ 
+         /* pending to call the ZSTD_endStream() */
+ 
+-        return NGX_AGAIN;
++        if (ctx->buffer_out.pos < ctx->buffer_out.size) {
++            return NGX_AGAIN;
++        }
++
++        /*
++         * the output buffer is full: pass it downstream now, otherwise
++         * ngx_http_zstd_filter_get_buf() replaces it and the data is lost
++         */
+ 
+     } else {
+         ctx->action = NGX_HTTP_ZSTD_FILTER_COMPRESS; /* restore */
diff --git a/www/nginx/files/extra-patch-zstd-last-truncation b/www/nginx/files/extra-patch-zstd-last-truncation
new file mode 100644
index 000000000000..bb3f8fc34004
--- /dev/null
+++ b/www/nginx/files/extra-patch-zstd-last-truncation
@@ -0,0 +1,60 @@
+--- ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c.orig
++++ ../zstd-nginx-module-f4ba115/filter/ngx_http_zstd_filter_module.c
+@@ -390,6 +390,7 @@
+ {
+     size_t        rc, pos_in, pos_out;
+     char         *hint;
++    ngx_uint_t    end;
+     ngx_chain_t  *cl;
+     ngx_buf_t    *b;
+ 
+@@ -440,6 +441,10 @@
+     ctx->out_buf->last += ctx->buffer_out.pos - pos_out;
+     ctx->redo = 0;
+ 
++    /* the frame is complete only when ZSTD_endStream() has flushed it all */
++
++    end = (rc == 0 && ctx->action == NGX_HTTP_ZSTD_FILTER_END);
++
+     if (rc > 0) {
+         if (ctx->action == NGX_HTTP_ZSTD_FILTER_COMPRESS) {
+             ctx->action = NGX_HTTP_ZSTD_FILTER_FLUSH;
+@@ -447,7 +452,11 @@
+ 
+         ctx->redo = 1;
+ 
+-    } else if (ctx->last && ctx->action != NGX_HTTP_ZSTD_FILTER_END) {
++    } else if (ctx->last && ctx->action != NGX_HTTP_ZSTD_FILTER_END
++               && ctx->buffer_in.pos == ctx->buffer_in.size)
++    {
++        /* end the frame only after the whole input has been consumed */
++
+         ctx->redo = 1;
+         ctx->action = NGX_HTTP_ZSTD_FILTER_END;
+ 
+@@ -502,13 +511,13 @@
+ 
+     b = ctx->out_buf;
+ 
+-    if (rc == 0 && (ctx->flush || ctx->last)) {
++    if (rc == 0 && (ctx->flush || end)) {
+         r->connection->buffered &= ~NGX_HTTP_GZIP_BUFFERED;
+ 
+         b->flush = ctx->flush;
+-        b->last_buf = ctx->last;
++        b->last_buf = end;
+ 
+-        ctx->done = ctx->last;
++        ctx->done = end;
+         ctx->flush = 0;
+     }
+ 
+@@ -522,7 +531,7 @@
+ 
+     ngx_memzero(&ctx->buffer_out, sizeof(ZSTD_outBuffer));
+ 
+-    return ctx->last && rc == 0 ? NGX_OK : NGX_AGAIN;
++    return end ? NGX_OK : NGX_AGAIN;
+ }
+ 
+