From noreply at nginx.com Wed Sep 3 16:29:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 3 Sep 2025 16:29:02 +0000 (UTC) Subject: [njs] QuickJS: fix nginx configure with not configured njs module. Message-ID: <20250903162902.8064346BD7@pubserv1.nginx> details: https://github.com/nginx/njs/commit/99aac0dca38c0b3dd247e02b15164b8bf73849f7 branches: master commit: 99aac0dca38c0b3dd247e02b15164b8bf73849f7 user: Vadim Zhestikov date: Fri, 29 Aug 2025 09:42:18 -0700 description: QuickJS: fix nginx configure with not configured njs module. This fixes issue #960 which was introduced in 8259f9a. --- .github/workflows/check-pr.yml | 2 ++ auto/quickjs | 14 +++++++------- external/njs_shell.c | 1 + nginx/config | 2 +- src/qjs.h | 23 +---------------------- src/quickjs_compat.h | 24 ++++++++++++++++++++++++ 6 files changed, 36 insertions(+), 30 deletions(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index 2be02e7b..c75f3cbe 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -134,7 +134,9 @@ jobs: - name: Configure and build nginx and njs modules with quickjs, asan, static modules run: | + $MAKE_UTILITY clean cd nginx-source + $MAKE_UTILITY clean $NGINX_CONFIGURE_CMD --with-cc-opt="$CC_OPT -I${{ github.workspace }}/quickjs -fsanitize=address -DNJS_DEBUG_MEMORY -DNGX_DEBUG_PALLOC -DNGX_DEBUG_MALLOC" --with-ld-opt="$LD_OPT -L${{ github.workspace }}/quickjs -fsanitize=address" --add-module=../nginx || cat objs/autoconf.err $MAKE_UTILITY -j$(nproc) diff --git a/auto/quickjs b/auto/quickjs index cbf860a2..8d6b544f 100644 --- a/auto/quickjs +++ b/auto/quickjs @@ -15,7 +15,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature_run=yes njs_feature_incs="$NJS_QUICKJS_DEFAULT_INCS" njs_feature_libs="-lquickjs.lto -lm -ldl -lpthread" - njs_feature_test="#include + njs_feature_test="#include int main() { JSRuntime *rt; @@ -62,7 +62,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_GetClassID()" njs_feature_name=NJS_HAVE_QUICKJS_GET_CLASS_ID - njs_feature_test="#include + njs_feature_test="#include int main() { (void) JS_GetClassID(JS_UNDEFINED); @@ -80,7 +80,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_NewTypedArray()" njs_feature_name=NJS_HAVE_QUICKJS_NEW_TYPED_ARRAY - njs_feature_test="#include + njs_feature_test="#include int main() { JSValue ta, argv; @@ -102,7 +102,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_IsSameValue()" njs_feature_name=NJS_HAVE_QUICKJS_IS_SAME_VALUE - njs_feature_test="#include + njs_feature_test="#include int main() { JSRuntime *rt; @@ -120,7 +120,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_IsArray()" njs_feature_name=NJS_HAVE_QUICKJS_IS_ARRAY_SINGLE_ARG - njs_feature_test="#include + njs_feature_test="#include int main() { JSRuntime *rt; @@ -138,7 +138,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS JS_AddIntrinsicBigInt()" njs_feature_name=NJS_HAVE_QUICKJS_ADD_INTRINSIC_BIG_INT - njs_feature_test="#include + njs_feature_test="#include int main() { JSRuntime *rt; @@ -157,7 +157,7 @@ if [ $NJS_TRY_QUICKJS = YES ]; then njs_feature="QuickJS version" njs_feature_name=NJS_QUICKJS_VERSION njs_feature_run=value - njs_feature_test="#include + njs_feature_test="#include int main() { #if defined(QJS_VERSION_MAJOR) diff --git a/external/njs_shell.c b/external/njs_shell.c index 75316339..c2294f75 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -13,6 +13,7 @@ #if (NJS_HAVE_QUICKJS) #include +#include #endif #if (!defined NJS_FUZZER_TARGET && defined NJS_HAVE_READLINE) diff --git a/nginx/config b/nginx/config index 474be8f3..3386dac0 100644 --- a/nginx/config +++ b/nginx/config @@ -31,7 +31,7 @@ if [ $NJS_QUICKJS != NO ]; then ngx_feature="QuickJS library -lquickjs.lto" ngx_feature_name=NJS_HAVE_QUICKJS ngx_feature_run=yes - ngx_feature_incs="#include " + ngx_feature_incs="#include " ngx_feature_path="$NJS_QUICKJS_DEFAULT_INCS" ngx_feature_libs="-lquickjs.lto -lm -ldl -lpthread" ngx_feature_test="JSRuntime *rt; diff --git a/src/qjs.h b/src/qjs.h index 954cc23c..cd5c2521 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -19,28 +19,7 @@ #include #include #include - -#ifndef __has_warning -# define __has_warning(x) 0 -#endif - -#if (defined(__GNUC__) && (__GNUC__ >= 8)) \ - || (defined(__clang__) && __has_warning("-Wcast-function-type")) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" -#endif - -#include - -#ifndef JS_BOOL -#define JS_BOOL bool -#endif - -#if (defined(__GNUC__) && (__GNUC__ >= 8)) \ - || (defined(__clang__) && __has_warning("-Wcast-function-type")) -#pragma GCC diagnostic pop -#endif -#include +#include #define QJS_CORE_CLASS_ID_OFFSET 64 diff --git a/src/quickjs_compat.h b/src/quickjs_compat.h new file mode 100644 index 00000000..3b7a618c --- /dev/null +++ b/src/quickjs_compat.h @@ -0,0 +1,24 @@ + +/* + * Copyright (C) F5, Inc. + */ + +#ifndef __has_warning + #define __has_warning(x) 0 +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 8)) \ + || (defined(__clang__) && __has_warning("-Wcast-function-type")) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wcast-function-type" + + #include + + #pragma GCC diagnostic pop +#else + #include +#endif + +#ifndef JS_BOOL + #define JS_BOOL bool +#endif From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: using enum instead of manual opaque id table. Message-ID: <20250908230002.BC9A247904@pubserv1.nginx> details: https://github.com/nginx/njs/commit/d029662fcb74e69ff5b46711b34ff08a9e822aa8 branches: master commit: d029662fcb74e69ff5b46711b34ff08a9e822aa8 user: Dmitry Volyntsev date: Mon, 25 Aug 2025 22:26:24 -0700 description: QuickJS: using enum instead of manual opaque id table. --- nginx/ngx_js.h | 37 ++++++++++++++++++++----------------- src/qjs.h | 31 ++++++++++++++++--------------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 5a5a79b3..0e217e90 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -39,6 +39,7 @@ #define ngx_js_buffer_type(btype) ((btype) & ~NGX_JS_DEPRECATED) +#if (NJS_HAVE_QUICKJS) /* * This static table solves the problem of a native QuickJS approach * which uses a static variables of type JSClassID and JS_NewClassID() to @@ -47,23 +48,25 @@ * are loaded dynamically. */ -#define NGX_QJS_CLASS_ID_OFFSET (QJS_CORE_CLASS_ID_LAST) -#define NGX_QJS_CLASS_ID_CONSOLE (NGX_QJS_CLASS_ID_OFFSET + 1) -#define NGX_QJS_CLASS_ID_HTTP_REQUEST (NGX_QJS_CLASS_ID_OFFSET + 2) -#define NGX_QJS_CLASS_ID_HTTP_PERIODIC (NGX_QJS_CLASS_ID_OFFSET + 3) -#define NGX_QJS_CLASS_ID_HTTP_VARS (NGX_QJS_CLASS_ID_OFFSET + 4) -#define NGX_QJS_CLASS_ID_HTTP_HEADERS_IN (NGX_QJS_CLASS_ID_OFFSET + 5) -#define NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT (NGX_QJS_CLASS_ID_OFFSET + 6) -#define NGX_QJS_CLASS_ID_STREAM_SESSION (NGX_QJS_CLASS_ID_OFFSET + 7) -#define NGX_QJS_CLASS_ID_STREAM_PERIODIC (NGX_QJS_CLASS_ID_OFFSET + 8) -#define NGX_QJS_CLASS_ID_STREAM_FLAGS (NGX_QJS_CLASS_ID_OFFSET + 9) -#define NGX_QJS_CLASS_ID_STREAM_VARS (NGX_QJS_CLASS_ID_OFFSET + 10) -#define NGX_QJS_CLASS_ID_SHARED (NGX_QJS_CLASS_ID_OFFSET + 11) -#define NGX_QJS_CLASS_ID_SHARED_DICT (NGX_QJS_CLASS_ID_OFFSET + 12) -#define NGX_QJS_CLASS_ID_SHARED_DICT_ERROR (NGX_QJS_CLASS_ID_OFFSET + 13) -#define NGX_QJS_CLASS_ID_FETCH_HEADERS (NGX_QJS_CLASS_ID_OFFSET + 14) -#define NGX_QJS_CLASS_ID_FETCH_REQUEST (NGX_QJS_CLASS_ID_OFFSET + 15) -#define NGX_QJS_CLASS_ID_FETCH_RESPONSE (NGX_QJS_CLASS_ID_OFFSET + 16) +enum { + NGX_QJS_CLASS_ID_CONSOLE = QJS_CORE_CLASS_ID_LAST, + NGX_QJS_CLASS_ID_HTTP_REQUEST, + NGX_QJS_CLASS_ID_HTTP_PERIODIC, + NGX_QJS_CLASS_ID_HTTP_VARS, + NGX_QJS_CLASS_ID_HTTP_HEADERS_IN, + NGX_QJS_CLASS_ID_HTTP_HEADERS_OUT, + NGX_QJS_CLASS_ID_STREAM_SESSION, + NGX_QJS_CLASS_ID_STREAM_PERIODIC, + NGX_QJS_CLASS_ID_STREAM_FLAGS, + NGX_QJS_CLASS_ID_STREAM_VARS, + NGX_QJS_CLASS_ID_SHARED, + NGX_QJS_CLASS_ID_SHARED_DICT, + NGX_QJS_CLASS_ID_SHARED_DICT_ERROR, + NGX_QJS_CLASS_ID_FETCH_HEADERS, + NGX_QJS_CLASS_ID_FETCH_REQUEST, + NGX_QJS_CLASS_ID_FETCH_RESPONSE, +}; +#endif typedef struct ngx_js_loc_conf_s ngx_js_loc_conf_t; diff --git a/src/qjs.h b/src/qjs.h index cd5c2521..3b50143e 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -22,21 +22,22 @@ #include -#define QJS_CORE_CLASS_ID_OFFSET 64 -#define QJS_CORE_CLASS_ID_BUFFER (QJS_CORE_CLASS_ID_OFFSET) -#define QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR (QJS_CORE_CLASS_ID_OFFSET + 1) -#define QJS_CORE_CLASS_ID_TEXT_DECODER (QJS_CORE_CLASS_ID_OFFSET + 2) -#define QJS_CORE_CLASS_ID_TEXT_ENCODER (QJS_CORE_CLASS_ID_OFFSET + 3) -#define QJS_CORE_CLASS_ID_FS_STATS (QJS_CORE_CLASS_ID_OFFSET + 4) -#define QJS_CORE_CLASS_ID_FS_DIRENT (QJS_CORE_CLASS_ID_OFFSET + 5) -#define QJS_CORE_CLASS_ID_FS_FILEHANDLE (QJS_CORE_CLASS_ID_OFFSET + 6) -#define QJS_CORE_CLASS_ID_WEBCRYPTO_KEY (QJS_CORE_CLASS_ID_OFFSET + 7) -#define QJS_CORE_CLASS_CRYPTO_HASH (QJS_CORE_CLASS_ID_OFFSET + 8) -#define QJS_CORE_CLASS_CRYPTO_HMAC (QJS_CORE_CLASS_ID_OFFSET + 9) -#define QJS_CORE_CLASS_ID_XML_DOC (QJS_CORE_CLASS_ID_OFFSET + 10) -#define QJS_CORE_CLASS_ID_XML_NODE (QJS_CORE_CLASS_ID_OFFSET + 11) -#define QJS_CORE_CLASS_ID_XML_ATTR (QJS_CORE_CLASS_ID_OFFSET + 12) -#define QJS_CORE_CLASS_ID_LAST (QJS_CORE_CLASS_ID_OFFSET + 13) +enum { + QJS_CORE_CLASS_ID_BUFFER = 64, + QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, + QJS_CORE_CLASS_ID_TEXT_DECODER, + QJS_CORE_CLASS_ID_TEXT_ENCODER, + QJS_CORE_CLASS_ID_FS_STATS, + QJS_CORE_CLASS_ID_FS_DIRENT, + QJS_CORE_CLASS_ID_FS_FILEHANDLE, + QJS_CORE_CLASS_ID_WEBCRYPTO_KEY, + QJS_CORE_CLASS_CRYPTO_HASH, + QJS_CORE_CLASS_CRYPTO_HMAC, + QJS_CORE_CLASS_ID_XML_DOC, + QJS_CORE_CLASS_ID_XML_NODE, + QJS_CORE_CLASS_ID_XML_ATTR, + QJS_CORE_CLASS_ID_LAST, +}; typedef JSModuleDef *(*qjs_addon_init_pt)(JSContext *ctx, const char *name); From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: added njs.on('exit') API support. Message-ID: <20250908230002.DD6774913C@pubserv1.nginx> details: https://github.com/nginx/njs/commit/04f6dfb91991a855095c41df760149d9e3847bd9 branches: master commit: 04f6dfb91991a855095c41df760149d9e3847bd9 user: Dmitry Volyntsev date: Tue, 26 Aug 2025 14:22:19 -0700 description: QuickJS: added njs.on('exit') API support. This closes #955 issue on Github. --- external/njs_shell.c | 2 + nginx/ngx_http_js_module.c | 4 +- nginx/ngx_js.c | 8 ++ nginx/t/js_exit.t | 76 ++++++++++++++++++ nginx/t/stream_js_exit.t | 2 - src/qjs.c | 194 +++++++++++++++++++++++++++++++++++++++++---- src/qjs.h | 2 + 7 files changed, 269 insertions(+), 19 deletions(-) diff --git a/external/njs_shell.c b/external/njs_shell.c index c2294f75..b29ccee2 100644 --- a/external/njs_shell.c +++ b/external/njs_shell.c @@ -2703,6 +2703,8 @@ njs_engine_qjs_destroy(njs_engine_t *engine) njs_queue_link_t *link; njs_rejected_promise_t *rejected_promise; + qjs_call_exit_hook(engine->u.qjs.ctx); + console = JS_GetRuntimeOpaque(engine->u.qjs.rt); if (console->rejected_promises != NULL) { diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index c73171b8..286a0a78 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -1609,7 +1609,7 @@ static ngx_int_t ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id) { ngx_http_js_ctx_t *ctx; - ngx_pool_cleanup_t *cln; + ngx_http_cleanup_t *cln; ngx_http_js_loc_conf_t *jlcf; jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); @@ -1644,7 +1644,7 @@ ngx_http_js_init_vm(ngx_http_request_t *r, njs_int_t proto_id) "http js vm clone %s: %p from: %p", jlcf->engine->name, ctx->engine, jlcf->engine); - cln = ngx_pool_cleanup_add(r->pool, 0); + cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { return NGX_ERROR; } diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index d2bb781c..c385e16e 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -1130,6 +1130,7 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, uint32_t i, length; ngx_str_t exception; JSRuntime *rt; + JSValue ret; JSContext *cx; JSClassID class_id; JSMemoryUsage stats; @@ -1142,6 +1143,13 @@ ngx_engine_qjs_destroy(ngx_engine_t *e, ngx_js_ctx_t *ctx, cx = e->u.qjs.ctx; if (ctx != NULL) { + ret = qjs_call_exit_hook(cx); + if (JS_IsException(ret)) { + ngx_qjs_exception(e, &exception); + ngx_log_error(NGX_LOG_ERR, ctx->log, 0, + "js exit hook exception: %V", &exception); + } + node = njs_rbtree_min(&ctx->waiting_events); while (njs_rbtree_is_there_successor(&ctx->waiting_events, node)) { diff --git a/nginx/t/js_exit.t b/nginx/t/js_exit.t new file mode 100644 index 00000000..c0ea90ee --- /dev/null +++ b/nginx/t/js_exit.t @@ -0,0 +1,76 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for http njs module, njs.on('exit', ...). + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /test { + js_content test.test; + } + } +} + +EOF + +$t->write_file('test.js', <try_run('no njs')->plan(2); + +############################################################################### + +like(http_get('/test'), qr/bs: 0/, 'response'); + +$t->stop(); + +like($t->read_file('error.log'), qr/\[warn\].*exit hook: bs: \d+/, 'exit hook logged'); + +############################################################################### diff --git a/nginx/t/stream_js_exit.t b/nginx/t/stream_js_exit.t index 01778f0f..41fbe1b3 100644 --- a/nginx/t/stream_js_exit.t +++ b/nginx/t/stream_js_exit.t @@ -108,8 +108,6 @@ EOF $t->try_run('no stream njs available'); -plan(skip_all => 'not yet') if http_get('/engine') =~ /QuickJS$/m; - $t->plan(2); $t->run_daemon(\&stream_daemon, port(8090)); diff --git a/src/qjs.c b/src/qjs.c index 9c0fcdb4..b7899158 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -19,6 +19,12 @@ typedef struct { } qjs_signal_entry_t; +typedef struct { +#define QJS_NJS_HOOK_EXIT 0 + JSValue hooks[1]; +} qjs_njs_t; + + typedef enum { QJS_ENCODING_UTF8, } qjs_encoding_t; @@ -42,7 +48,13 @@ typedef struct { extern char **environ; -static JSValue qjs_njs_getter(JSContext *ctx, JSValueConst this_val); +static int qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global); +static JSValue qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv); +static void qjs_njs_mark(JSRuntime *rt, JSValueConst val, + JS_MarkFunc *mark_func); +static void qjs_njs_finalizer(JSRuntime *rt, JSValue val); + static JSValue qjs_process_env(JSContext *ctx, JSValueConst this_val); static JSValue qjs_process_kill(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv); @@ -99,10 +111,6 @@ static qjs_encoding_label_t qjs_encoding_labels[] = }; -static const JSCFunctionListEntry qjs_global_proto[] = { - JS_CGETSET_DEF("njs", qjs_njs_getter, NULL), -}; - static const JSCFunctionListEntry qjs_text_decoder_proto[] = { JS_PROP_STRING_DEF("[Symbol.toStringTag]", "TextDecoder", JS_PROP_CONFIGURABLE), @@ -126,6 +134,7 @@ static const JSCFunctionListEntry qjs_njs_proto[] = { JS_PROP_INT32_DEF("version_number", NJS_VERSION_NUMBER, JS_PROP_C_W_E), JS_PROP_STRING_DEF("engine", "QuickJS", JS_PROP_C_W_E), + JS_CFUNC_DEF("on", 2, qjs_njs_on), }; static const JSCFunctionListEntry qjs_process_proto[] = { @@ -137,6 +146,13 @@ static const JSCFunctionListEntry qjs_process_proto[] = { }; +static JSClassDef qjs_njs_class = { + "njs", + .finalizer = qjs_njs_finalizer, + .gc_mark = qjs_njs_mark, +}; + + static JSClassDef qjs_text_decoder_class = { "TextDecoder", .finalizer = qjs_text_decoder_finalizer, @@ -186,6 +202,10 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) global_obj = JS_GetGlobalObject(ctx); + if (qjs_add_intrinsic_njs(ctx, global_obj) < 0) { + return NULL; + } + if (qjs_add_intrinsic_text_decoder(ctx, global_obj) < 0) { return NULL; } @@ -194,9 +214,6 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) return NULL; } - JS_SetPropertyFunctionList(ctx, global_obj, qjs_global_proto, - njs_nitems(qjs_global_proto)); - prop = JS_NewAtom(ctx, "eval"); if (prop == JS_ATOM_NULL) { return NULL; @@ -225,20 +242,167 @@ qjs_new_context(JSRuntime *rt, qjs_module_t **addons) } -static JSValue -qjs_njs_getter(JSContext *ctx, JSValueConst this_val) +JSValue +qjs_call_exit_hook(JSContext *ctx) { - JSValue obj; + JSValue global, obj, ret; + qjs_njs_t *njs; - obj = JS_NewObject(ctx); + global = JS_GetGlobalObject(ctx); + + obj = JS_GetPropertyStr(ctx, global, "njs"); + if (JS_IsException(obj) || JS_IsUndefined(obj)) { + goto done; + } + + njs = JS_GetOpaque(obj, QJS_CORE_CLASS_ID_NJS); + if (njs != NULL && JS_IsFunction(ctx, njs->hooks[QJS_NJS_HOOK_EXIT])) { + ret = JS_Call(ctx, njs->hooks[QJS_NJS_HOOK_EXIT], JS_UNDEFINED, + 0, NULL); + + JS_FreeValue(ctx, njs->hooks[QJS_NJS_HOOK_EXIT]); + njs->hooks[QJS_NJS_HOOK_EXIT] = JS_UNDEFINED; + + if (JS_IsException(ret)) { + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, global); + return ret; + } + + JS_FreeValue(ctx, ret); + } + +done: + + JS_FreeValue(ctx, obj); + JS_FreeValue(ctx, global); + + return JS_UNDEFINED; +} + + +static int +qjs_add_intrinsic_njs(JSContext *cx, JSValueConst global) +{ + JSValue obj, proto; + + if (JS_NewClass(JS_GetRuntime(cx), QJS_CORE_CLASS_ID_NJS, + &qjs_njs_class) < 0) + { + return -1; + } + + proto = JS_NewObject(cx); + if (JS_IsException(proto)) { + return -1; + } + + JS_SetPropertyFunctionList(cx, proto, qjs_njs_proto, + njs_nitems(qjs_njs_proto)); + + JS_SetClassProto(cx, QJS_CORE_CLASS_ID_NJS, proto); + + obj = JS_NewObjectClass(cx, QJS_CORE_CLASS_ID_NJS); if (JS_IsException(obj)) { + return -1; + } + + if (JS_SetPropertyStr(cx, global, "njs", obj) < 0) { + JS_FreeValue(cx, obj); + return -1; + } + + return 0; +} + + +static void +qjs_njs_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func) +{ + unsigned i; + qjs_njs_t *njs; + + njs = JS_GetOpaque(val, QJS_CORE_CLASS_ID_NJS); + if (njs != NULL) { + for (i = 0; i < njs_nitems(njs->hooks); i++) { + JS_MarkValue(rt, njs->hooks[i], mark_func); + } + } +} + + +static void +qjs_njs_finalizer(JSRuntime *rt, JSValue val) +{ + unsigned i; + qjs_njs_t *njs; + + njs = JS_GetOpaque(val, QJS_CORE_CLASS_ID_NJS); + if (njs != NULL) { + for (i = 0; i < njs_nitems(njs->hooks); i++) { + JS_FreeValueRT(rt, njs->hooks[i]); + } + + js_free_rt(rt, njs); + } +} + + +static JSValue +qjs_njs_on(JSContext *ctx, JSValueConst this_val, int argc, + JSValueConst *argv) +{ + unsigned i, n; + qjs_njs_t *njs; + njs_str_t name; + + static const njs_str_t hooks[] = { + njs_str("exit"), + }; + + njs = JS_GetOpaque(this_val, QJS_CORE_CLASS_ID_NJS); + if (njs == NULL) { + njs = js_mallocz(ctx, sizeof(qjs_njs_t)); + if (njs == NULL) { + return JS_ThrowOutOfMemory(ctx); + } + + JS_SetOpaque(this_val, njs); + } + + name.start = (u_char *) JS_ToCStringLen(ctx, &name.length, argv[0]); + if (name.start == NULL) { return JS_EXCEPTION; } - JS_SetPropertyFunctionList(ctx, obj, qjs_njs_proto, - njs_nitems(qjs_njs_proto)); + i = 0; + n = njs_nitems(hooks); - return obj; + while (i < n) { + if (njs_strstr_eq(&name, &hooks[i])) { + break; + } + + i++; + } + + if (i == n) { + JS_ThrowTypeError(ctx, "unknown hook \"%s\"", name.start); + JS_FreeCString(ctx, (const char *) name.start); + return JS_EXCEPTION; + } + + JS_FreeCString(ctx, (const char *) name.start); + + if (!JS_IsFunction(ctx, argv[1]) && !JS_IsNull(argv[1])) { + JS_ThrowTypeError(ctx, "callback is not a function or null"); + return JS_EXCEPTION; + } + + JS_FreeValue(ctx, njs->hooks[i]); + njs->hooks[i] = JS_DupValue(ctx, argv[1]); + + return JS_UNDEFINED; } diff --git a/src/qjs.h b/src/qjs.h index 3b50143e..df5bfd11 100644 --- a/src/qjs.h +++ b/src/qjs.h @@ -27,6 +27,7 @@ enum { QJS_CORE_CLASS_ID_UINT8_ARRAY_CTOR, QJS_CORE_CLASS_ID_TEXT_DECODER, QJS_CORE_CLASS_ID_TEXT_ENCODER, + QJS_CORE_CLASS_ID_NJS, QJS_CORE_CLASS_ID_FS_STATS, QJS_CORE_CLASS_ID_FS_DIRENT, QJS_CORE_CLASS_ID_FS_FILEHANDLE, @@ -49,6 +50,7 @@ typedef struct { JSContext *qjs_new_context(JSRuntime *rt, qjs_module_t **addons); +JSValue qjs_call_exit_hook(JSContext *ctx); JSValue qjs_new_uint8_array(JSContext *ctx, int argc, JSValueConst *argv); From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: freeing temporary strings. Message-ID: <20250908230002.C346749062@pubserv1.nginx> details: https://github.com/nginx/njs/commit/a9dabca11201f5e56196c876a6b4791e858c0358 branches: master commit: a9dabca11201f5e56196c876a6b4791e858c0358 user: Dmitry Volyntsev date: Thu, 28 Aug 2025 16:29:22 -0700 description: QuickJS: freeing temporary strings. --- nginx/ngx_js_shared_dict.c | 73 ++++++++++++++++++++++++++++++++------------ nginx/ngx_qjs_fetch.c | 46 +++++++++++++++++----------- nginx/ngx_stream_js_module.c | 33 ++++++++++---------- 3 files changed, 99 insertions(+), 53 deletions(-) diff --git a/nginx/ngx_js_shared_dict.c b/nginx/ngx_js_shared_dict.c index 5445b4f2..28eed0da 100644 --- a/nginx/ngx_js_shared_dict.c +++ b/nginx/ngx_js_shared_dict.c @@ -3202,6 +3202,7 @@ static JSValue ngx_qjs_ext_shared_dict_delete(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSValue ret; ngx_str_t key; ngx_shm_zone_t *shm_zone; @@ -3210,11 +3211,16 @@ ngx_qjs_ext_shared_dict_delete(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { return JS_EXCEPTION; } - return ngx_qjs_dict_delete(cx, shm_zone->data, &key, 0); + ret = ngx_qjs_dict_delete(cx, shm_zone->data, &key, 0); + + JS_FreeCString(cx, (char *) key.data); + + return ret; } @@ -3245,6 +3251,7 @@ static JSValue ngx_qjs_ext_shared_dict_get(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSValue ret; ngx_str_t key; ngx_shm_zone_t *shm_zone; @@ -3253,11 +3260,16 @@ ngx_qjs_ext_shared_dict_get(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { return JS_EXCEPTION; } - return ngx_qjs_dict_get(cx, shm_zone->data, &key); + ret = ngx_qjs_dict_get(cx, shm_zone->data, &key); + + JS_FreeCString(cx, (char *) key.data); + + return ret; } @@ -3277,7 +3289,8 @@ ngx_qjs_ext_shared_dict_has(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { return JS_EXCEPTION; } @@ -3298,6 +3311,8 @@ ngx_qjs_ext_shared_dict_has(JSContext *cx, JSValueConst this_val, ngx_rwlock_unlock(&dict->sh->rwlock); + JS_FreeCString(cx, (char *) key.data); + return JS_NewBool(cx, node != NULL); } @@ -3306,6 +3321,7 @@ static JSValue ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSValue ret; double delta, init; uint32_t timeout; ngx_str_t key; @@ -3323,10 +3339,6 @@ ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "shared dict is not a number dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { - return JS_EXCEPTION; - } - if (JS_ToFloat64(cx, &delta, argv[1]) < 0) { return JS_EXCEPTION; } @@ -3357,7 +3369,16 @@ ngx_qjs_ext_shared_dict_incr(JSContext *cx, JSValueConst this_val, timeout = dict->timeout; } - return ngx_qjs_dict_incr(cx, dict, &key, delta, init, timeout); + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { + return JS_EXCEPTION; + } + + ret = ngx_qjs_dict_incr(cx, dict, &key, delta, init, timeout); + + JS_FreeCString(cx, (char *) key.data); + + return ret; } @@ -3576,6 +3597,7 @@ static JSValue ngx_qjs_ext_shared_dict_pop(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { + JSValue ret; ngx_str_t key; ngx_shm_zone_t *shm_zone; @@ -3584,11 +3606,16 @@ ngx_qjs_ext_shared_dict_pop(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { return JS_EXCEPTION; } - return ngx_qjs_dict_delete(cx, shm_zone->data, &key, 1); + ret = ngx_qjs_dict_delete(cx, shm_zone->data, &key, 1); + + JS_FreeCString(cx, (char *) key.data); + + return ret; } @@ -3607,10 +3634,6 @@ ngx_qjs_ext_shared_dict_set(JSContext *cx, JSValueConst this_val, return JS_ThrowTypeError(cx, "\"this\" is not a shared dict"); } - if (ngx_qjs_string(cx, argv[0], &key) != NGX_OK) { - return JS_EXCEPTION; - } - dict = shm_zone->data; if (dict->type == NGX_JS_DICT_TYPE_STRING) { @@ -3647,17 +3670,29 @@ ngx_qjs_ext_shared_dict_set(JSContext *cx, JSValueConst this_val, timeout = dict->timeout; } + key.data = (u_char *) JS_ToCStringLen(cx, &key.len, argv[0]); + if (key.data == NULL) { + return JS_EXCEPTION; + } + ret = ngx_qjs_dict_set(cx, shm_zone->data, &key, argv[1], timeout, flags); if (JS_IsException(ret)) { - return JS_EXCEPTION; + goto done; } if (flags) { /* add() or replace(). */ - return ret; + goto done; } - return JS_DupValue(cx, this_val); + JS_FreeValue(cx, ret); + ret = JS_DupValue(cx, this_val); + +done: + + JS_FreeCString(cx, (char *) key.data); + + return ret; } diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 5ed8fc30..d043bd5d 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -1768,7 +1768,6 @@ static JSValue ngx_qjs_ext_fetch_headers_delete(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { - ngx_int_t rc; ngx_str_t name; ngx_uint_t i; ngx_list_part_t *part; @@ -1781,8 +1780,8 @@ ngx_qjs_ext_fetch_headers_delete(JSContext *cx, JSValueConst this_val, "\"this\" is not fetch headers object"); } - rc = ngx_qjs_string(cx, argv[0], &name); - if (rc != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { return JS_EXCEPTION; } @@ -1819,6 +1818,8 @@ ngx_qjs_ext_fetch_headers_delete(JSContext *cx, JSValueConst this_val, headers->content_type = NULL; } + JS_FreeCString(cx, (const char *) name.data); + return JS_UNDEFINED; } @@ -1827,7 +1828,6 @@ static JSValue ngx_qjs_ext_fetch_headers_foreach(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { - int ret; JSValue callback, keys, key; JSValue header, retval, arguments[2]; uint32_t length;; @@ -1862,13 +1862,14 @@ ngx_qjs_ext_fetch_headers_foreach(JSContext *cx, JSValueConst this_val, goto fail; } - ret = ngx_qjs_string(cx, key, &name); - if (ret != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, key); + if (name.data == NULL) { JS_FreeValue(cx, key); goto fail; } header = ngx_qjs_headers_get(cx, this_val, &name, 0); + JS_FreeCString(cx, (char *) name.data); if (JS_IsException(header)) { JS_FreeValue(cx, key); goto fail; @@ -1900,15 +1901,19 @@ static JSValue ngx_qjs_ext_fetch_headers_get(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) { - ngx_int_t rc; + JSValue ret; ngx_str_t name; - rc = ngx_qjs_string(cx, argv[0], &name); - if (rc != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { return JS_EXCEPTION; } - return ngx_qjs_headers_get(cx, this_val, &name, magic); + ret = ngx_qjs_headers_get(cx, this_val, &name, magic); + + JS_FreeCString(cx, (char *) name.data); + + return ret; } @@ -1920,12 +1925,13 @@ ngx_qjs_ext_fetch_headers_has(JSContext *cx, JSValueConst this_val, ngx_int_t rc; ngx_str_t name; - rc = ngx_qjs_string(cx, argv[0], &name); - if (rc != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { return JS_EXCEPTION; } retval = ngx_qjs_headers_get(cx, this_val, &name, 0); + JS_FreeCString(cx, (char *) name.data); if (JS_IsException(retval)) { return JS_EXCEPTION; } @@ -1954,13 +1960,14 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, "\"this\" is not fetch headers object"); } - rc = ngx_qjs_string(cx, argv[0], &name); - if (rc != NGX_OK) { + name.data = (u_char *) JS_ToCStringLen(cx, &name.len, argv[0]); + if (name.data == NULL) { return JS_EXCEPTION; } rc = ngx_qjs_string(cx, argv[1], &value); if (rc != NGX_OK) { + JS_FreeCString(cx, (const char *) name.data); return JS_EXCEPTION; } @@ -1997,12 +2004,15 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, *pp = NULL; } + JS_FreeCString(cx, (const char *) name.data); + return JS_UNDEFINED; } } rc = ngx_qjs_headers_append(cx, headers, name.data, name.len, value.data, value.len); + JS_FreeCString(cx, (const char *) name.data); if (rc != NGX_OK) { return JS_EXCEPTION; } @@ -2426,7 +2436,6 @@ ngx_qjs_fetch_flag_set(JSContext *cx, const ngx_qjs_entry_t *entries, JSValue object, const char *prop) { JSValue value; - ngx_int_t rc; ngx_str_t flag; const ngx_qjs_entry_t *e; @@ -2440,9 +2449,9 @@ ngx_qjs_fetch_flag_set(JSContext *cx, const ngx_qjs_entry_t *entries, return entries[0].value; } - rc = ngx_qjs_string(cx, value, &flag); + flag.data = (u_char *) JS_ToCStringLen(cx, &flag.len, value); JS_FreeValue(cx, value); - if (rc != NGX_OK) { + if (flag.data == NULL) { return NGX_ERROR; } @@ -2450,6 +2459,7 @@ ngx_qjs_fetch_flag_set(JSContext *cx, const ngx_qjs_entry_t *entries, if (flag.len == e->name.len && ngx_strncasecmp(e->name.data, flag.data, flag.len) == 0) { + JS_FreeCString(cx, (const char *) flag.data); return e->value; } } @@ -2457,6 +2467,8 @@ ngx_qjs_fetch_flag_set(JSContext *cx, const ngx_qjs_entry_t *entries, JS_ThrowInternalError(cx, "unknown %s type: %.*s", prop, (int) flag.len, flag.data); + JS_FreeCString(cx, (const char *) flag.data); + return NGX_ERROR; } diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 13b685e5..cb44aa1f 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -2085,9 +2085,10 @@ ngx_stream_qjs_ext_log(JSContext *cx, JSValueConst this_val, int argc, static const ngx_stream_qjs_event_t * -ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event) +ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, JSValue name) { ngx_uint_t i, n, type; + ngx_str_t event; ngx_stream_js_ctx_t *ctx; static const ngx_stream_qjs_event_t events[] = { @@ -2116,14 +2117,19 @@ ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event) }, }; + event.data = (u_char *) JS_ToCStringLen(cx, &event.len, name); + if (event.data == NULL) { + return NULL; + } + ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); i = 0; n = sizeof(events) / sizeof(events[0]); while (i < n) { - if (event->len == events[i].name.len - && ngx_memcmp(event->data, events[i].name.data, event->len) + if (event.len == events[i].name.len + && ngx_memcmp(event.data, events[i].name.data, event.len) == 0) { break; @@ -2134,10 +2140,13 @@ ngx_stream_qjs_event(ngx_stream_session_t *s, JSContext *cx, ngx_str_t *event) if (i == n) { (void) JS_ThrowInternalError(cx, "unknown event \"%.*s\"", - (int) event->len, event->data); + (int) event.len, event.data); + JS_FreeCString(cx, (char *) event.data); return NULL; } + JS_FreeCString(cx, (char *) event.data); + ctx->events[events[i].id].data_type = events[i].data_type; for (n = 0; n < NGX_JS_EVENT_MAX; n++) { @@ -2157,7 +2166,6 @@ static JSValue ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { - ngx_str_t name; ngx_stream_js_ctx_t *ctx; ngx_stream_qjs_session_t *ses; const ngx_stream_qjs_event_t *e; @@ -2169,18 +2177,14 @@ ngx_stream_qjs_ext_on(JSContext *cx, JSValueConst this_val, int argc, ctx = ngx_stream_get_module_ctx(ses->session, ngx_stream_js_module); - if (ngx_qjs_string(cx, argv[0], &name) != NGX_OK) { - return JS_EXCEPTION; - } - - e = ngx_stream_qjs_event(ses->session, cx, &name); + e = ngx_stream_qjs_event(ses->session, cx, argv[0]); if (e == NULL) { return JS_EXCEPTION; } if (JS_IsFunction(cx, ngx_qjs_arg(ctx->events[e->id].function))) { return JS_ThrowInternalError(cx, "event handler \"%s\" is already set", - name.data); + e->name.data); } if (!JS_IsFunction(cx, argv[1])) { @@ -2200,7 +2204,6 @@ static JSValue ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv) { - ngx_str_t name; ngx_stream_js_ctx_t *ctx; ngx_stream_session_t *s; const ngx_stream_qjs_event_t *e; @@ -2212,11 +2215,7 @@ ngx_stream_qjs_ext_off(JSContext *cx, JSValueConst this_val, int argc, ctx = ngx_stream_get_module_ctx(s, ngx_stream_js_module); - if (ngx_qjs_string(cx, argv[0], &name) != NGX_OK) { - return JS_EXCEPTION; - } - - e = ngx_stream_qjs_event(s, cx, &name); + e = ngx_stream_qjs_event(s, cx, argv[0]); if (e == NULL) { return JS_EXCEPTION; } From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: removed extra copy of body argument in ngx.fetch(). Message-ID: <20250908230002.D27C04913B@pubserv1.nginx> details: https://github.com/nginx/njs/commit/8cfb0b960be469107ce47c4b84be6c7e0f4056e6 branches: master commit: 8cfb0b960be469107ce47c4b84be6c7e0f4056e6 user: Dmitry Volyntsev date: Thu, 28 Aug 2025 19:15:01 -0700 description: QuickJS: removed extra copy of body argument in ngx.fetch(). --- nginx/ngx_qjs_fetch.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 7130a0c2..46b54eab 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -805,10 +805,12 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc, JSValueConst *argv) { int ret; + size_t byte_offset, byte_length; u_char *p, *end; - JSValue init, value, body, proto, obj; + JSValue init, value, body, proto, obj, buf; ngx_str_t bd; ngx_int_t rc; + const char *str; ngx_pool_t *pool; ngx_js_ctx_t *ctx; ngx_js_response_t *response; @@ -913,12 +915,39 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc, body = argv[0]; if (!JS_IsNullOrUndefined(body)) { - if (ngx_qjs_string(cx, pool, body, &bd) != NGX_OK) { - return JS_ThrowInternalError(cx, "invalid Response body"); + str = NULL; + if (JS_IsString(body)) { + goto string; + } + + buf = JS_GetTypedArrayBuffer(cx, body, &byte_offset, &byte_length, NULL); + if (!JS_IsException(buf)) { + bd.data = JS_GetArrayBuffer(cx, &bd.len, buf); + + JS_FreeValue(cx, buf); + + if (bd.data != NULL) { + bd.data += byte_offset; + bd.len = byte_length; + } + + } else { + +string: + str = JS_ToCStringLen(cx, &bd.len, body); + if (str == NULL) { + return JS_EXCEPTION; + } + + bd.data = (u_char *) str; } njs_chb_append(&response->chain, bd.data, bd.len); + if (str != NULL) { + JS_FreeCString(cx, str); + } + if (JS_IsString(body)) { rc = ngx_qjs_headers_append(cx, &response->headers, (u_char *) "Content-Type", From noreply at nginx.com Mon Sep 8 23:00:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 8 Sep 2025 23:00:02 +0000 (UTC) Subject: [njs] QuickJS: fixed potential heap-use-after-free. Message-ID: <20250908230002.CD3EC49132@pubserv1.nginx> details: https://github.com/nginx/njs/commit/4eeb4c527c8031b676c720c7ebb49ad356233a31 branches: master commit: 4eeb4c527c8031b676c720c7ebb49ad356233a31 user: Dmitry Volyntsev date: Thu, 28 Aug 2025 15:20:31 -0700 description: QuickJS: fixed potential heap-use-after-free. Previously in QuickJS engine, fields allocated from memory pool linked to QuickJS engine lifetime were stored in nginx data structs. This causes a heap-use-after-free if QuickJS engine is destroyed earlier than a last access from nginx. For example, it becomes visible when moving NJS cleanup handler from pool->cleanup to r->cleanup. The fix is to only store references in nginx objects allocated from nginx memory pool. --- nginx/ngx_http_js_module.c | 46 ++++++++++++++------------------------------ nginx/ngx_js.c | 7 ++++--- nginx/ngx_js.h | 3 ++- nginx/ngx_qjs_fetch.c | 26 +++++++++++++++---------- nginx/ngx_stream_js_module.c | 2 +- 5 files changed, 37 insertions(+), 47 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 3f38f86f..c73171b8 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -5173,7 +5173,7 @@ ngx_http_qjs_ext_internal_redirect(JSContext *cx, JSValueConst this_val, "internalRedirect cannot be called while filtering"); } - if (ngx_qjs_string(cx, argv[0], &ctx->redirect_uri) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, argv[0], &ctx->redirect_uri) != NGX_OK) { return JS_EXCEPTION; } @@ -5452,7 +5452,7 @@ ngx_http_qjs_ext_return(JSContext *cx, JSValueConst this_val, ctx = ngx_http_get_module_ctx(r, ngx_http_js_module); if (status < NGX_HTTP_BAD_REQUEST || !JS_IsNullOrUndefined(argv[1])) { - if (ngx_qjs_string(cx, argv[1], &body) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, argv[1], &body) != NGX_OK) { return JS_ThrowOutOfMemory(cx); } @@ -5557,7 +5557,7 @@ ngx_http_qjs_ext_send(JSContext *cx, JSValueConst this_val, ll = &out; for (n = 0; n < (ngx_uint_t) argc; n++) { - if (ngx_qjs_string(cx, argv[n], &s) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, argv[n], &s) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert arg"); } @@ -5905,7 +5905,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, "the primary request"); } - if (ngx_qjs_string(cx, argv[0], &uri) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, argv[0], &uri) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert uri arg"); } @@ -5931,7 +5931,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, arg = argv[1]; if (JS_IsString(arg)) { - if (ngx_qjs_string(cx, arg, &args) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, arg, &args) != NGX_OK) { return JS_ThrowTypeError(cx, "failed to convert args"); } @@ -5952,7 +5952,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(cx, value, &args); + rc = ngx_qjs_string(cx, r->pool, value, &args); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -5976,7 +5976,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(cx, value, &method_name); + rc = ngx_qjs_string(cx, r->pool, value, &method_name); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -6003,7 +6003,7 @@ ngx_http_qjs_ext_subrequest(JSContext *cx, JSValueConst this_val, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(cx, value, &body_arg); + rc = ngx_qjs_string(cx, r->pool, value, &body_arg); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -6397,7 +6397,7 @@ ngx_http_qjs_variables_set_property(JSContext *cx, JSValueConst obj, return -1; } - if (ngx_qjs_string(cx, value, &s) != NGX_OK) { + if (ngx_qjs_string(cx, r->pool, value, &s) != NGX_OK) { return -1; } @@ -6876,7 +6876,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, } } - rc = ngx_qjs_string(cx, v, &s); + rc = ngx_qjs_string(cx, r->pool, v, &s); if (qjs_is_array(cx, *value)) { JS_FreeValue(cx, v); @@ -6908,16 +6908,7 @@ ngx_http_qjs_headers_out_handler(JSContext *cx, ngx_http_request_t *r, h->key.data = p; h->key.len = name->len; - p = ngx_pnalloc(r->pool, s.len); - if (p == NULL) { - h->hash = 0; - (void) JS_ThrowOutOfMemory(cx); - return -1; - } - - ngx_memcpy(p, s.data, s.len); - - h->value.data = p; + h->value.data = s.data; h->value.len = s.len; h->hash = 1; @@ -6977,7 +6968,7 @@ ngx_http_qjs_headers_out_special_handler(JSContext *cx, ngx_http_request_t *r, setval = JS_UNDEFINED; } - rc = ngx_qjs_string(cx, setval, &s); + rc = ngx_qjs_string(cx, r->pool, setval, &s); if (value != NULL && qjs_is_array(cx, *value)) { JS_FreeValue(cx, setval); @@ -7046,16 +7037,7 @@ done: } if (h != NULL) { - p = ngx_pnalloc(r->pool, s.len); - if (p == NULL) { - h->hash = 0; - (void) JS_ThrowOutOfMemory(cx); - return -1; - } - - ngx_memcpy(p, s.data, s.len); - - h->value.data = p; + h->value.data = s.data; h->value.len = s.len; h->hash = 1; } @@ -7212,7 +7194,7 @@ ngx_http_qjs_headers_out_content_type(JSContext *cx, ngx_http_request_t *r, setval = *value; } - rc = ngx_qjs_string(cx, setval, &s); + rc = ngx_qjs_string(cx, r->pool, setval, &s); if (qjs_is_array(cx, *value)) { JS_FreeValue(cx, setval); diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 0678c3e6..d2bb781c 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -1462,7 +1462,8 @@ ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n) ngx_int_t -ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *dst) +ngx_qjs_string(JSContext *cx, ngx_pool_t *pool, JSValueConst val, + ngx_str_t *dst) { size_t len, byte_offset, byte_length; u_char *start; @@ -1496,7 +1497,7 @@ ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *dst) start += byte_offset; dst->len = byte_length; - dst->data = njs_mp_alloc(e->pool, dst->len); + dst->data = ngx_pnalloc(pool, dst->len); if (dst->data == NULL) { return NGX_ERROR; } @@ -1513,7 +1514,7 @@ string: return NGX_ERROR; } - start = njs_mp_alloc(e->pool, len); + start = ngx_pnalloc(pool, len); if (start == NULL) { JS_FreeCString(cx, str); return NGX_ERROR; diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 0e217e90..032bd415 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -354,7 +354,8 @@ ngx_int_t ngx_qjs_call(JSContext *cx, JSValue function, JSValue *argv, int argc); ngx_int_t ngx_qjs_exception(ngx_engine_t *e, ngx_str_t *s); ngx_int_t ngx_qjs_integer(JSContext *cx, JSValueConst val, ngx_int_t *n); -ngx_int_t ngx_qjs_string(JSContext *cx, JSValueConst val, ngx_str_t *str); +ngx_int_t ngx_qjs_string(JSContext *cx, ngx_pool_t *pool, JSValueConst val, + ngx_str_t *dst); JSValue ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, JSValueConst *argv); diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index d043bd5d..7130a0c2 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -622,7 +622,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request, } if (JS_IsString(input)) { - rc = ngx_qjs_string(cx, input, &request->url); + rc = ngx_qjs_string(cx, pool, input, &request->url); if (rc != NGX_OK) { JS_ThrowInternalError(cx, "failed to convert url arg"); return NGX_ERROR; @@ -694,7 +694,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request, } if (!JS_IsUndefined(value)) { - rc = ngx_qjs_string(cx, value, &request->method); + rc = ngx_qjs_string(cx, pool, value, &request->method); JS_FreeValue(cx, value); if (rc != NGX_OK) { @@ -774,7 +774,7 @@ ngx_qjs_request_ctor(JSContext *cx, ngx_js_request_t *request, } if (!JS_IsUndefined(value)) { - if (ngx_qjs_string(cx, value, &request->body) != NGX_OK) { + if (ngx_qjs_string(cx, pool, value, &request->body) != NGX_OK) { JS_FreeValue(cx, value); JS_ThrowInternalError(cx, "invalid Request body"); return NGX_ERROR; @@ -866,7 +866,7 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc, } if (!JS_IsUndefined(value)) { - ret = ngx_qjs_string(cx, value, &response->status_text); + ret = ngx_qjs_string(cx, pool, value, &response->status_text); JS_FreeValue(cx, value); if (ret < 0) { @@ -913,7 +913,7 @@ ngx_qjs_fetch_response_ctor(JSContext *cx, JSValueConst new_target, int argc, body = argv[0]; if (!JS_IsNullOrUndefined(body)) { - if (ngx_qjs_string(cx, body, &bd) != NGX_OK) { + if (ngx_qjs_string(cx, pool, body, &bd) != NGX_OK) { return JS_ThrowInternalError(cx, "invalid Response body"); } @@ -1054,16 +1054,19 @@ static ngx_int_t ngx_qjs_headers_fill_header_free(JSContext *cx, ngx_js_headers_t *headers, JSValue prop_name, JSValue prop_value) { - ngx_int_t rc; - ngx_str_t name, value; + ngx_int_t rc; + ngx_str_t name, value; + ngx_pool_t *pool; + + pool = ngx_qjs_external_pool(cx, JS_GetContextOpaque(cx)); - if (ngx_qjs_string(cx, prop_name, &name) != NGX_OK) { + if (ngx_qjs_string(cx, pool, prop_name, &name) != NGX_OK) { JS_FreeValue(cx, prop_name); JS_FreeValue(cx, prop_value); return NGX_ERROR; } - if (ngx_qjs_string(cx, prop_value, &value) != NGX_OK) { + if (ngx_qjs_string(cx, pool, prop_value, &value) != NGX_OK) { JS_FreeValue(cx, prop_name); JS_FreeValue(cx, prop_value); return NGX_ERROR; @@ -1950,6 +1953,7 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, ngx_int_t rc; ngx_str_t name, value; ngx_uint_t i; + ngx_pool_t *pool; ngx_list_part_t *part; ngx_js_tb_elt_t *h, **ph, **pp; ngx_js_headers_t *headers; @@ -1965,7 +1969,9 @@ ngx_qjs_ext_fetch_headers_set(JSContext *cx, JSValueConst this_val, return JS_EXCEPTION; } - rc = ngx_qjs_string(cx, argv[1], &value); + pool = ngx_qjs_external_pool(cx, JS_GetContextOpaque(cx)); + + rc = ngx_qjs_string(cx, pool, argv[1], &value); if (rc != NGX_OK) { JS_FreeCString(cx, (const char *) name.data); return JS_EXCEPTION; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index cb44aa1f..5ccc08a2 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -2651,7 +2651,7 @@ ngx_stream_qjs_variables_set_property(JSContext *cx, JSValueConst obj, return -1; } - if (ngx_qjs_string(cx, value, &val) != NGX_OK) { + if (ngx_qjs_string(cx, s->connection->pool, value, &val) != NGX_OK) { return -1; } From pluknet at nginx.com Wed Sep 10 14:15:06 2025 From: pluknet at nginx.com (Sergey Kandaurov) Date: Wed, 10 Sep 2025 18:15:06 +0400 Subject: stale SSL errors in quic In-Reply-To: References: Message-ID: > On 22 Aug 2025, at 11:49, Vladimir Homutov wrote: > > On Thu, Aug 21, 2025 at 06:25:25PM +0400, Sergey Kandaurov wrote: >> On Fri, Aug 15, 2025 at 10:56:40AM +0300, Vladimir Homutov wrote: >>> Hello, >>> >>> Commits 7468a10b62276be4adee0fcd6aaf6244270984ab >>> "QUIC: adjusted handling of callback errors." >>> >>> and 47f96993f669543c6cb4979dd3f680ad01314ee5 >>> "QUIC: logging of SSL library errors." >>> >>> lead to the situation when you may get spurious >>> "ignoring stale global SSL error" errors in unrelated connections. >>> >>> This happens due to the fact that openssl error queue is (thread) global, >>> and quic handshake handler does not read out the error after the failed >>> handshake and relies on qc->error set by callback. >>> >>> So the error stays in queue and may show itself in unrelated quic connection, >>> typically on SSL shutdown. >>> >>> config below may be used to reproduce the issue: >>> >>>>>> >>> daemon off; >>> error_log logs/error.log debug; >>> events { >>> } >>> >>> http { >>> ssl_certificate_key localhost.key; >>> ssl_certificate localhost.crt; >>> server { >>> error_log logs/good.log debug; >>> listen 127.0.0.1:8080 quic; >>> location / { return 200 OK; } >>> } >>> server { >>> error_log logs/reject.log debug; >>> listen 127.0.0.1:8081 quic; >>> ssl_reject_handshake on; >>> location / { return 200 OK; } >>> } >>> } >>> <<< >>> >>> start the server and run: >>> >>> $ curl -k --http3 https://127.0.0.1:8080/ >>> $ curl -k --http3 https://127.0.0.1:8081/ >>> >>> The result is alert in good.log: >>> >>> 2025/08/15 09:58:19 [alert] 1154786#1154786: *1 ignoring stale global SSL error (SSL: error:10000084:SSL routines:OPENSSL_internal:CLIENTHELLO_TLSEXT error:100000be:SSL routines:OPENSSL_internal:PARSE_TLSEXT) while preparing ack, client: 127.0.0.1, server: 127.0.0.1:8080 >>> >>> caused by failed handshake in server 2; >> >> Thanks for reporting. >> I was able to reproduce this with Test::Nginx reliably: >> >> my $s = Test::Nginx::HTTP3->new(8980); >> my $s2 = Test::Nginx::HTTP3->new(8981, probe => 1); >> >> select undef, undef, undef, 0.1; >> $s = undef; >> >> select undef, undef, undef, 0.2; >> >> $t->stop(); >> >> So the error queue remains non-empty until the 1st connection SSL shutdown. >> The following happens as seen in debug (filtered): >> >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 quic run >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_do_handshake: -1 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_do_handshake: 1 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *6 quic run >> 2025/08/21 15:52:02 [debug] 73543#1490328: *6 quic ngx_quic_send_alert() level:0 alert:112 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *6 SSL_do_handshake: -1 >> 2025/08/21 15:52:02 [alert] 73543#1490328: *1 ignoring stale global SSL error (SSL: error:10000084:SSL routines:OPENSSL_internal:CLIENTHELLO_TLSEXT error:100000be:SSL routines:OPENSSL_internal:PARSE_TLSEXT) while preparing ack, client: 127.0.0.1, server: 127.0.0.1:8980 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 quic ngx_quic_send_alert() level:3 alert:0 >> 2025/08/21 15:52:02 [debug] 73543#1490328: *1 SSL_shutdown: 1 >> >> The above is using BoringSSL to match the report. >> This is equivalently broken with all other libraries >> with a slightly different error message: >> >> 2025/08/21 16:03:40 [alert] 78666#1507465: *1 ignoring stale global SSL error (SSL: error:0A0000EA:SSL routines::callback failed) while preparing ack, client: 127.0.0.1, server: 127.0.0.1:8980 >> >> I don't think 47f96993f669543c6cb4979dd3f680ad01314ee5 is related. >> Although it formally changes reproduction surface by ngx_ssl_error(), >> this touches errors, which should not normally happen in practice. >> 7468a10b62276be4adee0fcd6aaf6244270984ab is a real culprit. >> >>> >>> when the ngx_http_ssl_servername() callback returns error, >>> the ngx_quic_send_alert() sets qc->error, and the ngx_quic_ssl_handshake() >>> checks qc->error after SSL_do_handshake() and closes connection. >>> But the error stays, and manifests itself later in another connection >>> in the same process. >>> >>> The quic fix is probably something like this: >>> >>> diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c >>> index e961c80cd..dc0a030ff 100644 >>> --- a/src/event/quic/ngx_event_quic_ssl.c >>> +++ b/src/event/quic/ngx_event_quic_ssl.c >>> @@ -696,6 +696,7 @@ ngx_quic_handshake(ngx_connection_t *c) >>> ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); >>> >>> if (qc->error) { >>> + ERR_clear_error(); >>> return NGX_ERROR; >>> } >>> >>> Or may be it makes sense to clear error in the moment of setting qc->error; >>> and do not clear everything, but try to log using ngx_ssl_error() with some >>> non-alert level. >> >> What happens here is the added early check for qc->error broke the >> check for handshake_rejected, resulting in a missing diagnostics >> for the SNI-rejected handshake, and in a missing ERR_clear_error(). >> >> This can be fixed by moving the qc->error check to later such that >> handshake_rejected is checked first to make the error queue cleared. >> Although not practicably visible as needed, this can be also accompanied >> by clearing the error queue under the qc->error case as well, to be safe. >> >> Fixing this is complicated by different codes returned from SSL_get_error() >> for handshake errors happened in QUIC callbacks in various SSL libraries: >> >> # define SSL_ERROR_SSL 1 >> # define SSL_ERROR_WANT_READ 2 >> # define SSL_ERROR_WANT_WRITE 3 >> >> compat 3.5 bssl qtls lssl >> tp 2 3* 2 2 2 >> sni 1 1 1 1 1 >> >> In this regard, checking c->ssl->handshake_rejected is moved out of >> "sslerr != SSL_ERROR_WANT_READ". It is a set only flag, so this should >> be safe to do. This also somewhat simplifies and streamlines code. >> >> Patch also reconstructs a missing "SSL_do_handshake() failed" diagnostics >> for the qc->error case. It is made this way to avoid logging at the crit >> log level because qc->error is expected to have an empty error queue. >> >> Please test if it works for you. >> >> diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c >> index e961c80cd..1de896291 100644 >> --- a/src/event/quic/ngx_event_quic_ssl.c >> +++ b/src/event/quic/ngx_event_quic_ssl.c >> @@ -695,25 +695,25 @@ ngx_quic_handshake(ngx_connection_t *c) >> >> ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); >> >> - if (qc->error) { >> - return NGX_ERROR; >> - } >> - >> if (n <= 0) { >> sslerr = SSL_get_error(ssl_conn, n); >> >> ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", >> sslerr); >> >> - if (sslerr != SSL_ERROR_WANT_READ) { >> - >> - if (c->ssl->handshake_rejected) { >> - ngx_connection_error(c, 0, "handshake rejected"); >> - ERR_clear_error(); >> + if (c->ssl->handshake_rejected) { >> + ngx_connection_error(c, 0, "handshake rejected"); >> + ERR_clear_error(); >> + return NGX_ERROR; >> + } >> >> - return NGX_ERROR; >> - } >> + if (qc->error) { >> + ngx_connection_error(c, 0, "SSL_do_handshake() failed"); >> + ERR_clear_error(); >> + return NGX_ERROR; >> + } >> >> + if (sslerr != SSL_ERROR_WANT_READ) { >> ngx_ssl_connection_error(c, sslerr, 0, "SSL_do_handshake() failed"); >> return NGX_ERROR; >> } > > yes, this works and totally makes sense. > > thank you! Thanks for testing! Just for the record, submitted as https://github.com/nginx/nginx/pull/889 -- Sergey Kandaurov From noreply at nginx.com Fri Sep 12 13:58:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 12 Sep 2025 13:58:02 +0000 (UTC) Subject: [nginx] QUIC: fixed ssl_reject_handshake error handling. Message-ID: <20250912135802.86C9A47871@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/eb5ebbbed74c8ce72465bb079bde0ad29966d170 branches: master commit: eb5ebbbed74c8ce72465bb079bde0ad29966d170 user: Sergey Kandaurov date: Wed, 10 Sep 2025 17:25:36 +0400 description: QUIC: fixed ssl_reject_handshake error handling. This was broken in 7468a10b6 (1.29.0), resulting in a missing diagnostics and SSL error queue not cleared for SSL handshakes rejected by SNI, seen as "ignoring stale global SSL error" alerts, for instance, when doing SSL shutdown of a long standing connection after rejecting another one by SNI. The fix is to move the qc->error check after c->ssl->handshake_rejected is handled first, to make the error queue cleared. Although not practicably visible as needed, this is accompanied by clearing the error queue under the qc->error case as well, to be on the safe side. As an implementation note, due to the way of handling invalid transport parameters for OpenSSL 3.5 and above, which leaves a passed pointer not advanced on error, SSL_get_error() may return either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE depending on a library. To cope with that, both qc->error and c->ssl->handshake_rejected checks were moved out of "sslerr != SSL_ERROR_WANT_READ". Also, this reconstructs a missing "SSL_do_handshake() failed" diagnostics for the qc->error case, replacing using ngx_ssl_connection_error() with ngx_connection_error(). It is made this way to avoid logging at the crit log level because qc->error set is expected to have an empty error queue. Reported and tested by Vladimir Homutov. --- src/event/quic/ngx_event_quic_ssl.c | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index e961c80cd..355348406 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -695,30 +695,35 @@ ngx_quic_handshake(ngx_connection_t *c) ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); - if (qc->error) { - return NGX_ERROR; - } - if (n <= 0) { sslerr = SSL_get_error(ssl_conn, n); ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr); - if (sslerr != SSL_ERROR_WANT_READ) { - - if (c->ssl->handshake_rejected) { - ngx_connection_error(c, 0, "handshake rejected"); - ERR_clear_error(); + if (c->ssl->handshake_rejected) { + ngx_connection_error(c, 0, "handshake rejected"); + ERR_clear_error(); + return NGX_ERROR; + } - return NGX_ERROR; - } + if (qc->error) { + ngx_connection_error(c, 0, "SSL_do_handshake() failed"); + ERR_clear_error(); + return NGX_ERROR; + } + if (sslerr != SSL_ERROR_WANT_READ) { ngx_ssl_connection_error(c, sslerr, 0, "SSL_do_handshake() failed"); return NGX_ERROR; } } + if (qc->error) { + ngx_connection_error(c, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } + if (!SSL_is_init_finished(ssl_conn)) { if (ngx_quic_keys_available(qc->keys, NGX_QUIC_ENCRYPTION_EARLY_DATA, 0) && qc->client_tp_done) From noreply at nginx.com Mon Sep 15 18:14:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 15 Sep 2025 18:14:02 +0000 (UTC) Subject: [nginx] Updated link to xslscript. Message-ID: <20250915181402.5C24D48F8B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/417c87b78dd9e69dbea3ec41313c9950b4721f8e branches: master commit: 417c87b78dd9e69dbea3ec41313c9950b4721f8e user: Sergey Kandaurov date: Mon, 18 Aug 2025 22:10:25 +0400 description: Updated link to xslscript. --- misc/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/README b/misc/README index 3f7b3236d..a5a88f2ba 100644 --- a/misc/README +++ b/misc/README @@ -3,7 +3,7 @@ make -f misc/GNUmakefile release the required tools: *) xsltproc to build CHANGES, -*) xslscript.pl ( http://hg.nginx.org/xslscript ) to build XSLTs +*) xslscript.pl ( https://github.com/nginx/xslscript ) to build XSLTs from XSLScript sources. From noreply at nginx.com Tue Sep 16 23:08:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 16 Sep 2025 23:08:02 +0000 (UTC) Subject: [njs] Modules: simplified access to nginx values from JS engines. Message-ID: <20250916230802.93A9748854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/51326759b5b5bebc960d7b89ab92a24c55639ffc branches: master commit: 51326759b5b5bebc960d7b89ab92a24c55639ffc user: Dmitry Volyntsev date: Wed, 3 Sep 2025 22:44:04 -0700 description: Modules: simplified access to nginx values from JS engines. --- nginx/ngx_http_js_module.c | 87 ++++++-------------------------------------- nginx/ngx_js.h | 76 ++++++++++++++++++++------------------ nginx/ngx_js_fetch.c | 21 +++++------ nginx/ngx_js_http.c | 6 +-- nginx/ngx_js_http.h | 2 +- nginx/ngx_qjs_fetch.c | 11 +++--- nginx/ngx_stream_js_module.c | 86 ++++++------------------------------------- 7 files changed, 83 insertions(+), 206 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 286a0a78..736635cd 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -358,10 +358,8 @@ static void ngx_http_qjs_periodic_finalizer(JSRuntime *rt, JSValue val); static ngx_pool_t *ngx_http_js_pool(ngx_http_request_t *r); static ngx_resolver_t *ngx_http_js_resolver(ngx_http_request_t *r); static ngx_msec_t ngx_http_js_resolver_timeout(ngx_http_request_t *r); -static ngx_msec_t ngx_http_js_fetch_timeout(ngx_http_request_t *r); -static size_t ngx_http_js_buffer_size(ngx_http_request_t *r); -static size_t ngx_http_js_max_response_buffer_size(ngx_http_request_t *r); static void ngx_http_js_event_finalize(ngx_http_request_t *r, ngx_int_t rc); +static ngx_js_loc_conf_t *ngx_http_js_loc_conf(ngx_http_request_t *r); static ngx_js_ctx_t *ngx_http_js_ctx(ngx_http_request_t *r); static void ngx_http_js_periodic_handler(ngx_event_t *ev); @@ -391,9 +389,6 @@ static void *ngx_http_js_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); -static ngx_ssl_t *ngx_http_js_ssl(ngx_http_request_t *r); -static ngx_flag_t ngx_http_js_ssl_verify(ngx_http_request_t *r); - static ngx_int_t ngx_http_js_parse_unsafe_uri(ngx_http_request_t *r, njs_str_t *uri, njs_str_t *args); @@ -972,13 +967,9 @@ static uintptr_t ngx_http_js_uptr[] = { (uintptr_t) ngx_http_js_resolver, (uintptr_t) ngx_http_js_resolver_timeout, (uintptr_t) ngx_http_js_event_finalize, - (uintptr_t) ngx_http_js_ssl, - (uintptr_t) ngx_http_js_ssl_verify, - (uintptr_t) ngx_http_js_fetch_timeout, - (uintptr_t) ngx_http_js_buffer_size, - (uintptr_t) ngx_http_js_max_response_buffer_size, - (uintptr_t) 0 /* main_conf ptr */, + (uintptr_t) ngx_http_js_loc_conf, (uintptr_t) ngx_http_js_ctx, + (uintptr_t) 0 /* main_conf ptr */, }; @@ -4666,39 +4657,6 @@ ngx_http_js_resolver_timeout(ngx_http_request_t *r) } -static ngx_msec_t -ngx_http_js_fetch_timeout(ngx_http_request_t *r) -{ - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->timeout; -} - - -static size_t -ngx_http_js_buffer_size(ngx_http_request_t *r) -{ - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->buffer_size; -} - - -static size_t -ngx_http_js_max_response_buffer_size(ngx_http_request_t *r) -{ - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->max_response_body_size; -} - - static void ngx_http_js_event_finalize(ngx_http_request_t *r, ngx_int_t rc) { @@ -4721,6 +4679,13 @@ ngx_http_js_event_finalize(ngx_http_request_t *r, ngx_int_t rc) } +static ngx_js_loc_conf_t * +ngx_http_js_loc_conf(ngx_http_request_t *r) +{ + return ngx_http_get_module_loc_conf(r, ngx_http_js_module); +} + + static ngx_js_ctx_t * ngx_http_js_ctx(ngx_http_request_t *r) { @@ -7724,7 +7689,7 @@ ngx_http_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) options.engine = conf->type; jmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_js_module); - ngx_http_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + ngx_http_js_uptr[NGX_JS_EXTERNAL_MAIN_CONF] = (uintptr_t) jmcf; if (conf->type == NGX_ENGINE_NJS) { options.u.njs.metas = &ngx_http_js_metas; @@ -8236,36 +8201,6 @@ ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) } -static ngx_ssl_t * -ngx_http_js_ssl(ngx_http_request_t *r) -{ -#if (NGX_HTTP_SSL) - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->ssl; -#else - return NULL; -#endif -} - - -static ngx_flag_t -ngx_http_js_ssl_verify(ngx_http_request_t *r) -{ -#if (NGX_HTTP_SSL) - ngx_http_js_loc_conf_t *jlcf; - - jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module); - - return jlcf->ssl_verify; -#else - return 0; -#endif -} - - static ngx_int_t ngx_http_js_parse_unsafe_uri(ngx_http_request_t *r, njs_str_t *uri, njs_str_t *args) diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 032bd415..257227e5 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -80,9 +80,7 @@ typedef ngx_pool_t *(*ngx_external_pool_pt)(njs_external_ptr_t e); typedef void (*ngx_js_event_finalize_pt)(njs_external_ptr_t e, ngx_int_t rc); typedef ngx_resolver_t *(*ngx_external_resolver_pt)(njs_external_ptr_t e); typedef ngx_msec_t (*ngx_external_timeout_pt)(njs_external_ptr_t e); -typedef ngx_flag_t (*ngx_external_flag_pt)(njs_external_ptr_t e); -typedef ngx_flag_t (*ngx_external_size_pt)(njs_external_ptr_t e); -typedef ngx_ssl_t *(*ngx_external_ssl_pt)(njs_external_ptr_t e); +typedef ngx_js_loc_conf_t *(*ngx_js_external_loc_conf_pt)(njs_external_ptr_t e); typedef ngx_js_ctx_t *(*ngx_js_external_ctx_pt)(njs_external_ptr_t e); @@ -272,31 +270,35 @@ struct ngx_engine_s { }; +enum { + NGX_JS_EXTERNAL_CONNECTION = 0, + NGX_JS_EXTERNAL_POOL, + NGX_JS_EXTERNAL_RESOLVER, + NGX_JS_EXTERNAL_RESOLVER_TIMEOUT, + NGX_JS_EXTERNAL_EVENT_FINALIZE, + NGX_JS_EXTERNAL_LOC_CONF, + NGX_JS_EXTERNAL_CTX, + NGX_JS_EXTERNAL_MAIN_CONF, +}; + #define ngx_external_connection(vm, e) \ - (*((ngx_connection_t **) ((u_char *) (e) + njs_vm_meta(vm, 0)))) + (*((ngx_connection_t **) \ + ((u_char *) (e) + njs_vm_meta(vm, NGX_JS_EXTERNAL_CONNECTION)))) #define ngx_external_pool(vm, e) \ - ((ngx_external_pool_pt) njs_vm_meta(vm, 1))(e) + ((ngx_external_pool_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_POOL))(e) #define ngx_external_resolver(vm, e) \ - ((ngx_external_resolver_pt) njs_vm_meta(vm, 2))(e) + ((ngx_external_resolver_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_RESOLVER))(e) #define ngx_external_resolver_timeout(vm, e) \ - ((ngx_external_timeout_pt) njs_vm_meta(vm, 3))(e) + ((ngx_external_timeout_pt) \ + njs_vm_meta(vm, NGX_JS_EXTERNAL_RESOLVER_TIMEOUT))(e) #define ngx_external_event_finalize(vm) \ - ((ngx_js_event_finalize_pt) njs_vm_meta(vm, 4)) -#define ngx_external_ssl(vm, e) \ - ((ngx_external_ssl_pt) njs_vm_meta(vm, 5))(e) -#define ngx_external_ssl_verify(vm, e) \ - ((ngx_external_flag_pt) njs_vm_meta(vm, 6))(e) -#define ngx_external_fetch_timeout(vm, e) \ - ((ngx_external_timeout_pt) njs_vm_meta(vm, 7))(e) -#define ngx_external_buffer_size(vm, e) \ - ((ngx_external_size_pt) njs_vm_meta(vm, 8))(e) -#define ngx_external_max_response_buffer_size(vm, e) \ - ((ngx_external_size_pt) njs_vm_meta(vm, 9))(e) -#define NGX_JS_MAIN_CONF_INDEX 10 -#define ngx_main_conf(vm) \ - ((ngx_js_main_conf_t *) njs_vm_meta(vm, NGX_JS_MAIN_CONF_INDEX)) + ((ngx_js_event_finalize_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_EVENT_FINALIZE)) +#define ngx_external_loc_conf(vm, e) \ + ((ngx_js_external_loc_conf_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_LOC_CONF))(e) #define ngx_external_ctx(vm, e) \ - ((ngx_js_external_ctx_pt) njs_vm_meta(vm, 11))(e) + ((ngx_js_external_ctx_pt) njs_vm_meta(vm, NGX_JS_EXTERNAL_CTX))(e) +#define ngx_main_conf(vm) \ + ((ngx_js_main_conf_t *) njs_vm_meta(vm, NGX_JS_EXTERNAL_MAIN_CONF)) #define ngx_js_prop(vm, type, value, start, len) \ @@ -367,29 +369,33 @@ JSValue ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, #define ngx_qjs_meta(cx, i) \ ((uintptr_t *) JS_GetRuntimeOpaque(JS_GetRuntime(cx)))[i] #define ngx_qjs_external_connection(cx, e) \ - (*((ngx_connection_t **) ((u_char *) (e) + ngx_qjs_meta(cx, 0)))) + (*((ngx_connection_t **) \ + ((u_char *) (e) + ngx_qjs_meta(cx, NGX_JS_EXTERNAL_CONNECTION)))) #define ngx_qjs_external_pool(cx, e) \ - ((ngx_external_pool_pt) ngx_qjs_meta(cx, 1))(e) + ((ngx_external_pool_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_POOL))(e) #define ngx_qjs_external_resolver(cx, e) \ - ((ngx_external_resolver_pt) ngx_qjs_meta(cx, 2))(e) + ((ngx_external_resolver_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_RESOLVER))(e) #define ngx_qjs_external_resolver_timeout(cx, e) \ - ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 3))(e) + ((ngx_external_timeout_pt) \ + ngx_qjs_meta(cx, NGX_JS_EXTERNAL_RESOLVER_TIMEOUT))(e) #define ngx_qjs_external_event_finalize(cx) \ - ((ngx_js_event_finalize_pt) ngx_qjs_meta(cx, 4)) + ((ngx_js_event_finalize_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_EVENT_FINALIZE)) +#define ngx_qjs_external_loc_conf(cx, e) \ + ((ngx_js_external_loc_conf_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_LOC_CONF))(e) #define ngx_qjs_external_ssl(cx, e) \ - ((ngx_external_ssl_pt) ngx_qjs_meta(cx, 5))(e) + (ngx_qjs_external_loc_conf(cx, e)->ssl) #define ngx_qjs_external_ssl_verify(cx, e) \ - ((ngx_external_flag_pt) ngx_qjs_meta(cx, 6))(e) + (ngx_qjs_external_loc_conf(cx, e)->ssl_verify) #define ngx_qjs_external_fetch_timeout(cx, e) \ - ((ngx_external_timeout_pt) ngx_qjs_meta(cx, 7))(e) + (ngx_qjs_external_loc_conf(cx, e)->timeout) #define ngx_qjs_external_buffer_size(cx, e) \ - ((ngx_external_size_pt) ngx_qjs_meta(cx, 8))(e) + (ngx_qjs_external_loc_conf(cx, e)->buffer_size) #define ngx_qjs_external_max_response_buffer_size(cx, e) \ - ((ngx_external_size_pt) ngx_qjs_meta(cx, 9))(e) -#define ngx_qjs_main_conf(cx) \ - ((ngx_js_main_conf_t *) ngx_qjs_meta(cx, NGX_JS_MAIN_CONF_INDEX)) + (ngx_qjs_external_loc_conf(cx, e)->max_response_body_size) #define ngx_qjs_external_ctx(cx, e) \ - ((ngx_js_external_ctx_pt) ngx_qjs_meta(cx, 11))(e) + ((ngx_js_external_ctx_pt) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_CTX))(e) +#define ngx_qjs_main_conf(cx) \ + ((ngx_js_main_conf_t *) ngx_qjs_meta(cx, NGX_JS_EXTERNAL_MAIN_CONF)) extern qjs_module_t qjs_webcrypto_module; extern qjs_module_t qjs_xml_module; diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index faa38aab..1c74bde6 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -40,7 +40,7 @@ static njs_int_t ngx_js_headers_inherit(njs_vm_t *vm, ngx_js_headers_t *headers, static njs_int_t ngx_js_headers_fill(njs_vm_t *vm, ngx_js_headers_t *headers, njs_value_t *init); static ngx_js_fetch_t *ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, - ngx_log_t *log); + ngx_log_t *log, ngx_js_loc_conf_t *conf); static void ngx_js_fetch_error(ngx_js_http_t *http, const char *err); static void ngx_js_fetch_destructor(ngx_js_event_t *event); static njs_int_t ngx_js_fetch_promissified_result(njs_vm_t *vm, @@ -537,7 +537,8 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, c = ngx_external_connection(vm, external); pool = ngx_external_pool(vm, external); - fetch = ngx_js_fetch_alloc(vm, pool, c->log); + fetch = ngx_js_fetch_alloc(vm, pool, c->log, + ngx_external_loc_conf(vm, external)); if (fetch == NULL) { return NJS_ERROR; } @@ -550,15 +551,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } http->response.url = request.url; - http->timeout = ngx_external_fetch_timeout(vm, external); - http->buffer_size = ngx_external_buffer_size(vm, external); - http->max_response_body_size = - ngx_external_max_response_buffer_size(vm, external); + http->buffer_size = http->conf->buffer_size; + http->max_response_body_size = http->conf->max_response_body_size; #if (NGX_SSL) if (u.default_port == 443) { - http->ssl = ngx_external_ssl(vm, external); - http->ssl_verify = ngx_external_ssl_verify(vm, external); + http->ssl = http->conf->ssl; + http->ssl_verify = http->conf->ssl_verify; } #endif @@ -1134,7 +1133,8 @@ ngx_js_headers_fill(njs_vm_t *vm, ngx_js_headers_t *headers, njs_value_t *init) static ngx_js_fetch_t * -ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log) +ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log, + ngx_js_loc_conf_t *conf) { njs_int_t ret; ngx_js_ctx_t *ctx; @@ -1152,8 +1152,7 @@ ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log) http->pool = pool; http->log = log; - - http->timeout = 10000; + http->conf = conf; http->http_parse.content_length_n = -1; diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index 4555a7ac..92e7b94b 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -255,8 +255,8 @@ ngx_js_http_connect(ngx_js_http_t *http) http->process = ngx_js_http_process_status_line; - ngx_add_timer(http->peer.connection->read, http->timeout); - ngx_add_timer(http->peer.connection->write, http->timeout); + ngx_add_timer(http->peer.connection->read, http->conf->timeout); + ngx_add_timer(http->peer.connection->write, http->conf->timeout); #if (NGX_SSL) if (http->ssl != NULL && http->peer.connection->ssl == NULL) { @@ -522,7 +522,7 @@ ngx_js_http_write_handler(ngx_event_t *wev) } if (!wev->timer_set) { - ngx_add_timer(wev, http->timeout); + ngx_add_timer(wev, http->conf->timeout); } } diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index 63d0f035..027d0b21 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -114,8 +114,8 @@ struct ngx_js_http_s { in_port_t port; ngx_peer_connection_t peer; - ngx_msec_t timeout; + ngx_js_loc_conf_t *conf; ngx_int_t buffer_size; ngx_int_t max_response_body_size; diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 46b54eab..f855e099 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -39,7 +39,7 @@ static ngx_int_t ngx_qjs_headers_inherit(JSContext *cx, static ngx_int_t ngx_qjs_headers_fill(JSContext *cx, ngx_js_headers_t *headers, JSValue init); static ngx_qjs_fetch_t *ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, - ngx_log_t *log); + ngx_log_t *log, ngx_js_loc_conf_t *conf); static void ngx_qjs_fetch_error(ngx_js_http_t *http, const char *err); static void ngx_qjs_fetch_destructor(ngx_qjs_event_t *event); static void ngx_qjs_fetch_done(ngx_qjs_fetch_t *fetch, JSValue retval, @@ -257,7 +257,8 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, c = ngx_qjs_external_connection(cx, external); pool = ngx_qjs_external_pool(cx, external); - fetch = ngx_qjs_fetch_alloc(cx, pool, c->log); + fetch = ngx_qjs_fetch_alloc(cx, pool, c->log, + ngx_qjs_external_loc_conf(cx, external)); if (fetch == NULL) { return JS_ThrowOutOfMemory(cx); } @@ -271,7 +272,6 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, http = &fetch->http; http->response.url = request.url; - http->timeout = ngx_qjs_external_fetch_timeout(cx, external); http->buffer_size = ngx_qjs_external_buffer_size(cx, external); http->max_response_body_size = ngx_qjs_external_max_response_buffer_size(cx, external); @@ -1193,7 +1193,8 @@ fail: static ngx_qjs_fetch_t * -ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, ngx_log_t *log) +ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, ngx_log_t *log, + ngx_js_loc_conf_t *conf) { ngx_js_ctx_t *ctx; ngx_js_http_t *http; @@ -1210,7 +1211,7 @@ ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, ngx_log_t *log) http->pool = pool; http->log = log; - http->timeout = 10000; + http->conf = conf; http->http_parse.content_length_n = -1; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 5ccc08a2..1257abb5 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -194,10 +194,8 @@ static void ngx_stream_qjs_periodic_finalizer(JSRuntime *rt, JSValue val); static ngx_pool_t *ngx_stream_js_pool(ngx_stream_session_t *s); static ngx_resolver_t *ngx_stream_js_resolver(ngx_stream_session_t *s); static ngx_msec_t ngx_stream_js_resolver_timeout(ngx_stream_session_t *s); -static ngx_msec_t ngx_stream_js_fetch_timeout(ngx_stream_session_t *s); -static size_t ngx_stream_js_buffer_size(ngx_stream_session_t *s); -static size_t ngx_stream_js_max_response_buffer_size(ngx_stream_session_t *s); static void ngx_stream_js_event_finalize(ngx_stream_session_t *s, ngx_int_t rc); +static ngx_js_loc_conf_t *ngx_stream_js_srv_conf(ngx_stream_session_t *s); static ngx_js_ctx_t *ngx_stream_js_ctx(ngx_stream_session_t *s); static void ngx_stream_js_periodic_handler(ngx_event_t *ev); @@ -225,8 +223,6 @@ static char *ngx_stream_js_merge_srv_conf(ngx_conf_t *cf, void *parent, static char *ngx_stream_js_shared_dict_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); -static ngx_ssl_t *ngx_stream_js_ssl(ngx_stream_session_t *s); -static ngx_flag_t ngx_stream_js_ssl_verify(ngx_stream_session_t *s); static ngx_conf_bitmask_t ngx_stream_js_engines[] = { { ngx_string("njs"), NGX_ENGINE_NJS }, @@ -710,13 +706,9 @@ static uintptr_t ngx_stream_js_uptr[] = { (uintptr_t) ngx_stream_js_resolver, (uintptr_t) ngx_stream_js_resolver_timeout, (uintptr_t) ngx_stream_js_event_finalize, - (uintptr_t) ngx_stream_js_ssl, - (uintptr_t) ngx_stream_js_ssl_verify, - (uintptr_t) ngx_stream_js_fetch_timeout, - (uintptr_t) ngx_stream_js_buffer_size, - (uintptr_t) ngx_stream_js_max_response_buffer_size, - (uintptr_t) 0 /* main_conf ptr */, + (uintptr_t) ngx_stream_js_srv_conf, (uintptr_t) ngx_stream_js_ctx, + (uintptr_t) 0 /* main_conf ptr */, }; @@ -1891,39 +1883,6 @@ ngx_stream_js_resolver_timeout(ngx_stream_session_t *s) } -static ngx_msec_t -ngx_stream_js_fetch_timeout(ngx_stream_session_t *s) -{ - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->timeout; -} - - -static size_t -ngx_stream_js_buffer_size(ngx_stream_session_t *s) -{ - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->buffer_size; -} - - -static size_t -ngx_stream_js_max_response_buffer_size(ngx_stream_session_t *s) -{ - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->max_response_body_size; -} - - static void ngx_stream_js_event_finalize(ngx_stream_session_t *s, ngx_int_t rc) { @@ -1946,6 +1905,13 @@ ngx_stream_js_event_finalize(ngx_stream_session_t *s, ngx_int_t rc) } +static ngx_js_loc_conf_t * +ngx_stream_js_srv_conf(ngx_stream_session_t *s) +{ + return ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); +} + + static ngx_js_ctx_t * ngx_stream_js_ctx(ngx_stream_session_t *s) { @@ -3025,7 +2991,7 @@ ngx_stream_js_init_conf_vm(ngx_conf_t *cf, ngx_js_loc_conf_t *conf) options.engine = conf->type; jmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_js_module); - ngx_stream_js_uptr[NGX_JS_MAIN_CONF_INDEX] = (uintptr_t) jmcf; + ngx_stream_js_uptr[NGX_JS_EXTERNAL_MAIN_CONF] = (uintptr_t) jmcf; if (conf->type == NGX_ENGINE_NJS) { options.u.njs.metas = &ngx_stream_js_metas; @@ -3716,33 +3682,3 @@ ngx_stream_js_init(ngx_conf_t *cf) return NGX_OK; } - - -static ngx_ssl_t * -ngx_stream_js_ssl(ngx_stream_session_t *s) -{ -#if (NGX_STREAM_SSL) - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->ssl; -#else - return NULL; -#endif -} - - -static ngx_flag_t -ngx_stream_js_ssl_verify(ngx_stream_session_t *s) -{ -#if (NGX_STREAM_SSL) - ngx_stream_js_srv_conf_t *jscf; - - jscf = ngx_stream_get_module_srv_conf(s, ngx_stream_js_module); - - return jscf->ssl_verify; -#else - return 0; -#endif -} From noreply at nginx.com Tue Sep 16 23:08:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 16 Sep 2025 23:08:02 +0000 (UTC) Subject: [njs] Fetch: added parsing of HTTP version. Message-ID: <20250916230802.9733048896@pubserv1.nginx> details: https://github.com/nginx/njs/commit/8fcffdc3090985f49e9c2ecb4043d246514bde50 branches: master commit: 8fcffdc3090985f49e9c2ecb4043d246514bde50 user: Dmitry Volyntsev date: Fri, 12 Sep 2025 19:10:32 -0700 description: Fetch: added parsing of HTTP version. --- nginx/ngx_js_http.c | 21 +++++++++++++++++++++ nginx/ngx_js_http.h | 3 +++ 2 files changed, 24 insertions(+) diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index 92e7b94b..3f52868e 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -942,6 +942,12 @@ ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) return NGX_ERROR; } + hp->http_major = ch - '0'; + + if (hp->http_major > 1) { + return NGX_ERROR; + } + state = sw_major_digit; break; @@ -956,6 +962,12 @@ ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) return NGX_ERROR; } + hp->http_major = hp->http_major * 10 + (ch - '0'); + + if (hp->http_major > 1) { + return NGX_ERROR; + } + break; /* the first digit of minor HTTP version */ @@ -964,6 +976,7 @@ ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) return NGX_ERROR; } + hp->http_minor = ch - '0'; state = sw_minor_digit; break; @@ -978,6 +991,12 @@ ngx_js_http_parse_status_line(ngx_js_http_parse_t *hp, ngx_buf_t *b) return NGX_ERROR; } + if (hp->http_minor > 99) { + return NGX_ERROR; + } + + hp->http_minor = hp->http_minor * 10 + (ch - '0'); + break; /* HTTP status code */ @@ -1055,6 +1074,8 @@ done: b->pos = p + 1; hp->state = sw_start; + hp->http_version = hp->http_major * 1000 + hp->http_minor; + return NGX_OK; } diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index 027d0b21..7adcc130 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -16,6 +16,9 @@ typedef struct ngx_js_http_s ngx_js_http_t; typedef struct { ngx_uint_t state; + unsigned http_major:16; + unsigned http_minor:16; + ngx_uint_t http_version; ngx_uint_t code; u_char *status_text; u_char *status_text_end; From noreply at nginx.com Tue Sep 16 23:08:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 16 Sep 2025 23:08:02 +0000 (UTC) Subject: [njs] Fetch: added keepalive support for ngx.fetch() API. Message-ID: <20250916230802.A6193492AF@pubserv1.nginx> details: https://github.com/nginx/njs/commit/7b3c8a66879dc0d5250bfeb53b2fbc85d15da637 branches: master commit: 7b3c8a66879dc0d5250bfeb53b2fbc85d15da637 user: Dmitry Volyntsev date: Wed, 3 Sep 2025 20:27:16 -0700 description: Fetch: added keepalive support for ngx.fetch() API. This closes #957 feature request on Github. --- nginx/ngx_http_js_module.c | 28 +++ nginx/ngx_js.c | 17 ++ nginx/ngx_js.h | 10 +- nginx/ngx_js_fetch.c | 30 ++- nginx/ngx_js_http.c | 390 ++++++++++++++++++++++++++++++++---- nginx/ngx_js_http.h | 12 +- nginx/ngx_qjs_fetch.c | 30 ++- nginx/ngx_stream_js_module.c | 28 +++ nginx/t/js_fetch_https_keepalive.t | 345 +++++++++++++++++++++++++++++++ nginx/t/js_fetch_keepalive.t | 289 ++++++++++++++++++++++++++ nginx/t/stream_js_fetch_keepalive.t | 200 ++++++++++++++++++ 11 files changed, 1309 insertions(+), 70 deletions(-) diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index 736635cd..f9d9721a 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -565,6 +565,34 @@ static ngx_command_t ngx_http_js_commands[] = { 0, NULL }, + { ngx_string("js_fetch_keepalive"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, fetch_keepalive), + NULL }, + + { ngx_string("js_fetch_keepalive_requests"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_requests), + NULL }, + + { ngx_string("js_fetch_keepalive_time"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_time), + NULL }, + + { ngx_string("js_fetch_keepalive_timeout"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_js_loc_conf_t, fetch_keepalive_timeout), + NULL }, + ngx_null_command }; diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index c385e16e..efef3467 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -10,6 +10,7 @@ #include #include #include "ngx_js.h" +#include "ngx_js_http.h" typedef struct { @@ -3986,6 +3987,11 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size) conf->max_response_body_size = NGX_CONF_UNSET_SIZE; conf->timeout = NGX_CONF_UNSET_MSEC; + conf->fetch_keepalive = NGX_CONF_UNSET_UINT; + conf->fetch_keepalive_requests = NGX_CONF_UNSET_UINT; + conf->fetch_keepalive_time = NGX_CONF_UNSET_MSEC; + conf->fetch_keepalive_timeout = NGX_CONF_UNSET_MSEC; + return conf; } @@ -4097,6 +4103,17 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, ngx_conf_merge_size_value(conf->max_response_body_size, prev->max_response_body_size, 1048576); + ngx_conf_merge_uint_value(conf->fetch_keepalive, prev->fetch_keepalive, 0); + ngx_conf_merge_uint_value(conf->fetch_keepalive_requests, + prev->fetch_keepalive_requests, 1000); + ngx_conf_merge_msec_value(conf->fetch_keepalive_time, + prev->fetch_keepalive_time, 3600000); + ngx_conf_merge_msec_value(conf->fetch_keepalive_timeout, + prev->fetch_keepalive_timeout, 60000); + + ngx_queue_init(&conf->fetch_keepalive_cache); + ngx_queue_init(&conf->fetch_keepalive_free); + if (ngx_js_merge_vm(cf, (ngx_js_loc_conf_t *) conf, (ngx_js_loc_conf_t *) prev, init_vm) diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index 257227e5..af19e007 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -133,7 +134,14 @@ typedef struct { \ size_t buffer_size; \ size_t max_response_body_size; \ - ngx_msec_t timeout + ngx_msec_t timeout; \ + \ + ngx_uint_t fetch_keepalive; \ + ngx_uint_t fetch_keepalive_requests; \ + ngx_msec_t fetch_keepalive_time; \ + ngx_msec_t fetch_keepalive_timeout; \ + ngx_queue_t fetch_keepalive_cache; \ + ngx_queue_t fetch_keepalive_free #if defined(NGX_HTTP_SSL) || defined(NGX_STREAM_SSL) diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 1c74bde6..ac1c1a27 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -7,10 +7,6 @@ */ -#include -#include -#include -#include #include "ngx_js.h" #include "ngx_js_http.h" @@ -550,6 +546,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, goto fail; } + if (u.host.len >= NGX_JS_HOST_MAX_LEN) { + njs_vm_error(vm, "Host name too long"); + goto fail; + } + + http->host = u.host; + http->port = u.port; http->response.url = request.url; http->buffer_size = http->conf->buffer_size; http->max_response_body_size = http->conf->max_response_body_size; @@ -681,18 +684,22 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, continue; } + if (h[i].key.len == 10 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Connection", 10) + == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); njs_chb_append_literal(&http->chain, CRLF); } - njs_chb_append_literal(&http->chain, "Connection: close" CRLF); - -#if (NGX_SSL) - http->tls_name.data = u.host.data; - http->tls_name.len = u.host.len; -#endif + if (!http->keepalive) { + njs_chb_append_literal(&http->chain, "Connection: close" CRLF); + } if (request.body.len != 0) { njs_chb_sprintf(&http->chain, 32, "Content-Length: %uz" CRLF CRLF, @@ -1154,7 +1161,8 @@ ngx_js_fetch_alloc(njs_vm_t *vm, ngx_pool_t *pool, ngx_log_t *log, http->log = log; http->conf = conf; - http->http_parse.content_length_n = -1; + http->content_length_n = -1; + http->keepalive = (conf->fetch_keepalive > 0); http->append_headers = ngx_js_fetch_append_headers; http->ready_handler = ngx_js_fetch_process_done; diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index 3f52868e..f07ccceb 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -7,19 +7,35 @@ */ -#include -#include -#include -#include #include "ngx_js.h" #include "ngx_js_http.h" +typedef struct { + ngx_js_loc_conf_t *conf; + ngx_queue_t queue; + ngx_connection_t *connection; + + ngx_flag_t ssl; + size_t host_len; + u_char host[NGX_JS_HOST_MAX_LEN]; + in_port_t port; +} ngx_js_http_keepalive_cache_t; + + +#define ngx_js_http_version(major, minor) ((major) * 1000 + (minor)) + + static void ngx_js_http_resolve_handler(ngx_resolver_ctx_t *ctx); static void ngx_js_http_next(ngx_js_http_t *http); static void ngx_js_http_write_handler(ngx_event_t *wev); static void ngx_js_http_read_handler(ngx_event_t *rev); static void ngx_js_http_dummy_handler(ngx_event_t *ev); +static void ngx_js_http_keepalive_close_handler(ngx_event_t *ev); +static void ngx_js_http_keepalive_dummy_handler(ngx_event_t *ev); + +static ngx_int_t ngx_js_http_get_keepalive_connection(ngx_js_http_t *http); +static ngx_int_t ngx_js_http_free_keepalive_connection(ngx_js_http_t *http); static ngx_int_t ngx_js_http_process_status_line(ngx_js_http_t *http); static ngx_int_t ngx_js_http_process_headers(ngx_js_http_t *http); @@ -177,12 +193,13 @@ failed: static void ngx_js_http_close_connection(ngx_connection_t *c) { - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, - "js http close connection: %d", c->fd); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "js http close connection: %p:%d", c, c->fd); #if (NGX_SSL) if (c->ssl) { c->ssl->no_wait_shutdown = 1; + c->ssl->no_send_shutdown = 1; if (ngx_ssl_shutdown(c) == NGX_AGAIN) { c->ssl->handler = ngx_js_http_close_connection; @@ -193,6 +210,7 @@ ngx_js_http_close_connection(ngx_connection_t *c) c->destroyed = 1; + ngx_destroy_pool(c->pool); ngx_close_connection(c); } @@ -210,18 +228,32 @@ ngx_js_http_resolve_done(ngx_js_http_t *http) void ngx_js_http_close_peer(ngx_js_http_t *http) { - if (http->peer.connection != NULL) { + if (http->peer.connection == NULL) { + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http close peer"); + + if (http->keepalive) { + if (ngx_js_http_free_keepalive_connection(http) != NGX_OK) { + ngx_js_http_close_connection(http->peer.connection); + } + + } else { ngx_js_http_close_connection(http->peer.connection); - http->peer.connection = NULL; } + + http->peer.connection = NULL; } void ngx_js_http_connect(ngx_js_http_t *http) { - ngx_int_t rc; - ngx_addr_t *addr; + ngx_int_t rc; + ngx_addr_t *addr; + ngx_connection_t *c; addr = &http->addrs[http->naddr]; @@ -235,38 +267,51 @@ ngx_js_http_connect(ngx_js_http_t *http) http->peer.log = http->log; http->peer.log_error = NGX_ERROR_ERR; - rc = ngx_event_connect_peer(&http->peer); + rc = ngx_js_http_get_keepalive_connection(http); + if (rc != NGX_OK) { + rc = ngx_event_connect_peer(&http->peer); + if (rc == NGX_ERROR) { + ngx_js_http_error(http, "connect failed"); + return; + } - if (rc == NGX_ERROR) { - ngx_js_http_error(http, "connect failed"); - return; + if (rc == NGX_BUSY || rc == NGX_DECLINED) { + ngx_js_http_next(http); + return; + } } - if (rc == NGX_BUSY || rc == NGX_DECLINED) { - ngx_js_http_next(http); - return; - } + c = http->peer.connection; - http->peer.connection->data = http; - http->peer.connection->pool = http->pool; + c->requests++; + c->data = http; - http->peer.connection->write->handler = ngx_js_http_write_handler; - http->peer.connection->read->handler = ngx_js_http_read_handler; + if (c->pool == NULL) { + /* we need separate pool here to be able to cache SSL connections */ + c->pool = ngx_create_pool(128, http->log); + if (c->pool == NULL) { + ngx_js_http_error(http, "create pool failed"); + return; + } + } + + c->write->handler = ngx_js_http_write_handler; + c->read->handler = ngx_js_http_read_handler; http->process = ngx_js_http_process_status_line; - ngx_add_timer(http->peer.connection->read, http->conf->timeout); - ngx_add_timer(http->peer.connection->write, http->conf->timeout); + ngx_add_timer(c->read, http->conf->timeout); + ngx_add_timer(c->write, http->conf->timeout); #if (NGX_SSL) - if (http->ssl != NULL && http->peer.connection->ssl == NULL) { + if (http->ssl != NULL && c->ssl == NULL) { ngx_js_http_ssl_init_connection(http); return; } #endif if (rc == NGX_OK) { - ngx_js_http_write_handler(http->peer.connection->write); + ngx_js_http_write_handler(c->write); } } @@ -346,10 +391,10 @@ ngx_js_http_ssl_handshake(ngx_js_http_t *http) goto failed; } - if (ngx_ssl_check_host(c, &http->tls_name) != NGX_OK) { + if (ngx_ssl_check_host(c, &http->host) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "js http SSL certificate does not match \"%V\"", - &http->tls_name); + &http->host); goto failed; } } @@ -380,7 +425,7 @@ ngx_js_http_ssl_name(ngx_js_http_t *http) u_char *p; /* as per RFC 6066, literal IPv4 and IPv6 addresses are not permitted */ - ngx_str_t *name = &http->tls_name; + ngx_str_t *name = &http->host; if (name->len == 0 || *name->data == '[') { goto done; @@ -571,6 +616,10 @@ ngx_js_http_read_handler(ngx_event_t *rev) return; } + if (rc == NGX_DONE) { + break; + } + continue; } @@ -631,6 +680,10 @@ ngx_js_http_process_status_line(ngx_js_http_t *http) http->response.status_text.len = hp->status_text_end - hp->status_text; http->process = ngx_js_http_process_headers; + if (http->keepalive) { + http->keepalive = (hp->http_version >= ngx_js_http_version(1, 1)); + } + return http->process(http); } @@ -694,21 +747,33 @@ ngx_js_http_process_headers(ngx_js_http_t *http) && ngx_strncasecmp(hp->header_start, (u_char *) "chunked", vlen) == 0) { - hp->chunked = 1; + http->chunked = 1; + } + + if (len == (sizeof("Connection") - 1) + && ngx_strncasecmp(hp->header_name_start, + (u_char *) "Connection", len) == 0) + { + if (vlen == (sizeof("close") - 1) + && ngx_strncasecmp(hp->header_start, (u_char *) "close", + vlen) == 0) + { + http->keepalive = 0; + } } if (len == (sizeof("Content-Length") - 1) && ngx_strncasecmp(hp->header_name_start, (u_char *) "Content-Length", len) == 0) { - hp->content_length_n = ngx_atoof(hp->header_start, vlen); - if (hp->content_length_n == NGX_ERROR) { + http->content_length_n = ngx_atoof(hp->header_start, vlen); + if (http->content_length_n == NGX_ERROR) { ngx_js_http_error(http, "invalid http content length"); return NGX_ERROR; } if (!http->header_only - && hp->content_length_n + && http->content_length_n > (off_t) http->max_response_body_size) { ngx_js_http_error(http, @@ -762,22 +827,22 @@ ngx_js_http_process_body(ngx_js_http_t *http) } if (!http->header_only - && http->http_parse.chunked - && http->http_parse.content_length_n == -1) + && http->chunked + && http->content_length_n == -1) { ngx_js_http_error(http, "invalid http chunked response"); return NGX_ERROR; } if (http->header_only - || http->http_parse.content_length_n == -1 - || size == http->http_parse.content_length_n) + || http->content_length_n == -1 + || size == http->content_length_n) { http->ready_handler(http); return NGX_DONE; } - if (size < http->http_parse.content_length_n) { + if (size < http->content_length_n) { return NGX_AGAIN; } @@ -787,7 +852,7 @@ ngx_js_http_process_body(ngx_js_http_t *http) b = http->buffer; - if (http->http_parse.chunked) { + if (http->chunked) { rc = ngx_js_http_parse_chunked(&http->http_chunk_parse, b, &http->response.chain); if (rc == NGX_ERROR) { @@ -798,7 +863,7 @@ ngx_js_http_process_body(ngx_js_http_t *http) size = njs_chb_size(&http->response.chain); if (rc == NGX_OK) { - http->http_parse.content_length_n = size; + http->content_length_n = size; } if (size > http->max_response_body_size * 10) { @@ -814,11 +879,11 @@ ngx_js_http_process_body(ngx_js_http_t *http) if (http->header_only) { need = 0; - } else if (http->http_parse.content_length_n == -1) { + } else if (http->content_length_n == -1) { need = http->max_response_body_size - size; } else { - need = http->http_parse.content_length_n - size; + need = http->content_length_n - size; } chsize = ngx_min(need, b->last - b->pos); @@ -1074,7 +1139,7 @@ done: b->pos = p + 1; hp->state = sw_start; - hp->http_version = hp->http_major * 1000 + hp->http_minor; + hp->http_version = ngx_js_http_version(hp->http_major, hp->http_minor); return NGX_OK; } @@ -1549,3 +1614,242 @@ ngx_js_check_header_name(u_char *name, size_t len) return NGX_OK; } + + +static ngx_int_t +ngx_js_http_get_keepalive_connection(ngx_js_http_t *http) +{ + ngx_str_t *host; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_js_loc_conf_t *conf; + ngx_js_http_keepalive_cache_t *cache; + + if (!http->keepalive) { + return NGX_DECLINED; + } + + conf = http->conf; + + host = &http->host; + + for (q = ngx_queue_head(&conf->fetch_keepalive_cache); + q != ngx_queue_sentinel(&conf->fetch_keepalive_cache); + q = ngx_queue_next(q)) + { + cache = ngx_queue_data(q, ngx_js_http_keepalive_cache_t, queue); + + if (host->len != cache->host_len) { + continue; + } + + if ((http->ssl != NULL) != (cache->ssl != 0)) { + continue; + } + + if (ngx_strncasecmp(host->data, cache->host, host->len) != 0) { + continue; + } + + if (http->port != cache->port) { + continue; + } + + c = cache->connection; + ngx_queue_remove(q); + ngx_queue_insert_head(&conf->fetch_keepalive_free, q); + + goto found; + } + + return NGX_DECLINED; + +found: + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http keepalive using cached connection: %p:%d", + c, c->fd); + + c->idle = 0; + c->sent = 0; + c->data = NULL; + c->log = http->log; + c->pool->log = http->log; + c->read->log = http->log; + c->write->log = http->log; + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + http->peer.cached = 1; + http->peer.connection = c; + + return NGX_OK; +} + + +static ngx_int_t +ngx_js_http_free_keepalive_connection(ngx_js_http_t *http) +{ + ngx_uint_t i; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_js_loc_conf_t *conf; + ngx_js_http_keepalive_cache_t *cache; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http free keepalive connection"); + + c = http->peer.connection; + + if (c == NULL + || c->read->eof + || c->read->error + || c->read->timedout + || c->write->error + || c->write->timedout) + { + return NGX_ERROR; + } + + if (c->requests >= http->conf->fetch_keepalive_requests) { + return NGX_DONE; + } + + if (ngx_current_msec - c->start_time > http->conf->fetch_keepalive_time) { + return NGX_DONE; + } + + if (ngx_terminate || ngx_exiting) { + return NGX_DONE; + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http free keepalive connection, " + "saving connection: %p:%d", c, c->fd); + + conf = http->conf; + + if (ngx_queue_empty(&conf->fetch_keepalive_cache) + && ngx_queue_empty(&conf->fetch_keepalive_free)) + { + cache = ngx_pcalloc(ngx_cycle->pool, + sizeof(ngx_js_http_keepalive_cache_t) * conf->fetch_keepalive); + if (cache == NULL) { + return NGX_ERROR; + } + + for (i = 0; i < conf->fetch_keepalive; i++) { + ngx_queue_insert_head(&conf->fetch_keepalive_free, + &cache[i].queue); + cache[i].conf = conf; + } + } + + if (ngx_queue_empty(&conf->fetch_keepalive_free)) { + /* evict from cache */ + q = ngx_queue_last(&conf->fetch_keepalive_cache); + ngx_queue_remove(q); + + cache = ngx_queue_data(q, ngx_js_http_keepalive_cache_t, queue); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, http->log, 0, + "js http free keepalive connection, evicting: %d", + cache->connection->fd); + ngx_js_http_close_connection(cache->connection); + + } else { + q = ngx_queue_head(&conf->fetch_keepalive_free); + ngx_queue_remove(q); + cache = ngx_queue_data(q, ngx_js_http_keepalive_cache_t, queue); + } + + ngx_queue_insert_head(&conf->fetch_keepalive_cache, q); + + c = http->peer.connection; + http->peer.connection = NULL; + + cache->connection = c; + + cache->ssl = (http->ssl != NULL); + ngx_memcpy(cache->host, http->host.data, http->host.len); + cache->host_len = http->host.len; + cache->port = http->port; + + c->read->delayed = 0; + ngx_add_timer(c->read, conf->fetch_keepalive_timeout); + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + c->data = cache; + c->write->handler = ngx_js_http_keepalive_dummy_handler; + c->read->handler = ngx_js_http_keepalive_close_handler; + + c->idle = 1; + c->log = ngx_cycle->log; + c->pool->log = ngx_cycle->log; + c->read->log = ngx_cycle->log; + c->write->log = ngx_cycle->log; + + if (c->read->ready) { + ngx_js_http_keepalive_close_handler(c->read); + } + + return NGX_OK; +} + + +static void +ngx_js_http_keepalive_dummy_handler(ngx_event_t *ev) +{ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "js http keepalive dummy handler"); +} + + +static void +ngx_js_http_keepalive_close_handler(ngx_event_t *ev) +{ + ssize_t n; + ngx_connection_t *c; + ngx_js_loc_conf_t *conf; + ngx_js_http_keepalive_cache_t *cache; + u_char buf[1]; + + c = ev->data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0, + "js http keepalive close handler: %d", c->fd); + + if (c->close || ev->timedout) { + goto close; + } + + n = recv(c->fd, buf, 1, MSG_PEEK); + + if (n == -1 && ngx_socket_errno == NGX_EAGAIN) { + ev->ready = 0; + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + goto close; + } + + return; + } + +close: + + cache = c->data; + conf = cache->conf; + + ngx_js_http_close_connection(c); + + ngx_queue_remove(&cache->queue); + ngx_queue_insert_head(&conf->fetch_keepalive_free, &cache->queue); +} diff --git a/nginx/ngx_js_http.h b/nginx/ngx_js_http.h index 7adcc130..acaa5802 100644 --- a/nginx/ngx_js_http.h +++ b/nginx/ngx_js_http.h @@ -11,8 +11,10 @@ #define _NGX_JS_HTTP_H_INCLUDED_ -typedef struct ngx_js_http_s ngx_js_http_t; +#define NGX_JS_HOST_MAX_LEN 256 + +typedef struct ngx_js_http_s ngx_js_http_t; typedef struct { ngx_uint_t state; @@ -23,8 +25,6 @@ typedef struct { u_char *status_text; u_char *status_text_end; ngx_uint_t count; - ngx_flag_t chunked; - off_t content_length_n; u_char *header_name_start; u_char *header_name_end; @@ -114,6 +114,7 @@ struct ngx_js_http_s { ngx_addr_t *addrs; ngx_uint_t naddrs; ngx_uint_t naddr; + ngx_str_t host; in_port_t port; ngx_peer_connection_t peer; @@ -124,8 +125,11 @@ struct ngx_js_http_s { unsigned header_only; + ngx_flag_t chunked; + ngx_flag_t keepalive; + off_t content_length_n; + #if (NGX_SSL) - ngx_str_t tls_name; ngx_ssl_t *ssl; njs_bool_t ssl_verify; #endif diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index f855e099..a211e0fe 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -5,10 +5,6 @@ */ -#include -#include -#include -#include #include "ngx_js.h" #include "ngx_js_http.h" @@ -270,7 +266,14 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, goto fail; } + if (u.host.len >= NGX_JS_HOST_MAX_LEN) { + JS_ThrowInternalError(cx, "Host name too long"); + goto fail; + } + http = &fetch->http; + http->host = u.host; + http->port = u.port; http->response.url = request.url; http->buffer_size = ngx_qjs_external_buffer_size(cx, external); http->max_response_body_size = @@ -418,18 +421,22 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, continue; } + if (h[i].key.len == 10 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Connection", 10) + == 0) + { + continue; + } + njs_chb_append(&http->chain, h[i].key.data, h[i].key.len); njs_chb_append_literal(&http->chain, ": "); njs_chb_append(&http->chain, h[i].value.data, h[i].value.len); njs_chb_append_literal(&http->chain, CRLF); } - njs_chb_append_literal(&http->chain, "Connection: close" CRLF); - -#if (NGX_SSL) - http->tls_name.data = u.host.data; - http->tls_name.len = u.host.len; -#endif + if (!http->keepalive) { + njs_chb_append_literal(&http->chain, "Connection: close" CRLF); + } if (request.body.len != 0) { njs_chb_sprintf(&http->chain, 32, "Content-Length: %uz" CRLF CRLF, @@ -1213,7 +1220,8 @@ ngx_qjs_fetch_alloc(JSContext *cx, ngx_pool_t *pool, ngx_log_t *log, http->conf = conf; - http->http_parse.content_length_n = -1; + http->content_length_n = -1; + http->keepalive = (conf->fetch_keepalive > 0); ngx_qjs_arg(http->response.header_value) = JS_UNDEFINED; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index 1257abb5..ab4e787d 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -351,6 +351,34 @@ static ngx_command_t ngx_stream_js_commands[] = { offsetof(ngx_stream_js_srv_conf_t, timeout), NULL }, + { ngx_string("js_fetch_keepalive"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive), + NULL }, + + { ngx_string("js_fetch_keepalive_requests"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive_requests), + NULL }, + + { ngx_string("js_fetch_keepalive_time"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive_time), + NULL }, + + { ngx_string("js_fetch_keepalive_timeout"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive_timeout), + NULL }, + #if (NGX_STREAM_SSL) { ngx_string("js_fetch_ciphers"), diff --git a/nginx/t/js_fetch_https_keepalive.t b/nginx/t/js_fetch_https_keepalive.t new file mode 100644 index 00000000..86f72d0a --- /dev/null +++ b/nginx/t/js_fetch_https_keepalive.t @@ -0,0 +1,345 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for http njs module, fetch method, https keepalive support. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http http_ssl rewrite/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + resolver 127.0.0.1:%%PORT_8981_UDP%%; + resolver_timeout 1s; + + location /njs { + js_content test.njs; + } + + location /https { + js_content test.https; + + js_fetch_keepalive 4; + js_fetch_ciphers HIGH:!aNull:!MD5; + js_fetch_protocols TLSv1.1 TLSv1.2; + js_fetch_trusted_certificate myca.crt; + } + + location /sni_isolation { + js_content test.sni_isolation; + + js_fetch_keepalive 4; + js_fetch_ciphers HIGH:!aNull:!MD5; + js_fetch_protocols TLSv1.1 TLSv1.2; + js_fetch_trusted_certificate myca.crt; + } + + location /plain_vs_https_isolation { + js_content test.plain_vs_https_isolation; + + js_fetch_keepalive 4; + js_fetch_ciphers HIGH:!aNull:!MD5; + js_fetch_protocols TLSv1.1 TLSv1.2; + js_fetch_trusted_certificate myca.crt; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name ka.example.com; + + keepalive_requests 100; + + ssl_certificate ka.example.com.chained.crt; + ssl_certificate_key ka.example.com.key; + + location /loc { + return 200 CONN:$connection_requests; + } + } + + server { + listen 127.0.0.1:8081 ssl; + server_name 1.example.com; + + ssl_certificate 1.example.com.chained.crt; + ssl_certificate_key 1.example.com.key; + + location /loc { + return 200 "You are at 1.example.com."; + } + } + + server { + listen 127.0.0.1:8082; + server_name plain.example.com; + + keepalive_requests 100; + + location /loc { + return 200 PLAIN:$connection_requests; + } + } +} + +EOF + +my $p1 = port(8081); +my $p2 = port(8082); + +$t->write_file('test.js', < reply.text()) + .then(body => r.return(200, body)) + .catch(e => r.return(501, e.message)) + } + + async function sni_isolation(r) { + try { + let resp = await ngx.fetch(`https://ka.example.com:$p1/loc`); + let body1 = await resp.text(); + + resp = await ngx.fetch(`https://1.example.com:$p1/loc`); + let body2 = await resp.text(); + + resp = await ngx.fetch(`https://ka.example.com:$p1/loc`); + let body3 = await resp.text(); + + r.return(200, `\${body1}|\${body2}|\${body3}`); + + } catch (e) { + r.return(501, e.message); + } + } + + async function plain_vs_https_isolation(r) { + try { + let resp = await ngx.fetch(`https://ka.example.com:$p1/loc`); + let body1 = await resp.text(); + + resp = await ngx.fetch(`http://plain.example.com:$p2/loc`); + let body2 = await resp.text(); + + resp = await ngx.fetch(`https://ka.example.com:$p1/loc`); + let body3 = await resp.text(); + + r.return(200, `\${body1}|\${body2}|\${body3}`); + + } catch (e) { + r.return(501, e.message); + } + } + + export default {njs: test_njs, https, sni_isolation, + plain_vs_https_isolation}; +EOF + +my $d = $t->testdir(); + +$t->write_file('openssl.conf', <write_file('myca.conf', <>$d/openssl.out 2>&1") == 0 + or die "Can't create self-signed certificate for CA: $!\n"; + +foreach my $name ('intermediate', '1.example.com', 'ka.example.com') { + system("openssl req -new " + . "-config $d/openssl.conf -subj /CN=$name/ " + . "-out $d/$name.csr -keyout $d/$name.key " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't create certificate signing req for $name: $!\n"; +} + +$t->write_file('certserial', '1000'); +$t->write_file('certindex', ''); + +system("openssl ca -batch -config $d/myca.conf " + . "-keyfile $d/myca.key -cert $d/myca.crt " + . "-subj /CN=intermediate/ -in $d/intermediate.csr " + . "-out $d/intermediate.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for intermediate: $!\n"; + +foreach my $name ('1.example.com', 'ka.example.com') { + system("openssl ca -batch -config $d/myca.conf " + . "-keyfile $d/intermediate.key -cert $d/intermediate.crt " + . "-subj /CN=$name/ -in $d/$name.csr -out $d/$name.crt " + . ">>$d/openssl.out 2>&1") == 0 + or die "Can't sign certificate for $name $!\n"; + $t->write_file("$name.chained.crt", $t->read_file("$name.crt") + . $t->read_file('intermediate.crt')); +} + +$t->try_run('no njs.fetch'); + +$t->plan(5); + +$t->run_daemon(\&dns_daemon, port(8981), $t); +$t->waitforfile($t->testdir . '/' . port(8981)); + +############################################################################### + +like(http_get('/https?domain=localhost'), + qr/connect failed/s, 'fetch https wrong CN certificate'); +like(http_get('/https?domain=ka.example.com'), + qr/CONN:1$/s, 'fetch https keepalive'); +like(http_get('/https?domain=ka.example.com'), + qr/CONN:2$/s, 'fetch https keepalive reused'); +like(http_get('/sni_isolation'), + qr/CONN:1\|You are at 1\.example\.com\.\|CONN:2$/s, + 'fetch https keepalive SNI isolation'); +like(http_get('/plain_vs_https_isolation'), + qr/CONN:1\|PLAIN:1\|CONN:2$/s, + 'fetch https->plain->https keepalive isolation'); + +############################################################################### + +sub reply_handler { + my ($recv_data, $port, %extra) = @_; + + my (@name, @rdata); + + use constant NOERROR => 0; + use constant A => 1; + use constant IN => 1; + + # default values + + my ($hdr, $rcode, $ttl) = (0x8180, NOERROR, 3600); + + # decode name + + my ($len, $offset) = (undef, 12); + while (1) { + $len = unpack("\@$offset C", $recv_data); + last if $len == 0; + $offset++; + push @name, unpack("\@$offset A$len", $recv_data); + $offset += $len; + } + + $offset -= 1; + my ($id, $type, $class) = unpack("n x$offset n2", $recv_data); + + my $name = join('.', @name); + + if ($type == A) { + push @rdata, rd_addr($ttl, '127.0.0.1'); + } + + $len = @name; + pack("n6 (C/a*)$len x n2", $id, $hdr | $rcode, 1, scalar @rdata, + 0, 0, @name, $type, $class) . join('', @rdata); +} + +sub rd_addr { + my ($ttl, $addr) = @_; + + my $code = 'split(/\./, $addr)'; + + return pack 'n3N', 0xc00c, A, IN, $ttl if $addr eq ''; + + pack 'n3N nC4', 0xc00c, A, IN, $ttl, eval "scalar $code", eval($code); +} + +sub dns_daemon { + my ($port, $t) = @_; + + my ($data, $recv_data); + my $socket = IO::Socket::INET->new( + LocalAddr => '127.0.0.1', + LocalPort => $port, + Proto => 'udp', + ) + or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + # signal we are ready + + open my $fh, '>', $t->testdir() . '/' . $port; + close $fh; + + while (1) { + $socket->recv($recv_data, 65536); + $data = reply_handler($recv_data, $port); + $socket->send($data); + } +} + +############################################################################### diff --git a/nginx/t/js_fetch_keepalive.t b/nginx/t/js_fetch_keepalive.t new file mode 100755 index 00000000..100be40c --- /dev/null +++ b/nginx/t/js_fetch_keepalive.t @@ -0,0 +1,289 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for http njs module, fetch method keepalive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use IO::Socket::INET; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /engine { + js_content test.engine; + } + + location /keepalive { + js_fetch_keepalive 4; + js_fetch_keepalive_requests 100; + js_fetch_keepalive_time 60s; + js_fetch_keepalive_timeout 60s; + js_content test.keepalive; + } + + location /keepalive_simultaneous { + js_fetch_keepalive 4; + js_content test.keepalive_simultaneous; + } + + location /keepalive_requests { + js_fetch_keepalive 4; + js_fetch_keepalive_requests 2; + js_content test.keepalive; + } + + location /keepalive_time { + js_fetch_keepalive 4; + js_fetch_keepalive_time 100ms; + js_content test.keepalive; + } + + location /keepalive_timeout { + js_fetch_keepalive 4; + js_fetch_keepalive_timeout 100ms; + js_content test.keepalive; + } + + location /no_keepalive { + js_fetch_keepalive 0; + js_content test.keepalive; + } + } + + server { + listen 127.0.0.1:8081; + keepalive_requests 100; + keepalive_timeout 60s; + + location /count { + add_header Connection-ID $connection_requests; + return 200 $connection_requests; + } + + location /count_close { + add_header Connection close; + add_header Connection-ID $connection_requests; + return 200 $connection_requests; + } + + location /count_close_mixed { + add_header cOnNeCtiOn ClOsE; + add_header Connection-ID $connection_requests; + return 200 $connection_requests; + } + } +} + +EOF + +my $p1 = port(8081); +my $p2 = port(8082); + +$t->write_file('test.js', < setTimeout(resolve, milliseconds)); + } + + async function keepalive(r) { + const path = r.args.path; + let port = $p1; + if (r.args.port) { + port = r.args.port; + } + + let responses = []; + for (let i = 0; i < 3; i++) { + let resp = await ngx.fetch(`http://127.0.0.1:\${port}/\${path}`) + .then(resp => resp.text()) + .catch(err => err.message); + responses.push(resp.trim()); + + if (r.args.sleep) { + await sleep(Number(r.args.sleep)); + } + } + + r.return(200, responses.toString()); + } + + async function keepalive_simultaneous(r) { + let promises = []; + for (let i = 0; i < Number(r.args.n); i++) { + promises.push(ngx.fetch('http://127.0.0.1:$p1/count')); + } + + let results = await Promise.all(promises); + let bodies = await Promise.all(results.map(r => r.text())); + let responses = bodies.map(b => parseInt(b.trim())); + + r.return(200, JSON.stringify(responses)); + } + + export default {engine, keepalive, keepalive_simultaneous}; +EOF + +$t->try_run('no js_fetch_keepalive'); + +$t->run_daemon(\&http_daemon, $p2); +$t->waitforsocket('127.0.0.1:' . $p2); + +$t->plan(16); + +############################################################################### + +like(http_get('/no_keepalive?path=count'), qr/1,1,1/, + 'no keepalive connections'); +like(http_get('/keepalive?path=count_close'), qr/1,1,1/, + 'upstream Connection: close (HTTP/1.1)'); +like(http_get('/keepalive?path=count_close_mixed'), qr/1,1,1/, + 'upstream Connection: close, mixed-case (HTTP/1.1)'); +like(http_get('/keepalive?path=count'), qr/1,2,3/, + 'keepalive reuses connection'); +like(http_get('/keepalive?path=count'), qr/4,5,6/, + 'keepalive reuses connection across requests'); +like(http_get('/keepalive_simultaneous?n=8'), qr/1,1,1,1,1,1,1,1/, + 'keepalive simultaneous requests'); +like(http_get('/keepalive_simultaneous?n=8'), qr/2,2,2,2,1,1,1,1/, + 'keepalive simultaneous requests reused connections'); +like(http_get('/keepalive_requests?path=count'), qr/1,2,1/, + 'keepalive with limited requests per connection'); + +like(http_get('/keepalive_time?path=count'), qr/1,2,3/, + 'keepalive with time limit, first round'); + +select undef, undef, undef, 0.15; + +like(http_get('/keepalive_time?path=count'), qr/4,1,2/, + 'keepalive with time limit, second round'); + +like(http_get('/keepalive_timeout?path=count'), qr/1,2,3/, + 'keepalive with timeout limit, first round'); + +select undef, undef, undef, 0.15; + +like(http_get('/keepalive_timeout?path=count'), qr/1,2,3/, + 'keepalive with timeout limit, second round'); + +like(http_get("/keepalive?path=broken_keepalive&port=$p2&sleep=1"), qr/1,1,1/, + 'upstream broken keepalive (connection closed by upstream)'); +like(http_get("/keepalive?path=http10&port=$p2"), qr/1,1,1/, + 'upstream HTTP/1.0 (no keepalive)'); +like(http_get("/keepalive?path=count&port=$p2&sleep=1"), qr/1,2,3/, + 'normal keepalive'); +like(http_get("/keepalive?path=assumed_keepalive&port=$p2&sleep=1"), qr/4,5,6/, + 'assumed keepalive'); + +############################################################################### + +sub http_daemon { + my $port = shift; + + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . $port, + Listen => 5, + Reuse => 1 + ) or die "Can't create listening socket: $!\n"; + + my $ccount = 0; + my $rcount = 0; + + # dumb server which is able to keep connections alive + + while (my $client = $server->accept()) { + Test::Nginx::log_core('||', + "connection from " . $client->peerhost()); + $client->autoflush(1); + $ccount++; + $rcount = 0; + + while (1) { + my $headers = ''; + my $uri = ''; + + while (<$client>) { + Test::Nginx::log_core('||', $_); + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + last if $headers eq ''; + $rcount++; + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + my $body = $rcount; + + if ($uri eq '/broken_keepalive') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: " . length($body) . CRLF . + "Connection: keep-alive" . CRLF . CRLF . + $body; + + last; + + } elsif ($uri eq '/assumed_keepalive') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: " . length($body) . CRLF . CRLF . + $body; + + } elsif ($uri eq '/count') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: " . length($body) . CRLF . + "Connection: keep-alive" . CRLF . CRLF . + $body; + + } elsif ($uri eq '/http10') { + print $client + "HTTP/1.0 200 OK" . CRLF . + "Content-Length: " . length($body) . CRLF . CRLF . + $body; + } + } + + close $client; + } +} + +############################################################################### diff --git a/nginx/t/stream_js_fetch_keepalive.t b/nginx/t/stream_js_fetch_keepalive.t new file mode 100644 index 00000000..e940eea5 --- /dev/null +++ b/nginx/t/stream_js_fetch_keepalive.t @@ -0,0 +1,200 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) F5, Inc. + +# Tests for stream njs module, fetch method keepalive. + +############################################################################### + +use warnings; +use strict; + +use Test::More; +use IO::Socket::INET; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; +use Test::Nginx::Stream qw/ stream /; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +my $t = Test::Nginx->new()->has(qw/http stream/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /engine { + js_content test.engine; + } + } + + server { + listen 127.0.0.1:8081; + keepalive_requests 100; + keepalive_timeout 60s; + + location /count { + add_header Connection-ID $connection_requests; + return 200 $connection_requests; + } + + location /headers { + return 200 "Connection: $http_connection"; + } + } +} + +stream { + %%TEST_GLOBALS_STREAM%% + + js_import test.js; + js_var $message; + + server { + listen 127.0.0.1:8082; + js_fetch_keepalive 4; + js_fetch_keepalive_requests 100; + js_fetch_keepalive_time 60s; + js_fetch_keepalive_timeout 60s; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8083; + js_fetch_keepalive 0; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8084; + js_fetch_keepalive 4; + js_fetch_keepalive_requests 2; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8085; + js_fetch_keepalive 4; + js_fetch_keepalive_time 100ms; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8086; + js_fetch_keepalive 4; + js_fetch_keepalive_timeout 100ms; + js_preread test.keepalive; + return $message; + } + + server { + listen 127.0.0.1:8087; + js_fetch_keepalive 4; + js_preread test.keepalive_simultaneous; + return $message; + } +} + +EOF + +my $p1 = port(8081); + +$t->write_file('test.js', < r.text())); + let responses = bodies.map(b => parseInt(b.trim())); + + s.variables.message = JSON.stringify(responses); + s.done(); + } + + export default {engine, keepalive, keepalive_simultaneous}; +EOF + +$t->try_run('no stream js_fetch_keepalive'); + +$t->plan(10); + +############################################################################### + +like(stream('127.0.0.1:' . port(8083))->io('GO'), qr/\[1,1,1]/, + 'no keepalive connections'); +like(stream('127.0.0.1:' . port(8082))->io('GO'), qr/\[1,2,3]/, + 'keepalive reuses connection'); +like(stream('127.0.0.1:' . port(8082))->io('GO'), qr/\[4,5,6]/, + 'keepalive reuses connection across sessions'); + +like(stream('127.0.0.1:' . port(8087))->io('GO'), qr/^\[(1,){7}1\]$/, + 'keepalive simultaneous requests'); +like(stream('127.0.0.1:' . port(8087))->io('GO'), + qr/\[2,2,2,2,1,1,1,1\]/, + 'keepalive simultaneous requests reused connections'); + +like(stream('127.0.0.1:' . port(8084))->io('GO'), qr/\[1,2,1]/, + 'keepalive with limited requests per connection'); + +like(stream('127.0.0.1:' . port(8085))->io('GO'), qr/\[1,2,3]/, + 'keepalive with time limit, first round'); + +select undef, undef, undef, 0.15; + +like(stream('127.0.0.1:' . port(8085))->io('GO'), qr/\[4,1,2]/, + 'keepalive with time limit, second round'); + +like(stream('127.0.0.1:' . port(8086))->io('GO'), qr/\[1,2,3]/, + 'keepalive with timeout limit, first round'); + +select undef, undef, undef, 0.15; + +like(stream('127.0.0.1:' . port(8086))->io('GO'), qr/\[1,2,3]/, + 'keepalive with timeout limit, second round'); + +############################################################################### From noreply at nginx.com Wed Sep 17 18:56:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 17 Sep 2025 18:56:02 +0000 (UTC) Subject: [njs] Fixed build with gcc-15 and -O3. Message-ID: <20250917185602.24B4B48854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/6b12358be530b158375f97b9b45a90d593da15f9 branches: master commit: 6b12358be530b158375f97b9b45a90d593da15f9 user: Dmitry Volyntsev date: Tue, 16 Sep 2025 18:28:05 -0700 description: Fixed build with gcc-15 and -O3. build/src/njs_object.dep -MT build/src/njs_object.o \ src/njs_object.c In file included from src/njs_main.h:18, from src/njs_object.c:8: In function ‘njs_utf8_copy’, inlined from ‘njs_object_enumerate_string’ at src/njs_object.c:769:21: src/njs_utf8.h:115:20: error: writing 1 byte into a region of size 0 [-Werror=stringop-overflow=] 115 | *dst++ = c; | ~~~~~~~^~~ src/njs_object.c: In function ‘njs_object_enumerate_string’: src/njs_object.c:719:24: note: at offset 4 into destination object ‘buf’ of size 4 719 | u_char buf[4], *c; | ^~~ In function ‘njs_utf8_copy’, inlined from ‘njs_object_enumerate_string’ at src/njs_object.c:769:21: src/njs_utf8.h:115:20: error: writing 1 byte into a region of size 0 [-Werror=stringop-overflow=] GCC-15 does not know that the loop in njs_utf8_copy() is bounded because the input is a valid UTF-8 here. This fixes #967 issue on Github. --- src/njs_array.c | 11 ++++------- src/njs_object.c | 17 ++++++----------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/njs_array.c b/src/njs_array.c index bcf428e9..b8cb9f94 100644 --- a/src/njs_array.c +++ b/src/njs_array.c @@ -788,7 +788,6 @@ static njs_int_t njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, int64_t start, int64_t length, njs_value_t *retval) { - u_char *c, buf[4]; size_t size; uint32_t n; njs_int_t ret; @@ -829,17 +828,15 @@ njs_array_prototype_slice_copy(njs_vm_t *vm, njs_value_t *this, do { value = &array->start[n++]; - c = buf; - c = njs_utf8_copy(c, &src, end); - size = c - buf; + size = njs_utf8_next(src, end) - src; - ret = njs_string_new(vm, value, buf, size, 1); + ret = njs_string_new(vm, value, src, size, 1); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } - length--; - } while (length != 0); + src += size; + } while (src != end); } else if (njs_is_object(this)) { diff --git a/src/njs_object.c b/src/njs_object.c index 7f0b1822..26e8182b 100644 --- a/src/njs_object.c +++ b/src/njs_object.c @@ -715,7 +715,6 @@ static njs_int_t njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, njs_array_t *items, uint32_t flags) { - u_char buf[4], *c; uint32_t i, len, size; njs_int_t ret; njs_value_t *item, *string; @@ -763,17 +762,15 @@ njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, end = src + str_prop.size; do { - c = buf; + size = njs_utf8_next(src, end) - src; - c = njs_utf8_copy(c, &src, end); - size = c - buf; - - ret = njs_string_new(vm, item, buf, size, 1); + ret = njs_string_new(vm, item, src, size, 1); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } item++; + src += size; } while (src != end); } @@ -828,12 +825,9 @@ njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, string = &entry->start[1]; - c = buf; - - c = njs_utf8_copy(c, &src, end); - size = c - buf; + size = njs_utf8_next(src, end) - src; - ret = njs_string_new(vm, string, buf, size, 1); + ret = njs_string_new(vm, string, src, size, 1); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } @@ -841,6 +835,7 @@ njs_object_enumerate_string(njs_vm_t *vm, const njs_value_t *value, njs_set_array(item, entry); item++; + src += size; } while (src != end); } From noreply at nginx.com Thu Sep 18 00:37:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 18 Sep 2025 00:37:02 +0000 (UTC) Subject: [njs] Modules: fixed building when http_ssl and stream_ssl unavailable. Message-ID: <20250918003702.A8A7C48854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/4ea1356af08e714076f57d02575f7845d6e80795 branches: master commit: 4ea1356af08e714076f57d02575f7845d6e80795 user: Dmitry Volyntsev date: Wed, 17 Sep 2025 15:34:19 -0700 description: Modules: fixed building when http_ssl and stream_ssl unavailable. This fixes issue introduced in 7b3c8a66. --- .github/workflows/check-pr.yml | 20 +++++++++++++++++++- nginx/ngx_http_js_module.c | 6 +++--- nginx/ngx_js.c | 4 ++-- nginx/ngx_js.h | 2 +- nginx/ngx_js_http.c | 4 ++++ nginx/ngx_stream_js_module.c | 6 +++--- nginx/t/js_webcrypto.t | 17 +++++++++++++++-- nginx/t/stream_js_webcrypto.t | 30 +++++++++++++++++++++++++++--- 8 files changed, 74 insertions(+), 15 deletions(-) diff --git a/.github/workflows/check-pr.yml b/.github/workflows/check-pr.yml index c75f3cbe..d5e0edd2 100644 --- a/.github/workflows/check-pr.yml +++ b/.github/workflows/check-pr.yml @@ -12,7 +12,10 @@ jobs: - name: Set the defaults and set up environment run: | - echo NGINX_CONFIGURE_CMD="auto/configure --prefix=/tmp --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-mail --with-mail_ssl_module --with-select_module --with-poll_module --with-http_auth_request_module --with-http_v2_module --with-http_slice_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-stream_realip_module --with-threads --with-cpp_test_module --with-compat --with-http_degradation_module --with-http_xslt_module --with-http_image_filter_module --with-http_perl_module --with-http_geoip_module --with-stream_geoip_module" >> $GITHUB_ENV + NGINX_BASE="auto/configure --prefix=/tmp --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-mail --with-select_module --with-poll_module --with-http_auth_request_module --with-http_v2_module --with-http_slice_module --with-stream --with-stream_realip_module --with-threads --with-cpp_test_module --with-compat --with-http_degradation_module --with-http_xslt_module --with-http_image_filter_module --with-http_perl_module --with-http_geoip_module --with-stream_geoip_module" + SSL_MODULES="--with-http_ssl_module --with-mail_ssl_module --with-stream_ssl_module --with-stream_ssl_preread_module" + echo NGINX_CONFIGURE_CMD="$NGINX_BASE $SSL_MODULES" >> $GITHUB_ENV + echo NGINX_CONFIGURE_CMD_NO_SSL="$NGINX_BASE" >> $GITHUB_ENV export DEB_BUILD_MAINT_OPTIONS="hardening=+all" export DEB_CFLAGS_MAINT_APPEND="-fPIC" export DEB_LDFLAGS_MAINT_APPEND=""-Wl,--as-needed"" @@ -126,6 +129,21 @@ jobs: TEST_NGINX_GLOBALS: "load_module ${{ github.workspace }}/nginx-source/objs/ngx_http_js_module.so; load_module ${{ github.workspace }}/nginx-source/objs/ngx_stream_js_module.so;" TEST_NGINX_VERBOSE: 1 + - name: Configure and build nginx and njs modules, no SSL, static modules + run: | + cd nginx-source + NJS_OPENSSL=NO $NGINX_CONFIGURE_CMD_NO_SSL --with-cc-opt="$CC_OPT" --with-ld-opt="$LD_OPT" --add-module=../nginx || cat objs/autoconf.err + $MAKE_UTILITY -j$(nproc) modules + $MAKE_UTILITY -j$(nproc) + + - name: Test njs modules, no SSL, static modules + run: | + ulimit -c unlimited + prove -v -j$(nproc) -Inginx-tests/lib --state=save nginx/t . || prove -v -Inginx-tests/lib --state=failed + env: + TEST_NGINX_BINARY: "${{ github.workspace }}/nginx-source/objs/nginx" + TEST_NGINX_VERBOSE: 1 + - name: Create LSAN suppression file run: | cat << EOF > lsan_suppressions.txt diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c index f9d9721a..2f3ce936 100644 --- a/nginx/ngx_http_js_module.c +++ b/nginx/ngx_http_js_module.c @@ -400,7 +400,7 @@ static ngx_conf_bitmask_t ngx_http_js_engines[] = { { ngx_null_string, 0 } }; -#if (NGX_HTTP_SSL) +#if (NGX_SSL) static ngx_conf_bitmask_t ngx_http_js_ssl_protocols[] = { { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, @@ -519,7 +519,7 @@ static ngx_command_t ngx_http_js_commands[] = { offsetof(ngx_http_js_loc_conf_t, timeout), NULL }, -#if (NGX_HTTP_SSL) +#if (NGX_SSL) { ngx_string("js_fetch_ciphers"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, @@ -8172,7 +8172,7 @@ ngx_http_js_create_loc_conf(ngx_conf_t *cf) return NULL; } -#if (NGX_HTTP_SSL) +#if (NGX_SSL) conf->ssl_verify = NGX_CONF_UNSET; conf->ssl_verify_depth = NGX_CONF_UNSET; #endif diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index efef3467..053c23c2 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -3996,7 +3996,7 @@ ngx_js_create_conf(ngx_conf_t *cf, size_t size) } -#if defined(NGX_HTTP_SSL) || defined(NGX_STREAM_SSL) +#if (NGX_SSL) static ngx_int_t ngx_js_merge_ssl(ngx_conf_t *cf, ngx_js_loc_conf_t *conf, @@ -4122,7 +4122,7 @@ ngx_js_merge_conf(ngx_conf_t *cf, void *parent, void *child, return NGX_CONF_ERROR; } -#if defined(NGX_HTTP_SSL) || defined(NGX_STREAM_SSL) +#if (NGX_SSL) if (ngx_js_merge_ssl(cf, conf, prev) != NGX_OK) { return NGX_CONF_ERROR; diff --git a/nginx/ngx_js.h b/nginx/ngx_js.h index af19e007..20ea85b1 100644 --- a/nginx/ngx_js.h +++ b/nginx/ngx_js.h @@ -144,7 +144,7 @@ typedef struct { ngx_queue_t fetch_keepalive_free -#if defined(NGX_HTTP_SSL) || defined(NGX_STREAM_SSL) +#if (NGX_SSL) #define NGX_JS_COMMON_LOC_CONF \ _NGX_JS_COMMON_LOC_CONF; \ \ diff --git a/nginx/ngx_js_http.c b/nginx/ngx_js_http.c index f07ccceb..35c67842 100644 --- a/nginx/ngx_js_http.c +++ b/nginx/ngx_js_http.c @@ -1643,9 +1643,11 @@ ngx_js_http_get_keepalive_connection(ngx_js_http_t *http) continue; } +#if (NGX_SSL) if ((http->ssl != NULL) != (cache->ssl != 0)) { continue; } +#endif if (ngx_strncasecmp(host->data, cache->host, host->len) != 0) { continue; @@ -1775,7 +1777,9 @@ ngx_js_http_free_keepalive_connection(ngx_js_http_t *http) cache->connection = c; +#if (NGX_SSL) cache->ssl = (http->ssl != NULL); +#endif ngx_memcpy(cache->host, http->host.data, http->host.len); cache->host_len = http->host.len; cache->port = http->port; diff --git a/nginx/ngx_stream_js_module.c b/nginx/ngx_stream_js_module.c index ab4e787d..212af400 100644 --- a/nginx/ngx_stream_js_module.c +++ b/nginx/ngx_stream_js_module.c @@ -232,7 +232,7 @@ static ngx_conf_bitmask_t ngx_stream_js_engines[] = { { ngx_null_string, 0 } }; -#if (NGX_STREAM_SSL) +#if (NGX_SSL) static ngx_conf_bitmask_t ngx_stream_js_ssl_protocols[] = { { ngx_string("TLSv1"), NGX_SSL_TLSv1 }, @@ -379,7 +379,7 @@ static ngx_command_t ngx_stream_js_commands[] = { offsetof(ngx_stream_js_srv_conf_t, fetch_keepalive_timeout), NULL }, -#if (NGX_STREAM_SSL) +#if (NGX_SSL) { ngx_string("js_fetch_ciphers"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, @@ -3620,7 +3620,7 @@ ngx_stream_js_create_srv_conf(ngx_conf_t *cf) return NULL; } -#if (NGX_STREAM_SSL) +#if (NGX_SSL) conf->ssl_verify = NGX_CONF_UNSET; conf->ssl_verify_depth = NGX_CONF_UNSET; #endif diff --git a/nginx/t/js_webcrypto.t b/nginx/t/js_webcrypto.t index f43f9d6d..33869574 100644 --- a/nginx/t/js_webcrypto.t +++ b/nginx/t/js_webcrypto.t @@ -41,6 +41,10 @@ http { listen 127.0.0.1:8080; server_name localhost; + location /has_crypto { + js_content test.has_crypto; + } + location /random_values_test { js_content test.random_values_test; } @@ -50,6 +54,10 @@ http { EOF $t->write_file('test.js', <write_file('test.js', < (mean - 10 * stdd) && bits1 < (mean + 10 * stdd)); } - export default {random_values_test}; + export default {has_crypto, random_values_test}; EOF -$t->try_run('no njs')->plan(1); +$t->try_run('no njs'); + +plan(skip_all => 'njs crypto module not available') + if http_get('/has_crypto') !~ /true/; + +$t->plan(1); ############################################################################### diff --git a/nginx/t/stream_js_webcrypto.t b/nginx/t/stream_js_webcrypto.t index d7b34396..619a093d 100644 --- a/nginx/t/stream_js_webcrypto.t +++ b/nginx/t/stream_js_webcrypto.t @@ -23,7 +23,7 @@ use Test::Nginx::Stream qw/ stream /; select STDERR; $| = 1; select STDOUT; $| = 1; -my $t = Test::Nginx->new()->has(qw/stream stream_return/) +my $t = Test::Nginx->new()->has(qw/http stream stream_return/) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% @@ -33,6 +33,21 @@ daemon off; events { } +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /has_crypto { + js_content test.has_crypto; + } + } +} + stream { %%TEST_GLOBALS_STREAM%% @@ -49,6 +64,10 @@ stream { EOF $t->write_file('test.js', <write_file('test.js', < (mean - 10 * stdd) && bits1 < (mean + 10 * stdd); } - export default {random_values_test}; + export default {has_crypto, random_values_test}; EOF -$t->try_run('no stream js_var')->plan(1); +$t->try_run('no stream js_var'); + +plan(skip_all => 'njs crypto module not available') + if http_get('/has_crypto') !~ /true/; + +$t->plan(1); ############################################################################### From noreply at nginx.com Thu Sep 18 14:17:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 18 Sep 2025 14:17:02 +0000 (UTC) Subject: [nginx] Fixed inaccurate index directive error report. Message-ID: <20250918141702.3DFD046C50@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/bc71625dcca1f1cbd0db7450af853feb90ebba85 branches: master commit: bc71625dcca1f1cbd0db7450af853feb90ebba85 user: willmafh date: Mon, 8 Sep 2025 22:03:30 +0800 description: Fixed inaccurate index directive error report. --- src/http/modules/ngx_http_index_module.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http/modules/ngx_http_index_module.c b/src/http/modules/ngx_http_index_module.c index 2ee1dd5a4..18e7049c5 100644 --- a/src/http/modules/ngx_http_index_module.c +++ b/src/http/modules/ngx_http_index_module.c @@ -490,7 +490,7 @@ ngx_http_index_set_index(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (value[i].len == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "index \"%V\" in \"index\" directive is invalid", - &value[1]); + &value[i]); return NGX_CONF_ERROR; } From noreply at nginx.com Tue Sep 23 16:05:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 23 Sep 2025 16:05:02 +0000 (UTC) Subject: [njs] Version 0.9.2. Message-ID: <20250923160502.36DC048854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/b31f7333c772ba837977363536297b2608f64047 branches: master commit: b31f7333c772ba837977363536297b2608f64047 user: Dmitry Volyntsev date: Mon, 22 Sep 2025 10:12:15 -0700 description: Version 0.9.2. --- CHANGES | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGES b/CHANGES index 719e25b4..29b51cee 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,30 @@ +Changes with njs 0.9.2 23 Sep 2025 + + nginx modules: + + *) Feature: added HTTP keepalive support for ngx.fetch() API. + + *) Improvement: added configure time check when js_import is + not defined. + + *) Bugfix: fixed merging of js_path directives. + + *) Bugfix: fixed building when http_ssl and stream_ssl + unavailable. + + Core: + + *) Change: increased the default stack size to 160k for njs VM. + + *) Feature: added njs.on('exit') API for qjs engine. + + *) Improvement: optimized memory consumption while streaming + in qjs. + + *) Bugfix: fixed building qjs engine with clang 19. + + *) Bugfix: fixed building with GCC 15 and -O3. + Changes with njs 0.9.1 10 Jul 2025 nginx modules: From noreply at nginx.com Tue Sep 23 16:06:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 23 Sep 2025 16:06:02 +0000 (UTC) Subject: [njs] Lightweight tag created: 0.9.2 Message-ID: <20250923160602.0281048F5C@pubserv1.nginx> details: https://github.com/nginx/njs/releases/tag/0.9.2 branches: commit: b31f7333c772ba837977363536297b2608f64047 user: Dmitry Volyntsev date: Mon Sep 22 10:12:15 2025 -0700 description: Version 0.9.2. From sb at nginx.com Thu Sep 25 14:51:25 2025 From: sb at nginx.com (Sergey Budnevich) Date: Thu, 25 Sep 2025 15:51:25 +0100 Subject: Important Announcement: All NGINX mailman mailing lists are retiring on September 30 Message-ID: <848CE867-9DAB-4663-A25F-AB0249CCC614@nginx.com> Hello All NGINX mailman mailing lists are retiring on September 30, as the NGINX development process has moved to GitHub. The mailing lists archives will remain accessible for historical reference at https://mailman.nginx.org You can find our GitHub repo here: https://github.com/nginx/nginx If you have questions about using NGINX or need technical help, visit the NGINX Community Forum at https://community.nginx.org. You can also find announcements, latest content, and our event schedule on the forum. The forum aims to provide an engaging environment to interact with our community. To file bugs, please create a GitHub issue. Subscribe to GitHub for release announcements. To submit a security alert, please report a vulnerability within the nginx GitHub repository, or directly to the F5 Security Incident Response Team at F5SIRT at f5.com. Thank you for being a part of the NGINX community. We appreciate your participation in the mailing lists over the years, and look forward to seeing you on GitHub and the NGINX Community Forum in the future. From noreply at nginx.com Thu Sep 25 15:26:03 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 25 Sep 2025 15:26:03 +0000 (UTC) Subject: [nginx] SNI: using the ClientHello callback. Message-ID: <20250925152603.05F063F92E@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/0373fe5d98c1515640e74fa6f4d32fac1f1d3ab2 branches: master commit: 0373fe5d98c1515640e74fa6f4d32fac1f1d3ab2 user: Sergey Kandaurov date: Tue, 28 Jan 2025 00:53:15 +0400 description: SNI: using the ClientHello callback. The change introduces an SNI based virtual server selection during early ClientHello processing. The callback is available since OpenSSL 1.1.1; for older OpenSSL versions, the previous behaviour is kept. Using the ClientHello callback sets a reasonable processing order for the "server_name" TLS extension. Notably, session resumption decision now happens after applying server configuration chosen by SNI, useful with enabled verification of client certificates, which brings consistency with BoringSSL behaviour. The change supersedes and reverts a fix made in 46b9f5d38 for TLSv1.3 resumed sessions. In addition, since the callback is invoked prior to the protocol version negotiation, this makes it possible to set "ssl_protocols" on a per-virtual server basis. To keep the $ssl_server_name variable working with TLSv1.2 resumed sessions, as previously fixed in fd97b2a80, a limited server name callback is preserved in order to acknowledge the extension. Note that to allow third-party modules to properly chain the call to ngx_ssl_client_hello_callback(), the servername callback function is passed through exdata. --- src/event/ngx_event_openssl.c | 85 ++++++++++++++++++++++++++++++++++ src/event/ngx_event_openssl.h | 15 ++++++ src/http/modules/ngx_http_ssl_module.c | 6 ++- src/http/ngx_http_request.c | 62 +++++++++++-------------- src/stream/ngx_stream_ssl_module.c | 68 +++++++++++++-------------- 5 files changed, 165 insertions(+), 71 deletions(-) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index e36f30c74..d9abcd082 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -128,6 +128,7 @@ int ngx_ssl_ticket_keys_index; int ngx_ssl_ocsp_index; int ngx_ssl_index; int ngx_ssl_certificate_name_index; +int ngx_ssl_client_hello_arg_index; u_char ngx_ssl_session_buffer[NGX_SSL_MAX_SESSION_SIZE]; @@ -270,6 +271,14 @@ ngx_ssl_init(ngx_log_t *log) return NGX_ERROR; } + ngx_ssl_client_hello_arg_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, + NULL, NULL); + if (ngx_ssl_client_hello_arg_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "SSL_CTX_get_ex_new_index() failed"); + return NGX_ERROR; + } + return NGX_OK; } @@ -1645,6 +1654,82 @@ ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) } +void +ngx_ssl_set_client_hello_callback(SSL_CTX *ssl_ctx, + ngx_ssl_client_hello_arg *cb) +{ +#ifdef SSL_CLIENT_HELLO_SUCCESS + + SSL_CTX_set_client_hello_cb(ssl_ctx, ngx_ssl_client_hello_callback, NULL); + SSL_CTX_set_ex_data(ssl_ctx, ngx_ssl_client_hello_arg_index, cb); + +#endif +} + + +#ifdef SSL_CLIENT_HELLO_SUCCESS + +int +ngx_ssl_client_hello_callback(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) +{ + u_char *p; + size_t len; + ngx_int_t rc; + ngx_str_t host; + ngx_connection_t *c; + ngx_ssl_client_hello_arg *cb; + + c = ngx_ssl_get_connection(ssl_conn); + cb = SSL_CTX_get_ex_data(c->ssl->session_ctx, + ngx_ssl_client_hello_arg_index); + + if (SSL_client_hello_get0_ext(ssl_conn, TLSEXT_TYPE_server_name, + (const unsigned char **) &p, &len) + == 0) + { + ngx_str_null(&host); + goto done; + } + + /* + * RFC 6066 mandates non-zero HostName length, we follow OpenSSL. + * No more than one ServerName is expected. + */ + + if (len < 5 + || (size_t) (p[0] << 8) + p[1] + 2 != len + || p[2] != TLSEXT_NAMETYPE_host_name + || (size_t) (p[3] << 8) + p[4] + 2 + 3 != len) + { + *ad = SSL_AD_DECODE_ERROR; + return SSL_CLIENT_HELLO_ERROR; + } + + len -= 5; + p += 5; + + if (len > TLSEXT_MAXLEN_host_name || ngx_strlchr(p, p + len, '\0')) { + *ad = SSL_AD_UNRECOGNIZED_NAME; + return SSL_CLIENT_HELLO_ERROR; + } + + host.len = len; + host.data = p; + +done: + + rc = cb->servername(ssl_conn, ad, &host); + + if (rc == SSL_TLSEXT_ERR_ALERT_FATAL) { + return SSL_CLIENT_HELLO_ERROR; + } + + return SSL_CLIENT_HELLO_SUCCESS; +} + +#endif + + ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags) { diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index e7ccd51e8..544703f61 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -151,6 +151,7 @@ struct ngx_ssl_connection_s { unsigned in_ocsp:1; unsigned early_preread:1; unsigned write_blocked:1; + unsigned sni_accepted:1; }; @@ -197,6 +198,13 @@ typedef struct { } ngx_ssl_session_cache_t; +typedef int (*ngx_ssl_servername_pt)(ngx_ssl_conn_t *, int *, void *); + +typedef struct { + ngx_ssl_servername_pt servername; +} ngx_ssl_client_hello_arg; + + #define NGX_SSL_SSLv2 0x0002 #define NGX_SSL_SSLv3 0x0004 #define NGX_SSL_TLSv1 0x0008 @@ -286,6 +294,12 @@ ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths); ngx_int_t ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data); +void ngx_ssl_set_client_hello_callback(SSL_CTX *ssl_ctx, + ngx_ssl_client_hello_arg *cb); +#ifdef SSL_CLIENT_HELLO_SUCCESS +int ngx_ssl_client_hello_callback(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); +#endif + ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags); @@ -382,6 +396,7 @@ extern int ngx_ssl_ticket_keys_index; extern int ngx_ssl_ocsp_index; extern int ngx_ssl_index; extern int ngx_ssl_certificate_name_index; +extern int ngx_ssl_client_hello_arg_index; extern u_char ngx_ssl_session_buffer[NGX_SSL_MAX_SESSION_SIZE]; diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index fbf4ab871..3778758e2 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -749,6 +749,10 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) cln->data = &conf->ssl; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + { + static ngx_ssl_client_hello_arg cb = { ngx_http_ssl_servername }; + + ngx_ssl_set_client_hello_callback(conf->ssl.ctx, &cb); if (SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, ngx_http_ssl_servername) @@ -759,7 +763,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) "dynamically to an OpenSSL library which has no tlsext support, " "therefore SNI is not available"); } - + } #endif #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 95cb1a133..6f6e975b7 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -891,27 +891,41 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) return SSL_TLSEXT_ERR_ALERT_FATAL; } + if (c->ssl->sni_accepted) { + return SSL_TLSEXT_ERR_OK; + } + hc = c->data; - servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + if (arg != NULL) { + host = *(ngx_str_t *) arg; - if (servername == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "SSL server name: null"); - goto done; + if (host.data == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "SSL server name: null"); + goto done; + } + + } else { + servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + + if (servername == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "SSL server name: null"); + goto done; + } + + host.len = ngx_strlen(servername); + host.data = (u_char *) servername; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "SSL server name: \"%s\"", servername); - - host.len = ngx_strlen(servername); + "SSL server name: \"%V\"", &host); if (host.len == 0) { goto done; } - host.data = (u_char *) servername; - rc = ngx_http_validate_host(&host, c->pool, 1); if (rc == NGX_ERROR) { @@ -933,31 +947,6 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) goto done; } - sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module); - -#if (defined TLS1_3_VERSION \ - && !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL) - - /* - * SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+, - * but servername being negotiated in every TLSv1.3 handshake - * is only returned in OpenSSL 1.1.1+ as well - */ - - if (sscf->verify) { - const char *hostname; - - hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn)); - - if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) { - c->ssl->handshake_rejected = 1; - *ad = SSL_AD_ACCESS_DENIED; - return SSL_TLSEXT_ERR_ALERT_FATAL; - } - } - -#endif - hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t)); if (hc->ssl_servername == NULL) { goto error; @@ -971,6 +960,8 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) ngx_set_connection_log(c, clcf->error_log); + sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module); + c->ssl->buffer_size = sscf->buffer_size; if (sscf->ssl.ctx) { @@ -1019,6 +1010,7 @@ done: return SSL_TLSEXT_ERR_ALERT_FATAL; } + c->ssl->sni_accepted = 1; return SSL_TLSEXT_ERR_OK; error: diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 7ce1175f1..7bf6304e4 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -555,27 +555,41 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) return SSL_TLSEXT_ERR_ALERT_FATAL; } + if (c->ssl->sni_accepted) { + return SSL_TLSEXT_ERR_OK; + } + s = c->data; - servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + if (arg) { + host = *(ngx_str_t *) arg; - if (servername == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, - "SSL server name: null"); - goto done; + if (host.data == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: null"); + goto done; + } + + } else { + servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + + if (servername == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: null"); + goto done; + } + + host.len = ngx_strlen(servername); + host.data = (u_char *) servername; } ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, - "SSL server name: \"%s\"", servername); - - host.len = ngx_strlen(servername); + "SSL server name: \"%V\"", &host); if (host.len == 0) { goto done; } - host.data = (u_char *) servername; - rc = ngx_stream_validate_host(&host, c->pool, 1); if (rc == NGX_ERROR) { @@ -596,35 +610,12 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) goto done; } - sscf = ngx_stream_get_module_srv_conf(cscf->ctx, ngx_stream_ssl_module); - -#if (defined TLS1_3_VERSION \ - && !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL) - - /* - * SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+, - * but servername being negotiated in every TLSv1.3 handshake - * is only returned in OpenSSL 1.1.1+ as well - */ - - if (sscf->verify) { - const char *hostname; - - hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn)); - - if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) { - c->ssl->handshake_rejected = 1; - *ad = SSL_AD_ACCESS_DENIED; - return SSL_TLSEXT_ERR_ALERT_FATAL; - } - } - -#endif - s->srv_conf = cscf->ctx->srv_conf; ngx_set_connection_log(c, cscf->error_log); + sscf = ngx_stream_get_module_srv_conf(cscf->ctx, ngx_stream_ssl_module); + if (sscf->ssl.ctx) { if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { goto error; @@ -663,6 +654,7 @@ done: return SSL_TLSEXT_ERR_ALERT_FATAL; } + c->ssl->sni_accepted = 1; return SSL_TLSEXT_ERR_OK; error: @@ -1002,8 +994,14 @@ ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) cln->data = &conf->ssl; #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + { + static ngx_ssl_client_hello_arg cb = { ngx_stream_ssl_servername }; + + ngx_ssl_set_client_hello_callback(conf->ssl.ctx, &cb); + SSL_CTX_set_tlsext_servername_callback(conf->ssl.ctx, ngx_stream_ssl_servername); + } #endif #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation From noreply at nginx.com Thu Sep 25 15:26:03 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 25 Sep 2025 15:26:03 +0000 (UTC) Subject: [nginx] SNI: support for early ClientHello callback with BoringSSL. Message-ID: <20250925152603.0D08A3F92F@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/7f9ced0ce0d70ae60f46ef3ed759efa75e711db4 branches: master commit: 7f9ced0ce0d70ae60f46ef3ed759efa75e711db4 user: Sergey Kandaurov date: Mon, 22 Sep 2025 19:55:16 +0400 description: SNI: support for early ClientHello callback with BoringSSL. This brings feature parity with OpenSSL after the previous change, making it possible to set SSL protocols per virtual server. --- src/event/ngx_event_openssl.c | 36 ++++++++++++++++++++++++++++++++++++ src/event/ngx_event_openssl.h | 3 +++ src/http/ngx_http_request.c | 5 +++++ src/stream/ngx_stream_ssl_module.c | 5 +++++ 4 files changed, 49 insertions(+) diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index d9abcd082..375d58be6 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -1663,6 +1663,11 @@ ngx_ssl_set_client_hello_callback(SSL_CTX *ssl_ctx, SSL_CTX_set_client_hello_cb(ssl_ctx, ngx_ssl_client_hello_callback, NULL); SSL_CTX_set_ex_data(ssl_ctx, ngx_ssl_client_hello_arg_index, cb); +#elif defined OPENSSL_IS_BORINGSSL + + SSL_CTX_set_select_certificate_cb(ssl_ctx, ngx_ssl_select_certificate); + SSL_CTX_set_ex_data(ssl_ctx, ngx_ssl_client_hello_arg_index, cb); + #endif } @@ -1727,6 +1732,37 @@ done: return SSL_CLIENT_HELLO_SUCCESS; } +#elif defined OPENSSL_IS_BORINGSSL + +enum ssl_select_cert_result_t ngx_ssl_select_certificate( + const SSL_CLIENT_HELLO *client_hello) +{ + int ad; + ngx_int_t rc; + ngx_ssl_conn_t *ssl_conn; + ngx_connection_t *c; + ngx_ssl_client_hello_arg *cb; + + ssl_conn = client_hello->ssl; + c = ngx_ssl_get_connection(ssl_conn); + cb = SSL_CTX_get_ex_data(c->ssl->session_ctx, + ngx_ssl_client_hello_arg_index); + + /* + * BoringSSL sends a hardcoded "handshake_failure" alert on errors, + * we use it to map SSL_AD_INTERNAL_ERROR. To preserve other alert + * values, error handling is postponed to the servername callback. + */ + + rc = cb->servername(ssl_conn, &ad, NULL); + + if (rc == SSL_TLSEXT_ERR_ALERT_FATAL && ad == SSL_AD_INTERNAL_ERROR) { + return ssl_select_cert_error; + } + + return ssl_select_cert_success; +} + #endif diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 544703f61..9943ee430 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -298,6 +298,9 @@ void ngx_ssl_set_client_hello_callback(SSL_CTX *ssl_ctx, ngx_ssl_client_hello_arg *cb); #ifdef SSL_CLIENT_HELLO_SUCCESS int ngx_ssl_client_hello_callback(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg); +#elif defined OPENSSL_IS_BORINGSSL +enum ssl_select_cert_result_t ngx_ssl_select_certificate( + const SSL_CLIENT_HELLO *client_hello); #endif ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 6f6e975b7..16d79c490 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -895,6 +895,11 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) return SSL_TLSEXT_ERR_OK; } + if (c->ssl->handshake_rejected) { + *ad = SSL_AD_UNRECOGNIZED_NAME; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + hc = c->data; if (arg != NULL) { diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index 7bf6304e4..75938b0a2 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -559,6 +559,11 @@ ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) return SSL_TLSEXT_ERR_OK; } + if (c->ssl->handshake_rejected) { + *ad = SSL_AD_UNRECOGNIZED_NAME; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + s = c->data; if (arg) { From noreply at nginx.com Thu Sep 25 15:29:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 25 Sep 2025 15:29:02 +0000 (UTC) Subject: [nginx] QUIC: localized OpenSSL headers used for QUIC protection. Message-ID: <20250925152902.108C93F92F@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/4c9ae11dff0da5ae76c0e4b6571ce30f7f8024bc branches: master commit: 4c9ae11dff0da5ae76c0e4b6571ce30f7f8024bc user: Sergey Kandaurov date: Wed, 30 Jul 2025 16:09:21 +0400 description: QUIC: localized OpenSSL headers used for QUIC protection. --- src/event/ngx_event_openssl.h | 8 -------- src/event/quic/ngx_event_quic_protection.c | 6 ++++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index 9943ee430..3e5483791 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -26,14 +26,6 @@ #include #endif #include -#if (NGX_QUIC) -#ifdef OPENSSL_IS_BORINGSSL -#include -#include -#else -#include -#endif -#endif #include #ifndef OPENSSL_NO_OCSP #include diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index 885843d72..c94d6ea31 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -8,6 +8,12 @@ #include #include #include +#ifdef OPENSSL_IS_BORINGSSL +#include +#include +#else +#include +#endif /* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ From noreply at nginx.com Thu Sep 25 15:29:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 25 Sep 2025 15:29:02 +0000 (UTC) Subject: [nginx] SSL: AWS-LC compatibility. Message-ID: <20250925152902.1A5C93F937@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/93ff1ee12cd33ea978fbc331988ce265b14fbdab branches: master commit: 93ff1ee12cd33ea978fbc331988ce265b14fbdab user: Sergey Kandaurov date: Wed, 30 Jul 2025 16:26:21 +0400 description: SSL: AWS-LC compatibility. --- src/event/quic/ngx_event_quic.h | 3 ++- src/event/quic/ngx_event_quic_protection.h | 2 +- src/event/quic/ngx_event_quic_ssl.c | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index bab085f46..4f899ec0a 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -18,7 +18,8 @@ #elif (defined SSL_R_MISSING_QUIC_TRANSPORT_PARAMETERS_EXTENSION) #define NGX_QUIC_QUICTLS_API 1 -#elif (defined OPENSSL_IS_BORINGSSL || defined LIBRESSL_VERSION_NUMBER) +#elif (defined OPENSSL_IS_BORINGSSL || defined OPENSSL_IS_AWSLC \ + || defined LIBRESSL_VERSION_NUMBER) #define NGX_QUIC_BORINGSSL_API 1 #else diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index b8914ddf4..7c5cf3153 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -22,7 +22,7 @@ #define NGX_QUIC_MAX_MD_SIZE 48 -#ifdef OPENSSL_IS_BORINGSSL +#if (defined OPENSSL_IS_BORINGSSL || defined OPENSSL_IS_AWSLC) #define NGX_QUIC_BORINGSSL_EVP_API 1 #define ngx_quic_cipher_t EVP_AEAD #define ngx_quic_crypto_ctx_t EVP_AEAD_CTX diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c index 355348406..a502431f4 100644 --- a/src/event/quic/ngx_event_quic_ssl.c +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -973,7 +973,7 @@ ngx_quic_init_connection(ngx_connection_t *c) } #endif -#ifdef OPENSSL_IS_BORINGSSL +#if (defined OPENSSL_IS_BORINGSSL || defined OPENSSL_IS_AWSLC) if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "quic SSL_set_quic_early_data_context() failed"); From noreply at nginx.com Thu Sep 25 15:29:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 25 Sep 2025 15:29:02 +0000 (UTC) Subject: [nginx] QUIC: a new macro to differentiate BoringSSL specific EVP API. Message-ID: <20250925152902.164B33F934@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/af436c58ca388b9926b17f8c3929ae2b343e4019 branches: master commit: af436c58ca388b9926b17f8c3929ae2b343e4019 user: Sergey Kandaurov date: Wed, 30 Jul 2025 16:23:43 +0400 description: QUIC: a new macro to differentiate BoringSSL specific EVP API. --- src/event/quic/ngx_event_quic_protection.c | 32 +++++++++++++++--------------- src/event/quic/ngx_event_quic_protection.h | 2 ++ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c index c94d6ea31..2f28737a2 100644 --- a/src/event/quic/ngx_event_quic_protection.c +++ b/src/event/quic/ngx_event_quic_protection.c @@ -8,7 +8,7 @@ #include #include #include -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) #include #include #else @@ -39,7 +39,7 @@ static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, static ngx_int_t ngx_quic_crypto_open(ngx_quic_secret_t *s, ngx_str_t *out, const u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); -#ifndef OPENSSL_IS_BORINGSSL +#if !(NGX_QUIC_BORINGSSL_EVP_API) static ngx_int_t ngx_quic_crypto_common(ngx_quic_secret_t *s, ngx_str_t *out, const u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); #endif @@ -64,7 +64,7 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers) switch (id) { case TLS1_3_CK_AES_128_GCM_SHA256: -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) ciphers->c = EVP_aead_aes_128_gcm(); #else ciphers->c = EVP_aes_128_gcm(); @@ -75,7 +75,7 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers) break; case TLS1_3_CK_AES_256_GCM_SHA384: -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) ciphers->c = EVP_aead_aes_256_gcm(); #else ciphers->c = EVP_aes_256_gcm(); @@ -86,12 +86,12 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers) break; case TLS1_3_CK_CHACHA20_POLY1305_SHA256: -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) ciphers->c = EVP_aead_chacha20_poly1305(); #else ciphers->c = EVP_chacha20_poly1305(); #endif -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); #else ciphers->hp = EVP_chacha20(); @@ -100,7 +100,7 @@ ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers) len = 32; break; -#ifndef OPENSSL_IS_BORINGSSL +#if !(NGX_QUIC_BORINGSSL_EVP_API) case TLS1_3_CK_AES_128_CCM_SHA256: ciphers->c = EVP_aes_128_ccm(); ciphers->hp = EVP_aes_128_ctr(); @@ -269,7 +269,7 @@ static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) { -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) == 0) @@ -331,7 +331,7 @@ ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, const u_char *secret, size_t secret_len, const u_char *salt, size_t salt_len) { -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, salt_len) @@ -394,7 +394,7 @@ ngx_quic_crypto_init(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, ngx_quic_md_t *key, ngx_int_t enc, ngx_log_t *log) { -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) EVP_AEAD_CTX *ctx; ctx = EVP_AEAD_CTX_new(cipher, key->data, key->len, @@ -454,7 +454,7 @@ static ngx_int_t ngx_quic_crypto_open(ngx_quic_secret_t *s, ngx_str_t *out, const u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) { -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) if (EVP_AEAD_CTX_open(s->ctx, out->data, &out->len, out->len, nonce, s->iv.len, in->data, in->len, ad->data, ad->len) != 1) @@ -474,7 +474,7 @@ ngx_int_t ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, const u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) { -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) if (EVP_AEAD_CTX_seal(s->ctx, out->data, &out->len, out->len, nonce, s->iv.len, in->data, in->len, ad->data, ad->len) != 1) @@ -490,7 +490,7 @@ ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, const u_char *nonce, } -#ifndef OPENSSL_IS_BORINGSSL +#if !(NGX_QUIC_BORINGSSL_EVP_API) static ngx_int_t ngx_quic_crypto_common(ngx_quic_secret_t *s, ngx_str_t *out, @@ -569,7 +569,7 @@ void ngx_quic_crypto_cleanup(ngx_quic_secret_t *s) { if (s->ctx) { -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) EVP_AEAD_CTX_free(s->ctx); #else EVP_CIPHER_CTX_free(s->ctx); @@ -585,7 +585,7 @@ ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, ngx_quic_secret_t *s, { EVP_CIPHER_CTX *ctx; -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) if (cipher == (EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { /* no EVP interface */ s->hp_ctx = NULL; @@ -621,7 +621,7 @@ ngx_quic_crypto_hp(ngx_quic_secret_t *s, u_char *out, u_char *in, ctx = s->hp_ctx; -#ifdef OPENSSL_IS_BORINGSSL +#if (NGX_QUIC_BORINGSSL_EVP_API) uint32_t cnt; if (ctx == NULL) { diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h index fddc6083a..b8914ddf4 100644 --- a/src/event/quic/ngx_event_quic_protection.h +++ b/src/event/quic/ngx_event_quic_protection.h @@ -23,9 +23,11 @@ #ifdef OPENSSL_IS_BORINGSSL +#define NGX_QUIC_BORINGSSL_EVP_API 1 #define ngx_quic_cipher_t EVP_AEAD #define ngx_quic_crypto_ctx_t EVP_AEAD_CTX #else +#define NGX_QUIC_BORINGSSL_EVP_API 0 #define ngx_quic_cipher_t EVP_CIPHER #define ngx_quic_crypto_ctx_t EVP_CIPHER_CTX #endif From noreply at nginx.com Fri Sep 26 12:51:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 26 Sep 2025 12:51:02 +0000 (UTC) Subject: [nginx] Upstream: overflow detection in Cache-Control delta-seconds. Message-ID: <20250926125102.C38943FEAC@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/8255bd29ac9a7bcdc317a8889420554e00d435cb branches: master commit: 8255bd29ac9a7bcdc317a8889420554e00d435cb user: Sergey Kandaurov date: Wed, 10 Sep 2025 18:39:52 +0400 description: Upstream: overflow detection in Cache-Control delta-seconds. Overflowing calculations are now aligned to the greatest positive integer as specified in RFC 9111, Section 1.2.2. --- src/http/ngx_http_upstream.c | 81 +++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index de0f92a4f..07e51a966 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -116,6 +116,10 @@ static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, static ngx_int_t ngx_http_upstream_process_cache_control(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); +#if (NGX_HTTP_CACHE) +static ngx_int_t ngx_http_upstream_process_delta_seconds(u_char *p, + u_char *last); +#endif static ngx_int_t ngx_http_upstream_ignore_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_expires(ngx_http_request_t *r, @@ -5066,18 +5070,9 @@ ngx_http_upstream_process_cache_control(ngx_http_request_t *r, } if (p) { - n = 0; - - for (p += offset; p < last; p++) { - if (*p == ',' || *p == ';' || *p == ' ') { - break; - } - - if (*p >= '0' && *p <= '9') { - n = n * 10 + (*p - '0'); - continue; - } + n = ngx_http_upstream_process_delta_seconds(p + offset, last); + if (n == NGX_ERROR) { u->cacheable = 0; return NGX_OK; } @@ -5087,7 +5082,8 @@ ngx_http_upstream_process_cache_control(ngx_http_request_t *r, return NGX_OK; } - r->cache->valid_sec = ngx_time() + n; + r->cache->valid_sec = ngx_min((ngx_uint_t) ngx_time() + n, + NGX_MAX_INT_T_VALUE); u->headers_in.expired = 0; } @@ -5097,18 +5093,9 @@ extensions: 23 - 1); if (p) { - n = 0; - - for (p += 23; p < last; p++) { - if (*p == ',' || *p == ';' || *p == ' ') { - break; - } - - if (*p >= '0' && *p <= '9') { - n = n * 10 + (*p - '0'); - continue; - } + n = ngx_http_upstream_process_delta_seconds(p + 23, last); + if (n == NGX_ERROR) { u->cacheable = 0; return NGX_OK; } @@ -5120,18 +5107,9 @@ extensions: p = ngx_strlcasestrn(start, last, (u_char *) "stale-if-error=", 15 - 1); if (p) { - n = 0; - - for (p += 15; p < last; p++) { - if (*p == ',' || *p == ';' || *p == ' ') { - break; - } - - if (*p >= '0' && *p <= '9') { - n = n * 10 + (*p - '0'); - continue; - } + n = ngx_http_upstream_process_delta_seconds(p + 15, last); + if (n == NGX_ERROR) { u->cacheable = 0; return NGX_OK; } @@ -5145,6 +5123,41 @@ extensions: } +#if (NGX_HTTP_CACHE) + +static ngx_int_t +ngx_http_upstream_process_delta_seconds(u_char *p, u_char *last) +{ + ngx_int_t n, cutoff, cutlim; + + cutoff = NGX_MAX_INT_T_VALUE / 10; + cutlim = NGX_MAX_INT_T_VALUE % 10; + + n = 0; + + for ( /* void */ ; p < last; p++) { + if (*p == ',' || *p == ';' || *p == ' ') { + break; + } + + if (*p < '0' || *p > '9') { + return NGX_ERROR; + } + + if (n >= cutoff && (n > cutoff || *p - '0' > cutlim)) { + n = NGX_MAX_INT_T_VALUE; + break; + } + + n = n * 10 + (*p - '0'); + } + + return n; +} + +#endif + + static ngx_int_t ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) From noreply at nginx.com Fri Sep 26 13:05:03 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 26 Sep 2025 13:05:03 +0000 (UTC) Subject: [nginx] Mail: xtext encoding (RFC 3461) in XCLIENT LOGIN. Message-ID: <20250926130503.096A23FEC4@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/6f81314a070201afc4e25b975b1f915698cff634 branches: master commit: 6f81314a070201afc4e25b975b1f915698cff634 user: Sergey Kandaurov date: Thu, 11 Sep 2025 18:23:10 +0400 description: Mail: xtext encoding (RFC 3461) in XCLIENT LOGIN. The XCLIENT command uses xtext encoding for attribute values, as specified in https://www.postfix.org/XCLIENT_README.html. Reported by Igor Morgenstern of Aisle Research. --- src/core/ngx_string.c | 32 +++++++++++++++++++++++++++++--- src/core/ngx_string.h | 1 + src/mail/ngx_mail_proxy_module.c | 14 +++++++++++++- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/core/ngx_string.c b/src/core/ngx_string.c index f8f738472..10fe764c3 100644 --- a/src/core/ngx_string.c +++ b/src/core/ngx_string.c @@ -1494,8 +1494,9 @@ ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len) uintptr_t ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) { - ngx_uint_t n; + u_char prefix; uint32_t *escape; + ngx_uint_t n; static u_char hex[] = "0123456789ABCDEF"; /* @@ -1633,11 +1634,36 @@ ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) /* mail_auth is the same as memcached */ + /* " ", "+", "=", not allowed */ + + static uint32_t mail_xtext[] = { + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + + /* ?>=< ;:98 7654 3210 /.-, +*)( '&%$ #"! */ + 0x20000801, /* 0010 0000 0000 0000 0000 1000 0000 0001 */ + + /* _^]\ [ZYX WVUT SRQP ONML KJIH GFED CBA@ */ + 0x00000000, /* 0000 0000 0000 0000 0000 0000 0000 0000 */ + + /* ~}| {zyx wvut srqp onml kjih gfed cba` */ + 0x80000000, /* 1000 0000 0000 0000 0000 0000 0000 0000 */ + + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + 0xffffffff, /* 1111 1111 1111 1111 1111 1111 1111 1111 */ + }; + static uint32_t *map[] = - { uri, args, uri_component, html, refresh, memcached, memcached }; + { uri, args, uri_component, html, refresh, memcached, memcached, + mail_xtext }; + + static u_char map_char[] = + { '%', '%', '%', '%', '%', '%', '%', '+' }; escape = map[type]; + prefix = map_char[type]; if (dst == NULL) { @@ -1658,7 +1684,7 @@ ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type) while (size) { if (escape[*src >> 5] & (1U << (*src & 0x1f))) { - *dst++ = '%'; + *dst++ = prefix; *dst++ = hex[*src >> 4]; *dst++ = hex[*src & 0xf]; src++; diff --git a/src/core/ngx_string.h b/src/core/ngx_string.h index 713eb42a7..183a20521 100644 --- a/src/core/ngx_string.h +++ b/src/core/ngx_string.h @@ -203,6 +203,7 @@ u_char *ngx_utf8_cpystrn(u_char *dst, u_char *src, size_t n, size_t len); #define NGX_ESCAPE_REFRESH 4 #define NGX_ESCAPE_MEMCACHED 5 #define NGX_ESCAPE_MAIL_AUTH 6 +#define NGX_ESCAPE_MAIL_XTEXT 7 #define NGX_UNESCAPE_URI 1 #define NGX_UNESCAPE_REDIRECT 2 diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c index 1c6d0372e..84a7f61a4 100644 --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -531,6 +531,7 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) ngx_int_t rc; ngx_str_t line, auth, encoded; ngx_buf_t *b; + uintptr_t n; ngx_connection_t *c; ngx_mail_session_t *s; ngx_mail_proxy_conf_t *pcf; @@ -627,6 +628,10 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) CRLF) - 1 + s->connection->addr_text.len + s->login.len + s->host.len; + n = ngx_escape_uri(NULL, s->login.data, s->login.len, + NGX_ESCAPE_MAIL_XTEXT); + line.len += n * 2; + #if (NGX_HAVE_INET6) if (s->connection->sockaddr->sa_family == AF_INET6) { line.len += sizeof("IPV6:") - 1; @@ -654,7 +659,14 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) if (s->login.len && !pcf->smtp_auth) { p = ngx_cpymem(p, " LOGIN=", sizeof(" LOGIN=") - 1); - p = ngx_copy(p, s->login.data, s->login.len); + + if (n == 0) { + p = ngx_copy(p, s->login.data, s->login.len); + + } else { + p = (u_char *) ngx_escape_uri(p, s->login.data, s->login.len, + NGX_ESCAPE_MAIL_XTEXT); + } } p = ngx_cpymem(p, " NAME=", sizeof(" NAME=") - 1); From noreply at nginx.com Tue Sep 30 18:45:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 30 Sep 2025 18:45:02 +0000 (UTC) Subject: [nginx] Added F5 CLA workflow. Message-ID: <20250930184502.CE9453F93B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/c2a266fa7859bf4dc242a1b029077b72afe93184 branches: master commit: c2a266fa7859bf4dc242a1b029077b72afe93184 user: Maryna-f5 <84339011+Maryna-f5 at users.noreply.github.com> date: Thu, 25 Sep 2025 12:51:40 -0700 description: Added F5 CLA workflow. --- .github/workflows/f5_cla.yml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/.github/workflows/f5_cla.yml b/.github/workflows/f5_cla.yml new file mode 100644 index 000000000..43e473eab --- /dev/null +++ b/.github/workflows/f5_cla.yml @@ -0,0 +1,41 @@ +--- +name: F5 CLA +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] +permissions: read-all +jobs: + f5-cla: + name: F5 CLA + runs-on: ubuntu-24.04 + permissions: + actions: write + pull-requests: write + statuses: write + steps: + - name: Run F5 Contributor License Agreement (CLA) assistant + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have hereby read the F5 CLA and agree to its terms') || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action at ca4a40a7d1004f18d9960b404b97e5f30a505a08 # v2.6.1 + with: + # Path to the CLA document. + path-to-document: https://github.com/f5/f5-cla/blob/main/docs/f5_cla.md + # Custom CLA messages. + custom-notsigned-prcomment: '🎉 Thank you for your contribution! It appears you have not yet signed the [F5 Contributor License Agreement (CLA)](https://github.com/f5/f5-cla/blob/main/docs/f5_cla.md), which is required for your changes to be incorporated into an F5 Open Source Software (OSS) project. Please kindly read the [F5 CLA](https://github.com/f5/f5-cla/blob/main/docs/f5_cla.md) and reply on a new comment with the following text to agree:' + custom-pr-sign-comment: 'I have hereby read the F5 CLA and agree to its terms' + custom-allsigned-prcomment: '✅ All required contributors have signed the F5 CLA for this PR. Thank you!' + # Remote repository storing CLA signatures. + remote-organization-name: f5 + remote-repository-name: f5-cla-data + # Branch where CLA signatures are stored. + branch: main + path-to-signatures: signatures/signatures.json + # Comma separated list of usernames for maintainers or any other individuals who should not be prompted for a CLA. + # NOTE: You will want to edit the usernames to suit your project needs. + allowlist: bot* + # Do not lock PRs after a merge. + lock-pullrequest-aftermerge: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PERSONAL_ACCESS_TOKEN: ${{ secrets.F5_CLA_TOKEN }}