]> git.kaiwu.me - njs.git/log
njs.git
5 weeks agoWebCrypto: added Ed25519 and X25519 support.
Dmitry Volyntsev [Fri, 3 Apr 2026 07:00:58 +0000 (00:00 -0700)]
WebCrypto: added Ed25519 and X25519 support.

Implemented Ed25519 sign/verify/generateKey/importKey/exportKey
Supports raw, PKCS8, SPKI, and JWK (OKP) key formats.

Implemented X25519 deriveBits/deriveKey/generateKey/importKey/
exportKey.

5 weeks agoWebCrypto: added AES-KW algorithm support.
Dmitry Volyntsev [Fri, 3 Apr 2026 05:32:24 +0000 (22:32 -0700)]
WebCrypto: added AES-KW algorithm support.

Supports 128, 192, and 256-bit key sizes with generateKey,
importKey, and exportKey operations in raw and JWK formats.

Also fixed deriveKey to accept 192-bit AES key lengths.

5 weeks agoWebCrypto: added crypto.randomUUID().
Dmitry Volyntsev [Fri, 3 Apr 2026 05:25:08 +0000 (22:25 -0700)]
WebCrypto: added crypto.randomUUID().

5 weeks agoWebCrypto: fixed RSA JWK test data for Node.js compatibility.
Dmitry Volyntsev [Sat, 4 Apr 2026 01:22:22 +0000 (18:22 -0700)]
WebCrypto: fixed RSA JWK test data for Node.js compatibility.

Removed the incorrect "alg":"RS256" field from RSA JWK test data
files.  The field is optional per RFC 7517, and the value RS256
(RSASSA-PKCS1-v1_5) was wrong for tests that use RSA-OAEP and
RSA-PSS algorithms.  Node.js correctly rejects JWK imports when
the "alg" field does not match the requested algorithm.

The hash mismatch negative test was updated to use an inline JWK
with an explicit "alg" field instead of the shared file.

This ensures: `test/test262 --binary=node test/webcrypto` pass.

5 weeks agoFixed length prop of an Array instance redefinition error handing.
Dmitry Volyntsev [Fri, 3 Apr 2026 00:49:31 +0000 (17:49 -0700)]
Fixed length prop of an Array instance redefinition error handing.

Previously, when njs_array_length_redefine() failed, the function
returned directly, bypassing the cleanup of the "keys" array.

6 weeks agoCrypto: switched to OpenSSL EVP for hashing.
Dmitry Volyntsev [Tue, 24 Mar 2026 01:46:02 +0000 (18:46 -0700)]
Crypto: switched to OpenSSL EVP for hashing.

Previously, the crypto module used built-in software implementations
for a limited set of hash algorithms (md5, sha1, sha256).  This
prevented users from using algorithms like sha384, sha512, and sha3
family, even when the underlying OpenSSL library supported them.

The change replaces built-in hash implementations with OpenSSL
EVP_MD_CTX for createHash() and HMAC_CTX for createHmac(), following
the webcrypto module.  Algorithm lookup now uses EVP_get_digestbyname(),
making any digest supported by the linked OpenSSL available to
JavaScript code.

The module now requires OpenSSL and is conditionally compiled, same as
the webcrypto module.  Builds without OpenSSL (--no-openssl) will no
longer have the crypto module available.

Tested with OpenSSL 3.0, OpenSSL 1.1.1w, LibreSSL 3.9.2, and
BoringSSL.  SHA-3 tests are skipped when the SSL library does not
support them (e.g. BoringSSL).

This closes #1037 feature request on Github.

6 weeks agoModules: removed shared dict expiration from read-locked paths.
Dmitry Volyntsev [Fri, 27 Mar 2026 01:03:41 +0000 (18:03 -0700)]
Modules: removed shared dict expiration from read-locked paths.

Previously, keys(), items(), and size() called ngx_js_dict_expire()
under a read lock.  Since ngx_js_dict_expire() deletes nodes from
both rbtrees and frees slab memory, concurrent readers on different
worker processes could corrupt shared memory by freeing the same
expired nodes simultaneously.

The fix removes ngx_js_dict_expire() calls from all read-locked
paths and instead skips expired entries during iteration, consistent
with how get() and has() already handle expiry.  Actual cleanup of
expired entries is deferred to write-side operations (set, add,
delete, clear).

6 weeks agoModules: improved shared dict eviction strategy.
Dmitry Volyntsev [Fri, 27 Mar 2026 00:16:25 +0000 (17:16 -0700)]
Modules: improved shared dict eviction strategy.

Previously, when a slab allocation failed in evict mode, only 16
entries were evicted with a single retry.  This could still result
in SharedMemoryError when the freed slab slots did not match the
requested allocation size class, even though the zone had plenty
of evictable entries.

In practice, it might happen when the following conditions are met:
    - The shared zone is full
    - evict flag is enabled
    - key/value entries differ in size

The allocation now retries in a loop, evicting 16 entries at a time,
until the allocation succeeds or no more entries remain in the expire
tree.

After this change, allocation with evict enabled can only fail when:
    - the value is larger than the zone's usable space
    - the expire tree has no entries left to evict
    - zone metadata overhead leaves insufficient room

6 weeks agoModules: fixed double-free in shared dict update with eviction.
Dmitry Volyntsev [Fri, 27 Mar 2026 00:16:11 +0000 (17:16 -0700)]
Modules: fixed double-free in shared dict update with eviction.

Previously, when updating an existing key's string value in a shared
dictionary with timeout and evict enabled, ngx_js_dict_alloc() could
trigger ngx_js_dict_evict() if the zone was full.  Since the node being
updated was still in the expire tree, eviction could free it.  The
subsequent ngx_slab_free_locked() call in the update path then freed the
already-freed string data, causing the "chunk is already free" alert
followed by a segfault.

The fix removes the node from the expire tree before allocating
memory for the new value, preventing eviction from reaching it.
On allocation failure the node is re-inserted with its original
expiry time.

7 weeks agoModules: preserved per-entry TTL on shared dict incr() calls.
Dmitry Volyntsev [Thu, 12 Mar 2026 23:50:34 +0000 (16:50 -0700)]
Modules: preserved per-entry TTL on shared dict incr() calls.

Previously, incr() without an explicit timeout argument always
reset the entry expiry to the directive default, discarding any
per-entry timeout set by a prior add(), set(), or incr() call.

This aligns the behavior with Redis INCR and OpenResty
ngx.shared.DICT:incr() where value mutation does not touch the
existing TTL.  An explicit timeout argument still updates it.

7 weeks agoModules: added ttl() method to shared dictionaries.
Dmitry Volyntsev [Thu, 12 Mar 2026 23:48:14 +0000 (16:48 -0700)]
Modules: added ttl() method to shared dictionaries.

The method returns the remaining time-to-live in milliseconds
for a given key, or undefined if the key does not exist or has
expired.  Throws TypeError if the dictionary was declared without
the timeout parameter.

8 weeks agoParser: allow await expressions in tagged templates.
Dmitry Volyntsev [Wed, 4 Mar 2026 23:16:15 +0000 (15:16 -0800)]
Parser: allow await expressions in tagged templates.

8 weeks agoParser: allow await expressions in call arguments.
Dmitry Volyntsev [Wed, 4 Mar 2026 07:22:59 +0000 (23:22 -0800)]
Parser: allow await expressions in call arguments.

8 weeks agoFixed call argument evaluation.
Dmitry Volyntsev [Wed, 4 Mar 2026 07:22:54 +0000 (23:22 -0800)]
Fixed call argument evaluation.

Previously, call lowering created FUNCTION_FRAME and METHOD_FRAME before
argument evaluation.  This made call ordering observably wrong: for
non-callable callees, the error was thrown before arguments with side
effects were evaluated, violating the ECMAScript specification.  It also
prevented await expressions in call arguments, which were rejected at
parse time because suspending inside a half-created frame was not
supported.

The fix evaluates arguments first, then emits the frame, PUT_ARG, and
FUNCTION_CALL.  Callee and receiver values are captured into temporaries
before argument evaluation to guard against argument side effects.
Method properties are resolved via PROPERTY_GET before arguments.

METHOD_FRAME is redefined from a composite opcode (property lookup +
callability check + frame creation) to a pure frame-creation opcode
that takes an already-resolved function and explicit "this" value.
The parser always wraps call expressions in a NJS_TOKEN_FUNCTION_CALL
node, removing the NJS_TOKEN_NAME special case.

8 weeks agoGenerator: derive property swap from node.
Dmitry Volyntsev [Wed, 4 Mar 2026 07:22:52 +0000 (23:22 -0800)]
Generator: derive property swap from node.

Previously, the "in" operator swap flag was relayed through generator
context.  This broke when call-end handlers switched to
njs_generator_stack_pop(NULL), which released the context before the
swap was read in njs_generate_3addr_operation_end().

The fix derives the swap directly from the node token type
(NJS_TOKEN_IN), eliminating the context relay.

8 weeks agoParser: isolate optional preserve wrapper details.
Dmitry Volyntsev [Wed, 4 Mar 2026 04:40:57 +0000 (20:40 -0800)]
Parser: isolate optional preserve wrapper details.

Introduce NJS_TOKEN_OPTIONAL_PRESERVE for optional-chain preserve nodes
instead of reusing OBJECT_VALUE, so OBJECT_VALUE remains strictly an
object/array literal structure token.

Route optional-preserve, object-value, and optional method-preserve
access through dedicated helper functions with narrow assertions,
removing direct u.object/left/right access from general parser and
generator paths.

No behavior change.

8 weeks agoPreserve "this" for grouped optional calls.
Dmitry Volyntsev [Wed, 4 Mar 2026 04:40:19 +0000 (20:40 -0800)]
Preserve "this" for grouped optional calls.

Previously, grouped optional calls like (o?.m)() resolved the callee
through the optional chain but dispatched via plain FUNCTION_CALL,
losing the original receiver.

The fix stores the receiver on the call node so the upcoming
call-argument reorder can emit METHOD_FRAME with an explicit "this".
Call-expression setup and optional-chain preserve lookup are routed
through named helpers with generator-side validation.

8 weeks agoParser: lower property consumers to PROPERTY_REF.
Dmitry Volyntsev [Wed, 4 Mar 2026 04:40:09 +0000 (20:40 -0800)]
Parser: lower property consumers to PROPERTY_REF.

Previously, the generator inferred reference intent from raw AST shape.

Now the parser marks the relevant property node as PROPERTY_REF before
building METHOD_CALL, assignment, or update nodes, and the generator
accepts both PROPERTY and PROPERTY_REF via
njs_generate_is_property_lvalue().

Introduce NJS_TOKEN_PROPERTY_REF as an explicit parser-side marker for
property accesses that carry reference semantics (assignment targets,
delete operands, increment/decrement, method-call receivers).

8 weeks agoVersion bump.
Dmitry Volyntsev [Wed, 4 Mar 2026 04:58:33 +0000 (20:58 -0800)]
Version bump.

2 months agoVersion 0.9.6. 0.9.6
Dmitry Volyntsev [Tue, 3 Mar 2026 01:15:26 +0000 (17:15 -0800)]
Version 0.9.6.

2 months agoWebCrypto: validate JWK key type against algorithm in importKey().
Dmitry Volyntsev [Mon, 2 Mar 2026 17:35:48 +0000 (09:35 -0800)]
WebCrypto: validate JWK key type against algorithm in importKey().

Previously, importKey() did not verify that the JWK "kty" field
matched the requested algorithm.  For example, importing a JWK with
kty "oct" (symmetric) while specifying an asymmetric algorithm like
ECDH caused a SEGV in EVP_PKEY_free() during cleanup.  This happened
because the symmetric key data written into the union's "raw" member
overlapped with the "pkey" pointer, corrupting it.

The fix validates kty before calling any JWK import function:
    - "RSA" is only accepted for RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5
    - "EC" is only accepted for ECDSA, ECDH
    - "oct" is only accepted for HMAC, AES-GCM, AES-CTR, AES-CBC

Found by Akshay Jain (akshaythe@gmail.com).

2 months agoFixed string offset map corruption in scope values hash.
Dmitry Volyntsev [Thu, 26 Feb 2026 15:45:46 +0000 (07:45 -0800)]
Fixed string offset map corruption in scope values hash.

The issue was introduced in e7caa46d (0.9.5).  When compile-time
UTF-8 string constants were copied into the values hash in
njs_scope_value_index(), the string data layout was calculated
incorrectly: the map offset did not account for the null terminator
added in e7caa46d, the "size" variable was overwritten corrupting
the subsequent memcpy, and the offset map was never initialized
to zero.

This caused SEGV/SIGBUS crashes for any multi-byte UTF-8 string
constant with more than 32 characters when accessing a character
at index >= 32 (e.g. via .replace() or bracket notation).  The bug
only manifested when the string byte size was 4-byte aligned, as
otherwise alignment padding absorbed the missing byte.

The fix factors out njs_string_data_size() and njs_string_data_init()
helpers shared by njs_string_alloc() and njs_scope_value_index(),
eliminating the duplicated layout logic that caused the divergence.

Found by Akshay Jain (akshaythe@gmail.com).

2 months agoFixed logical assignment short-circuit with non-writable properties.
Dmitry Volyntsev [Tue, 3 Mar 2026 02:00:09 +0000 (18:00 -0800)]
Fixed logical assignment short-circuit with non-writable properties.

When the logical condition was already satisfied (e.g., falsy for &&=,
truthy for ||=, non-nullish for ??=), the short-circuit jump landed on
the property set instruction instead of past it.  This caused spurious
TypeErrors for non-writable, getter-only, and non-extensible property
targets even though no assignment should occur.

This fixes logical assignment introduced in 1a64ba68.
This change fixes 9 more tests in test262.

2 months agoAdded support for ??= operator.
Dmitry Volyntsev [Wed, 11 Feb 2026 16:16:00 +0000 (08:16 -0800)]
Added support for ??= operator.

2 months agoAdded support for ||= and &&= logical assignment operators.
Dmitry Volyntsev [Wed, 11 Feb 2026 15:13:06 +0000 (07:13 -0800)]
Added support for ||= and &&= logical assignment operators.

Unlike regular compound assignments (+=, -=), these operators
short-circuit: the RHS is not evaluated and no assignment occurs
if the logical condition is already satisfied.

2 months agoImplement optional chaining.
Dmitry Volyntsev [Sat, 28 Feb 2026 07:42:40 +0000 (23:42 -0800)]
Implement optional chaining.

2 months agoGenerator: factor property lvalue setup.
Dmitry Volyntsev [Sun, 1 Mar 2026 00:16:22 +0000 (16:16 -0800)]
Generator: factor property lvalue setup.

2 months agoGenerator: factor property assignment reads.
Dmitry Volyntsev [Sat, 28 Feb 2026 23:32:20 +0000 (15:32 -0800)]
Generator: factor property assignment reads.

2 months agoGenerator: factor test jump emission.
Dmitry Volyntsev [Sat, 28 Feb 2026 23:31:47 +0000 (15:31 -0800)]
Generator: factor test jump emission.

2 months agoGenerator: factor property assignment helpers.
Dmitry Volyntsev [Sat, 28 Feb 2026 23:31:24 +0000 (15:31 -0800)]
Generator: factor property assignment helpers.

2 months agoParser: factor right-link pop helper.
Dmitry Volyntsev [Sat, 28 Feb 2026 23:52:09 +0000 (15:52 -0800)]
Parser: factor right-link pop helper.

2 months agoParser: refactor call argument setup.
Dmitry Volyntsev [Sat, 28 Feb 2026 01:41:26 +0000 (17:41 -0800)]
Parser: refactor call argument setup.

2 months agoCI: added extra debug flags and enabled ASAN for njs basic tests.
Dmitry Volyntsev [Wed, 18 Feb 2026 02:08:41 +0000 (18:08 -0800)]
CI: added extra debug flags and enabled ASAN for njs basic tests.

2 months agoShell: fixed interactive mode detection for piped stdin.
Dmitry Volyntsev [Fri, 13 Feb 2026 05:31:23 +0000 (21:31 -0800)]
Shell: fixed interactive mode detection for piped stdin.

Previously, libedit callback API (rl_callback_read_char) was used for
interactive input processing.  Unlike the blocking readline() API, the
callback interface does not work correctly when stdin is a pipe: input
characters are silently dropped and the line handler is never invoked.

The issue was introduced in 4988565c6ea7 (0.8.0).

The fix is to check isatty(STDIN_FILENO) and fall back to the file
processing mode when stdin is not a terminal.

2 months agoReplace per-VM indexed array for modules with hash-based lookup.
Dmitry Volyntsev [Wed, 18 Feb 2026 01:56:22 +0000 (17:56 -0800)]
Replace per-VM indexed array for modules with hash-based lookup.

Previously, evaluated module values were cached in a separate
vm->modules array indexed by module->index assigned at compile time.
This required keeping the array size in sync with shared->module_items,
which was error-prone in interactive mode where new modules could be
compiled across commands.

Instead, store evaluated module values directly in the per-VM module
copy already maintained by vm->modules_hash.  This unifies the import
and require() caching paths and eliminates the index-based array along
with module->index and shared->module_items fields.

This fixes `make shell_test` when configured with --debug=YES.

2 months agoModules: suppressed slab log_nomem for evict shared dict zones.
Dmitry Volyntsev [Wed, 25 Feb 2026 07:13:54 +0000 (23:13 -0800)]
Modules: suppressed slab log_nomem for evict shared dict zones.

When evict is enabled, memory allocation failures are expected
and handled by evicting old entries.  The slab allocator's
"no memory" log messages are now suppressed for such zones.

2 months agoModules: fixed expire field truncation in shared dict state files.
Dmitry Volyntsev [Fri, 20 Feb 2026 23:45:25 +0000 (15:45 -0800)]
Modules: fixed expire field truncation in shared dict state files.

The njs_sprintf buffer for the expire field was sized for 10-digit
numbers, but current millisecond timestamps are 13 digits. This caused
silent truncation, making entries appear expired on a full restart.

The issue has been present since eca03622 (0.9.1), which introduced
the shared dictionary state file support.

2 months agoFixed heap-buffer-overflow in atom hash caused by Symbol().
Dmitry Volyntsev [Tue, 17 Feb 2026 00:20:20 +0000 (16:20 -0800)]
Fixed heap-buffer-overflow in atom hash caused by Symbol().

Previously, there was a key_hash collision between strings and
symbols in the atom hash table that led to use of uninitialized memory
and desynchronization of vm->atom_id_generator.

In the atom hash, string entries used djb_hash(content) as key_hash
while symbol entries used raw atom_id. Since djb_hash produces arbitrary
uint32_t values and atom_ids grow monotonically from NJS_ATOM_SIZE,
these spaces overlapped. When a symbol's atom_id matched an existing
string's djb_hash, njs_flathsh_insert() invoked the test function which
read uninitialized fhq.key fields (the function expected string data,
but the caller never initialized it for symbols). Additionally,
atom_id_generator was incremented before the insert, so on failure the
counter diverged from the actual element count, causing subsequent
njs_atom_to_value() calls to read out of bounds.

The fix is to partition the key_hash space using bit 31.

Found by OSS-Fuzz.

2 months agoRefactored atom hash into orthogonal find and add operations.
Dmitry Volyntsev [Tue, 17 Feb 2026 00:16:12 +0000 (16:16 -0800)]
Refactored atom hash into orthogonal find and add operations.

Previously, njs_atom_find_or_add() and njs_atom_find_or_add_string()
duplicated the lookup logic and coupled find with insert. Splitting
them into njs_atom_find() and njs_atom_add() separates concerns:
find does a pure lookup with raw bytes, add inserts a pre-built value.

2 months agoAlign SyntaxError reporting with other JS engines.
Dmitry Volyntsev [Tue, 10 Feb 2026 00:39:31 +0000 (16:39 -0800)]
Align SyntaxError reporting with other JS engines.

Previously, "in <file>:<file>" was a part of the error message. Now
the file name is reported as "stack" property.

This fixes #1005 issue on Github.

3 months agoAttach JS stack trace for exceptions thrown by C code.
Dmitry Volyntsev [Sat, 7 Feb 2026 00:29:20 +0000 (16:29 -0800)]
Attach JS stack trace for exceptions thrown by C code.

This fixes #1019 issue on Github.

3 months agoModules: fixed stack attach for native fetch exceptions for qjs.
Dmitry Volyntsev [Wed, 4 Feb 2026 02:17:16 +0000 (18:17 -0800)]
Modules: fixed stack attach for native fetch exceptions for qjs.

JS_ThrowInternalError() captures backtrace outside of JS call, resulting
in empty "stack" string.  Later, while unwinding, is_backtrace_needed()
returns false because "stack" property exists.

JS_NewError() does not try to attach stack, as a result a stack is
attached later during unwinding.

3 months agoAttach stack in error constructors.
Dmitry Volyntsev [Fri, 30 Jan 2026 01:18:31 +0000 (17:18 -0800)]
Attach stack in error constructors.

This aligns njs with QuickJS and most other JS engines.

3 months agoMark anonymous and unknown functions with <>.
Dmitry Volyntsev [Fri, 30 Jan 2026 00:13:13 +0000 (16:13 -0800)]
Mark anonymous and unknown functions with <>.

To separate from normal functions with the same name.

3 months agoMake Error.stack faster.
Dmitry Volyntsev [Thu, 29 Jan 2026 02:36:39 +0000 (18:36 -0800)]
Make Error.stack faster.

Previously, error.stack reported full names for native function (for
example Array.prototype.map -> map), but it was achieved by iteration
through a global object which is slow.

The fix is to report only function name at hand, this loses a bit
of verbosity but make it ~100 times faster.

make benchmark
before
        ...
exception.stack: 38.421µs, 26027 times/s
exception.native.stack: 226.711µs, 4410 times/s
after
        ...
exception.stack: 1.239µs, 807356 times/s
exception.native.stack: 2.419µs, 413339 times/s

3 months agoFixed stack attaching by stringifying function addresses in place.
Dmitry Volyntsev [Wed, 28 Jan 2026 22:54:29 +0000 (14:54 -0800)]
Fixed stack attaching by stringifying function addresses in place.

This fixed Error stack output on CLI.

3 months agoFixed PTR macro compatibility with newer BFD library.
Dmitry Volyntsev [Wed, 4 Feb 2026 04:17:51 +0000 (20:17 -0800)]
Fixed PTR macro compatibility with newer BFD library.

The deprecated PTR macro was removed in recent versions of binutils.
Replaced with (void **) cast.

3 months agoFixed build on MacOS after 220b9b03 (0.9.5).
Dmitry Volyntsev [Tue, 27 Jan 2026 02:19:05 +0000 (18:19 -0800)]
Fixed build on MacOS after 220b9b03 (0.9.5).

MacOS linker does not support -Wl,-E as it exports binary
symbols by default.

This fixes #1015 on Github.

3 months agoauto/cc: Use portable/POSIX 'command -v' instead of 'which'
Zurab Kvachadze [Mon, 26 Jan 2026 14:54:51 +0000 (15:54 +0100)]
auto/cc: Use portable/POSIX 'command -v' instead of 'which'

'which' is not a portable utility as it is not specified by POSIX. Since
auto/cc is already a shell script, use the more direct and portable
'command' builtin to detect $CC.

There are two bugs linked here. The first one is a downstream report of
this issue. The second one is more general information on why 'which'
usage is an issue and should be avoided.

Bug: https://bugs.gentoo.org/969288
Bug: https://bugs.gentoo.org/646588
Signed-off-by: Zurab Kvachadze <zurabid2016@gmail.com>
3 months agoShell: fixed fuzzer harness to add NULL termination.
OwenSanzas [Thu, 15 Jan 2026 00:15:35 +0000 (00:15 +0000)]
Shell: fixed fuzzer harness to add NULL termination.

Guided-by: Dmitry Volyntsev <xeioex@nginx.com>
Signed-off-by: Ze Sheng <OwenSanzas@gmail.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
3 months agoMaking unit tests less brittle when libpcre2 changes.
Dmitry Volyntsev [Wed, 14 Jan 2026 22:43:58 +0000 (14:43 -0800)]
Making unit tests less brittle when libpcre2 changes.

This fixes #1011 issue on Github.

3 months agoVersion bump.
Dmitry Volyntsev [Wed, 14 Jan 2026 22:46:11 +0000 (14:46 -0800)]
Version bump.

3 months ago Version 0.9.5. 0.9.5
Dmitry Volyntsev [Mon, 12 Jan 2026 23:01:50 +0000 (15:01 -0800)]
 Version 0.9.5.

3 months agoQuickJS: added native module support in CLI.
Dmitry Volyntsev [Thu, 8 Jan 2026 02:09:20 +0000 (18:09 -0800)]
QuickJS: added native module support in CLI.

3 months agoQuickJS: added native module support.
Dmitry Volyntsev [Wed, 3 Dec 2025 01:39:44 +0000 (17:39 -0800)]
QuickJS: added native module support.

Added "js_load_http_native_module" and "js_load_stream_native_module"
main nginx.conf level directives. The directives load a dynamic
library. For security reason it is only allowed in the main context.
Later, JS code may import modules loaded with these directives
with standard import syntax.

example.conf:
    ...
    js_load_http_native_module /path/to/lib.so;
    js_load_http_native_module /path/to/lib2.so as lib2;

    http {
        js_import main.js;
...

main.js:
    import * as lib from 'lib.so';
    import * as lib2 from 'lib2';
    ...

See quickjs.h for the complete QuickJS API reference and
nginx/t/js_native_module.t for a working example.

This closes #968 feature request on Github.

3 months agoModules: extracted config-time merging into separate function.
Dmitry Volyntsev [Sat, 6 Dec 2025 01:08:54 +0000 (17:08 -0800)]
Modules: extracted config-time merging into separate function.

This introduces ngx_js_merge_conftime_loc_conf() to handle merging
of configuration-time properties.

Normally ngx_js_merge_conf() does all the default value initialization
for child location configurations.

There is a special case for global "http" or "stream" configuration
where the parent configuration needs to be initialized (so it can be
reused by server configurations if no additional directives were
defined in them). But parent configurations are not initialized by
ngx_js_merge_conf().

Most of the ngx_js_loc_conf_t values are only used at runtime, so
only configuration-time values need to be merged in the parent.
The runtime values will be provided from the appropriate
ngx_js_loc_conf_t during request processing.

Previously, configuration-time merging was done inline. Extracting
it into a dedicated function simplifies adding new configuration-time
properties.

3 months agoQuickJS: fixed js_body_filter with multiple chunks.
Dmitry Volyntsev [Sat, 10 Jan 2026 02:08:56 +0000 (18:08 -0800)]
QuickJS: fixed js_body_filter with multiple chunks.

Previously, last_key atoms was freed too early. Also, js_body_filter.t
is modified to ensure js_body_filter sees multiple data chains to catch
the issue.

4 months agoHTTP: improved r.subrequest() error handling after d34fcb0 (0.8.5).
Dmitry Volyntsev [Tue, 6 Jan 2026 01:23:50 +0000 (17:23 -0800)]
HTTP: improved r.subrequest() error handling after d34fcb0 (0.8.5).

This reverts code changes introduced in d34fcb0. To fix the original
problem JS errors are not propagated as nginx errors in r.subrequest().

This also fixes a problem of a lost write event when the njs handler
making r.subrequest() is called from a lua handler as a subrequest:

nginx.conf:
    ...
    location = /subrequest {
        access_by_lua_file scripts/subrequest.lua;
proxy_pass  http://localhost:8080/pass;
    }

    location = /nested/subrequest {
        internal;
        js_content main.getToken;
    }

scripts/subrequest.lua:
    local res = ngx.location.capture('/nested/subrequest')
    ngx.log(ngx.STDERR, "Subrequest  status: " .. res.status)

    if res.status ~= 200 then
      ngx.log(ngx.STDERR, "Subrequest failed with status: " .. res.status)
      return nil
    end

    return res.body

main.js:
    async function getToken(r) {
        var reply = await r.subrequest('/api/auth');
        r.error("reply.status :: " + reply.status);

        var status = reply.status;
        r.return(status, "{"result":"OK!"}");
    }

    export default { getToken };

$ curl localhost:8080/subrequest

error.log (before the fix):
    ...
    *1 http js event finalize rc: 0
    *1 post event 0000B4BCA64C5FF0
    *1 http log handler
    *1 http request count:3 blk:0
    timer delta: 0
    posted event 0000B4BCA64C5FF0
    *1 delete posted event 0000B4BCA64C5FF0
    *1 http run request: "/subrequest?"
    *1 access phase: 11
    *1 lua access handler, uri:"/subrequest" c:2
    *1 lua run write event handler: timedout:0, ready:1, writing_raw_req_socket:0
    *1 useless lua write event handler <<<---

error.log (after the fix):
    ...
    *1 http js event finalize rc: 0
    *1 http posted request: "/nested/subrequest?"
    *1 http js content write event handler
    *1 http js content rc: 0
    *1 http finalize request: 0, "/nested/subrequest?" a:0, c:3
    *1 lua run post subrequest handler, rc:0 c:3
    *1 http log handler
    *1 http wake parent request: "/subrequest?"
    *1 http posted request: "/subrequest?"
    *1 access phase: 11
    *1 lua access handler, uri:"/subrequest" c:2
    *1 lua run subrequests done, resuming lua thread

4 months agoTypes: fixed CipherAlgorithm and Pbkdf2Params
Martin Kaesberger [Sun, 14 Dec 2025 15:29:17 +0000 (16:29 +0100)]
Types: fixed CipherAlgorithm and Pbkdf2Params

4 months agoConfigure: respecting user provided CFLAGS.
Dmitry Volyntsev [Tue, 25 Nov 2025 00:19:59 +0000 (16:19 -0800)]
Configure: respecting user provided CFLAGS.

See https://www.gnu.org/prep/standards/html_node/Command-Variables.html
and discussion in #990 issue on Github.

4 months agoConfigure: added option to configure position independent code flag.
Dmitry Volyntsev [Tue, 25 Nov 2025 01:49:35 +0000 (17:49 -0800)]
Configure: added option to configure position independent code flag.

4 months agoHTTP: fixed buffer_type inheritance in if blocks.
Dmitry Volyntsev [Fri, 12 Dec 2025 05:57:18 +0000 (21:57 -0800)]
HTTP: fixed buffer_type inheritance in if blocks.

Previously, when js_body_filter was used inside an if block that
evaluated to true, the data parameter received Buffer type instead
of the expected String type. This happened because buffer_type field
in ngx_http_js_loc_conf_t was not properly initialized, causing the
configuration merge to fail when nginx created a new location context
for if blocks.

This fixes #999 issue on Github.

5 months agoFixed js_body_filter when data is not in memory.
Dmitry Volyntsev [Tue, 2 Dec 2025 01:03:25 +0000 (17:03 -0800)]
Fixed js_body_filter when data is not in memory.

Previously, when upstream data was delivered from nginx cache
js_body_filter was not able to process it correctly. In particular,
it was treated as a chain of empty buffers.

The fix is to set r->filter_need_in_memory flag, which ensures
that ngx_http_core_module reads the data into memory before
js_body_filter sees it.

This fixes #992 issue on Github.

5 months agoFS: fixed path restoration in fs.mkdir() and friends on error.
Dmitry Volyntsev [Wed, 26 Nov 2025 03:08:33 +0000 (19:08 -0800)]
FS: fixed path restoration in fs.mkdir() and friends on error.

5 months agoFS: fixed fs.mkdir() and friends.
Dmitry Volyntsev [Wed, 26 Nov 2025 03:08:10 +0000 (19:08 -0800)]
FS: fixed fs.mkdir() and friends.

5 months agoFixed missed allocation check in promise code.
Dmitry Volyntsev [Tue, 25 Nov 2025 23:58:38 +0000 (15:58 -0800)]
Fixed missed allocation check in promise code.

Found by Coverity (CID 16469311646932).

5 months agoXML: fixed XMLNode update.
Dmitry Volyntsev [Sat, 22 Nov 2025 01:41:53 +0000 (17:41 -0800)]
XML: fixed XMLNode update.

5 months agoXML: fixed XMLAttr object.
Dmitry Volyntsev [Sat, 22 Nov 2025 00:32:15 +0000 (16:32 -0800)]
XML: fixed XMLAttr object.

Pointer to xmlAttr to which XMLAttr used to point might become invalid
when the parent XMLNode was modified.

The fix is to hold a pointer to xmlNode.

5 months agoRemoved non-compliant Buffer.prototype.length.
Dmitry Volyntsev [Thu, 20 Nov 2025 23:28:49 +0000 (15:28 -0800)]
Removed non-compliant Buffer.prototype.length.

5 months agoFixed ArrayBuffer with detached buffers.
Dmitry Volyntsev [Thu, 20 Nov 2025 23:07:07 +0000 (15:07 -0800)]
Fixed ArrayBuffer with detached buffers.

5 months agoAdded missing detached array checks.
Dmitry Volyntsev [Tue, 18 Nov 2025 02:55:16 +0000 (18:55 -0800)]
Added missing detached array checks.

5 months agoPreserving strings are zero-terminated in njs_string_truncate().
Dmitry Volyntsev [Tue, 18 Nov 2025 03:37:48 +0000 (19:37 -0800)]
Preserving strings are zero-terminated in njs_string_truncate().

This is linked to changes introduced in e7caa46.

5 months agoCI: cancel stale workflows.
Dmitry Volyntsev [Wed, 19 Nov 2025 01:57:37 +0000 (17:57 -0800)]
CI: cancel stale workflows.

5 months agoCI: using meson and pkg-config for QuickJS-NG.
Dmitry Volyntsev [Wed, 19 Nov 2025 01:54:45 +0000 (17:54 -0800)]
CI: using meson and pkg-config for QuickJS-NG.

5 months agoAdded pkg-config discovery for QuickJS-NG.
Dmitry Volyntsev [Wed, 19 Nov 2025 01:46:41 +0000 (17:46 -0800)]
Added pkg-config discovery for QuickJS-NG.

5 months agoQuickJS: fixed compatibility issue with QuickJS-NG 0.11.0.
Dmitry Volyntsev [Wed, 19 Nov 2025 00:10:52 +0000 (16:10 -0800)]
QuickJS: fixed compatibility issue with QuickJS-NG 0.11.0.

5 months agoModules: removed extra not needed QuickJS checks.
Dmitry Volyntsev [Wed, 19 Nov 2025 01:12:48 +0000 (17:12 -0800)]
Modules: removed extra not needed QuickJS checks.

5 months agoUsing printing and parsing library from QuickJS.
Dmitry Volyntsev [Fri, 24 Oct 2025 03:06:42 +0000 (20:06 -0700)]
Using printing and parsing library from QuickJS.

- Number.prototype.toString(radix)
    Improved accuracy for edge cases
    Reimplemented using njs_dtoa2() with JS_DTOA_FORMAT_FREE | JS_DTOA_EXP_DISABLED
- Number.prototype.toFixed(frac)
    Reimplemented using njs_dtoa2() with JS_DTOA_FORMAT_FIXED
    Removed old njs_fixed_dtoa() implementation
- Number.prototype.toPrecision(prec)
    Reimplemented using njs_dtoa2() with precision format
    Removed old njs_dtoa_precision() implementation
- Number.prototype.toExponential(frac)
    Reimplemented using njs_dtoa2() with exponential format
    Removed old njs_dtoa_exponential() implementation
- parseInt()
    Simplified parsing implementation
    Removed custom njs_number_radix_parse() helper
- parseFloat()
    Simplified parsing implementation

Removed custom njs_number_bin_parse(), njs_number_oct_parse(),
njs_number_dec_parse() and njs_strtod.c module Better handling of large
numbers and denormal floats and invalid inputs.

5 months agoEnsuring string values are zero-terminated.
Dmitry Volyntsev [Sat, 15 Nov 2025 02:22:56 +0000 (18:22 -0800)]
Ensuring string values are zero-terminated.

This helps when some code requires a string value to be zero-terminated
to avoid copying and adding \0 byte.

It adds small overhead (less than 3% in worst case of an empty string
value) to existing sizeof(njs_value_t) + sizeof(njs_string_t) = 32.

5 months agoFixed --debug=YES build after d4cedc0.
Dmitry Volyntsev [Fri, 14 Nov 2025 21:07:20 +0000 (13:07 -0800)]
Fixed --debug=YES build after d4cedc0.

5 months agoAdded Symbol.toPrimitive to Date and Symbol prototypes.
Vadim Zhestikov [Tue, 11 Nov 2025 18:01:01 +0000 (10:01 -0800)]
Added Symbol.toPrimitive to Date and Symbol prototypes.

5 months agoAdded Object.hasOwn().
Vadim Zhestikov [Mon, 10 Nov 2025 17:50:48 +0000 (09:50 -0800)]
Added Object.hasOwn().

5 months agoFixed Object prototype methods according to the spec.
Vadim Zhestikov [Mon, 10 Nov 2025 16:56:28 +0000 (08:56 -0800)]
Fixed Object prototype methods according to the spec.

6 months agoAdded promises execution after njs.on('exit', ..) hook.
Vadim Zhestikov [Wed, 5 Nov 2025 01:35:35 +0000 (17:35 -0800)]
Added promises execution after njs.on('exit', ..) hook.

6 months agoModules: improved handling of results of async handlers.
Vadim Zhestikov [Fri, 25 Jul 2025 00:10:36 +0000 (17:10 -0700)]
Modules: improved handling of results of async handlers.

Previously, r.setReturnValue() had to be used when returning a value from async js_set handler.

async function hash(r) {
    let hash = await crypto.subtle.digest('SHA-512', r.headersIn.host);
    r.setReturnValue(Buffer.from(hash).toString('hex'));
}

Now r.setReturnValue() is not needed:

async function hash(r) {
    let hash = await crypto.subtle.digest('SHA-512', r.headersIn.host);
    return Buffer.from(hash).toString('hex');
}

Also added promise handling in global qjs code.

6 months agoAdded API to work with promises.
Vadim Zhestikov [Tue, 4 Nov 2025 16:23:39 +0000 (08:23 -0800)]
Added API to work with promises.

6 months agoVersion bump.
Vadim Zhestikov [Wed, 5 Nov 2025 00:12:37 +0000 (16:12 -0800)]
Version bump.

6 months agoFetch: making sure catch handler is executed asynchronously.
Vadim Zhestikov [Mon, 27 Oct 2025 21:14:02 +0000 (14:14 -0700)]
Fetch: making sure catch handler is executed asynchronously.

6 months agoVersion 0.9.4. 0.9.4
Dmitry Volyntsev [Mon, 27 Oct 2025 22:45:00 +0000 (15:45 -0700)]
Version 0.9.4.

6 months agoFetch: added forward proxy support with HTTPS tunneling.
Dmitry Volyntsev [Thu, 9 Oct 2025 23:28:24 +0000 (16:28 -0700)]
Fetch: added forward proxy support with HTTPS tunneling.

Supports Basic authentication via Proxy-Authorization header.

- js_fetch_proxy - configures forward proxy URL. It takes proxy URL
    as a parameter. The URL may optionally contain user and password.
     Parameter value can contain variables. If value is empty,
     forward proxy is disabled.

example.conf:
...
  http {
      js_import main.js;

      server {
          listen 8080;

          resolver   127.0.0.1:5353;

          location /api {
              js_fetch_proxy http://user:pass@proxy.example.com:3128;
              js_content main.fetch_handler;
          }
      }
  }

This implements #956 feature request on Github.

6 months agoFetch: added default user agent.
Dmitry Volyntsev [Thu, 16 Oct 2025 01:56:48 +0000 (18:56 -0700)]
Fetch: added default user agent.

6 months agoFetch: introduced ngx_js_chain_to_buf() helper function.
Dmitry Volyntsev [Wed, 15 Oct 2025 00:42:07 +0000 (17:42 -0700)]
Fetch: introduced ngx_js_chain_to_buf() helper function.

6 months agoFetch: refactored request building logic into separate function.
Dmitry Volyntsev [Thu, 9 Oct 2025 23:16:54 +0000 (16:16 -0700)]
Fetch: refactored request building logic into separate function.

6 months agoFetch: introduced port and protocol helper macros.
Dmitry Volyntsev [Thu, 9 Oct 2025 22:57:44 +0000 (15:57 -0700)]
Fetch: introduced port and protocol helper macros.

6 months agoTests: making sure dns stub in js_fetch_https.t resolves only known hosts.
Dmitry Volyntsev [Thu, 9 Oct 2025 01:41:47 +0000 (18:41 -0700)]
Tests: making sure dns stub in js_fetch_https.t resolves only known hosts.

6 months agoFetch: simplified ngx_js_http_resolve().
Dmitry Volyntsev [Wed, 15 Oct 2025 22:29:14 +0000 (15:29 -0700)]
Fetch: simplified ngx_js_http_resolve().

6 months agoModules: added error handling when logging a js exception string.
Vadim Zhestikov [Thu, 23 Oct 2025 20:27:41 +0000 (13:27 -0700)]
Modules: added error handling when logging a js exception string.

6 months agoQuickJS: fixed r.subrequest() to a location with JS handler.
Dmitry Volyntsev [Tue, 14 Oct 2025 00:37:11 +0000 (17:37 -0700)]
QuickJS: fixed r.subrequest() to a location with JS handler.

Previously, when a subrequest location had a JS handler, an object of a
subrequest JS context was provided as an argument to a parent contexts.
This may cause all sorts of problems due to incorrect reference
counting.

After this change in bellard/quickjs@42eb2795 the bug became apparent.

6 months agoVersion bump.
Dmitry Volyntsev [Thu, 9 Oct 2025 01:42:16 +0000 (18:42 -0700)]
Version bump.

7 months agoVersion 0.9.3. 0.9.3
Dmitry Volyntsev [Mon, 6 Oct 2025 16:13:36 +0000 (09:13 -0700)]
Version 0.9.3.