From 75692197e65ba4934008678730aea307cf9c6dfb Mon Sep 17 00:00:00 2001 From: Kirill Saied Date: Thu, 16 Jul 2026 13:27:35 +0200 Subject: [PATCH 1/5] fs: fix cp symlink and EEXIST handling on Windows Fixes: https://github.com/nodejs/node/issues/59636 Signed-off-by: Kirill Saied PR-URL: https://github.com/nodejs/node/pull/64353 Reviewed-By: Yagiz Nizipli Reviewed-By: Stefan Stojanovic --- src/node_file.cc | 14 +++++++++++++- test/parallel/parallel.status | 2 -- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/node_file.cc b/src/node_file.cc index d93f213202ec43..b8b8be835980cf 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -3680,6 +3680,12 @@ std::vector normalizePathToArray( const std::filesystem::path& path) { std::vector parts; std::filesystem::path absPath = std::filesystem::absolute(path); +#ifdef _WIN32 + auto wstr = absPath.wstring(); + if (wstr.starts_with(L"\\\\?\\")) { + absPath = std::filesystem::path(wstr.substr(4)); + } +#endif for (const auto& part : absPath) { if (!part.empty()) parts.push_back(part.string()); } @@ -3818,6 +3824,12 @@ static void CpSyncCopyDir(const FunctionCallbackInfo& args) { } auto symlink_target_absolute = std::filesystem::weakly_canonical( std::filesystem::absolute(src / symlink_target)); +#ifdef _WIN32 + auto wstr = symlink_target_absolute.wstring(); + if (wstr.starts_with(L"\\\\?\\")) { + symlink_target_absolute = std::filesystem::path(wstr.substr(4)); + } +#endif if (dir_entry.is_directory()) { std::filesystem::create_directory_symlink( symlink_target_absolute, dest_file_path, error); @@ -3841,7 +3853,7 @@ static void CpSyncCopyDir(const FunctionCallbackInfo& args) { std::filesystem::copy_file( dir_entry.path(), dest_file_path, file_copy_opts, error); if (error) { - if (error.value() == EEXIST) { + if (error == std::errc::file_exists) { THROW_ERR_FS_CP_EEXIST(isolate, "[ERR_FS_CP_EEXIST]: Target already exists: " "cp returned EEXIST (%s already exists)", diff --git a/test/parallel/parallel.status b/test/parallel/parallel.status index fb285503351bdf..7b15183e2fdbd7 100644 --- a/test/parallel/parallel.status +++ b/test/parallel/parallel.status @@ -23,10 +23,8 @@ test-snapshot-incompatible: SKIP # https://github.com/nodejs/node/issues/59090 test-inspector-network-fetch: PASS, FLAKY # https://github.com/nodejs/node/issues/59636 -test-fs-cp-sync-error-on-exist: SKIP test-fs-cp-sync-symlink-points-to-dest-error: SKIP test-fs-cp-async-symlink-points-to-dest: SKIP -test-fs-cp-sync-unicode-folder-names: SKIP # Windows on ARM [$system==win32 && $arch==arm64] From cf882a79042cba4146acfdb7993b6a97c21e7239 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Thu, 16 Jul 2026 13:34:09 +0200 Subject: [PATCH 2/5] stream: fold desired-size check into WHATWG backpressure update writableStreamUpdateBackpressure ran two release-mode assertions and took a precomputed backpressure value that every caller derived with a separate GetBackpressure/GetDesiredSize call pair. All callers already hold the 'writable', not-close-queued invariant the assertions checked, so derive the backpressure from the desired size inside the function and drop the assertions and the helper. This runs up to twice per write. writer.write() loop: +10%, pipe-to: +4.6% avg over 16 configs (local harness / benchmark, all configs positive). Signed-off-by: Matteo Collina PR-URL: https://github.com/nodejs/node/pull/64451 Reviewed-By: James M Snell Reviewed-By: Trivikram Kamat --- lib/internal/webstreams/writablestream.js | 33 +++++++++-------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js index 32b526595e6fbd..f0eb687be129d1 100644 --- a/lib/internal/webstreams/writablestream.js +++ b/lib/internal/webstreams/writablestream.js @@ -757,13 +757,15 @@ function writableStreamClose(stream) { return promise; } -function writableStreamUpdateBackpressure(stream, backpressure) { - const streamState = stream[kState]; - assert(streamState.state === 'writable'); - assert(!streamState.closeQueuedOrInFlight); - const { - writer, - } = streamState; +// Callers invoke this only in the 'writable' state with no close queued or +// in flight (the two conditions the spec's assertions check), so the +// backpressure value is derived here from the desired size instead of being +// computed and passed in by each caller. +function writableStreamUpdateBackpressure(controller, streamState) { + const controllerState = controller[kState]; + const backpressure = + controllerState.highWaterMark - controllerState.queueTotalSize <= 0; + const writer = streamState.writer; if (writer !== undefined && streamState.backpressure !== backpressure) { if (backpressure) { // The spec replaces [[readyPromise]] with a fresh pending promise; @@ -1100,9 +1102,7 @@ function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) { const streamState = stream[kState]; if (!streamState.closeQueuedOrInFlight && streamState.state === 'writable') { - writableStreamUpdateBackpressure( - stream, - writableStreamDefaultControllerGetBackpressure(controller)); + writableStreamUpdateBackpressure(controller, streamState); } writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); } @@ -1128,9 +1128,7 @@ function writableStreamDefaultControllerProcessWrite(controller, chunk) { dequeueValue(controller); if (!streamState.closeQueuedOrInFlight && state === 'writable') { - writableStreamUpdateBackpressure( - stream, - writableStreamDefaultControllerGetBackpressure(controller)); + writableStreamUpdateBackpressure(controller, streamState); } writableStreamDefaultControllerAdvanceQueueIfNeeded(controller); }; @@ -1229,10 +1227,6 @@ function writableStreamDefaultControllerClearAlgorithms(controller) { controller[kState].sizeAlgorithm = undefined; } -function writableStreamDefaultControllerGetBackpressure(controller) { - return writableStreamDefaultControllerGetDesiredSize(controller) <= 0; -} - function writableStreamDefaultControllerAdvanceQueueIfNeeded(controller) { const { queue, @@ -1317,9 +1311,7 @@ function setupWritableStreamDefaultController( }; stream[kState].controller = controller; - writableStreamUpdateBackpressure( - stream, - writableStreamDefaultControllerGetBackpressure(controller)); + writableStreamUpdateBackpressure(controller, stream[kState]); const startResult = startAlgorithm(); @@ -1402,7 +1394,6 @@ module.exports = { writableStreamDefaultControllerError, writableStreamDefaultControllerClose, writableStreamDefaultControllerClearAlgorithms, - writableStreamDefaultControllerGetBackpressure, writableStreamDefaultControllerAdvanceQueueIfNeeded, setupWritableStreamDefaultControllerFromSink, setupWritableStreamDefaultController, From cc8594a9b1dfc157f6f6872a238f09cc0cf44b62 Mon Sep 17 00:00:00 2001 From: Sebastian Beltran Date: Thu, 16 Jul 2026 12:10:24 -0500 Subject: [PATCH 3/5] quic: add support for TLS certificate compression Signed-off-by: Sebastian Beltran PR-URL: https://github.com/nodejs/node/pull/64434 Reviewed-By: James M Snell Reviewed-By: Tim Perry --- doc/api/quic.md | 38 +++++- lib/internal/quic/quic.js | 42 +++++++ src/quic/bindingdata.h | 1 + src/quic/tlscontext.cc | 53 ++++++++- src/quic/tlscontext.h | 10 ++ .../test-quic-certificate-compression.mjs | 112 ++++++++++++++++++ 6 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 test/parallel/test-quic-certificate-compression.mjs diff --git a/doc/api/quic.md b/doc/api/quic.md index a19ff2befb5731..23dca30ae81b21 100644 --- a/doc/api/quic.md +++ b/doc/api/quic.md @@ -137,8 +137,12 @@ To avoid this, servers should use compact certificate chains: large RSA intermediates. The choice of CA directly affects handshake latency. Certificate compression ([RFC 8879][]) can also address this issue by -compressing the certificate chain during the handshake. However, Node.js does -not currently support TLS certificate compression. +compressing the certificate chain during the handshake, often keeping the +server's Certificate message within the amplification limit and avoiding the +extra round trip. Certificate compression is opt-in via the +[`certificateCompression`][] TLS option and is disabled by default. When +enabled, it applies to both the server's certificate and, for mutual TLS, +the client's certificate. ### Rate limiting @@ -2918,6 +2922,34 @@ added: v23.8.0 The TLS certificates to use for client sessions. For server sessions, certificates are specified per-identity in the [`sessionOptions.sni`][] map. +#### `sessionOptions.certificateCompression` + + + +* Type: {string\[]} One or more of `'zlib'`, `'brotli'`, or `'zstd'`, in + preference order. + +Enables TLS certificate compression ([RFC 8879][]) for this session. When +omitted, certificate compression is disabled. + +On the server side, the certificate chain is compressed using the first +listed algorithm that the client advertises support for. On the client side, +the listed algorithms are advertised to the server so that the server may +compress its certificate. When client authentication is in use, the option +also controls compression of the client's certificate. + +Compressing the certificate chain is especially useful for QUIC because it +reduces the size of the server's first flight, which is bounded by the +anti-amplification limit (see [Certificate size and handshake +performance][]). Certificate compression requires TLS 1.3, which QUIC always +uses. + +At most three algorithms may be specified. The option is silently ignored if +Node.js was built against a shared OpenSSL that lacks certificate compression +support. + #### `sessionOptions.ciphers`