From noreply at nginx.com Wed Jun 4 05:22:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Jun 2025 05:22:02 +0000 (UTC) Subject: [njs] Fixed Function constructor template injection. Message-ID: <20250604052202.DB9E647E9D@pubserv1.nginx> details: https://github.com/nginx/njs/commit/aa358e16337e535240d7976c9232dd06fe0ef5d8 branches: master commit: aa358e16337e535240d7976c9232dd06fe0ef5d8 user: Dmitry Volyntsev date: Mon, 2 Jun 2025 17:18:22 -0700 description: Fixed Function constructor template injection. The Function constructor uses a `(function() {})` template to construct new functions. This approach was vulnerable to template injection where malicious code could close the function body early. This fixes issue #921. --- src/njs_function.c | 14 +++++++++++--- src/test/njs_unit_test.c | 26 +++++++++++++++++--------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/njs_function.c b/src/njs_function.c index 0840d437..1db0d7ab 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -1049,7 +1049,7 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } - njs_chb_append_literal(&chain, "){"); + njs_chb_append_literal(&chain, "\n){\n"); if (nargs > 1) { ret = njs_value_to_chain(vm, &chain, njs_argument(args, nargs - 1)); @@ -1058,7 +1058,7 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } } - njs_chb_append_literal(&chain, "})"); + njs_chb_append_literal(&chain, "\n})"); ret = njs_chb_join(&chain, &str); if (njs_slow_path(ret != NJS_OK)) { @@ -1125,7 +1125,15 @@ njs_function_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_chb_destroy(&chain); - lambda = ((njs_vmcode_function_t *) generator.code_start)->lambda; + if ((code->end - code->start) + != (sizeof(njs_vmcode_function_t) + sizeof(njs_vmcode_return_t)) + || ((njs_vmcode_generic_t *) code->start)->code != NJS_VMCODE_FUNCTION) + { + njs_syntax_error(vm, "single function literal required"); + return NJS_ERROR; + } + + lambda = ((njs_vmcode_function_t *) code->start)->lambda; function = njs_function_alloc(vm, lambda, (njs_bool_t) async); if (njs_slow_path(function == NULL)) { diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c index 27fcbd82..3d466b96 100644 --- a/src/test/njs_unit_test.c +++ b/src/test/njs_unit_test.c @@ -14189,22 +14189,22 @@ static njs_unit_test_t njs_test[] = njs_str("true") }, { njs_str("new Function('('.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, { njs_str("new Function('{'.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \")\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \")\" in runtime") }, { njs_str("new Function('['.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, { njs_str("new Function('`'.repeat(2**13));"), njs_str("[object Function]") }, { njs_str("new Function('{['.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, { njs_str("new Function('{;'.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \")\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \")\" in runtime") }, { njs_str("(new Function('1;'.repeat(2**13) + 'return 2'))()"), njs_str("2") }, @@ -14216,7 +14216,7 @@ static njs_unit_test_t njs_test[] = njs_str("-4") }, { njs_str("new Function('new '.repeat(2**13));"), - njs_str("SyntaxError: Unexpected token \"}\" in runtime:1") }, + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, { njs_str("(new Function('return ' + 'typeof '.repeat(2**13) + 'x'))()"), njs_str("string") }, @@ -14282,7 +14282,13 @@ static njs_unit_test_t njs_test[] = njs_str("ReferenceError: \"foo\" is not defined") }, { njs_str("this.NN = {}; var f = Function('eval = 42;'); f()"), - njs_str("SyntaxError: Identifier \"eval\" is forbidden as left-hand in assignment in runtime:1") }, + njs_str("SyntaxError: Identifier \"eval\" is forbidden as left-hand in assignment in runtime") }, + + { njs_str("new Function('}); let a; a; function o(){}; //')"), + njs_str("SyntaxError: Unexpected token \"}\" in runtime") }, + + { njs_str("new Function('}); let a; a; function o(){}; ({')"), + njs_str("SyntaxError: single function literal required") }, { njs_str("RegExp()"), njs_str("/(?:)/") }, @@ -19811,7 +19817,7 @@ static njs_unit_test_t njs_test[] = njs_str("[object AsyncFunction]") }, { njs_str("let f = new Function('x', 'await 1; return x'); f(1)"), - njs_str("SyntaxError: await is only valid in async functions in runtime:1") }, + njs_str("SyntaxError: await is only valid in async functions in runtime") }, { njs_str("new AsyncFunction()"), njs_str("ReferenceError: \"AsyncFunction\" is not defined") }, @@ -21676,7 +21682,9 @@ done: return NJS_ERROR; } - success = njs_strstr_eq(&expected->ret, &s); + success = expected->ret.length <= s.length + && (memcmp(expected->ret.start, s.start, expected->ret.length) + == 0); if (!success) { njs_stderror("njs(\"%V\")\nexpected: \"%V\"\n got: \"%V\"\n", &expected->script, &expected->ret, &s); From noreply at nginx.com Wed Jun 4 16:03:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Jun 2025 16:03:02 +0000 (UTC) Subject: [njs] Improved README.md clarity with angle bracket explanations. Message-ID: <20250604160302.99F22477E7@pubserv1.nginx> details: https://github.com/nginx/njs/commit/5b791257e499fc5e76181121011415086735c094 branches: master commit: 5b791257e499fc5e76181121011415086735c094 user: Dmitry Volyntsev date: Tue, 3 Jun 2025 22:37:32 -0700 description: Improved README.md clarity with angle bracket explanations. --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 10ef0ba7..f81075ce 100644 --- a/README.md +++ b/README.md @@ -310,20 +310,30 @@ https://github.com/nginx/nginx.git ## Building NGINX JavaScript as a module of NGINX To build NGINX JavaScript as a dynamic module, execute the following commands from the NGINX source code repository's root directory: +> [!NOTE] +> Replace `` with the actual path to your NJS source directory. + ```bash auto/configure --add-dynamic-module=/nginx ``` To build with [QuickJS](https://nginx.org/en/docs/njs/engine.html) support, provide include and library path using `--with-cc-opt=` and `--with-ld-opt=` options: + +> [!NOTE] +> Replace `` with the actual path to your NJS source directory and `` with the actual path to your QuickJS source directory. + ```bash auto/configure --add-dynamic-module=/nginx \ - --with-cc-opt="-I" --with-ld-opt="-L" + --with-cc-opt="-I" \ + --with-ld-opt="-L" ``` > [!WARNING] > By default, this method will only build the `ngx_http_js_module` module. To use NJS with the NGINX Stream module, you'll need to enable it during the `configure` step so it builds with the NGINX binary. Doing so will automatically compile the `ngx_stream_js_module` module when NJS is added to the build. One way of accomplishing this is to alter the `configure` step to: +> > ```bash -> auto/configure --with-stream --add-dynamic-module=/nginx +> auto/configure --with-stream \ +> --add-dynamic-module=/nginx > ``` Compile the module From noreply at nginx.com Wed Jun 4 23:56:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 4 Jun 2025 23:56:02 +0000 (UTC) Subject: [njs] Fixed gcc compilation with O3 optimization level. Message-ID: <20250604235602.923CA48C14@pubserv1.nginx> details: https://github.com/nginx/njs/commit/98ac2d7ee5e697c1f9830c63d6e1d4d5388729b3 branches: master commit: 98ac2d7ee5e697c1f9830c63d6e1d4d5388729b3 user: Dmitry Volyntsev date: Mon, 2 Jun 2025 19:08:27 -0700 description: Fixed gcc compilation with O3 optimization level. --- src/qjs.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/qjs.c b/src/qjs.c index 7993de1b..9c0fcdb4 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -911,6 +911,11 @@ qjs_to_bytes(JSContext *ctx, qjs_bytes_t *bytes, JSValueConst value) goto string; } + /* GCC complains about uninitialized variables. */ + + byte_offset = 0; + byte_length = 0; + val = JS_GetTypedArrayBuffer(ctx, value, &byte_offset, &byte_length, NULL); if (!JS_IsException(val)) { bytes->start = JS_GetArrayBuffer(ctx, &bytes->length, val); @@ -967,6 +972,11 @@ qjs_typed_array_data(JSContext *ctx, JSValueConst value, njs_str_t *data) size_t byte_offset, byte_length; JSValue ab; + /* GCC complains about uninitialized variables. */ + + byte_offset = 0; + byte_length = 0; + /* TODO: DataView. */ ab = JS_GetTypedArrayBuffer(ctx, value, &byte_offset, &byte_length, From noreply at nginx.com Sun Jun 8 05:38:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sun, 8 Jun 2025 05:38:02 +0000 (UTC) Subject: [njs] Fixed constant is too large for 'long' warning on mips -mabi=n32. Message-ID: <20250608053802.D018048854@pubserv1.nginx> details: https://github.com/nginx/njs/commit/78656d481141bef164eb53a749e40019f18f1aec branches: master commit: 78656d481141bef164eb53a749e40019f18f1aec user: Dmitry Volyntsev date: Wed, 4 Jun 2025 22:54:54 -0700 description: Fixed constant is too large for 'long' warning on mips -mabi=n32. Prodded by Orgad Shaneh. --- src/njs_clang.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/njs_clang.h b/src/njs_clang.h index 2bd45a74..af9f3350 100644 --- a/src/njs_clang.h +++ b/src/njs_clang.h @@ -111,7 +111,7 @@ njs_leading_zeros64(uint64_t x) n = 0; - while ((x & 0x8000000000000000) == 0) { + while ((x & 0x8000000000000000ULL) == 0) { n++; x <<= 1; } From noreply at nginx.com Sun Jun 8 05:38:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sun, 8 Jun 2025 05:38:02 +0000 (UTC) Subject: [njs] Fixed compilation with old gcc. Message-ID: <20250608053802.D496448F66@pubserv1.nginx> details: https://github.com/nginx/njs/commit/1334534ef7a0733b3a2f27a813ce7f15ae4d2354 branches: master commit: 1334534ef7a0733b3a2f27a813ce7f15ae4d2354 user: Dmitry Volyntsev date: Wed, 4 Jun 2025 23:07:11 -0700 description: Fixed compilation with old gcc. This fixed compilation issues with gcc-4.1. --- src/njs_value.c | 5 +++-- src/njs_vmcode.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/njs_value.c b/src/njs_value.c index a097e575..86fb6444 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -8,8 +8,9 @@ #include -static njs_int_t njs_object_property_query(njs_vm_t *vm, - njs_property_query_t *pq, njs_object_t *object, uint32_t atom_id); +njs_inline njs_int_t +njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq, + njs_object_t *object, uint32_t atom_id); static njs_int_t njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_array_t *array, uint32_t index, uint32_t atom_id); diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index 3826f9eb..c0e8333d 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -2591,7 +2591,7 @@ njs_function_frame_create(njs_vm_t *vm, njs_value_t *value, } -inline njs_object_t * +njs_object_t * njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor) { njs_value_t proto, bound; From noreply at nginx.com Thu Jun 12 03:25:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 12 Jun 2025 03:25:02 +0000 (UTC) Subject: [njs] Removed NGX_MAX_PATH define. Message-ID: <20250612032502.4958F47EB5@pubserv1.nginx> details: https://github.com/nginx/njs/commit/3733afd906187fcc38f5935c8f79c3dfe28bbd79 branches: master commit: 3733afd906187fcc38f5935c8f79c3dfe28bbd79 user: Orgad Shaneh date: Mon, 9 Jun 2025 16:59:22 +0300 description: Removed NGX_MAX_PATH define. Already defined in ngx_files.h (included from ngx_core.h). --- nginx/ngx_js.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nginx/ngx_js.c b/nginx/ngx_js.c index 515218b9..448073b9 100644 --- a/nginx/ngx_js.c +++ b/nginx/ngx_js.c @@ -31,12 +31,6 @@ typedef struct { } ngx_js_rejected_promise_t; -#if defined(PATH_MAX) -#define NGX_MAX_PATH PATH_MAX -#else -#define NGX_MAX_PATH 4096 -#endif - typedef struct { int fd; njs_str_t name; From noreply at nginx.com Fri Jun 13 19:58:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 13 Jun 2025 19:58:02 +0000 (UTC) Subject: [njs] Fixed %TypedArray%.from() with buffer is detached by mapper. Message-ID: <20250613195802.02C2A3F4D8@pubserv1.nginx> details: https://github.com/nginx/njs/commit/d10a85b24981ad2ba602da7b29c870cc596fda70 branches: master commit: d10a85b24981ad2ba602da7b29c870cc596fda70 user: Dmitry Volyntsev date: Thu, 12 Jun 2025 15:16:41 -0700 description: Fixed %TypedArray%.from() with buffer is detached by mapper. --- src/njs_typed_array.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c index 19399e3b..83e3b9f1 100644 --- a/src/njs_typed_array.c +++ b/src/njs_typed_array.c @@ -324,13 +324,14 @@ static njs_int_t njs_typed_array_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { - double num; - int64_t length, i; - njs_int_t ret; - njs_value_t *this, *source, *mapfn; - njs_value_t arguments[3], value; - njs_function_t *function; - njs_typed_array_t *array; + double num; + int64_t length, i; + njs_int_t ret; + njs_value_t *this, *source, *mapfn; + njs_value_t arguments[3], value; + njs_function_t *function; + njs_typed_array_t *array; + njs_array_buffer_t *buffer; this = njs_argument(args, 0); @@ -371,6 +372,7 @@ njs_typed_array_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, } array = njs_typed_array(retval); + buffer = njs_typed_array_buffer(array); arguments[0] = *njs_arg(args, nargs, 3); for (i = 0; i < length; i++) { @@ -393,7 +395,9 @@ njs_typed_array_from(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, return NJS_ERROR; } - njs_typed_array_prop_set(vm, array, i, num); + if (!njs_is_detached_buffer(buffer)) { + njs_typed_array_prop_set(vm, array, i, num); + } } njs_set_typed_array(retval, array); From noreply at nginx.com Fri Jun 13 19:58:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 13 Jun 2025 19:58:02 +0000 (UTC) Subject: [njs] Fixed %TypedArray%.prototype.slice() with overlapping buffers. Message-ID: <20250613195802.077664863D@pubserv1.nginx> details: https://github.com/nginx/njs/commit/efb0454a59f49dc8874d772c8c245460f49e6671 branches: master commit: efb0454a59f49dc8874d772c8c245460f49e6671 user: Dmitry Volyntsev date: Thu, 12 Jun 2025 15:34:39 -0700 description: Fixed %TypedArray%.prototype.slice() with overlapping buffers. --- src/njs_typed_array.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c index 83e3b9f1..f886dca6 100644 --- a/src/njs_typed_array.c +++ b/src/njs_typed_array.c @@ -912,6 +912,20 @@ njs_typed_array_prototype_fill(njs_vm_t *vm, njs_value_t *args, } +static void +njs_slice_memcpy(uint8_t *dst, const uint8_t *src, size_t len) +{ + if (dst + len <= src || dst >= src + len) { + /* no overlap: can use memcpy */ + memcpy(dst, src, len); + + } else { + while (len-- != 0) + *dst++ = *src++; + } +} + + njs_int_t njs_typed_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t copy, njs_value_t *retval) @@ -990,7 +1004,7 @@ njs_typed_array_prototype_slice(njs_vm_t *vm, njs_value_t *args, start = start * element_size; count = count * element_size; - memcpy(&new_buffer->u.u8[0], &buffer->u.u8[start], count); + njs_slice_memcpy(&new_buffer->u.u8[0], &buffer->u.u8[start], count); } else { for (i = 0; i < count; i++) { From noreply at nginx.com Fri Jun 13 19:58:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 13 Jun 2025 19:58:02 +0000 (UTC) Subject: [njs] Fixed handling of detached buffer for typed arrays. Message-ID: <20250613195802.0D5FF4863E@pubserv1.nginx> details: https://github.com/nginx/njs/commit/d5bcd36d321fb578ffd1c804ec2d206c3b3e945e branches: master commit: d5bcd36d321fb578ffd1c804ec2d206c3b3e945e user: Dmitry Volyntsev date: Thu, 12 Jun 2025 17:20:07 -0700 description: Fixed handling of detached buffer for typed arrays. --- src/njs_typed_array.c | 5 +++++ src/njs_value.c | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/njs_typed_array.c b/src/njs_typed_array.c index f886dca6..d7ca2e83 100644 --- a/src/njs_typed_array.c +++ b/src/njs_typed_array.c @@ -655,6 +655,11 @@ njs_typed_array_set_value(njs_vm_t *vm, njs_typed_array_t *array, return ret; } + buffer = njs_typed_array_buffer(array); + if (njs_slow_path(njs_is_detached_buffer(buffer))) { + return NJS_OK; + } + buffer = njs_typed_array_writable(vm, array); if (njs_slow_path(buffer == NULL)) { return NJS_ERROR; diff --git a/src/njs_value.c b/src/njs_value.c index 86fb6444..0c616a37 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -1019,8 +1019,7 @@ njs_value_property(njs_vm_t *vm, njs_value_t *value, uint32_t atom_id, tarray = njs_typed_array(value); if (njs_slow_path(njs_is_detached_buffer(tarray->buffer))) { - njs_type_error(vm, "detached buffer"); - return NJS_ERROR; + goto not_found; } if (njs_slow_path(index >= njs_typed_array_length(tarray))) { @@ -1109,6 +1108,7 @@ slow_path: break; case NJS_DECLINED: +not_found: njs_set_undefined(retval); return NJS_DECLINED; From noreply at nginx.com Fri Jun 13 19:58:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Fri, 13 Jun 2025 19:58:02 +0000 (UTC) Subject: [njs] Tests: tuned periodic test for delays. Message-ID: <20250613195802.1222F4863F@pubserv1.nginx> details: https://github.com/nginx/njs/commit/3a22f42628e57f711bfc328e10388b2345f58647 branches: master commit: 3a22f42628e57f711bfc328e10388b2345f58647 user: Dmitry Volyntsev date: Thu, 12 Jun 2025 17:54:43 -0700 description: Tests: tuned periodic test for delays. --- nginx/t/js_periodic.t | 2 +- nginx/t/js_periodic_fetch.t | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/nginx/t/js_periodic.t b/nginx/t/js_periodic.t index d6868935..7e134588 100644 --- a/nginx/t/js_periodic.t +++ b/nginx/t/js_periodic.t @@ -56,7 +56,7 @@ http { server_name localhost; location @periodic { - js_periodic test.tick interval=30ms jitter=1ms; + js_periodic test.tick interval=20ms jitter=1ms; js_periodic test.timer interval=1s worker_affinity=all; js_periodic test.overrun interval=30ms; js_periodic test.affinity interval=50ms worker_affinity=0101; diff --git a/nginx/t/js_periodic_fetch.t b/nginx/t/js_periodic_fetch.t index a323afb8..39385132 100644 --- a/nginx/t/js_periodic_fetch.t +++ b/nginx/t/js_periodic_fetch.t @@ -99,12 +99,11 @@ $t->write_file('test.js', <plan(3); select undef, undef, undef, 0.1; -like(http_get('/test_fetch'), qr/true/, 'periodic fetch test'); -like(http_get('/test_multiple_fetches'), qr/true/, 'multiple fetch test'); +like(http_get('/test_fetch'), qr/(ok)+/, 'periodic fetch test'); +like(http_get('/test_multiple_fetches'), qr/ok\@foo/, 'multiple fetch test'); $t->stop(); From noreply at nginx.com Tue Jun 17 00:37:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 17 Jun 2025 00:37:02 +0000 (UTC) Subject: [njs] Moving child function declaration instantiation to bytecode. Message-ID: <20250617003702.CD43249060@pubserv1.nginx> details: https://github.com/nginx/njs/commit/60e28d7bde0f83adf825976fbabf9f73ccae698c branches: master commit: 60e28d7bde0f83adf825976fbabf9f73ccae698c user: Dmitry Volyntsev date: Mon, 9 Jun 2025 18:38:15 -0700 description: Moving child function declaration instantiation to bytecode. Children functions need to be hoisted and instantiated at the beginning of a function call. Previously, it was done as a part of C code that implements JS function call. Now, each child function is instantiated by FUNCTION instruction at a function prelude. This makes global and function code similar, which in turn allows to get rid of FUNCTION COPY instruction which was only needed for global code. --- src/njs_disassembler.c | 3 -- src/njs_function.c | 24 -------------- src/njs_function.h | 3 -- src/njs_generator.c | 88 +++++++++++++++++++++++++++++++------------------- src/njs_parser.h | 3 +- src/njs_variable.c | 3 +- src/njs_vm.c | 5 --- src/njs_vmcode.c | 47 +++------------------------ src/njs_vmcode.h | 8 ----- 9 files changed, 63 insertions(+), 121 deletions(-) diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c index c7927bf5..72ea63b9 100644 --- a/src/njs_disassembler.c +++ b/src/njs_disassembler.c @@ -30,9 +30,6 @@ static njs_code_name_t code_names[] = { { NJS_VMCODE_TEMPLATE_LITERAL, sizeof(njs_vmcode_template_literal_t), njs_str("TEMPLATE LITERAL") }, - { NJS_VMCODE_FUNCTION_COPY, sizeof(njs_vmcode_function_copy_t), - njs_str("FUNCTION COPY ") }, - { NJS_VMCODE_PROPERTY_GET, sizeof(njs_vmcode_prop_get_t), njs_str("PROP GET ") }, { NJS_VMCODE_PROPERTY_ATOM_GET, sizeof(njs_vmcode_prop_get_t), diff --git a/src/njs_function.c b/src/njs_function.c index 1db0d7ab..06948177 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -523,7 +523,6 @@ njs_function_lambda_call(njs_vm_t *vm, njs_value_t *retval, void *promise_cap) njs_value_t *args, **local, *value; njs_value_t **cur_local, **cur_closures; njs_function_t *function; - njs_declaration_t *declr; njs_function_lambda_t *lambda; frame = (njs_frame_t *) vm->top_frame; @@ -582,29 +581,6 @@ njs_function_lambda_call(njs_vm_t *vm, njs_value_t *retval, void *promise_cap) vm->active_frame = frame; - /* Closures */ - - n = lambda->ndeclarations; - - while (n != 0) { - n--; - - declr = &lambda->declarations[n]; - value = njs_scope_value(vm, declr->index); - - *value = *declr->value; - - function = njs_function_value_copy(vm, value); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - - ret = njs_function_capture_closure(vm, function, function->u.lambda); - if (njs_slow_path(ret != NJS_OK)) { - return ret; - } - } - ret = njs_vmcode_interpreter(vm, lambda->start, retval, promise_cap, NULL); /* Restore current level. */ diff --git a/src/njs_function.h b/src/njs_function.h index 6516ff78..5aa98dd3 100644 --- a/src/njs_function.h +++ b/src/njs_function.h @@ -13,9 +13,6 @@ struct njs_function_lambda_s { uint32_t nclosures; uint32_t nlocal; - njs_declaration_t *declarations; - uint32_t ndeclarations; - njs_index_t self; uint32_t nargs; diff --git a/src/njs_generator.c b/src/njs_generator.c index e8c61c0a..bb443017 100644 --- a/src/njs_generator.c +++ b/src/njs_generator.c @@ -890,11 +890,10 @@ static njs_int_t njs_generate_name(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node) { - njs_int_t ret; - njs_variable_t *var; - njs_parser_scope_t *scope; - njs_vmcode_variable_t *variable; - njs_vmcode_function_copy_t *copy; + njs_int_t ret; + njs_variable_t *var; + njs_parser_scope_t *scope; + njs_vmcode_variable_t *variable; var = njs_variable_reference(vm, node); if (njs_slow_path(var == NULL)) { @@ -906,13 +905,6 @@ njs_generate_name(njs_vm_t *vm, njs_generator_t *generator, return njs_generator_stack_pop(vm, generator, NULL); } - if (var->function && var->type == NJS_VARIABLE_FUNCTION) { - njs_generate_code(generator, njs_vmcode_function_copy_t, copy, - NJS_VMCODE_FUNCTION_COPY, node); - copy->function = &var->value; - copy->retval = node->index; - } - if (var->init) { return njs_generator_stack_pop(vm, generator, NULL); } @@ -935,10 +927,9 @@ static njs_int_t njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar) { - njs_variable_t *var; - njs_parser_scope_t *scope; - njs_vmcode_variable_t *variable; - njs_vmcode_function_copy_t *copy; + njs_variable_t *var; + njs_parser_scope_t *scope; + njs_vmcode_variable_t *variable; var = njs_variable_reference(vm, node); @@ -958,13 +949,6 @@ njs_generate_variable(njs_vm_t *vm, njs_generator_t *generator, } } - if (var->function && var->type == NJS_VARIABLE_FUNCTION) { - njs_generate_code(generator, njs_vmcode_function_copy_t, copy, - NJS_VMCODE_FUNCTION_COPY, node); - copy->function = &var->value; - copy->retval = node->index; - } - if (var->init) { return NJS_OK; } @@ -4293,7 +4277,6 @@ njs_generate_function_scope(njs_vm_t *vm, njs_generator_t *prev, const njs_str_t *name) { njs_int_t ret; - njs_arr_t *arr; njs_uint_t depth; njs_vm_code_t *code; njs_generator_t generator; @@ -4327,10 +4310,6 @@ njs_generate_function_scope(njs_vm_t *vm, njs_generator_t *prev, lambda->nclosures = generator.closures->items; lambda->nlocal = node->scope->items; - arr = node->scope->declarations; - lambda->declarations = (arr != NULL) ? arr->start : NULL; - lambda->ndeclarations = (arr != NULL) ? arr->items : 0; - return NJS_OK; } @@ -4339,11 +4318,15 @@ njs_vm_code_t * njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator, njs_parser_scope_t *scope, const njs_str_t *name) { - u_char *p; - int64_t nargs; - njs_int_t ret; - njs_uint_t index; - njs_vm_code_t *code; + u_char *p, *code_start; + size_t code_size, prelude; + int64_t nargs; + njs_int_t ret; + njs_arr_t *arr; + njs_uint_t index, n; + njs_vm_code_t *code; + njs_declaration_t *declr; + njs_vmcode_function_t *fun; generator->code_size = 128; @@ -4414,6 +4397,45 @@ njs_generate_scope(njs_vm_t *vm, njs_generator_t *generator, } while (generator->state != NULL); + arr = scope->declarations; + scope->declarations = NULL; + + if (arr != NULL && arr->items > 0) { + declr = arr->start; + prelude = sizeof(njs_vmcode_function_t) * arr->items; + code_size = generator->code_end - generator->code_start; + + for (n = 0; n < arr->items; n++) { + fun = (njs_vmcode_function_t *) njs_generate_reserve(vm, + generator, sizeof(njs_vmcode_function_t)); + if (njs_slow_path(fun == NULL)) { + njs_memory_error(vm); + return NULL; + } + + generator->code_end += sizeof(njs_vmcode_function_t); + fun->code = NJS_VMCODE_FUNCTION; + + fun->lambda = declr[n].lambda; + fun->async = declr[n].async; + fun->retval = declr[n].index; + } + + code_start = njs_mp_alloc(vm->mem_pool, code_size + prelude); + if (njs_slow_path(code_start == NULL)) { + njs_memory_error(vm); + return NULL; + } + + memcpy(code_start, &generator->code_start[code_size], prelude); + memcpy(&code_start[prelude], generator->code_start, code_size); + + njs_mp_free(vm->mem_pool, generator->code_start); + + generator->code_start = code_start; + generator->code_end = code_start + code_size + prelude; + } + code = njs_arr_item(vm->codes, index); code->start = generator->code_start; code->end = generator->code_end; diff --git a/src/njs_parser.h b/src/njs_parser.h index 2f7cff79..ebe5e686 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -109,8 +109,9 @@ typedef struct { typedef struct { - njs_value_t *value; + njs_function_lambda_t *lambda; njs_index_t index; + njs_bool_t async; } njs_declaration_t; diff --git a/src/njs_variable.c b/src/njs_variable.c index 40924ccf..bdf0d959 100644 --- a/src/njs_variable.c +++ b/src/njs_variable.c @@ -79,7 +79,8 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL, type); - declr->value = &var->value; + declr->lambda = lambda; + declr->async = !ctor; declr->index = var->index; root->items++; diff --git a/src/njs_vm.c b/src/njs_vm.c index 008f9d04..18a1c8e6 100644 --- a/src/njs_vm.c +++ b/src/njs_vm.c @@ -310,7 +310,6 @@ njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name, u_char **start, u_char *end) { njs_int_t ret; - njs_arr_t *arr; njs_mod_t *module; njs_parser_t parser; njs_vm_code_t *code; @@ -366,10 +365,6 @@ njs_vm_compile_module(njs_vm_t *vm, njs_str_t *name, u_char **start, lambda->start = generator.code_start; lambda->nlocal = scope->items; - arr = scope->declarations; - lambda->declarations = (arr != NULL) ? arr->start : NULL; - lambda->ndeclarations = (arr != NULL) ? arr->items : 0; - module->function.u.lambda = lambda; return module; diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c index c0e8333d..c7adf63f 100644 --- a/src/njs_vmcode.c +++ b/src/njs_vmcode.c @@ -16,15 +16,12 @@ struct njs_property_next_s { static njs_jump_off_t njs_vmcode_object(njs_vm_t *vm, njs_value_t *retval); static njs_jump_off_t njs_vmcode_array(njs_vm_t *vm, u_char *pc, njs_value_t *retval); -static njs_jump_off_t njs_vmcode_function(njs_vm_t *vm, u_char *pc, - njs_value_t *retval); +static njs_jump_off_t njs_vmcode_function(njs_vm_t *vm, u_char *pc); static njs_jump_off_t njs_vmcode_arguments(njs_vm_t *vm, u_char *pc); static njs_jump_off_t njs_vmcode_regexp(njs_vm_t *vm, u_char *pc, njs_value_t *retval); static njs_jump_off_t njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *retval); -static njs_jump_off_t njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, - njs_index_t retval); static njs_jump_off_t njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, njs_value_t *retval); @@ -113,7 +110,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval, njs_vmcode_equal_jump_t *equal; njs_vmcode_try_return_t *try_return; njs_vmcode_method_frame_t *method_frame; - njs_vmcode_function_copy_t *fcopy; njs_vmcode_prop_accessor_t *accessor; njs_vmcode_try_trampoline_t *try_trampoline; njs_vmcode_function_frame_t *function_frame; @@ -157,7 +153,6 @@ njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc, njs_value_t *rval, NJS_GOTO_ROW(NJS_VMCODE_IF_EQUAL_JUMP), NJS_GOTO_ROW(NJS_VMCODE_PROPERTY_INIT), NJS_GOTO_ROW(NJS_VMCODE_RETURN), - NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_COPY), NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_FRAME), NJS_GOTO_ROW(NJS_VMCODE_METHOD_FRAME), NJS_GOTO_ROW(NJS_VMCODE_FUNCTION_CALL), @@ -1155,9 +1150,7 @@ NEXT_LBL; CASE (NJS_VMCODE_FUNCTION): njs_vmcode_debug_opcode(); - njs_vmcode_operand(vm, vmcode->operand1, retval); - - ret = njs_vmcode_function(vm, pc, retval); + ret = njs_vmcode_function(vm, pc); if (njs_slow_path(ret < 0 && ret >= NJS_PREEMPT)) { goto error; } @@ -1422,17 +1415,6 @@ NEXT_LBL; return NJS_OK; - CASE (NJS_VMCODE_FUNCTION_COPY): - njs_vmcode_debug_opcode(); - - fcopy = (njs_vmcode_function_copy_t *) pc; - ret = njs_vmcode_function_copy(vm, fcopy->function, fcopy->retval); - if (njs_slow_path(ret == NJS_ERROR)) { - goto error; - } - - BREAK; - CASE (NJS_VMCODE_FUNCTION_FRAME): njs_vmcode_debug_opcode(); @@ -1928,7 +1910,7 @@ njs_vmcode_array(njs_vm_t *vm, u_char *pc, njs_value_t *retval) static njs_jump_off_t -njs_vmcode_function(njs_vm_t *vm, u_char *pc, njs_value_t *retval) +njs_vmcode_function(njs_vm_t *vm, u_char *pc) { njs_function_t *function; njs_vmcode_function_t *code; @@ -1948,7 +1930,7 @@ njs_vmcode_function(njs_vm_t *vm, u_char *pc, njs_value_t *retval) function->args_count = lambda->nargs - lambda->rest_parameters; - njs_set_function(retval, function); + njs_set_function(njs_scope_value(vm, code->retval), function); return sizeof(njs_vmcode_function_t); } @@ -2032,27 +2014,6 @@ njs_vmcode_template_literal(njs_vm_t *vm, njs_value_t *retval) } -static njs_jump_off_t -njs_vmcode_function_copy(njs_vm_t *vm, njs_value_t *value, njs_index_t retidx) -{ - njs_value_t *retval; - njs_function_t *function; - - retval = njs_scope_value(vm, retidx); - - if (!njs_is_valid(retval)) { - *retval = *value; - - function = njs_function_value_copy(vm, retval); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - } - - return sizeof(njs_vmcode_function_copy_t); -} - - static njs_jump_off_t njs_vmcode_property_init(njs_vm_t *vm, njs_value_t *value, njs_value_t *key, njs_value_t *init) diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h index 83507f63..5170f29f 100644 --- a/src/njs_vmcode.h +++ b/src/njs_vmcode.h @@ -38,7 +38,6 @@ enum { NJS_VMCODE_IF_EQUAL_JUMP, NJS_VMCODE_PROPERTY_INIT, NJS_VMCODE_RETURN, - NJS_VMCODE_FUNCTION_COPY, NJS_VMCODE_FUNCTION_FRAME, NJS_VMCODE_METHOD_FRAME, NJS_VMCODE_FUNCTION_CALL, @@ -381,13 +380,6 @@ typedef struct { } njs_vmcode_error_t; -typedef struct { - njs_vmcode_t code; - njs_value_t *function; - njs_index_t retval; -} njs_vmcode_function_copy_t; - - typedef struct { njs_vmcode_t code; njs_index_t retval; From noreply at nginx.com Tue Jun 17 00:37:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 17 Jun 2025 00:37:02 +0000 (UTC) Subject: [njs] Fixed frame saving for awaited function with closures. Message-ID: <20250617003702.D257949061@pubserv1.nginx> details: https://github.com/nginx/njs/commit/ef7ef9fe94fdcb029f5a61bed64067e303d4bb49 branches: master commit: ef7ef9fe94fdcb029f5a61bed64067e303d4bb49 user: Dmitry Volyntsev date: Wed, 11 Jun 2025 16:17:42 -0700 description: Fixed frame saving for awaited function with closures. The issue was introduced in 6335367 (0.7.0). This fixes #530 issue on Github. --- src/njs_function.c | 88 ++++++++++++++++++++++------------------ src/njs_function.h | 26 +++--------- test/js/async_closure.t.js | 24 +++++++++++ test/js/async_closure_arg.t.js | 24 +++++++++++ test/js/async_closure_share.t.js | 28 +++++++++++++ 5 files changed, 130 insertions(+), 60 deletions(-) diff --git a/src/njs_function.c b/src/njs_function.c index 06948177..90a54204 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -394,6 +394,19 @@ njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function, lambda = function->u.lambda; + /* + * Lambda frame has the following layout: + * njs_frame_t | p0 , p2, ..., pn | v0, v1, ..., vn + * where: + * p0, p1, ..., pn - pointers to arguments and locals, + * v0, v1, ..., vn - values of arguments and locals. + * n - number of arguments + locals. + * + * Normally, the pointers point to the values directly after them, + * but if a value was captured as a closure by an inner function, + * pn points to a value allocated from the heap. + */ + args_count = njs_max(nargs, lambda->nargs); value_count = args_count + lambda->nlocal; @@ -675,11 +688,11 @@ njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *native) njs_int_t njs_function_frame_save(njs_vm_t *vm, njs_frame_t *frame, u_char *pc) { - size_t args_count, value_count, n; - njs_value_t *start, *end, *p, **new, *value, **local; - njs_function_t *function; + size_t args_count, value_count, n; + njs_value_t **map, *value, **current_map; + njs_function_t *function; + njs_native_frame_t *active, *native; njs_function_lambda_t *lambda; - njs_native_frame_t *active, *native; *frame = *vm->active_frame; @@ -697,34 +710,37 @@ njs_function_frame_save(njs_vm_t *vm, njs_frame_t *frame, u_char *pc) args_count = njs_max(native->nargs, lambda->nargs); value_count = args_count + lambda->nlocal; - new = (njs_value_t **) ((u_char *) native + NJS_FRAME_SIZE); - value = (njs_value_t *) (new + value_count); - - native->arguments = value; - native->local = new + njs_function_frame_args_count(active); - native->pc = pc; - - start = njs_function_frame_values(active, &end); - p = native->arguments; + /* + * We need to save the current frame state because it will be freed + * when the function returns. + * + * To detect whether a value is captured as a closure, + * we check whether the pointer is within the frame. In this case + * the pointer is copied as is because the value it points to + * is already allocated in the heap and will not be freed. + * See njs_function_capture_closure() and njs_function_lambda_frame() + * for details. + */ - while (start < end) { - njs_value_assign(p, start++); - *new++ = p++; - } + map = (njs_value_t **) ((u_char *) native + NJS_FRAME_SIZE); + value = (njs_value_t *) (map + value_count); - /* Move all arguments. */ + current_map = (njs_value_t **) ((u_char *) active + NJS_FRAME_SIZE); - p = native->arguments; - local = native->local + 1 /* this */; + for (n = 0; n < value_count; n++) { + if (njs_is_value_allocated_on_frame(active, current_map[n])) { + map[n] = &value[n]; + njs_value_assign(&value[n], current_map[n]); - for (n = 0; n < function->args_count; n++) { - if (!njs_is_valid(p)) { - njs_set_undefined(p); + } else { + map[n] = current_map[n]; } - - *local++ = p++; } + native->arguments = value; + native->local = map + args_count; + native->pc = pc; + return NJS_OK; } @@ -746,7 +762,6 @@ njs_int_t njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, njs_function_lambda_t *lambda) { - void *start, *end; uint32_t n; njs_value_t *value, **closure; njs_native_frame_t *frame; @@ -761,9 +776,6 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, frame = frame->previous; } - start = frame; - end = frame->free; - closure = njs_function_closures(function); n = lambda->nclosures; @@ -772,7 +784,7 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, value = njs_scope_value(vm, lambda->closures[n]); - if (start <= (void *) value && (void *) value < end) { + if (njs_is_value_allocated_on_frame(frame, value)) { value = njs_scope_value_clone(vm, lambda->closures[n], value); if (njs_slow_path(value == NULL)) { return NJS_ERROR; @@ -788,14 +800,14 @@ njs_function_capture_closure(njs_vm_t *vm, njs_function_t *function, njs_inline njs_value_t * -njs_function_closure_value(njs_vm_t *vm, njs_value_t **scope, njs_index_t index, - void *start, void *end) +njs_function_closure_value(njs_vm_t *vm, njs_native_frame_t *frame, + njs_value_t **scope, njs_index_t index) { njs_value_t *value, *newval; value = scope[njs_scope_index_value(index)]; - if (start <= (void *) value && end > (void *) value) { + if (njs_is_value_allocated_on_frame(frame, value)) { newval = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t)); if (njs_slow_path(newval == NULL)) { njs_memory_error(vm); @@ -815,7 +827,6 @@ njs_function_closure_value(njs_vm_t *vm, njs_value_t **scope, njs_index_t index, njs_int_t njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function) { - void *start, *end; uint32_t n; njs_value_t *value, **refs, **global; njs_index_t *indexes, index; @@ -834,9 +845,6 @@ njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function) native = native->previous; } - start = native; - end = native->free; - indexes = lambda->closures; refs = njs_function_closures(function); @@ -851,12 +859,12 @@ njs_function_capture_global_closures(njs_vm_t *vm, njs_function_t *function) switch (njs_scope_index_type(index)) { case NJS_LEVEL_LOCAL: - value = njs_function_closure_value(vm, native->local, index, - start, end); + value = njs_function_closure_value(vm, native, native->local, + index); break; case NJS_LEVEL_GLOBAL: - value = njs_function_closure_value(vm, global, index, start, end); + value = njs_function_closure_value(vm, native, global, index); break; default: diff --git a/src/njs_function.h b/src/njs_function.h index 5aa98dd3..1b5de544 100644 --- a/src/njs_function.h +++ b/src/njs_function.h @@ -202,29 +202,15 @@ njs_function_frame_size(njs_native_frame_t *frame) } -njs_inline size_t -njs_function_frame_args_count(njs_native_frame_t *frame) -{ - uintptr_t start; - - start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE); - - return ((uintptr_t) frame->local - start) / sizeof(njs_value_t *); -} - - -njs_inline njs_value_t * -njs_function_frame_values(njs_native_frame_t *frame, njs_value_t **end) +njs_inline njs_bool_t +njs_is_value_allocated_on_frame(njs_native_frame_t *frame, njs_value_t *value) { - size_t count; - uintptr_t start; - - start = (uintptr_t) ((u_char *) frame + NJS_FRAME_SIZE); - count = ((uintptr_t) frame->arguments - start) / sizeof(njs_value_t *); + void *start, *end; - *end = frame->arguments + count; + start = frame; + end = frame->free; - return frame->arguments; + return start <= (void *) value && (void *) value < end; } diff --git a/test/js/async_closure.t.js b/test/js/async_closure.t.js new file mode 100644 index 00000000..9a387ff2 --- /dev/null +++ b/test/js/async_closure.t.js @@ -0,0 +1,24 @@ +/*--- +includes: [] +flags: [async] +---*/ + +async function f() { + await 1; + var v = 2; + + function g() { + return v + 1; + } + + function s() { + g + 1; + } + + return g(); +} + +f().then(v => { + assert.sameValue(v, 3) +}) +.then($DONE, $DONE); diff --git a/test/js/async_closure_arg.t.js b/test/js/async_closure_arg.t.js new file mode 100644 index 00000000..d6aa8ab6 --- /dev/null +++ b/test/js/async_closure_arg.t.js @@ -0,0 +1,24 @@ +/*--- +includes: [] +flags: [async] +---*/ + +async function f(v) { + await 1; + v = 2; + + function g() { + return v + 1; + } + + function s() { + g + 1; + } + + return g(); +} + +f(42).then(v => { + assert.sameValue(v, 3) +}) +.then($DONE, $DONE); diff --git a/test/js/async_closure_share.t.js b/test/js/async_closure_share.t.js new file mode 100644 index 00000000..d78f92c5 --- /dev/null +++ b/test/js/async_closure_share.t.js @@ -0,0 +1,28 @@ +/*--- +includes: [] +flags: [async] +---*/ + +async function f() { + await 1; + var v = 'f'; + + function g() { + v += ':g'; + return v; + } + + function s() { + v += ':s'; + return v; + } + + return [g, s]; +} + +f().then(pair => { + pair[0](); + var v = pair[1](); + assert.sameValue(v, 'f:g:s'); +}) +.then($DONE, $DONE); From noreply at nginx.com Tue Jun 17 00:37:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 17 Jun 2025 00:37:02 +0000 (UTC) Subject: [njs] Parser: unused strict_semicolon cleanup. Message-ID: <20250617003702.E3CC949063@pubserv1.nginx> details: https://github.com/nginx/njs/commit/5431ea1f16d7587eddccda1b4e6579325070e76b branches: master commit: 5431ea1f16d7587eddccda1b4e6579325070e76b user: Dmitry Volyntsev date: Fri, 13 Jun 2025 20:19:36 -0700 description: Parser: unused strict_semicolon cleanup. --- src/njs_parser.c | 22 +++++++--------------- src/njs_parser.h | 1 - 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/njs_parser.c b/src/njs_parser.c index de11b8c9..cfaad740 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -839,10 +839,9 @@ njs_inline njs_int_t njs_parser_expect_semicolon(njs_parser_t *parser, njs_lexer_token_t *token) { if (token->type != NJS_TOKEN_SEMICOLON) { - if (parser->strict_semicolon - || (token->type != NJS_TOKEN_END - && token->type != NJS_TOKEN_CLOSE_BRACE - && parser->lexer->prev_type != NJS_TOKEN_LINE_END)) + if (token->type != NJS_TOKEN_END + && token->type != NJS_TOKEN_CLOSE_BRACE + && parser->lexer->prev_type != NJS_TOKEN_LINE_END) { return NJS_DECLINED; } @@ -5408,10 +5407,6 @@ static njs_int_t njs_parser_do_while_semicolon(njs_parser_t *parser, njs_lexer_token_t *token, njs_queue_link_t *current) { - if (parser->strict_semicolon) { - return njs_parser_failed(parser); - } - parser->target->right = parser->node; parser->node = parser->target; @@ -6257,10 +6252,9 @@ njs_parser_break_continue(njs_parser_t *parser, njs_lexer_token_t *token, break; } - if (parser->strict_semicolon - || (token->type != NJS_TOKEN_END - && token->type != NJS_TOKEN_CLOSE_BRACE - && parser->lexer->prev_type != NJS_TOKEN_LINE_END)) + if (token->type != NJS_TOKEN_END + && token->type != NJS_TOKEN_CLOSE_BRACE + && parser->lexer->prev_type != NJS_TOKEN_LINE_END) { return njs_parser_failed(parser); } @@ -6314,9 +6308,7 @@ njs_parser_return_statement(njs_parser_t *parser, njs_lexer_token_t *token, return njs_parser_failed(parser); default: - if (!parser->strict_semicolon - && parser->lexer->prev_type == NJS_TOKEN_LINE_END) - { + if (parser->lexer->prev_type == NJS_TOKEN_LINE_END) { break; } diff --git a/src/njs_parser.h b/src/njs_parser.h index ebe5e686..8a67bdab 100644 --- a/src/njs_parser.h +++ b/src/njs_parser.h @@ -84,7 +84,6 @@ struct njs_parser_s { uint8_t use_lhs; uint8_t module; - njs_bool_t strict_semicolon; njs_str_t file; uint32_t line; From noreply at nginx.com Tue Jun 17 00:37:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 17 Jun 2025 00:37:02 +0000 (UTC) Subject: [njs] Parser: simplifed working with function variables. Message-ID: <20250617003702.DC12049062@pubserv1.nginx> details: https://github.com/nginx/njs/commit/030712507797c42db146c7ee0399a882f47e6ebe branches: master commit: 030712507797c42db146c7ee0399a882f47e6ebe user: Dmitry Volyntsev date: Thu, 12 Jun 2025 17:33:35 -0700 description: Parser: simplifed working with function variables. --- src/njs_builtin.c | 10 ---------- src/njs_function.c | 10 ++-------- src/njs_parser.c | 3 +-- src/njs_value.c | 11 +---------- src/njs_variable.c | 13 ++++++------- src/njs_variable.h | 3 +-- 6 files changed, 11 insertions(+), 39 deletions(-) diff --git a/src/njs_builtin.c b/src/njs_builtin.c index 230b4c1c..c960fe1f 100644 --- a/src/njs_builtin.c +++ b/src/njs_builtin.c @@ -759,7 +759,6 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop, { njs_value_t *value; njs_variable_t *var; - njs_function_t *function; njs_rbtree_node_t *rb_node; njs_variable_node_t *node, var_node; @@ -788,15 +787,6 @@ njs_global_this_prop_handler(njs_vm_t *vm, njs_object_prop_t *prop, value = njs_scope_valid_value(vm, var->index); - if (var->type == NJS_VARIABLE_FUNCTION && njs_is_undefined(value)) { - njs_value_assign(value, &var->value); - - function = njs_function_value_copy(vm, value); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - } - if (setval != NULL) { njs_value_assign(value, setval); } diff --git a/src/njs_function.c b/src/njs_function.c index 90a54204..7db342f6 100644 --- a/src/njs_function.c +++ b/src/njs_function.c @@ -934,9 +934,8 @@ njs_function_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused, njs_value_t *value, njs_value_t *setval, njs_value_t *retval) { - njs_value_t *proto, proto_value, *cons; - njs_object_t *prototype; - njs_function_t *function; + njs_value_t *proto, proto_value, *cons; + njs_object_t *prototype; if (setval == NULL) { prototype = njs_object_alloc(vm); @@ -949,11 +948,6 @@ njs_function_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop, setval = &proto_value; } - function = njs_function_value_copy(vm, value); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - proto = njs_function_property_prototype_set(vm, njs_object_hash(value), setval); if (njs_slow_path(proto == NULL)) { diff --git a/src/njs_parser.c b/src/njs_parser.c index 3cd56fdf..de11b8c9 100644 --- a/src/njs_parser.c +++ b/src/njs_parser.c @@ -7091,8 +7091,7 @@ njs_parser_function_declaration(njs_parser_t *parser, njs_lexer_token_t *token, njs_lexer_consume_token(parser->lexer, 1); - var = njs_variable_function_add(parser, parser->scope, atom_id, - NJS_VARIABLE_FUNCTION); + var = njs_variable_function_add(parser, parser->scope, atom_id); if (var == NULL) { return NJS_ERROR; } diff --git a/src/njs_value.c b/src/njs_value.c index 0c616a37..fe64afe6 100644 --- a/src/njs_value.c +++ b/src/njs_value.c @@ -562,7 +562,6 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, uint32_t index; njs_int_t ret; njs_object_t *obj; - njs_function_t *function; njs_assert(atom_id != NJS_ATOM_STRING_unknown); @@ -585,6 +584,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, case NJS_OBJECT: case NJS_ARRAY: + case NJS_FUNCTION: case NJS_ARRAY_BUFFER: case NJS_DATA_VIEW: case NJS_TYPED_ARRAY: @@ -595,15 +595,6 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value, obj = njs_object(value); break; - case NJS_FUNCTION: - function = njs_function_value_copy(vm, value); - if (njs_slow_path(function == NULL)) { - return NJS_ERROR; - } - - obj = &function->object; - break; - case NJS_UNDEFINED: case NJS_NULL: default: diff --git a/src/njs_variable.c b/src/njs_variable.c index bdf0d959..b65e5934 100644 --- a/src/njs_variable.c +++ b/src/njs_variable.c @@ -36,7 +36,7 @@ njs_variable_add(njs_parser_t *parser, njs_parser_scope_t *scope, njs_variable_t * njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, - uintptr_t atom_id, njs_variable_type_t type) + uintptr_t atom_id) { njs_bool_t ctor; njs_variable_t *var; @@ -44,14 +44,15 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, njs_parser_scope_t *root; njs_function_lambda_t *lambda; - root = njs_variable_scope_find(parser, scope, atom_id, type); + root = njs_variable_scope_find(parser, scope, atom_id, + NJS_VARIABLE_FUNCTION); if (njs_slow_path(root == NULL)) { njs_parser_ref_error(parser, "scope not found"); return NULL; } - var = njs_variable_scope_add(parser, root, scope, atom_id, type, - NJS_INDEX_ERROR); + var = njs_variable_scope_add(parser, root, scope, atom_id, + NJS_VARIABLE_FUNCTION, NJS_INDEX_ERROR); if (njs_slow_path(var == NULL)) { return NULL; } @@ -77,7 +78,7 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, } var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL, - type); + NJS_VARIABLE_FUNCTION); declr->lambda = lambda; declr->async = !ctor; @@ -86,7 +87,6 @@ njs_variable_function_add(njs_parser_t *parser, njs_parser_scope_t *scope, root->items++; var->type = NJS_VARIABLE_FUNCTION; - var->function = 1; return var; } @@ -174,7 +174,6 @@ njs_variable_scope_find(njs_parser_t *parser, njs_parser_scope_t *scope, if (var != NULL && var->scope == root) { if (var->self) { - var->function = 0; return scope; } diff --git a/src/njs_variable.h b/src/njs_variable.h index 2ed5220a..db40de75 100644 --- a/src/njs_variable.h +++ b/src/njs_variable.h @@ -26,7 +26,6 @@ typedef struct { njs_bool_t self; njs_bool_t init; njs_bool_t closure; - njs_bool_t function; njs_parser_scope_t *scope; njs_parser_scope_t *original; @@ -62,7 +61,7 @@ typedef struct { njs_variable_t *njs_variable_add(njs_parser_t *parser, njs_parser_scope_t *scope, uintptr_t atom_id, njs_variable_type_t type); njs_variable_t *njs_variable_function_add(njs_parser_t *parser, - njs_parser_scope_t *scope, uintptr_t atom_id, njs_variable_type_t type); + njs_parser_scope_t *scope, uintptr_t atom_id); njs_variable_t * njs_label_add(njs_vm_t *vm, njs_parser_scope_t *scope, uintptr_t atom_id); njs_variable_t *njs_label_find(njs_vm_t *vm, njs_parser_scope_t *scope, From noreply at nginx.com Tue Jun 17 05:12:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 17 Jun 2025 05:12:02 +0000 (UTC) Subject: [njs] Types: updated TS definitions. Message-ID: <20250617051202.A8A7549061@pubserv1.nginx> details: https://github.com/nginx/njs/commit/dfcafd3dddbb5c28dfb25bac207e85fc6ea4a929 branches: master commit: dfcafd3dddbb5c28dfb25bac207e85fc6ea4a929 user: Dmitry Volyntsev date: Mon, 16 Jun 2025 18:03:41 -0700 description: Types: updated TS definitions. --- ts/ngx_core.d.ts | 9 ++++++--- ts/ngx_http_js_module.d.ts | 2 +- ts/ngx_stream_js_module.d.ts | 2 +- ts/njs_core.d.ts | 1 + 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ts/ngx_core.d.ts b/ts/ngx_core.d.ts index b459c929..d35cb40f 100644 --- a/ts/ngx_core.d.ts +++ b/ts/ngx_core.d.ts @@ -276,6 +276,7 @@ interface NgxSharedDict { * * @param key The key of the item to add. * @param value The value of the item to add. + * @param timeout Overrides the default timeout for this item in milliseconds. * @returns `true` if the value has been added successfully, `false` * if the `key` already exists in this dictionary. * @throws {SharedMemoryError} if there's not enough free space in this @@ -283,7 +284,7 @@ interface NgxSharedDict { * @throws {TypeError} if the `value` is of a different type than expected * by this dictionary. */ - add(key: string, value: V): boolean; + add(key: string, value: V, timeout?: number): boolean; /** * Removes all items from this dictionary. */ @@ -307,13 +308,14 @@ interface NgxSharedDict { * @param delta The number to increment/decrement the value by. * @param init The number to initialize the item with if it didn't exist * (default is `0`). + * @param timeout Overrides the default timeout for this item in milliseconds. * @returns The new value. * @throws {SharedMemoryError} if there's not enough free space in this * dictionary. * @throws {TypeError} if this dictionary does not expect numbers. */ incr: V extends number - ? (key: string, delta: V, init?: number) => number + ? (key: string, delta: V, init?: number, timeout?: number) => number : never; /** * @param maxCount The maximum number of pairs to retrieve (default is 1024). @@ -371,13 +373,14 @@ interface NgxSharedDict { * * @param key The key of the item to set. * @param value The value of the item to set. + * @param timeout Overrides the default timeout for this item in milliseconds. * @returns This dictionary (for method chaining). * @throws {SharedMemoryError} if there's not enough free space in this * dictionary. * @throws {TypeError} if the `value` is of a different type than expected * by this dictionary. */ - set(key: string, value: V): this; + set(key: string, value: V, timeout?: number): this; /** * @returns The number of items in this shared dictionary. */ diff --git a/ts/ngx_http_js_module.d.ts b/ts/ngx_http_js_module.d.ts index 37932893..d7dd1c9e 100644 --- a/ts/ngx_http_js_module.d.ts +++ b/ts/ngx_http_js_module.d.ts @@ -468,7 +468,7 @@ interface NginxHTTPRequest { /** * nginx variables as strings. * - * **Warning:** Bytes invalid in UTF-8 encoding may be converted into the replacement character. + * After 0.8.5 bytes invalid in UTF-8 encoding are converted into the replacement characters. * * @see rawVariables */ diff --git a/ts/ngx_stream_js_module.d.ts b/ts/ngx_stream_js_module.d.ts index 58c4d908..c78d008b 100644 --- a/ts/ngx_stream_js_module.d.ts +++ b/ts/ngx_stream_js_module.d.ts @@ -200,7 +200,7 @@ interface NginxStreamRequest { /** * nginx variables as strings. * - * **Warning:** Bytes invalid in UTF-8 encoding may be converted into the replacement character. + * After 0.8.5 bytes invalid in UTF-8 encoding are converted into the replacement characters. * * @see rawVariables */ diff --git a/ts/njs_core.d.ts b/ts/njs_core.d.ts index 2f1d45d0..f64c9576 100644 --- a/ts/njs_core.d.ts +++ b/ts/njs_core.d.ts @@ -581,6 +581,7 @@ interface NjsProcess { readonly env: NjsEnv; /** + * Send signal to a process by its PID. * @since 0.8.8 */ kill(pid: number, signal?: string | number): true; From noreply at nginx.com Tue Jun 17 05:12:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 17 Jun 2025 05:12:02 +0000 (UTC) Subject: [njs] Types: added TS definitions for WebCrypto ECDH. Message-ID: <20250617051202.AC07649062@pubserv1.nginx> details: https://github.com/nginx/njs/commit/c5b1d59fe0a18a77b6a4a3fd9f93ad2b4d497225 branches: master commit: c5b1d59fe0a18a77b6a4a3fd9f93ad2b4d497225 user: Dmitry Volyntsev date: Mon, 16 Jun 2025 18:16:46 -0700 description: Types: added TS definitions for WebCrypto ECDH. --- ts/njs_webcrypto.d.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/ts/njs_webcrypto.d.ts b/ts/njs_webcrypto.d.ts index 058359cb..4e6f2057 100644 --- a/ts/njs_webcrypto.d.ts +++ b/ts/njs_webcrypto.d.ts @@ -41,12 +41,12 @@ interface RsaHashedKeyGenParams { } interface EcKeyImportParams { - name: "ECDSA"; + name: "ECDSA" | "ECDH"; namedCurve: "P-256" | "P-384" | "P-521"; } interface EcKeyGenParams { - name: "ECDSA"; + name: "ECDSA" | "ECDH"; namedCurve: "P-256" | "P-384" | "P-521"; } @@ -68,7 +68,8 @@ type ImportAlgorithm = | AesImportParams | AesVariants | "PBKDF2" - | "HKDF"; + | "HKDF" + | "ECDH"; type GenerateAlgorithm = | RsaHashedKeyGenParams @@ -99,9 +100,15 @@ interface Pbkdf2Params { interations: number; } +interface EcdhParams { + name: "ECDH"; + public: CryptoKey; +} + type DeriveAlgorithm = | HkdfParams - | Pbkdf2Params; + | Pbkdf2Params + | EcdhParams; interface HmacKeyGenParams { name: "HMAC"; From noreply at nginx.com Wed Jun 18 21:12:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 18 Jun 2025 21:12:02 +0000 (UTC) Subject: [njs] Fetch: fixed handling of Content-Length header when body is provided. Message-ID: <20250618211202.30B114884B@pubserv1.nginx> details: https://github.com/nginx/njs/commit/34b80511acfd44a5cbbbce835d7540081e5d7527 branches: master commit: 34b80511acfd44a5cbbbce835d7540081e5d7527 user: Dmitry Volyntsev date: Mon, 16 Jun 2025 19:36:35 -0700 description: Fetch: fixed handling of Content-Length header when body is provided. body value length takes precedence over Content-Length from header list. https://fetch.spec.whatwg.org/#http-network-or-cache-fetch Let contentLength be httpRequest’s body’s length, if httpRequest’s body is non-null; otherwise null. Let contentLengthHeaderValue be null. If httpRequest’s body is null and httpRequest’s method is `POST` or `PUT`, then set contentLengthHeaderValue to `0`. If contentLength is non-null, then set contentLengthHeaderValue to contentLength, serialized and isomorphic encoded. If contentLengthHeaderValue is non-null, then append (`Content-Length`, contentLengthHeaderValue) to httpRequest’s header list. This fixes #930 issue in Github. --- nginx/ngx_js_fetch.c | 21 ++++++++++++++++++++- nginx/ngx_qjs_fetch.c | 21 ++++++++++++++++++++- nginx/t/js_fetch.t | 23 +++++++++++++++++++++-- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/nginx/ngx_js_fetch.c b/nginx/ngx_js_fetch.c index 45f2dc10..faa38aab 100644 --- a/nginx/ngx_js_fetch.c +++ b/nginx/ngx_js_fetch.c @@ -514,6 +514,7 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, ngx_url_t u; ngx_uint_t i; njs_bool_t has_host; + ngx_str_t method; ngx_pool_t *pool; njs_value_t *init, *value; ngx_js_http_t *http; @@ -674,6 +675,13 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, continue; } + if (h[i].key.len == 14 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Content-Length", 14) + == 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); @@ -693,7 +701,18 @@ ngx_js_ext_fetch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_chb_append(&http->chain, request.body.data, request.body.len); } else { - njs_chb_append_literal(&http->chain, CRLF); + method = request.method; + + if ((method.len == 4 + && (ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0)) + || (method.len == 3 + && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0)) + { + njs_chb_append_literal(&http->chain, "Content-Length: 0" CRLF CRLF); + + } else { + njs_chb_append_literal(&http->chain, CRLF); + } } if (u.addrs == NULL) { diff --git a/nginx/ngx_qjs_fetch.c b/nginx/ngx_qjs_fetch.c index 084162ba..5ed8fc30 100644 --- a/nginx/ngx_qjs_fetch.c +++ b/nginx/ngx_qjs_fetch.c @@ -241,6 +241,7 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, JSValue init, value, promise; ngx_int_t rc; ngx_url_t u; + ngx_str_t method; ngx_uint_t i; ngx_pool_t *pool; ngx_js_ctx_t *ctx; @@ -410,6 +411,13 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, continue; } + if (h[i].key.len == 14 + && ngx_strncasecmp(h[i].key.data, (u_char *) "Content-Length", 14) + == 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); @@ -429,7 +437,18 @@ ngx_qjs_ext_fetch(JSContext *cx, JSValueConst this_val, int argc, njs_chb_append(&http->chain, request.body.data, request.body.len); } else { - njs_chb_append_literal(&http->chain, CRLF); + method = request.method; + + if ((method.len == 4 + && (ngx_strncasecmp(method.data, (u_char *) "POST", 4) == 0)) + || (method.len == 3 + && ngx_strncasecmp(method.data, (u_char *) "PUT", 3) == 0)) + { + njs_chb_append_literal(&http->chain, "Content-Length: 0" CRLF CRLF); + + } else { + njs_chb_append_literal(&http->chain, CRLF); + } } if (u.addrs == NULL) { diff --git a/nginx/t/js_fetch.t b/nginx/t/js_fetch.t index 1c6fde77..76d9238d 100644 --- a/nginx/t/js_fetch.t +++ b/nginx/t/js_fetch.t @@ -64,6 +64,10 @@ http { js_content test.body; } + location /body_content_length { + js_content test.body_content_length; + } + location /body_special { js_content test.body_special; } @@ -156,6 +160,13 @@ $t->write_file('test.js', < r.return(501, e.message)) } + async function body_content_length(r) { + let resp = await ngx.fetch(`http://127.0.0.1:$p0/loc`, + {headers: {'Content-Length': '100'}, + body: "CONTENT-BODY"}); + r.return(resp.status); + } + function property(r) { var opts = {headers:{}}; @@ -400,12 +411,12 @@ $t->write_file('test.js', <try_run('no njs.fetch'); -$t->plan(37); +$t->plan(38); $t->run_daemon(\&http_daemon, port(8082)); $t->waitforsocket('127.0.0.1:' . port(8082)); @@ -508,6 +519,14 @@ like(http_get('/body_special?loc=head/large&method=HEAD'), qr/200 OK.*$/s, 'fetch head method large content-length'); } +TODO: { +local $TODO = 'not yet' unless has_version('0.9.1'); + +like(http_get('/body_content_length'), qr/200 OK/s, + 'fetch body content-length'); + +} + ############################################################################### sub has_version { From noreply at nginx.com Thu Jun 19 06:20:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 19 Jun 2025 06:20:02 +0000 (UTC) Subject: [nginx] HTTP/2: added function declaration. Message-ID: <20250619062002.B215948FB1@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/ea001feb10a294ccd53c896b9919f17f5cbda468 branches: master commit: ea001feb10a294ccd53c896b9919f17f5cbda468 user: Roman Arutyunyan date: Wed, 18 Jun 2025 19:48:19 +0400 description: HTTP/2: added function declaration. --- src/http/v2/ngx_http_v2_filter_module.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c index b63e343a0..556446ed2 100644 --- a/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/http/v2/ngx_http_v2_filter_module.c @@ -27,6 +27,8 @@ #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 +static ngx_int_t ngx_http_v2_header_filter(ngx_http_request_t *r); + static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin); static ngx_http_v2_out_frame_t *ngx_http_v2_create_trailers_frame( From noreply at nginx.com Thu Jun 19 06:20:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Thu, 19 Jun 2025 06:20:02 +0000 (UTC) Subject: [nginx] Upstream: early hints support. Message-ID: <20250619062002.BFEF648FB2@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/662c1dd2a97afd6c7ca09b8f5a74347ee017b86b branches: master commit: 662c1dd2a97afd6c7ca09b8f5a74347ee017b86b user: Roman Arutyunyan date: Fri, 15 Nov 2024 08:23:53 +0400 description: Upstream: early hints support. The change implements processing upstream early hints response in ngx_http_proxy_module and ngx_http_grpc_module. A new directive "early_hints" enables sending early hints to the client. By default, sending early hints is disabled. Example: map $http_sec_fetch_mode $early_hints { navigate $http2$http3; } early_hints $early_hints; proxy_pass http://example.com; --- src/http/modules/ngx_http_grpc_module.c | 8 +- src/http/modules/ngx_http_proxy_module.c | 27 ++++- src/http/ngx_http.c | 1 + src/http/ngx_http.h | 2 + src/http/ngx_http_core_module.c | 41 +++++++ src/http/ngx_http_core_module.h | 2 + src/http/ngx_http_header_filter_module.c | 107 +++++++++++++++++ src/http/ngx_http_request.h | 1 + src/http/ngx_http_upstream.c | 163 +++++++++++++++++++++++++ src/http/ngx_http_upstream.h | 2 + src/http/v2/ngx_http_v2.h | 1 + src/http/v2/ngx_http_v2_filter_module.c | 200 ++++++++++++++++++++++++++++++- src/http/v3/ngx_http_v3_filter_module.c | 153 +++++++++++++++++++++++ 13 files changed, 698 insertions(+), 10 deletions(-) diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 80046d6a4..d74b17708 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -1869,7 +1869,8 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) return NGX_HTTP_UPSTREAM_INVALID_HEADER; } - if (status < NGX_HTTP_OK) { + if (status < NGX_HTTP_OK && status != NGX_HTTP_EARLY_HINTS) + { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent unexpected :status \"%V\"", status_line); @@ -1902,6 +1903,10 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) h->lowcase_key = h->key.data; h->hash = ngx_hash_key(h->key.data, h->key.len); + if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + continue; + } + hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); @@ -4413,6 +4418,7 @@ ngx_http_grpc_create_loc_conf(ngx_conf_t *cf) conf->upstream.pass_request_body = 1; conf->upstream.force_ranges = 0; conf->upstream.pass_trailers = 1; + conf->upstream.pass_early_hints = 1; conf->upstream.preserve_output = 1; conf->headers_source = NGX_CONF_UNSET_PTR; diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index d4c5abf62..dbe998b13 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -1888,6 +1888,13 @@ ngx_http_proxy_process_status_line(ngx_http_request_t *r) u->headers_in.status_n, &u->headers_in.status_line); if (ctx->status.http_version < NGX_HTTP_VERSION_11) { + + if (ctx->status.code == NGX_HTTP_EARLY_HINTS) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent HTTP/1.0 response with early hints"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + u->headers_in.connection_close = 1; } @@ -1949,6 +1956,14 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \"%V: %V\"", + &h->key, &h->value); + + if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + continue; + } + hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); @@ -1960,10 +1975,6 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) } } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http proxy header: \"%V: %V\"", - &h->key, &h->value); - continue; } @@ -1974,6 +1985,10 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header done"); + if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + return NGX_OK; + } + /* * if no "Server" and "Date" in header line, * then add the special empty headers @@ -3628,10 +3643,10 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf) conf->ssl_conf_commands = NGX_CONF_UNSET_PTR; #endif - /* "proxy_cyclic_temp_file" is disabled */ + /* the hardcoded values */ conf->upstream.cyclic_temp_file = 0; - conf->upstream.change_buffering = 1; + conf->upstream.pass_early_hints = 1; conf->headers_source = NGX_CONF_UNSET_PTR; diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index d835f896e..7f2b4225a 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -72,6 +72,7 @@ ngx_uint_t ngx_http_max_module; ngx_http_output_header_filter_pt ngx_http_top_header_filter; +ngx_http_output_header_filter_pt ngx_http_top_early_hints_filter; ngx_http_output_body_filter_pt ngx_http_top_body_filter; ngx_http_request_body_filter_pt ngx_http_top_request_body_filter; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index cb4a1e68a..7d98f5cd7 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -152,6 +152,7 @@ ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_int_t ngx_http_read_unbuffered_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_send_header(ngx_http_request_t *r); +ngx_int_t ngx_http_send_early_hints(ngx_http_request_t *r); ngx_int_t ngx_http_special_response_handler(ngx_http_request_t *r, ngx_int_t error); ngx_int_t ngx_http_filter_finalize_request(ngx_http_request_t *r, @@ -191,6 +192,7 @@ extern ngx_str_t ngx_http_html_default_types[]; extern ngx_http_output_header_filter_pt ngx_http_top_header_filter; +extern ngx_http_output_header_filter_pt ngx_http_top_early_hints_filter; extern ngx_http_output_body_filter_pt ngx_http_top_body_filter; extern ngx_http_request_body_filter_pt ngx_http_top_request_body_filter; diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 92c3eae8a..2d62674d9 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -670,6 +670,13 @@ static ngx_command_t ngx_http_core_commands[] = { offsetof(ngx_http_core_loc_conf_t, etag), NULL }, + { ngx_string("early_hints"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, + ngx_http_set_predicate_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_core_loc_conf_t, early_hints), + NULL }, + { ngx_string("error_page"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF |NGX_CONF_2MORE, @@ -1857,6 +1864,37 @@ ngx_http_send_header(ngx_http_request_t *r) } +ngx_int_t +ngx_http_send_early_hints(ngx_http_request_t *r) +{ + ngx_int_t rc; + ngx_http_core_loc_conf_t *clcf; + + if (r->post_action) { + return NGX_OK; + } + + if (r->header_sent) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "header already sent"); + return NGX_ERROR; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + rc = ngx_http_test_predicates(r, clcf->early_hints); + + if (rc != NGX_DECLINED) { + return rc; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http send early hints \"%V?%V\"", &r->uri, &r->args); + + return ngx_http_top_early_hints_filter(r); +} + + ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in) { @@ -3637,6 +3675,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t *cf) clcf->chunked_transfer_encoding = NGX_CONF_UNSET; clcf->etag = NGX_CONF_UNSET; clcf->server_tokens = NGX_CONF_UNSET_UINT; + clcf->early_hints = NGX_CONF_UNSET_PTR; clcf->types_hash_max_size = NGX_CONF_UNSET_UINT; clcf->types_hash_bucket_size = NGX_CONF_UNSET_UINT; @@ -3917,6 +3956,8 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->server_tokens, prev->server_tokens, NGX_HTTP_SERVER_TOKENS_ON); + ngx_conf_merge_ptr_value(conf->early_hints, prev->early_hints, NULL); + ngx_conf_merge_ptr_value(conf->open_file_cache, prev->open_file_cache, NULL); diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index e7e266bf8..a794144aa 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -430,6 +430,8 @@ struct ngx_http_core_loc_conf_s { ngx_http_complex_value_t *disable_symlinks_from; #endif + ngx_array_t *early_hints; /* early_hints */ + ngx_array_t *error_pages; /* error_page */ ngx_path_t *client_body_temp_path; /* client_body_temp_path */ diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c index 76f6e9629..789938329 100644 --- a/src/http/ngx_http_header_filter_module.c +++ b/src/http/ngx_http_header_filter_module.c @@ -13,6 +13,7 @@ static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf); static ngx_int_t ngx_http_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_early_hints_filter(ngx_http_request_t *r); static ngx_http_module_t ngx_http_header_filter_module_ctx = { @@ -50,6 +51,9 @@ static u_char ngx_http_server_string[] = "Server: nginx" CRLF; static u_char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF; static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD CRLF; +static ngx_str_t ngx_http_early_hints_status_line = + ngx_string("HTTP/1.1 103 Early Hints" CRLF); + static ngx_str_t ngx_http_status_lines[] = { @@ -625,10 +629,113 @@ ngx_http_header_filter(ngx_http_request_t *r) } +static ngx_int_t +ngx_http_early_hints_filter(ngx_http_request_t *r) +{ + size_t len; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t out; + ngx_list_part_t *part; + ngx_table_elt_t *header; + + if (r != r->main) { + return NGX_OK; + } + + if (r->http_version < NGX_HTTP_VERSION_11) { + return NGX_OK; + } + + len = 0; + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len + + sizeof(CRLF) - 1; + } + + if (len == 0) { + return NGX_OK; + } + + len += ngx_http_early_hints_status_line.len + /* the end of the early hints */ + + sizeof(CRLF) - 1; + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = ngx_copy(b->last, ngx_http_early_hints_status_line.data, + ngx_http_early_hints_status_line.len); + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len); + *b->last++ = ':'; *b->last++ = ' '; + + b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); + *b->last++ = CR; *b->last++ = LF; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "%*s", (size_t) (b->last - b->pos), b->pos); + + /* the end of HTTP early hints */ + *b->last++ = CR; *b->last++ = LF; + + r->header_size = b->last - b->pos; + + b->flush = 1; + + out.buf = b; + out.next = NULL; + + return ngx_http_write_filter(r, &out); +} + + static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf) { ngx_http_top_header_filter = ngx_http_header_filter; + ngx_http_top_early_hints_filter = ngx_http_early_hints_filter; return NGX_OK; } diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index 9407f46ae..ad11f147f 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -74,6 +74,7 @@ #define NGX_HTTP_CONTINUE 100 #define NGX_HTTP_SWITCHING_PROTOCOLS 101 #define NGX_HTTP_PROCESSING 102 +#define NGX_HTTP_EARLY_HINTS 103 #define NGX_HTTP_OK 200 #define NGX_HTTP_CREATED 201 diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index d4cf1b7fe..d1bcdbbe0 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -48,6 +48,9 @@ static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r, static void ngx_http_upstream_read_request_handler(ngx_http_request_t *r); static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u); +static ngx_int_t ngx_http_upstream_process_early_hints(ngx_http_request_t *r, + ngx_http_upstream_t *u); +static void ngx_http_upstream_early_hints_writer(ngx_http_request_t *r); static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u); static ngx_int_t ngx_http_upstream_intercept_errors(ngx_http_request_t *r, @@ -2530,6 +2533,20 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) continue; } + if (rc == NGX_OK + && u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) + { + rc = ngx_http_upstream_process_early_hints(r, u); + + if (rc == NGX_OK) { + rc = u->process_header(r); + + if (rc == NGX_AGAIN) { + continue; + } + } + } + break; } @@ -2567,6 +2584,152 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) } +static ngx_int_t +ngx_http_upstream_process_early_hints(ngx_http_request_t *r, + ngx_http_upstream_t *u) +{ + u_char *p; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *h, *ho; + ngx_connection_t *c; + + c = r->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream early hints"); + + if (u->conf->pass_early_hints) { + + u->early_hints_length += u->buffer.pos - u->buffer.start; + + if (u->early_hints_length <= (off_t) u->conf->buffer_size) { + + part = &u->headers_in.headers.part; + h = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + h = part->elts; + i = 0; + } + + if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, + h[i].lowcase_key, h[i].key.len)) + { + continue; + } + + ho = ngx_list_push(&r->headers_out.headers); + if (ho == NULL) { + return NGX_ERROR; + } + + *ho = h[i]; + } + + if (ngx_http_send_early_hints(r) == NGX_ERROR) { + return NGX_ERROR; + } + + if (c->buffered) { + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + return NGX_ERROR; + } + + r->write_event_handler = ngx_http_upstream_early_hints_writer; + } + + } else { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "upstream sent too big early hints"); + } + } + + if (u->reinit_request(r) != NGX_OK) { + return NGX_ERROR; + } + + ngx_http_clean_header(r); + + ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); + u->headers_in.content_length_n = -1; + u->headers_in.last_modified_time = -1; + + if (ngx_list_init(&u->headers_in.headers, r->pool, 8, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_list_init(&u->headers_in.trailers, r->pool, 2, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + p = u->buffer.pos; + + u->buffer.pos = u->buffer.start; + +#if (NGX_HTTP_CACHE) + + if (r->cache) { + u->buffer.pos += r->cache->header_start; + } + +#endif + + u->buffer.last = ngx_movemem(u->buffer.pos, p, u->buffer.last - p); + + return NGX_OK; +} + + +static void +ngx_http_upstream_early_hints_writer(ngx_http_request_t *r) +{ + ngx_connection_t *c; + ngx_http_upstream_t *u; + + c = r->connection; + u = r->upstream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http upstream early hints writer"); + + c->log->action = "sending early hints to client"; + + if (ngx_http_write_filter(r, NULL) == NGX_ERROR) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (!c->buffered) { + if (!u->store && !r->post_action && !u->conf->ignore_client_abort) { + r->write_event_handler = + ngx_http_upstream_wr_check_broken_connection; + + } else { + r->write_event_handler = ngx_http_request_empty_handler; + } + } + + if (ngx_handle_write_event(c->write, 0) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } +} + + static ngx_int_t ngx_http_upstream_test_next(ngx_http_request_t *r, ngx_http_upstream_t *u) { diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index e0a903669..7a47675eb 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -185,6 +185,7 @@ typedef struct { ngx_flag_t pass_request_headers; ngx_flag_t pass_request_body; ngx_flag_t pass_trailers; + ngx_flag_t pass_early_hints; ngx_flag_t ignore_client_abort; ngx_flag_t intercept_errors; @@ -354,6 +355,7 @@ struct ngx_http_upstream_s { ngx_buf_t buffer; off_t length; + off_t early_hints_length; ngx_chain_t *out_bufs; ngx_chain_t *busy_bufs; diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index 6751b3026..9605c8a23 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -213,6 +213,7 @@ struct ngx_http_v2_stream_s { ngx_pool_t *pool; + unsigned initialized:1; unsigned waiting:1; unsigned blocked:1; unsigned exhausted:1; diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c index 556446ed2..907906a88 100644 --- a/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/http/v2/ngx_http_v2_filter_module.c @@ -28,6 +28,8 @@ static ngx_int_t ngx_http_v2_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_early_hints_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_init_stream(ngx_http_request_t *r); static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin); @@ -97,6 +99,7 @@ ngx_module_t ngx_http_v2_filter_module = { static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_header_filter_pt ngx_http_next_early_hints_filter; static ngx_int_t @@ -109,7 +112,6 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *fc; - ngx_http_cleanup_t *cln; ngx_http_v2_stream_t *stream; ngx_http_v2_out_frame_t *frame; ngx_http_v2_connection_t *h2c; @@ -614,7 +616,196 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) ngx_http_v2_queue_blocked_frame(h2c, frame); - stream->queued = 1; + stream->queued++; + + if (ngx_http_v2_init_stream(r) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_http_v2_filter_send(fc, stream); +} + + +static ngx_int_t +ngx_http_v2_early_hints_filter(ngx_http_request_t *r) +{ + u_char *pos, *start, *tmp; + size_t len, tmp_len; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *fc; + ngx_http_v2_stream_t *stream; + ngx_http_v2_out_frame_t *frame; + ngx_http_v2_connection_t *h2c; + + stream = r->stream; + + if (!stream) { + return ngx_http_next_early_hints_filter(r); + } + + if (r != r->main) { + return NGX_OK; + } + + fc = r->connection; + + if (fc->error) { + return NGX_ERROR; + } + + len = 0; + tmp_len = 0; + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + if (header[i].key.len > NGX_HTTP_V2_MAX_FIELD) { + ngx_log_error(NGX_LOG_CRIT, fc->log, 0, + "too long response header name: \"%V\"", + &header[i].key); + return NGX_ERROR; + } + + if (header[i].value.len > NGX_HTTP_V2_MAX_FIELD) { + ngx_log_error(NGX_LOG_CRIT, fc->log, 0, + "too long response header value: \"%V: %V\"", + &header[i].key, &header[i].value); + return NGX_ERROR; + } + + len += 1 + NGX_HTTP_V2_INT_OCTETS + header[i].key.len + + NGX_HTTP_V2_INT_OCTETS + header[i].value.len; + + if (header[i].key.len > tmp_len) { + tmp_len = header[i].key.len; + } + + if (header[i].value.len > tmp_len) { + tmp_len = header[i].value.len; + } + } + + if (len == 0) { + return NGX_OK; + } + + h2c = stream->connection; + + len += h2c->table_update ? 1 : 0; + len += 1 + ngx_http_v2_literal_size("418"); + + tmp = ngx_palloc(r->pool, tmp_len); + pos = ngx_pnalloc(r->pool, len); + + if (pos == NULL || tmp == NULL) { + return NGX_ERROR; + } + + start = pos; + + if (h2c->table_update) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 table size update: 0"); + *pos++ = (1 << 5) | 0; + h2c->table_update = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \":status: %03ui\"", + (ngx_uint_t) NGX_HTTP_EARLY_HINTS); + + *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_STATUS_INDEX); + *pos++ = NGX_HTTP_V2_ENCODE_RAW | 3; + pos = ngx_sprintf(pos, "%03ui", (ngx_uint_t) NGX_HTTP_EARLY_HINTS); + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + +#if (NGX_DEBUG) + if (fc->log->log_level & NGX_LOG_DEBUG_HTTP) { + ngx_strlow(tmp, header[i].key.data, header[i].key.len); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, fc->log, 0, + "http2 output header: \"%*s: %V\"", + header[i].key.len, tmp, &header[i].value); + } +#endif + + *pos++ = 0; + + pos = ngx_http_v2_write_name(pos, header[i].key.data, + header[i].key.len, tmp); + + pos = ngx_http_v2_write_value(pos, header[i].value.data, + header[i].value.len, tmp); + } + + frame = ngx_http_v2_create_headers_frame(r, start, pos, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + stream->queued++; + + if (ngx_http_v2_init_stream(r) != NGX_OK) { + return NGX_ERROR; + } + + return ngx_http_v2_filter_send(fc, stream); +} + + +static ngx_int_t +ngx_http_v2_init_stream(ngx_http_request_t *r) +{ + ngx_connection_t *fc; + ngx_http_cleanup_t *cln; + ngx_http_v2_stream_t *stream; + + stream = r->stream; + fc = r->connection; + + if (stream->initialized) { + return NGX_OK; + } + + stream->initialized = 1; cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { @@ -628,7 +819,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) fc->need_last_buf = 1; fc->need_flush_buf = 1; - return ngx_http_v2_filter_send(fc, stream); + return NGX_OK; } @@ -1569,5 +1760,8 @@ ngx_http_v2_filter_init(ngx_conf_t *cf) ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_v2_header_filter; + ngx_http_next_early_hints_filter = ngx_http_top_early_hints_filter; + ngx_http_top_early_hints_filter = ngx_http_v2_early_hints_filter; + return NGX_OK; } diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 4d2276dc0..64e6b8e8d 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -36,6 +36,7 @@ typedef struct { static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_early_hints_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, @@ -75,6 +76,7 @@ ngx_module_t ngx_http_v3_filter_module = { static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_header_filter_pt ngx_http_next_early_hints_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; @@ -588,6 +590,154 @@ ngx_http_v3_header_filter(ngx_http_request_t *r) } +static ngx_int_t +ngx_http_v3_early_hints_filter(ngx_http_request_t *r) +{ + size_t len, n; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *out, *hl, *cl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_v3_session_t *h3c; + + if (r->http_version != NGX_HTTP_VERSION_30) { + return ngx_http_next_early_hints_filter(r); + } + + if (r != r->main) { + return NGX_OK; + } + + len = 0; + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + if (len == 0) { + return NGX_OK; + } + + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header len:%uz", len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 output header: \":status: %03ui\"", + (ngx_uint_t) NGX_HTTP_EARLY_HINTS); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + b->last = ngx_sprintf(b->last, "%03ui", (ngx_uint_t) NGX_HTTP_EARLY_HINTS); + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + b->flush = 1; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + h3c = ngx_http_v3_get_session(r->connection); + h3c->payload_bytes += n; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NGX_ERROR; + } + + hl->buf = b; + hl->next = cl; + + out = hl; + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + r->header_size += cl->buf->last - cl->buf->pos; + } + + return ngx_http_write_filter(r, out); +} + + static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { @@ -845,6 +995,9 @@ ngx_http_v3_filter_init(ngx_conf_t *cf) ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_v3_header_filter; + ngx_http_next_early_hints_filter = ngx_http_top_early_hints_filter; + ngx_http_top_early_hints_filter = ngx_http_v3_early_hints_filter; + ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_v3_body_filter; From noreply at nginx.com Mon Jun 23 16:13:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 23 Jun 2025 16:13:02 +0000 (UTC) Subject: [nginx] Upstream: fixed reinit request with gRPC and Early Hints. Message-ID: <20250623161302.799FB48FF8@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/cdf7a9c6cb7f344efc80d790fbacdc1c94ab16e3 branches: master commit: cdf7a9c6cb7f344efc80d790fbacdc1c94ab16e3 user: Sergey Kandaurov date: Mon, 23 Jun 2025 14:55:32 +0400 description: Upstream: fixed reinit request with gRPC and Early Hints. The gRPC module context has connection specific state, which can be lost after request reinitialization when it comes to processing early hints. The fix is to do only a portion of u->reinit_request() implementation required after processing early hints, now inlined in modules. Now NGX_HTTP_UPSTREAM_EARLY_HINTS is returned from u->process_header() for early hints. When reading a cached response, this code is mapped to NGX_HTTP_UPSTREAM_INVALID_HEADER to indicate invalid header format. --- src/http/modules/ngx_http_grpc_module.c | 11 +++++++++++ src/http/modules/ngx_http_proxy_module.c | 14 +++++++++++--- src/http/ngx_http_upstream.c | 10 ++-------- src/http/ngx_http_upstream.h | 1 + 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index d74b17708..4c95525b4 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -1928,6 +1928,17 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "grpc header done"); + if (u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { + if (ctx->end_stream) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream prematurely closed stream"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + ctx->status = 0; + return NGX_HTTP_UPSTREAM_EARLY_HINTS; + } + if (ctx->end_stream) { u->headers_in.content_length_n = 0; diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index ff7460e44..8d5385c1d 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -1985,8 +1985,18 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http proxy header done"); + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + if (r->upstream->headers_in.status_n == NGX_HTTP_EARLY_HINTS) { - return NGX_OK; + ctx->status.code = 0; + ctx->status.count = 0; + ctx->status.start = NULL; + ctx->status.end = NULL; + + r->upstream->process_header = + ngx_http_proxy_process_status_line; + r->state = 0; + return NGX_HTTP_UPSTREAM_EARLY_HINTS; } /* @@ -2036,8 +2046,6 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) * connections alive in case of r->header_only or X-Accel-Redirect */ - ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); - if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED || ctx->head diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index d1bcdbbe0..de0f92a4f 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -1123,7 +1123,7 @@ ngx_http_upstream_cache_send(ngx_http_request_t *r, ngx_http_upstream_t *u) return NGX_ERROR; } - if (rc == NGX_AGAIN) { + if (rc == NGX_AGAIN || rc == NGX_HTTP_UPSTREAM_EARLY_HINTS) { rc = NGX_HTTP_UPSTREAM_INVALID_HEADER; } @@ -2533,9 +2533,7 @@ ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u) continue; } - if (rc == NGX_OK - && u->headers_in.status_n == NGX_HTTP_EARLY_HINTS) - { + if (rc == NGX_HTTP_UPSTREAM_EARLY_HINTS) { rc = ngx_http_upstream_process_early_hints(r, u); if (rc == NGX_OK) { @@ -2651,10 +2649,6 @@ ngx_http_upstream_process_early_hints(ngx_http_request_t *r, } } - if (u->reinit_request(r) != NGX_OK) { - return NGX_ERROR; - } - ngx_http_clean_header(r); ngx_memzero(&u->headers_in, sizeof(ngx_http_upstream_headers_in_t)); diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 7a47675eb..f3e9f7979 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -43,6 +43,7 @@ |NGX_HTTP_UPSTREAM_FT_HTTP_429) #define NGX_HTTP_UPSTREAM_INVALID_HEADER 40 +#define NGX_HTTP_UPSTREAM_EARLY_HINTS 41 #define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT 0x00000002 From noreply at nginx.com Sat Jun 21 06:37:03 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 21 Jun 2025 06:37:03 +0000 (UTC) Subject: [nginx] Use NULL instead of 0 for null pointer constant. Message-ID: <20250621063703.3ABDF4884B@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/4eaecc5e8aa7bbaf9e58bf56560a8b1e67d0a8b7 branches: master commit: 4eaecc5e8aa7bbaf9e58bf56560a8b1e67d0a8b7 user: Andrew Clayton date: Wed, 21 May 2025 22:30:20 +0100 description: Use NULL instead of 0 for null pointer constant. There were a few random places where 0 was being used as a null pointer constant. We have a NULL macro for this very purpose, use it. There is also some interest in actually deprecating the use of 0 as a null pointer constant in C. This was found with -Wzero-as-null-pointer-constant which was enabled for C in GCC 15 (not enabled with Wall or Wextra... yet). Link: --- src/core/ngx_thread_pool.c | 2 +- src/event/ngx_event_openssl_cache.c | 2 +- src/event/quic/ngx_event_quic.c | 2 +- src/http/modules/ngx_http_gunzip_filter_module.c | 2 +- src/http/modules/ngx_http_ssi_filter_module.c | 2 +- src/os/unix/ngx_time.c | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/ngx_thread_pool.c b/src/core/ngx_thread_pool.c index 7fb0f7f8d..7bf90e958 100644 --- a/src/core/ngx_thread_pool.c +++ b/src/core/ngx_thread_pool.c @@ -207,7 +207,7 @@ ngx_thread_pool_exit_handler(void *data, ngx_log_t *log) *lock = 0; - pthread_exit(0); + pthread_exit(NULL); } diff --git a/src/event/ngx_event_openssl_cache.c b/src/event/ngx_event_openssl_cache.c index 18efc73d0..42f5e1c9f 100644 --- a/src/event/ngx_event_openssl_cache.c +++ b/src/event/ngx_event_openssl_cache.c @@ -707,7 +707,7 @@ ngx_ssl_cache_pkey_create(ngx_ssl_cache_key_t *id, char **err, void *data) return NULL; } - pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0); + pkey = ENGINE_load_private_key(engine, (char *) last, NULL, NULL); if (pkey == NULL) { *err = "ENGINE_load_private_key() failed"; diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 8df487773..6a59aaf93 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -964,7 +964,7 @@ ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) qc = ngx_quic_get_connection(c); qc->error = 0; - qc->error_reason = 0; + qc->error_reason = NULL; c->log->action = "decrypting packet"; diff --git a/src/http/modules/ngx_http_gunzip_filter_module.c b/src/http/modules/ngx_http_gunzip_filter_module.c index 5d170a1ba..22e75e300 100644 --- a/src/http/modules/ngx_http_gunzip_filter_module.c +++ b/src/http/modules/ngx_http_gunzip_filter_module.c @@ -304,7 +304,7 @@ ngx_http_gunzip_filter_inflate_start(ngx_http_request_t *r, { int rc; - ctx->zstream.next_in = Z_NULL; + ctx->zstream.next_in = NULL; ctx->zstream.avail_in = 0; ctx->zstream.zalloc = ngx_http_gunzip_filter_alloc; diff --git a/src/http/modules/ngx_http_ssi_filter_module.c b/src/http/modules/ngx_http_ssi_filter_module.c index c862be05a..65ca03440 100644 --- a/src/http/modules/ngx_http_ssi_filter_module.c +++ b/src/http/modules/ngx_http_ssi_filter_module.c @@ -820,7 +820,7 @@ ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } for (prm = cmd->params; prm->name.len; prm++) { - if (prm->mandatory && params[prm->index] == 0) { + if (prm->mandatory && params[prm->index] == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "mandatory \"%V\" parameter is absent " "in \"%V\" SSI command", diff --git a/src/os/unix/ngx_time.c b/src/os/unix/ngx_time.c index cc760b2eb..c97bae2ed 100644 --- a/src/os/unix/ngx_time.c +++ b/src/os/unix/ngx_time.c @@ -43,7 +43,7 @@ ngx_timezone_update(void) struct tm *t; char buf[4]; - s = time(0); + s = time(NULL); t = localtime(&s); From noreply at nginx.com Sat Jun 21 06:37:03 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 21 Jun 2025 06:37:03 +0000 (UTC) Subject: [nginx] Use NGX_CONF_OK in some function return checks. Message-ID: <20250621063703.3377348762@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/c370ac8a51152cc67f803b553579bfc16299efc3 branches: master commit: c370ac8a51152cc67f803b553579bfc16299efc3 user: Andrew Clayton date: Wed, 21 May 2025 22:19:32 +0100 description: Use NGX_CONF_OK in some function return checks. The functions ngx_http_merge_types() & ngx_conf_merge_path_value() return either NGX_CONF_OK aka NULL aka ((void *)0) (probably) or NGX_CONF_ERROR aka ((void *)-1). They don't return an integer constant which is what NGX_OK aka (0) is. Lets use the right thing in the function return check. This was found with -Wzero-as-null-pointer-constant which was enabled for C in GCC 15 (not enabled with Wall or Wextra... yet). Link: --- src/http/modules/ngx_http_addition_filter_module.c | 2 +- src/http/modules/ngx_http_charset_filter_module.c | 2 +- src/http/modules/ngx_http_fastcgi_module.c | 2 +- src/http/modules/ngx_http_gzip_filter_module.c | 2 +- src/http/modules/ngx_http_proxy_module.c | 2 +- src/http/modules/ngx_http_scgi_module.c | 2 +- src/http/modules/ngx_http_ssi_filter_module.c | 2 +- src/http/modules/ngx_http_sub_filter_module.c | 2 +- src/http/modules/ngx_http_uwsgi_module.c | 2 +- src/http/modules/ngx_http_xslt_filter_module.c | 2 +- src/http/ngx_http_core_module.c | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/http/modules/ngx_http_addition_filter_module.c b/src/http/modules/ngx_http_addition_filter_module.c index e546f0d60..040244c7e 100644 --- a/src/http/modules/ngx_http_addition_filter_module.c +++ b/src/http/modules/ngx_http_addition_filter_module.c @@ -245,7 +245,7 @@ ngx_http_addition_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_charset_filter_module.c b/src/http/modules/ngx_http_charset_filter_module.c index d44da6233..482960040 100644 --- a/src/http/modules/ngx_http_charset_filter_module.c +++ b/src/http/modules/ngx_http_charset_filter_module.c @@ -1564,7 +1564,7 @@ ngx_http_charset_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_charset_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c index a41b496dc..6b1977340 100644 --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -3130,7 +3130,7 @@ ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_fastcgi_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c index 7113df695..dac21bb18 100644 --- a/src/http/modules/ngx_http_gzip_filter_module.c +++ b/src/http/modules/ngx_http_gzip_filter_module.c @@ -1115,7 +1115,7 @@ ngx_http_gzip_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index dbe998b13..ff7460e44 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -3859,7 +3859,7 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_proxy_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c index 9023a36e4..49977b07b 100644 --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -1543,7 +1543,7 @@ ngx_http_scgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_scgi_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_ssi_filter_module.c b/src/http/modules/ngx_http_ssi_filter_module.c index 47068f755..c862be05a 100644 --- a/src/http/modules/ngx_http_ssi_filter_module.c +++ b/src/http/modules/ngx_http_ssi_filter_module.c @@ -2942,7 +2942,7 @@ ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_sub_filter_module.c b/src/http/modules/ngx_http_sub_filter_module.c index 456bb27e3..b50a25235 100644 --- a/src/http/modules/ngx_http_sub_filter_module.c +++ b/src/http/modules/ngx_http_sub_filter_module.c @@ -901,7 +901,7 @@ ngx_http_sub_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_html_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c index 51a861d9a..c1d0035cc 100644 --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -1803,7 +1803,7 @@ ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->upstream.temp_path, prev->upstream.temp_path, &ngx_http_uwsgi_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/modules/ngx_http_xslt_filter_module.c b/src/http/modules/ngx_http_xslt_filter_module.c index 8afd656af..4e6e1b99d 100644 --- a/src/http/modules/ngx_http_xslt_filter_module.c +++ b/src/http/modules/ngx_http_xslt_filter_module.c @@ -1112,7 +1112,7 @@ ngx_http_xslt_filter_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types, &prev->types_keys, &prev->types, ngx_http_xslt_default_types) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index 2d62674d9..c75ddb849 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -3931,7 +3931,7 @@ ngx_http_core_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) if (ngx_conf_merge_path_value(cf, &conf->client_body_temp_path, prev->client_body_temp_path, &ngx_http_client_temp_path) - != NGX_OK) + != NGX_CONF_OK) { return NGX_CONF_ERROR; } From noreply at nginx.com Sat Jun 21 06:37:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Sat, 21 Jun 2025 06:37:02 +0000 (UTC) Subject: [nginx] HTTP/3: indexed field line encoding for "103 Early Hints". Message-ID: <20250621063702.63E0348762@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/ba917b1360c6df3dda312325daccf714a3145718 branches: master commit: ba917b1360c6df3dda312325daccf714a3145718 user: Sergey Kandaurov date: Fri, 20 Jun 2025 18:46:17 +0400 description: HTTP/3: indexed field line encoding for "103 Early Hints". --- src/http/v3/ngx_http_v3_filter_module.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c index 64e6b8e8d..e3f15368f 100644 --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -20,6 +20,7 @@ #define NGX_HTTP_V3_HEADER_METHOD_GET 17 #define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 #define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 +#define NGX_HTTP_V3_HEADER_STATUS_103 24 #define NGX_HTTP_V3_HEADER_STATUS_200 25 #define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 #define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 @@ -640,9 +641,7 @@ ngx_http_v3_early_hints_filter(ngx_http_request_t *r) len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); - len += ngx_http_v3_encode_field_lri(NULL, 0, - NGX_HTTP_V3_HEADER_STATUS_200, - NULL, 3); + len += ngx_http_v3_encode_field_ri(NULL, 0, NGX_HTTP_V3_HEADER_STATUS_103); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 header len:%uz", len); @@ -659,10 +658,8 @@ ngx_http_v3_early_hints_filter(ngx_http_request_t *r) "http3 output header: \":status: %03ui\"", (ngx_uint_t) NGX_HTTP_EARLY_HINTS); - b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, - NGX_HTTP_V3_HEADER_STATUS_200, - NULL, 3); - b->last = ngx_sprintf(b->last, "%03ui", (ngx_uint_t) NGX_HTTP_EARLY_HINTS); + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_103); part = &r->headers_out.headers.part; header = part->elts; From noreply at nginx.com Mon Jun 23 18:36:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Mon, 23 Jun 2025 18:36:02 +0000 (UTC) Subject: [nginx] QUIC: disabled OpenSSL 3.5 QUIC API support by default. Message-ID: <20250623183602.D92D748FFF@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/cedb855d75ceefd7fe513f9c27c9364678582786 branches: master commit: cedb855d75ceefd7fe513f9c27c9364678582786 user: Sergey Kandaurov date: Tue, 27 May 2025 21:56:40 +0400 description: QUIC: disabled OpenSSL 3.5 QUIC API support by default. In OpenSSL 3.5.0, the "quic_transport_parameters" extension set internally by the QUIC API is cleared on the SSL context switch, which disables sending QUIC transport parameters if switching to a different server block on SNI. See the initial report in [1]. This is fixed post OpenSSL 3.5.0 [2]. The fix is anticipated in OpenSSL 3.5.1, which has not been released yet. When building with OpenSSL 3.5, OpenSSL compat layer is now used by default. The OpenSSL 3.5 QUIC API support can be switched back using --with-cc-opt='-DNGX_QUIC_OPENSSL_API=1'. [1] https://github.com/nginx/nginx/issues/711 [2] https://github.com/openssl/openssl/commit/45bd3c3798 --- src/event/quic/ngx_event_quic.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h index d95d3d85b..335d87191 100644 --- a/src/event/quic/ngx_event_quic.h +++ b/src/event/quic/ngx_event_quic.h @@ -13,7 +13,10 @@ #ifdef OSSL_RECORD_PROTECTION_LEVEL_NONE -#define NGX_QUIC_OPENSSL_API 1 +#ifndef NGX_QUIC_OPENSSL_API +#define NGX_QUIC_BORINGSSL_API 1 +#define NGX_QUIC_OPENSSL_COMPAT 1 +#endif #elif (defined SSL_R_MISSING_QUIC_TRANSPORT_PARAMETERS_EXTENSION) #define NGX_QUIC_QUICTLS_API 1 From noreply at nginx.com Tue Jun 24 17:23:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 24 Jun 2025 17:23:02 +0000 (UTC) Subject: [nginx] Win32: skip OpenSSL dependency generation to conserve time. Message-ID: <20250624172302.CD55F48FF5@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/b997be14f5e19942a99d90eeeee53289f5d3a3a8 branches: master commit: b997be14f5e19942a99d90eeeee53289f5d3a3a8 user: Sergey Kandaurov date: Tue, 24 Jun 2025 16:42:20 +0400 description: Win32: skip OpenSSL dependency generation to conserve time. Disabling the build dependency feature is safe assuming that nginx/Windows release zip is always built from a clean tree. This allows to speed up total build time by around 40%. As it may not be suitable in general, the option resides here and not in configure. --- misc/GNUmakefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/misc/GNUmakefile b/misc/GNUmakefile index b7e76b942..6f8a5d396 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -76,7 +76,8 @@ win32: --with-stream_realip_module \ --with-stream_ssl_preread_module \ --with-openssl=$(OBJS)/lib/$(OPENSSL) \ - --with-openssl-opt="no-asm no-tests -D_WIN32_WINNT=0x0501" \ + --with-openssl-opt="no-asm no-tests no-makedepend \ + -D_WIN32_WINNT=0x0501" \ --with-http_ssl_module \ --with-mail_ssl_module \ --with-stream_ssl_module From noreply at nginx.com Tue Jun 24 17:23:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 24 Jun 2025 17:23:02 +0000 (UTC) Subject: [nginx] Updated OpenSSL and PCRE used for win32 builds. Message-ID: <20250624172302.D05C548FFA@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/1263d6bec3956df0e472507817ca29940e6e3b04 branches: master commit: 1263d6bec3956df0e472507817ca29940e6e3b04 user: Sergey Kandaurov date: Tue, 24 Jun 2025 15:20:27 +0400 description: Updated OpenSSL and PCRE used for win32 builds. --- misc/GNUmakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 6f8a5d396..724014ab0 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -6,9 +6,9 @@ TEMP = tmp CC = cl OBJS = objs.msvc8 -OPENSSL = openssl-3.0.15 +OPENSSL = openssl-3.5.0 ZLIB = zlib-1.3.1 -PCRE = pcre2-10.39 +PCRE = pcre2-10.45 release: export From noreply at nginx.com Tue Jun 24 17:23:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 24 Jun 2025 17:23:02 +0000 (UTC) Subject: [nginx] nginx-1.29.0-RELEASE Message-ID: <20250624172302.DF96748FFB@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/235f409907fd60eb2d8f6ecdc0e5cb163dd6d45f branches: master commit: 235f409907fd60eb2d8f6ecdc0e5cb163dd6d45f user: Sergey Kandaurov date: Tue, 24 Jun 2025 16:40:21 +0400 description: nginx-1.29.0-RELEASE --- docs/xml/nginx/changes.xml | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/docs/xml/nginx/changes.xml b/docs/xml/nginx/changes.xml index ecb063d57..45b8d4efe 100644 --- a/docs/xml/nginx/changes.xml +++ b/docs/xml/nginx/changes.xml @@ -5,6 +5,99 @@ + + + + +поддержка ответа с кодом 103 от proxy- и gRPC-бэкендов; +директива early_hints. + + +support for response code 103 from proxy and gRPC backends; +the "early_hints" directive. + + + + + +возможность загрузки секретных ключей с аппаратных устройств +с помощью OpenSSL provider. + + +loading of secret keys from hardware tokens +with OpenSSL provider. + + + + + +поддержка параметра so_keepalive директивы listen на macOS. + + +support for the "so_keepalive" parameter of the "listen" directive on macOS. + + + + + +уровень логгирования ошибок SSL в QUIC handshake +изменён с уровня error на crit для критических ошибок +и на info для всех остальных; +уровень логгирования неподдерживаемых транспортных параметров QUIC +понижен с уровня info до debug. + + +the logging level of SSL errors in a QUIC handshake +has been changed from "error" to "crit" for critical errors, +and to "info" for the rest; +the logging level of unsupported QUIC transport parameters +has been lowered from "info" to "debug". + + + + + +бинарная версия nginx/Windows теперь использует для сборки Windows SDK 10. + + +the native nginx/Windows binary release is now built using Windows SDK 10. + + + + + +nginx не собирался gcc 15, +если использовались модули ngx_http_v2_module и ngx_http_v3_module. + + +nginx could not be built by gcc 15 +if ngx_http_v2_module or ngx_http_v3_module modules were used. + + + + + +nginx мог не собираться gcc 14 и новее с оптимизацией -O3 -flto, +если использовался модуль ngx_http_v3_module. + + +nginx might not be built by gcc 14 or newer with -O3 -flto optimization +if ngx_http_v3_module was used. + + + + + +Исправления и улучшения в HTTP/3. + + +Bugfixes and improvements in HTTP/3. + + + + + + From noreply at nginx.com Tue Jun 24 17:28:03 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Tue, 24 Jun 2025 17:28:03 +0000 (UTC) Subject: [nginx] Annotated tag created: release-1.29.0 Message-ID: <20250624172803.4DA8948FFA@pubserv1.nginx> details: https://github.com/nginx/nginx/releases/tag/release-1.29.0 branches: commit: 235f409907fd60eb2d8f6ecdc0e5cb163dd6d45f user: Sergey Kandaurov date: Tue Jun 24 21:25:18 2025 +0400 description: release-1.29.0 tag From noreply at nginx.com Wed Jun 25 10:20:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 25 Jun 2025 10:20:02 +0000 (UTC) Subject: [nginx] Version bump. Message-ID: <20250625102002.DD2343F53D@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/279fe352cbe9745b6b8e93737fdd3df55f003150 branches: master commit: 279fe352cbe9745b6b8e93737fdd3df55f003150 user: Sergey Kandaurov date: Tue, 24 Jun 2025 22:45:32 +0400 description: Version bump. --- src/core/nginx.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/nginx.h b/src/core/nginx.h index 72664a531..cf230af64 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1029000 -#define NGINX_VERSION "1.29.0" +#define nginx_version 1029001 +#define NGINX_VERSION "1.29.1" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD From noreply at nginx.com Wed Jun 25 10:20:02 2025 From: noreply at nginx.com (noreply at nginx.com) Date: Wed, 25 Jun 2025 10:20:02 +0000 (UTC) Subject: [nginx] Win32: fixed PCRE license for nginx/Windows zip. Message-ID: <20250625102002.E04723F53E@pubserv1.nginx> details: https://github.com/nginx/nginx/commit/d1843e1d9b9aec98c2aceabf4709e40ed4b2a96f branches: master commit: d1843e1d9b9aec98c2aceabf4709e40ed4b2a96f user: Sergey Kandaurov date: Tue, 24 Jun 2025 22:46:00 +0400 description: Win32: fixed PCRE license for nginx/Windows zip. --- misc/GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/GNUmakefile b/misc/GNUmakefile index 724014ab0..22eed135f 100644 --- a/misc/GNUmakefile +++ b/misc/GNUmakefile @@ -110,7 +110,7 @@ zip: export cp -p $(OBJS)/lib/$(OPENSSL)/LICENSE.txt \ $(TEMP)/$(NGINX)/docs/OpenSSL.LICENSE - cp -p $(OBJS)/lib/$(PCRE)/LICENCE \ + cp -p $(OBJS)/lib/$(PCRE)/LICENCE.md \ $(TEMP)/$(NGINX)/docs/PCRE.LICENCE sed -ne '/^ (C) 1995-20/,/^ jloup at gzip\.org/p' \