diff --git a/pkgs/by-name/cm/cmake/009-cmCurl-Avoid-using-undocumented-type-for-CURLOPT_NETRC-values.diff b/pkgs/by-name/cm/cmake/009-cmCurl-Avoid-using-undocumented-type-for-CURLOPT_NETRC-values.diff new file mode 100644 index 000000000000..7749a95053a0 --- /dev/null +++ b/pkgs/by-name/cm/cmake/009-cmCurl-Avoid-using-undocumented-type-for-CURLOPT_NETRC-values.diff @@ -0,0 +1,13 @@ +diff --git a/Source/cmCurl.cxx b/Source/cmCurl.cxx +index b9133ed7d47b9023de66a94ed5ff15a0f1ba440c..0cf8a71a72daaa07cb42b3f5eef81d2d04300cd6 100644 +--- a/Source/cmCurl.cxx ++++ b/Source/cmCurl.cxx +@@ -170,7 +170,7 @@ std::string cmCurlSetNETRCOption(::CURL* curl, const std::string& netrc_level, + const std::string& netrc_file) + { + std::string e; +- CURL_NETRC_OPTION curl_netrc_level = CURL_NETRC_LAST; ++ long curl_netrc_level = CURL_NETRC_LAST; + ::CURLcode res; + + if (!netrc_level.empty()) { diff --git a/pkgs/by-name/cm/cmake/package.nix b/pkgs/by-name/cm/cmake/package.nix index 67fad206fe40..15dc2765fcc6 100644 --- a/pkgs/by-name/cm/cmake/package.nix +++ b/pkgs/by-name/cm/cmake/package.nix @@ -76,6 +76,8 @@ stdenv.mkDerivation (finalAttrs: { # Backport of https://gitlab.kitware.com/cmake/cmake/-/merge_requests/9900 # Needed to correctly link curl in pkgsStatic. ./008-FindCURL-Add-more-target-properties-from-pkg-config.diff + # Backport of https://gitlab.kitware.com/cmake/cmake/-/commit/1b0c92a3a1b782ff3e1c4499b6ab8db614d45bcd + ./009-cmCurl-Avoid-using-undocumented-type-for-CURLOPT_NETRC-values.diff ]; outputs = diff --git a/pkgs/by-name/cu/curlMinimal/0001-http2-fix-stream-window-size-after-unpausing.patch b/pkgs/by-name/cu/curlMinimal/0001-http2-fix-stream-window-size-after-unpausing.patch new file mode 100644 index 000000000000..8960f617d3f1 --- /dev/null +++ b/pkgs/by-name/cu/curlMinimal/0001-http2-fix-stream-window-size-after-unpausing.patch @@ -0,0 +1,155 @@ +From 5fbd78eb2dc4afbd8884e8eed27147fc3d4318f6 Mon Sep 17 00:00:00 2001 +From: Stefan Eissing +Date: Fri, 4 Apr 2025 10:43:13 +0200 +Subject: [PATCH] http2: fix stream window size after unpausing + +When pausing a HTTP/2 transfer, the stream's local window size +is reduced to 0 to prevent the server from sending further data +which curl cannot write out to the application. + +When unpausing again, the stream's window size was not correctly +increased again. The attempt to trigger a window update was +ignored by nghttp2, the server never received it and the transfer +stalled. + +Add a debug feature to allow use of small window sizes which +reproduces this bug in test_02_21. + +Fixes #16955 +Closes #16960 +--- + docs/libcurl/libcurl-env-dbg.md | 5 +++++ + lib/http2.c | 31 +++++++++++++++++++++++++++++++ + tests/http/test_02_download.py | 27 +++++++++++++++++++++++++-- + 3 files changed, 61 insertions(+), 2 deletions(-) + +diff --git a/docs/libcurl/libcurl-env-dbg.md b/docs/libcurl/libcurl-env-dbg.md +index 471533625f6b..60c887bfd5a9 100644 +--- a/docs/libcurl/libcurl-env-dbg.md ++++ b/docs/libcurl/libcurl-env-dbg.md +@@ -147,3 +147,8 @@ Make a blocking, graceful shutdown of all remaining connections when + a multi handle is destroyed. This implicitly triggers for easy handles + that are run via easy_perform. The value of the environment variable + gives the shutdown timeout in milliseconds. ++ ++## `CURL_H2_STREAM_WIN_MAX` ++ ++Set to a positive 32-bit number to override the HTTP/2 stream window's ++default of 10MB. Used in testing to verify correct window update handling. +diff --git a/lib/http2.c b/lib/http2.c +index 88fbcceb7135..a1221dcc51de 100644 +--- a/lib/http2.c ++++ b/lib/http2.c +@@ -44,6 +44,7 @@ + #include "connect.h" + #include "rand.h" + #include "strdup.h" ++#include "strparse.h" + #include "transfer.h" + #include "dynbuf.h" + #include "headers.h" +@@ -141,6 +142,9 @@ struct cf_h2_ctx { + uint32_t goaway_error; /* goaway error code from server */ + int32_t remote_max_sid; /* max id processed by server */ + int32_t local_max_sid; /* max id processed by us */ ++#ifdef DEBUGBUILD ++ int32_t stream_win_max; /* max h2 stream window size */ ++#endif + BIT(initialized); + BIT(via_h1_upgrade); + BIT(conn_closed); +@@ -166,6 +170,18 @@ static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade) + Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free); + ctx->remote_max_sid = 2147483647; + ctx->via_h1_upgrade = via_h1_upgrade; ++#ifdef DEBUGBUILD ++ { ++ const char *p = getenv("CURL_H2_STREAM_WIN_MAX"); ++ ++ ctx->stream_win_max = H2_STREAM_WINDOW_SIZE_MAX; ++ if(p) { ++ curl_off_t l; ++ if(!Curl_str_number(&p, &l, INT_MAX)) ++ ctx->stream_win_max = (int32_t)l; ++ } ++ } ++#endif + ctx->initialized = TRUE; + } + +@@ -285,7 +301,15 @@ static int32_t cf_h2_get_desired_local_win(struct Curl_cfilter *cf, + * This gets less precise the higher the latency. */ + return (int32_t)data->set.max_recv_speed; + } ++#ifdef DEBUGBUILD ++ else { ++ struct cf_h2_ctx *ctx = cf->ctx; ++ CURL_TRC_CF(data, cf, "stream_win_max=%d", ctx->stream_win_max); ++ return ctx->stream_win_max; ++ } ++#else + return H2_STREAM_WINDOW_SIZE_MAX; ++#endif + } + + static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, +@@ -302,6 +326,13 @@ static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf, + int32_t wsize = nghttp2_session_get_stream_effective_local_window_size( + ctx->h2, stream->id); + if(dwsize > wsize) { ++ rv = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, ++ stream->id, dwsize); ++ if(rv) { ++ failf(data, "[%d] nghttp2 set_local_window_size(%d) failed: " ++ "%s(%d)", stream->id, dwsize, nghttp2_strerror(rv), rv); ++ return CURLE_HTTP2; ++ } + rv = nghttp2_submit_window_update(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, dwsize - wsize); + if(rv) { +diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py +index 4b9ae3caefab..b55f022338ad 100644 +--- a/tests/http/test_02_download.py ++++ b/tests/http/test_02_download.py +@@ -313,9 +313,9 @@ def test_02_20_h2_small_frames(self, env: Env, httpd): + assert httpd.stop() + assert httpd.start() + +- # download via lib client, 1 at a time, pause/resume at different offsets ++ # download serial via lib client, pause/resume at different offsets + @pytest.mark.parametrize("pause_offset", [0, 10*1024, 100*1023, 640000]) +- @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) ++ @pytest.mark.parametrize("proto", ['http/1.1', 'h3']) + def test_02_21_lib_serial(self, env: Env, httpd, nghttpx, proto, pause_offset): + if proto == 'h3' and not env.have_h3(): + pytest.skip("h3 not supported") +@@ -332,6 +332,29 @@ def test_02_21_lib_serial(self, env: Env, httpd, nghttpx, proto, pause_offset): + srcfile = os.path.join(httpd.docs_dir, docname) + self.check_downloads(client, srcfile, count) + ++ # h2 download parallel via lib client, pause/resume at different offsets ++ # debug-override stream window size to reproduce #16955 ++ @pytest.mark.parametrize("pause_offset", [0, 10*1024, 100*1023, 640000]) ++ @pytest.mark.parametrize("swin_max", [0, 10*1024]) ++ def test_02_21_h2_lib_serial(self, env: Env, httpd, pause_offset, swin_max): ++ proto = 'h2' ++ count = 2 ++ docname = 'data-10m' ++ url = f'https://localhost:{env.https_port}/{docname}' ++ run_env = os.environ.copy() ++ run_env['CURL_DEBUG'] = 'multi,http/2' ++ if swin_max > 0: ++ run_env['CURL_H2_STREAM_WIN_MAX'] = f'{swin_max}' ++ client = LocalClient(name='hx-download', env=env, run_env=run_env) ++ if not client.exists(): ++ pytest.skip(f'example client not built: {client.name}') ++ r = client.run(args=[ ++ '-n', f'{count}', '-P', f'{pause_offset}', '-V', proto, url ++ ]) ++ r.check_exit_code(0) ++ srcfile = os.path.join(httpd.docs_dir, docname) ++ self.check_downloads(client, srcfile, count) ++ + # download via lib client, several at a time, pause/resume + @pytest.mark.parametrize("pause_offset", [100*1023]) + @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3']) diff --git a/pkgs/by-name/cu/curlMinimal/package.nix b/pkgs/by-name/cu/curlMinimal/package.nix index c6fd0ebeee21..9f3b02d2fc59 100644 --- a/pkgs/by-name/cu/curlMinimal/package.nix +++ b/pkgs/by-name/cu/curlMinimal/package.nix @@ -91,7 +91,7 @@ in stdenv.mkDerivation (finalAttrs: { pname = "curl"; - version = "8.12.1"; + version = "8.13.0"; src = fetchurl { urls = [ @@ -100,9 +100,14 @@ stdenv.mkDerivation (finalAttrs: { builtins.replaceStrings [ "." ] [ "_" ] finalAttrs.version }/curl-${finalAttrs.version}.tar.xz" ]; - hash = "sha256-A0Hx7ZeibIEauuvTfWK4M5VnkrdgfqPxXQAWE8dt4gI="; + hash = "sha256-Sgk5eaPC0C3i+8AFSaMncQB/LngDLG+qXs0vep4VICU="; }; + patches = [ + # Backport of https://github.com/curl/curl/commit/5fbd78eb2dc4afbd8884e8eed27147fc3d4318f6 + ./0001-http2-fix-stream-window-size-after-unpausing.patch + ]; + # this could be accomplished by updateAutotoolsGnuConfigScriptsHook, but that causes infinite recursion # necessary for FreeBSD code path in configure postPatch = ''